tiny-essentials 1.10.2 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/dist/node_modules/firebase-functions/lib/common/trace.cjs +82 -0
- package/dist/node_modules/firebase-functions/lib/logger/common.cjs +57 -0
- package/dist/node_modules/firebase-functions/lib/logger/index.cjs +158 -0
- package/dist/{TinyEssentials.js → v1/TinyBasicsEs.js} +1726 -1362
- package/dist/v1/TinyBasicsEs.min.js +2 -0
- package/dist/v1/TinyDragDropDetector.js +307 -0
- package/dist/v1/TinyDragDropDetector.min.js +1 -0
- package/dist/{TinyBasicsEs.js → v1/TinyDragger.js} +1004 -506
- package/dist/v1/TinyDragger.min.js +2 -0
- package/dist/v1/TinyEssentials.js +7938 -0
- package/dist/v1/TinyEssentials.min.js +2 -0
- package/dist/v1/TinyEssentials.min.js.LICENSE.txt +8 -0
- package/dist/v1/TinyNotifyCenter.js +493 -0
- package/dist/v1/TinyNotifyCenter.min.js +1 -0
- package/dist/v1/TinyToastNotify.js +441 -0
- package/dist/v1/TinyToastNotify.min.js +1 -0
- package/dist/v1/TinyUploadClicker.js +5235 -0
- package/dist/v1/TinyUploadClicker.min.js +2 -0
- package/dist/v1/TinyUploadClicker.min.js.LICENSE.txt +8 -0
- package/dist/v1/basics/fileManager.cjs +524 -0
- package/dist/v1/basics/fileManager.d.mts +209 -0
- package/dist/v1/basics/fileManager.mjs +443 -0
- package/dist/v1/basics/fullScreen.cjs +134 -0
- package/dist/v1/basics/fullScreen.d.mts +8 -0
- package/dist/v1/basics/fullScreen.mjs +121 -0
- package/dist/v1/basics/html.cjs +262 -0
- package/dist/v1/basics/html.d.mts +63 -0
- package/dist/v1/basics/html.mjs +208 -0
- package/dist/v1/basics/index.cjs +40 -0
- package/dist/v1/basics/index.d.mts +38 -1
- package/dist/v1/basics/index.mjs +4 -1
- package/dist/v1/build/TinyDragDropDetector.cjs +7 -0
- package/dist/v1/build/TinyDragDropDetector.d.mts +3 -0
- package/dist/v1/build/TinyDragDropDetector.mjs +2 -0
- package/dist/v1/build/TinyDragger.cjs +7 -0
- package/dist/v1/build/TinyDragger.d.mts +3 -0
- package/dist/v1/build/TinyDragger.mjs +2 -0
- package/dist/v1/build/TinyNotifyCenter.cjs +7 -0
- package/dist/v1/build/TinyNotifyCenter.d.mts +3 -0
- package/dist/v1/build/TinyNotifyCenter.mjs +2 -0
- package/dist/v1/build/TinyToastNotify.cjs +7 -0
- package/dist/v1/build/TinyToastNotify.d.mts +3 -0
- package/dist/v1/build/TinyToastNotify.mjs +2 -0
- package/dist/v1/build/TinyUploadClicker.cjs +7 -0
- package/dist/v1/build/TinyUploadClicker.d.mts +3 -0
- package/dist/v1/build/TinyUploadClicker.mjs +2 -0
- package/dist/v1/css/TinyDraggerExample.css +21 -0
- package/dist/v1/css/TinyDraggerExample.min.css +1 -0
- package/dist/v1/css/TinyNotify.css +350 -0
- package/dist/v1/css/TinyNotify.min.css +1 -0
- package/dist/v1/index.cjs +48 -0
- package/dist/v1/index.d.mts +42 -1
- package/dist/v1/index.mjs +8 -1
- package/dist/v1/libs/TinyDragDropDetector.cjs +268 -0
- package/dist/v1/libs/TinyDragDropDetector.d.mts +123 -0
- package/dist/v1/libs/TinyDragDropDetector.mjs +228 -0
- package/dist/v1/libs/TinyDragger.cjs +801 -0
- package/dist/v1/libs/TinyDragger.d.mts +300 -0
- package/dist/v1/libs/TinyDragger.mjs +689 -0
- package/dist/v1/libs/TinyNotifyCenter.cjs +454 -0
- package/dist/v1/libs/TinyNotifyCenter.d.mts +175 -0
- package/dist/v1/libs/TinyNotifyCenter.mjs +413 -0
- package/dist/v1/libs/TinyToastNotify.cjs +402 -0
- package/dist/v1/libs/TinyToastNotify.d.mts +165 -0
- package/dist/v1/libs/TinyToastNotify.mjs +352 -0
- package/dist/v1/libs/TinyUploadClicker.cjs +223 -0
- package/dist/v1/libs/TinyUploadClicker.d.mts +80 -0
- package/dist/v1/libs/TinyUploadClicker.mjs +182 -0
- package/docs/{README.md → v1/README.md} +8 -0
- package/docs/v1/basics/fileManager.md +222 -0
- package/docs/v1/basics/fullScreen.md +183 -0
- package/docs/v1/basics/html.md +243 -0
- package/docs/{basics → v1/basics}/text.md +2 -2
- package/docs/v1/libs/TinyDragDropDetector.md +143 -0
- package/docs/v1/libs/TinyDragger.md +415 -0
- package/docs/{libs → v1/libs}/TinyLevelUp.md +1 -1
- package/docs/v1/libs/TinyNotifyCenter.md +311 -0
- package/docs/v1/libs/TinyToastNotify.md +309 -0
- package/docs/v1/libs/TinyUploadClicker.md +108 -0
- package/package.json +14 -2
- package/dist/TinyBasicsEs.min.js +0 -2
- package/dist/TinyEssentials.min.js +0 -2
- package/dist/v1/libs/TinyRateLimit.cjs +0 -196
- package/dist/v1/libs/TinyRateLimit.d.mts +0 -86
- package/dist/v1/libs/TinyRateLimit.mjs +0 -171
- /package/dist/{ColorSafeStringify.js → v1/ColorSafeStringify.js} +0 -0
- /package/dist/{ColorSafeStringify.min.js → v1/ColorSafeStringify.min.js} +0 -0
- /package/dist/{TinyBasicsEs.min.js.LICENSE.txt → v1/TinyBasicsEs.min.js.LICENSE.txt} +0 -0
- /package/dist/{TinyEssentials.min.js.LICENSE.txt → v1/TinyDragger.min.js.LICENSE.txt} +0 -0
- /package/dist/{TinyLevelUp.js → v1/TinyLevelUp.js} +0 -0
- /package/dist/{TinyLevelUp.min.js → v1/TinyLevelUp.min.js} +0 -0
- /package/dist/{TinyPromiseQueue.js → v1/TinyPromiseQueue.js} +0 -0
- /package/dist/{TinyPromiseQueue.min.js → v1/TinyPromiseQueue.min.js} +0 -0
- /package/dist/{TinyRateLimiter.js → v1/TinyRateLimiter.js} +0 -0
- /package/dist/{TinyRateLimiter.min.js → v1/TinyRateLimiter.min.js} +0 -0
- /package/dist/{aiMarker.css → v1/css/aiMarker.css} +0 -0
- /package/dist/{aiMarker.min.css → v1/css/aiMarker.min.css} +0 -0
- /package/docs/{basics → v1/basics}/array.md +0 -0
- /package/docs/{basics → v1/basics}/asyncReplace.md +0 -0
- /package/docs/{basics → v1/basics}/clock.md +0 -0
- /package/docs/{basics → v1/basics}/objFilter.md +0 -0
- /package/docs/{basics → v1/basics}/simpleMath.md +0 -0
- /package/docs/{libs → v1/libs}/ColorSafeStringify.md +0 -0
- /package/docs/{libs → v1/libs}/TinyPromiseQueue.md +0 -0
- /package/docs/{libs → v1/libs}/TinyRateLimiter.md +0 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/******/ (() => { // webpackBootstrap
|
|
2
2
|
/******/ var __webpack_modules__ = ({
|
|
3
3
|
|
|
4
|
+
/***/ 41:
|
|
5
|
+
/***/ (() => {
|
|
6
|
+
|
|
7
|
+
/* (ignored) */
|
|
8
|
+
|
|
9
|
+
/***/ }),
|
|
10
|
+
|
|
4
11
|
/***/ 251:
|
|
5
12
|
/***/ ((__unused_webpack_module, exports) => {
|
|
6
13
|
|
|
@@ -2075,6 +2082,735 @@ function fromByteArray (uint8) {
|
|
|
2075
2082
|
}
|
|
2076
2083
|
|
|
2077
2084
|
|
|
2085
|
+
/***/ }),
|
|
2086
|
+
|
|
2087
|
+
/***/ 606:
|
|
2088
|
+
/***/ ((module) => {
|
|
2089
|
+
|
|
2090
|
+
// shim for using process in browser
|
|
2091
|
+
var process = module.exports = {};
|
|
2092
|
+
|
|
2093
|
+
// cached from whatever global is present so that test runners that stub it
|
|
2094
|
+
// don't break things. But we need to wrap it in a try catch in case it is
|
|
2095
|
+
// wrapped in strict mode code which doesn't define any globals. It's inside a
|
|
2096
|
+
// function because try/catches deoptimize in certain engines.
|
|
2097
|
+
|
|
2098
|
+
var cachedSetTimeout;
|
|
2099
|
+
var cachedClearTimeout;
|
|
2100
|
+
|
|
2101
|
+
function defaultSetTimout() {
|
|
2102
|
+
throw new Error('setTimeout has not been defined');
|
|
2103
|
+
}
|
|
2104
|
+
function defaultClearTimeout () {
|
|
2105
|
+
throw new Error('clearTimeout has not been defined');
|
|
2106
|
+
}
|
|
2107
|
+
(function () {
|
|
2108
|
+
try {
|
|
2109
|
+
if (typeof setTimeout === 'function') {
|
|
2110
|
+
cachedSetTimeout = setTimeout;
|
|
2111
|
+
} else {
|
|
2112
|
+
cachedSetTimeout = defaultSetTimout;
|
|
2113
|
+
}
|
|
2114
|
+
} catch (e) {
|
|
2115
|
+
cachedSetTimeout = defaultSetTimout;
|
|
2116
|
+
}
|
|
2117
|
+
try {
|
|
2118
|
+
if (typeof clearTimeout === 'function') {
|
|
2119
|
+
cachedClearTimeout = clearTimeout;
|
|
2120
|
+
} else {
|
|
2121
|
+
cachedClearTimeout = defaultClearTimeout;
|
|
2122
|
+
}
|
|
2123
|
+
} catch (e) {
|
|
2124
|
+
cachedClearTimeout = defaultClearTimeout;
|
|
2125
|
+
}
|
|
2126
|
+
} ())
|
|
2127
|
+
function runTimeout(fun) {
|
|
2128
|
+
if (cachedSetTimeout === setTimeout) {
|
|
2129
|
+
//normal enviroments in sane situations
|
|
2130
|
+
return setTimeout(fun, 0);
|
|
2131
|
+
}
|
|
2132
|
+
// if setTimeout wasn't available but was latter defined
|
|
2133
|
+
if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {
|
|
2134
|
+
cachedSetTimeout = setTimeout;
|
|
2135
|
+
return setTimeout(fun, 0);
|
|
2136
|
+
}
|
|
2137
|
+
try {
|
|
2138
|
+
// when when somebody has screwed with setTimeout but no I.E. maddness
|
|
2139
|
+
return cachedSetTimeout(fun, 0);
|
|
2140
|
+
} catch(e){
|
|
2141
|
+
try {
|
|
2142
|
+
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
|
2143
|
+
return cachedSetTimeout.call(null, fun, 0);
|
|
2144
|
+
} catch(e){
|
|
2145
|
+
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error
|
|
2146
|
+
return cachedSetTimeout.call(this, fun, 0);
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
}
|
|
2152
|
+
function runClearTimeout(marker) {
|
|
2153
|
+
if (cachedClearTimeout === clearTimeout) {
|
|
2154
|
+
//normal enviroments in sane situations
|
|
2155
|
+
return clearTimeout(marker);
|
|
2156
|
+
}
|
|
2157
|
+
// if clearTimeout wasn't available but was latter defined
|
|
2158
|
+
if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {
|
|
2159
|
+
cachedClearTimeout = clearTimeout;
|
|
2160
|
+
return clearTimeout(marker);
|
|
2161
|
+
}
|
|
2162
|
+
try {
|
|
2163
|
+
// when when somebody has screwed with setTimeout but no I.E. maddness
|
|
2164
|
+
return cachedClearTimeout(marker);
|
|
2165
|
+
} catch (e){
|
|
2166
|
+
try {
|
|
2167
|
+
// When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally
|
|
2168
|
+
return cachedClearTimeout.call(null, marker);
|
|
2169
|
+
} catch (e){
|
|
2170
|
+
// same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.
|
|
2171
|
+
// Some versions of I.E. have different rules for clearTimeout vs setTimeout
|
|
2172
|
+
return cachedClearTimeout.call(this, marker);
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
|
|
2177
|
+
|
|
2178
|
+
}
|
|
2179
|
+
var queue = [];
|
|
2180
|
+
var draining = false;
|
|
2181
|
+
var currentQueue;
|
|
2182
|
+
var queueIndex = -1;
|
|
2183
|
+
|
|
2184
|
+
function cleanUpNextTick() {
|
|
2185
|
+
if (!draining || !currentQueue) {
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
draining = false;
|
|
2189
|
+
if (currentQueue.length) {
|
|
2190
|
+
queue = currentQueue.concat(queue);
|
|
2191
|
+
} else {
|
|
2192
|
+
queueIndex = -1;
|
|
2193
|
+
}
|
|
2194
|
+
if (queue.length) {
|
|
2195
|
+
drainQueue();
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
function drainQueue() {
|
|
2200
|
+
if (draining) {
|
|
2201
|
+
return;
|
|
2202
|
+
}
|
|
2203
|
+
var timeout = runTimeout(cleanUpNextTick);
|
|
2204
|
+
draining = true;
|
|
2205
|
+
|
|
2206
|
+
var len = queue.length;
|
|
2207
|
+
while(len) {
|
|
2208
|
+
currentQueue = queue;
|
|
2209
|
+
queue = [];
|
|
2210
|
+
while (++queueIndex < len) {
|
|
2211
|
+
if (currentQueue) {
|
|
2212
|
+
currentQueue[queueIndex].run();
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
queueIndex = -1;
|
|
2216
|
+
len = queue.length;
|
|
2217
|
+
}
|
|
2218
|
+
currentQueue = null;
|
|
2219
|
+
draining = false;
|
|
2220
|
+
runClearTimeout(timeout);
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
process.nextTick = function (fun) {
|
|
2224
|
+
var args = new Array(arguments.length - 1);
|
|
2225
|
+
if (arguments.length > 1) {
|
|
2226
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
2227
|
+
args[i - 1] = arguments[i];
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
queue.push(new Item(fun, args));
|
|
2231
|
+
if (queue.length === 1 && !draining) {
|
|
2232
|
+
runTimeout(drainQueue);
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2236
|
+
// v8 likes predictible objects
|
|
2237
|
+
function Item(fun, array) {
|
|
2238
|
+
this.fun = fun;
|
|
2239
|
+
this.array = array;
|
|
2240
|
+
}
|
|
2241
|
+
Item.prototype.run = function () {
|
|
2242
|
+
this.fun.apply(null, this.array);
|
|
2243
|
+
};
|
|
2244
|
+
process.title = 'browser';
|
|
2245
|
+
process.browser = true;
|
|
2246
|
+
process.env = {};
|
|
2247
|
+
process.argv = [];
|
|
2248
|
+
process.version = ''; // empty string to avoid regexp issues
|
|
2249
|
+
process.versions = {};
|
|
2250
|
+
|
|
2251
|
+
function noop() {}
|
|
2252
|
+
|
|
2253
|
+
process.on = noop;
|
|
2254
|
+
process.addListener = noop;
|
|
2255
|
+
process.once = noop;
|
|
2256
|
+
process.off = noop;
|
|
2257
|
+
process.removeListener = noop;
|
|
2258
|
+
process.removeAllListeners = noop;
|
|
2259
|
+
process.emit = noop;
|
|
2260
|
+
process.prependListener = noop;
|
|
2261
|
+
process.prependOnceListener = noop;
|
|
2262
|
+
|
|
2263
|
+
process.listeners = function (name) { return [] }
|
|
2264
|
+
|
|
2265
|
+
process.binding = function (name) {
|
|
2266
|
+
throw new Error('process.binding is not supported');
|
|
2267
|
+
};
|
|
2268
|
+
|
|
2269
|
+
process.cwd = function () { return '/' };
|
|
2270
|
+
process.chdir = function (dir) {
|
|
2271
|
+
throw new Error('process.chdir is not supported');
|
|
2272
|
+
};
|
|
2273
|
+
process.umask = function() { return 0; };
|
|
2274
|
+
|
|
2275
|
+
|
|
2276
|
+
/***/ }),
|
|
2277
|
+
|
|
2278
|
+
/***/ 975:
|
|
2279
|
+
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
|
2280
|
+
|
|
2281
|
+
"use strict";
|
|
2282
|
+
/* provided dependency */ var process = __webpack_require__(606);
|
|
2283
|
+
// 'path' module extracted from Node.js v8.11.1 (only the posix part)
|
|
2284
|
+
// transplited with Babel
|
|
2285
|
+
|
|
2286
|
+
// Copyright Joyent, Inc. and other Node contributors.
|
|
2287
|
+
//
|
|
2288
|
+
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
2289
|
+
// copy of this software and associated documentation files (the
|
|
2290
|
+
// "Software"), to deal in the Software without restriction, including
|
|
2291
|
+
// without limitation the rights to use, copy, modify, merge, publish,
|
|
2292
|
+
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
2293
|
+
// persons to whom the Software is furnished to do so, subject to the
|
|
2294
|
+
// following conditions:
|
|
2295
|
+
//
|
|
2296
|
+
// The above copyright notice and this permission notice shall be included
|
|
2297
|
+
// in all copies or substantial portions of the Software.
|
|
2298
|
+
//
|
|
2299
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
2300
|
+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
2301
|
+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
2302
|
+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
2303
|
+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
2304
|
+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
2305
|
+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
2306
|
+
|
|
2307
|
+
|
|
2308
|
+
|
|
2309
|
+
function assertPath(path) {
|
|
2310
|
+
if (typeof path !== 'string') {
|
|
2311
|
+
throw new TypeError('Path must be a string. Received ' + JSON.stringify(path));
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// Resolves . and .. elements in a path with directory names
|
|
2316
|
+
function normalizeStringPosix(path, allowAboveRoot) {
|
|
2317
|
+
var res = '';
|
|
2318
|
+
var lastSegmentLength = 0;
|
|
2319
|
+
var lastSlash = -1;
|
|
2320
|
+
var dots = 0;
|
|
2321
|
+
var code;
|
|
2322
|
+
for (var i = 0; i <= path.length; ++i) {
|
|
2323
|
+
if (i < path.length)
|
|
2324
|
+
code = path.charCodeAt(i);
|
|
2325
|
+
else if (code === 47 /*/*/)
|
|
2326
|
+
break;
|
|
2327
|
+
else
|
|
2328
|
+
code = 47 /*/*/;
|
|
2329
|
+
if (code === 47 /*/*/) {
|
|
2330
|
+
if (lastSlash === i - 1 || dots === 1) {
|
|
2331
|
+
// NOOP
|
|
2332
|
+
} else if (lastSlash !== i - 1 && dots === 2) {
|
|
2333
|
+
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 /*.*/ || res.charCodeAt(res.length - 2) !== 46 /*.*/) {
|
|
2334
|
+
if (res.length > 2) {
|
|
2335
|
+
var lastSlashIndex = res.lastIndexOf('/');
|
|
2336
|
+
if (lastSlashIndex !== res.length - 1) {
|
|
2337
|
+
if (lastSlashIndex === -1) {
|
|
2338
|
+
res = '';
|
|
2339
|
+
lastSegmentLength = 0;
|
|
2340
|
+
} else {
|
|
2341
|
+
res = res.slice(0, lastSlashIndex);
|
|
2342
|
+
lastSegmentLength = res.length - 1 - res.lastIndexOf('/');
|
|
2343
|
+
}
|
|
2344
|
+
lastSlash = i;
|
|
2345
|
+
dots = 0;
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
} else if (res.length === 2 || res.length === 1) {
|
|
2349
|
+
res = '';
|
|
2350
|
+
lastSegmentLength = 0;
|
|
2351
|
+
lastSlash = i;
|
|
2352
|
+
dots = 0;
|
|
2353
|
+
continue;
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
if (allowAboveRoot) {
|
|
2357
|
+
if (res.length > 0)
|
|
2358
|
+
res += '/..';
|
|
2359
|
+
else
|
|
2360
|
+
res = '..';
|
|
2361
|
+
lastSegmentLength = 2;
|
|
2362
|
+
}
|
|
2363
|
+
} else {
|
|
2364
|
+
if (res.length > 0)
|
|
2365
|
+
res += '/' + path.slice(lastSlash + 1, i);
|
|
2366
|
+
else
|
|
2367
|
+
res = path.slice(lastSlash + 1, i);
|
|
2368
|
+
lastSegmentLength = i - lastSlash - 1;
|
|
2369
|
+
}
|
|
2370
|
+
lastSlash = i;
|
|
2371
|
+
dots = 0;
|
|
2372
|
+
} else if (code === 46 /*.*/ && dots !== -1) {
|
|
2373
|
+
++dots;
|
|
2374
|
+
} else {
|
|
2375
|
+
dots = -1;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
return res;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
function _format(sep, pathObject) {
|
|
2382
|
+
var dir = pathObject.dir || pathObject.root;
|
|
2383
|
+
var base = pathObject.base || (pathObject.name || '') + (pathObject.ext || '');
|
|
2384
|
+
if (!dir) {
|
|
2385
|
+
return base;
|
|
2386
|
+
}
|
|
2387
|
+
if (dir === pathObject.root) {
|
|
2388
|
+
return dir + base;
|
|
2389
|
+
}
|
|
2390
|
+
return dir + sep + base;
|
|
2391
|
+
}
|
|
2392
|
+
|
|
2393
|
+
var posix = {
|
|
2394
|
+
// path.resolve([from ...], to)
|
|
2395
|
+
resolve: function resolve() {
|
|
2396
|
+
var resolvedPath = '';
|
|
2397
|
+
var resolvedAbsolute = false;
|
|
2398
|
+
var cwd;
|
|
2399
|
+
|
|
2400
|
+
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
|
2401
|
+
var path;
|
|
2402
|
+
if (i >= 0)
|
|
2403
|
+
path = arguments[i];
|
|
2404
|
+
else {
|
|
2405
|
+
if (cwd === undefined)
|
|
2406
|
+
cwd = process.cwd();
|
|
2407
|
+
path = cwd;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
assertPath(path);
|
|
2411
|
+
|
|
2412
|
+
// Skip empty entries
|
|
2413
|
+
if (path.length === 0) {
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
resolvedPath = path + '/' + resolvedPath;
|
|
2418
|
+
resolvedAbsolute = path.charCodeAt(0) === 47 /*/*/;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// At this point the path should be resolved to a full absolute path, but
|
|
2422
|
+
// handle relative paths to be safe (might happen when process.cwd() fails)
|
|
2423
|
+
|
|
2424
|
+
// Normalize the path
|
|
2425
|
+
resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute);
|
|
2426
|
+
|
|
2427
|
+
if (resolvedAbsolute) {
|
|
2428
|
+
if (resolvedPath.length > 0)
|
|
2429
|
+
return '/' + resolvedPath;
|
|
2430
|
+
else
|
|
2431
|
+
return '/';
|
|
2432
|
+
} else if (resolvedPath.length > 0) {
|
|
2433
|
+
return resolvedPath;
|
|
2434
|
+
} else {
|
|
2435
|
+
return '.';
|
|
2436
|
+
}
|
|
2437
|
+
},
|
|
2438
|
+
|
|
2439
|
+
normalize: function normalize(path) {
|
|
2440
|
+
assertPath(path);
|
|
2441
|
+
|
|
2442
|
+
if (path.length === 0) return '.';
|
|
2443
|
+
|
|
2444
|
+
var isAbsolute = path.charCodeAt(0) === 47 /*/*/;
|
|
2445
|
+
var trailingSeparator = path.charCodeAt(path.length - 1) === 47 /*/*/;
|
|
2446
|
+
|
|
2447
|
+
// Normalize the path
|
|
2448
|
+
path = normalizeStringPosix(path, !isAbsolute);
|
|
2449
|
+
|
|
2450
|
+
if (path.length === 0 && !isAbsolute) path = '.';
|
|
2451
|
+
if (path.length > 0 && trailingSeparator) path += '/';
|
|
2452
|
+
|
|
2453
|
+
if (isAbsolute) return '/' + path;
|
|
2454
|
+
return path;
|
|
2455
|
+
},
|
|
2456
|
+
|
|
2457
|
+
isAbsolute: function isAbsolute(path) {
|
|
2458
|
+
assertPath(path);
|
|
2459
|
+
return path.length > 0 && path.charCodeAt(0) === 47 /*/*/;
|
|
2460
|
+
},
|
|
2461
|
+
|
|
2462
|
+
join: function join() {
|
|
2463
|
+
if (arguments.length === 0)
|
|
2464
|
+
return '.';
|
|
2465
|
+
var joined;
|
|
2466
|
+
for (var i = 0; i < arguments.length; ++i) {
|
|
2467
|
+
var arg = arguments[i];
|
|
2468
|
+
assertPath(arg);
|
|
2469
|
+
if (arg.length > 0) {
|
|
2470
|
+
if (joined === undefined)
|
|
2471
|
+
joined = arg;
|
|
2472
|
+
else
|
|
2473
|
+
joined += '/' + arg;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
if (joined === undefined)
|
|
2477
|
+
return '.';
|
|
2478
|
+
return posix.normalize(joined);
|
|
2479
|
+
},
|
|
2480
|
+
|
|
2481
|
+
relative: function relative(from, to) {
|
|
2482
|
+
assertPath(from);
|
|
2483
|
+
assertPath(to);
|
|
2484
|
+
|
|
2485
|
+
if (from === to) return '';
|
|
2486
|
+
|
|
2487
|
+
from = posix.resolve(from);
|
|
2488
|
+
to = posix.resolve(to);
|
|
2489
|
+
|
|
2490
|
+
if (from === to) return '';
|
|
2491
|
+
|
|
2492
|
+
// Trim any leading backslashes
|
|
2493
|
+
var fromStart = 1;
|
|
2494
|
+
for (; fromStart < from.length; ++fromStart) {
|
|
2495
|
+
if (from.charCodeAt(fromStart) !== 47 /*/*/)
|
|
2496
|
+
break;
|
|
2497
|
+
}
|
|
2498
|
+
var fromEnd = from.length;
|
|
2499
|
+
var fromLen = fromEnd - fromStart;
|
|
2500
|
+
|
|
2501
|
+
// Trim any leading backslashes
|
|
2502
|
+
var toStart = 1;
|
|
2503
|
+
for (; toStart < to.length; ++toStart) {
|
|
2504
|
+
if (to.charCodeAt(toStart) !== 47 /*/*/)
|
|
2505
|
+
break;
|
|
2506
|
+
}
|
|
2507
|
+
var toEnd = to.length;
|
|
2508
|
+
var toLen = toEnd - toStart;
|
|
2509
|
+
|
|
2510
|
+
// Compare paths to find the longest common path from root
|
|
2511
|
+
var length = fromLen < toLen ? fromLen : toLen;
|
|
2512
|
+
var lastCommonSep = -1;
|
|
2513
|
+
var i = 0;
|
|
2514
|
+
for (; i <= length; ++i) {
|
|
2515
|
+
if (i === length) {
|
|
2516
|
+
if (toLen > length) {
|
|
2517
|
+
if (to.charCodeAt(toStart + i) === 47 /*/*/) {
|
|
2518
|
+
// We get here if `from` is the exact base path for `to`.
|
|
2519
|
+
// For example: from='/foo/bar'; to='/foo/bar/baz'
|
|
2520
|
+
return to.slice(toStart + i + 1);
|
|
2521
|
+
} else if (i === 0) {
|
|
2522
|
+
// We get here if `from` is the root
|
|
2523
|
+
// For example: from='/'; to='/foo'
|
|
2524
|
+
return to.slice(toStart + i);
|
|
2525
|
+
}
|
|
2526
|
+
} else if (fromLen > length) {
|
|
2527
|
+
if (from.charCodeAt(fromStart + i) === 47 /*/*/) {
|
|
2528
|
+
// We get here if `to` is the exact base path for `from`.
|
|
2529
|
+
// For example: from='/foo/bar/baz'; to='/foo/bar'
|
|
2530
|
+
lastCommonSep = i;
|
|
2531
|
+
} else if (i === 0) {
|
|
2532
|
+
// We get here if `to` is the root.
|
|
2533
|
+
// For example: from='/foo'; to='/'
|
|
2534
|
+
lastCommonSep = 0;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
break;
|
|
2538
|
+
}
|
|
2539
|
+
var fromCode = from.charCodeAt(fromStart + i);
|
|
2540
|
+
var toCode = to.charCodeAt(toStart + i);
|
|
2541
|
+
if (fromCode !== toCode)
|
|
2542
|
+
break;
|
|
2543
|
+
else if (fromCode === 47 /*/*/)
|
|
2544
|
+
lastCommonSep = i;
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
var out = '';
|
|
2548
|
+
// Generate the relative path based on the path difference between `to`
|
|
2549
|
+
// and `from`
|
|
2550
|
+
for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
|
|
2551
|
+
if (i === fromEnd || from.charCodeAt(i) === 47 /*/*/) {
|
|
2552
|
+
if (out.length === 0)
|
|
2553
|
+
out += '..';
|
|
2554
|
+
else
|
|
2555
|
+
out += '/..';
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
// Lastly, append the rest of the destination (`to`) path that comes after
|
|
2560
|
+
// the common path parts
|
|
2561
|
+
if (out.length > 0)
|
|
2562
|
+
return out + to.slice(toStart + lastCommonSep);
|
|
2563
|
+
else {
|
|
2564
|
+
toStart += lastCommonSep;
|
|
2565
|
+
if (to.charCodeAt(toStart) === 47 /*/*/)
|
|
2566
|
+
++toStart;
|
|
2567
|
+
return to.slice(toStart);
|
|
2568
|
+
}
|
|
2569
|
+
},
|
|
2570
|
+
|
|
2571
|
+
_makeLong: function _makeLong(path) {
|
|
2572
|
+
return path;
|
|
2573
|
+
},
|
|
2574
|
+
|
|
2575
|
+
dirname: function dirname(path) {
|
|
2576
|
+
assertPath(path);
|
|
2577
|
+
if (path.length === 0) return '.';
|
|
2578
|
+
var code = path.charCodeAt(0);
|
|
2579
|
+
var hasRoot = code === 47 /*/*/;
|
|
2580
|
+
var end = -1;
|
|
2581
|
+
var matchedSlash = true;
|
|
2582
|
+
for (var i = path.length - 1; i >= 1; --i) {
|
|
2583
|
+
code = path.charCodeAt(i);
|
|
2584
|
+
if (code === 47 /*/*/) {
|
|
2585
|
+
if (!matchedSlash) {
|
|
2586
|
+
end = i;
|
|
2587
|
+
break;
|
|
2588
|
+
}
|
|
2589
|
+
} else {
|
|
2590
|
+
// We saw the first non-path separator
|
|
2591
|
+
matchedSlash = false;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
if (end === -1) return hasRoot ? '/' : '.';
|
|
2596
|
+
if (hasRoot && end === 1) return '//';
|
|
2597
|
+
return path.slice(0, end);
|
|
2598
|
+
},
|
|
2599
|
+
|
|
2600
|
+
basename: function basename(path, ext) {
|
|
2601
|
+
if (ext !== undefined && typeof ext !== 'string') throw new TypeError('"ext" argument must be a string');
|
|
2602
|
+
assertPath(path);
|
|
2603
|
+
|
|
2604
|
+
var start = 0;
|
|
2605
|
+
var end = -1;
|
|
2606
|
+
var matchedSlash = true;
|
|
2607
|
+
var i;
|
|
2608
|
+
|
|
2609
|
+
if (ext !== undefined && ext.length > 0 && ext.length <= path.length) {
|
|
2610
|
+
if (ext.length === path.length && ext === path) return '';
|
|
2611
|
+
var extIdx = ext.length - 1;
|
|
2612
|
+
var firstNonSlashEnd = -1;
|
|
2613
|
+
for (i = path.length - 1; i >= 0; --i) {
|
|
2614
|
+
var code = path.charCodeAt(i);
|
|
2615
|
+
if (code === 47 /*/*/) {
|
|
2616
|
+
// If we reached a path separator that was not part of a set of path
|
|
2617
|
+
// separators at the end of the string, stop now
|
|
2618
|
+
if (!matchedSlash) {
|
|
2619
|
+
start = i + 1;
|
|
2620
|
+
break;
|
|
2621
|
+
}
|
|
2622
|
+
} else {
|
|
2623
|
+
if (firstNonSlashEnd === -1) {
|
|
2624
|
+
// We saw the first non-path separator, remember this index in case
|
|
2625
|
+
// we need it if the extension ends up not matching
|
|
2626
|
+
matchedSlash = false;
|
|
2627
|
+
firstNonSlashEnd = i + 1;
|
|
2628
|
+
}
|
|
2629
|
+
if (extIdx >= 0) {
|
|
2630
|
+
// Try to match the explicit extension
|
|
2631
|
+
if (code === ext.charCodeAt(extIdx)) {
|
|
2632
|
+
if (--extIdx === -1) {
|
|
2633
|
+
// We matched the extension, so mark this as the end of our path
|
|
2634
|
+
// component
|
|
2635
|
+
end = i;
|
|
2636
|
+
}
|
|
2637
|
+
} else {
|
|
2638
|
+
// Extension does not match, so our result is the entire path
|
|
2639
|
+
// component
|
|
2640
|
+
extIdx = -1;
|
|
2641
|
+
end = firstNonSlashEnd;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
if (start === end) end = firstNonSlashEnd;else if (end === -1) end = path.length;
|
|
2648
|
+
return path.slice(start, end);
|
|
2649
|
+
} else {
|
|
2650
|
+
for (i = path.length - 1; i >= 0; --i) {
|
|
2651
|
+
if (path.charCodeAt(i) === 47 /*/*/) {
|
|
2652
|
+
// If we reached a path separator that was not part of a set of path
|
|
2653
|
+
// separators at the end of the string, stop now
|
|
2654
|
+
if (!matchedSlash) {
|
|
2655
|
+
start = i + 1;
|
|
2656
|
+
break;
|
|
2657
|
+
}
|
|
2658
|
+
} else if (end === -1) {
|
|
2659
|
+
// We saw the first non-path separator, mark this as the end of our
|
|
2660
|
+
// path component
|
|
2661
|
+
matchedSlash = false;
|
|
2662
|
+
end = i + 1;
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
if (end === -1) return '';
|
|
2667
|
+
return path.slice(start, end);
|
|
2668
|
+
}
|
|
2669
|
+
},
|
|
2670
|
+
|
|
2671
|
+
extname: function extname(path) {
|
|
2672
|
+
assertPath(path);
|
|
2673
|
+
var startDot = -1;
|
|
2674
|
+
var startPart = 0;
|
|
2675
|
+
var end = -1;
|
|
2676
|
+
var matchedSlash = true;
|
|
2677
|
+
// Track the state of characters (if any) we see before our first dot and
|
|
2678
|
+
// after any path separator we find
|
|
2679
|
+
var preDotState = 0;
|
|
2680
|
+
for (var i = path.length - 1; i >= 0; --i) {
|
|
2681
|
+
var code = path.charCodeAt(i);
|
|
2682
|
+
if (code === 47 /*/*/) {
|
|
2683
|
+
// If we reached a path separator that was not part of a set of path
|
|
2684
|
+
// separators at the end of the string, stop now
|
|
2685
|
+
if (!matchedSlash) {
|
|
2686
|
+
startPart = i + 1;
|
|
2687
|
+
break;
|
|
2688
|
+
}
|
|
2689
|
+
continue;
|
|
2690
|
+
}
|
|
2691
|
+
if (end === -1) {
|
|
2692
|
+
// We saw the first non-path separator, mark this as the end of our
|
|
2693
|
+
// extension
|
|
2694
|
+
matchedSlash = false;
|
|
2695
|
+
end = i + 1;
|
|
2696
|
+
}
|
|
2697
|
+
if (code === 46 /*.*/) {
|
|
2698
|
+
// If this is our first dot, mark it as the start of our extension
|
|
2699
|
+
if (startDot === -1)
|
|
2700
|
+
startDot = i;
|
|
2701
|
+
else if (preDotState !== 1)
|
|
2702
|
+
preDotState = 1;
|
|
2703
|
+
} else if (startDot !== -1) {
|
|
2704
|
+
// We saw a non-dot and non-path separator before our dot, so we should
|
|
2705
|
+
// have a good chance at having a non-empty extension
|
|
2706
|
+
preDotState = -1;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
if (startDot === -1 || end === -1 ||
|
|
2711
|
+
// We saw a non-dot character immediately before the dot
|
|
2712
|
+
preDotState === 0 ||
|
|
2713
|
+
// The (right-most) trimmed path component is exactly '..'
|
|
2714
|
+
preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
|
2715
|
+
return '';
|
|
2716
|
+
}
|
|
2717
|
+
return path.slice(startDot, end);
|
|
2718
|
+
},
|
|
2719
|
+
|
|
2720
|
+
format: function format(pathObject) {
|
|
2721
|
+
if (pathObject === null || typeof pathObject !== 'object') {
|
|
2722
|
+
throw new TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
|
|
2723
|
+
}
|
|
2724
|
+
return _format('/', pathObject);
|
|
2725
|
+
},
|
|
2726
|
+
|
|
2727
|
+
parse: function parse(path) {
|
|
2728
|
+
assertPath(path);
|
|
2729
|
+
|
|
2730
|
+
var ret = { root: '', dir: '', base: '', ext: '', name: '' };
|
|
2731
|
+
if (path.length === 0) return ret;
|
|
2732
|
+
var code = path.charCodeAt(0);
|
|
2733
|
+
var isAbsolute = code === 47 /*/*/;
|
|
2734
|
+
var start;
|
|
2735
|
+
if (isAbsolute) {
|
|
2736
|
+
ret.root = '/';
|
|
2737
|
+
start = 1;
|
|
2738
|
+
} else {
|
|
2739
|
+
start = 0;
|
|
2740
|
+
}
|
|
2741
|
+
var startDot = -1;
|
|
2742
|
+
var startPart = 0;
|
|
2743
|
+
var end = -1;
|
|
2744
|
+
var matchedSlash = true;
|
|
2745
|
+
var i = path.length - 1;
|
|
2746
|
+
|
|
2747
|
+
// Track the state of characters (if any) we see before our first dot and
|
|
2748
|
+
// after any path separator we find
|
|
2749
|
+
var preDotState = 0;
|
|
2750
|
+
|
|
2751
|
+
// Get non-dir info
|
|
2752
|
+
for (; i >= start; --i) {
|
|
2753
|
+
code = path.charCodeAt(i);
|
|
2754
|
+
if (code === 47 /*/*/) {
|
|
2755
|
+
// If we reached a path separator that was not part of a set of path
|
|
2756
|
+
// separators at the end of the string, stop now
|
|
2757
|
+
if (!matchedSlash) {
|
|
2758
|
+
startPart = i + 1;
|
|
2759
|
+
break;
|
|
2760
|
+
}
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
if (end === -1) {
|
|
2764
|
+
// We saw the first non-path separator, mark this as the end of our
|
|
2765
|
+
// extension
|
|
2766
|
+
matchedSlash = false;
|
|
2767
|
+
end = i + 1;
|
|
2768
|
+
}
|
|
2769
|
+
if (code === 46 /*.*/) {
|
|
2770
|
+
// If this is our first dot, mark it as the start of our extension
|
|
2771
|
+
if (startDot === -1) startDot = i;else if (preDotState !== 1) preDotState = 1;
|
|
2772
|
+
} else if (startDot !== -1) {
|
|
2773
|
+
// We saw a non-dot and non-path separator before our dot, so we should
|
|
2774
|
+
// have a good chance at having a non-empty extension
|
|
2775
|
+
preDotState = -1;
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
if (startDot === -1 || end === -1 ||
|
|
2780
|
+
// We saw a non-dot character immediately before the dot
|
|
2781
|
+
preDotState === 0 ||
|
|
2782
|
+
// The (right-most) trimmed path component is exactly '..'
|
|
2783
|
+
preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
|
2784
|
+
if (end !== -1) {
|
|
2785
|
+
if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(1, end);else ret.base = ret.name = path.slice(startPart, end);
|
|
2786
|
+
}
|
|
2787
|
+
} else {
|
|
2788
|
+
if (startPart === 0 && isAbsolute) {
|
|
2789
|
+
ret.name = path.slice(1, startDot);
|
|
2790
|
+
ret.base = path.slice(1, end);
|
|
2791
|
+
} else {
|
|
2792
|
+
ret.name = path.slice(startPart, startDot);
|
|
2793
|
+
ret.base = path.slice(startPart, end);
|
|
2794
|
+
}
|
|
2795
|
+
ret.ext = path.slice(startDot, end);
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
if (startPart > 0) ret.dir = path.slice(0, startPart - 1);else if (isAbsolute) ret.dir = '/';
|
|
2799
|
+
|
|
2800
|
+
return ret;
|
|
2801
|
+
},
|
|
2802
|
+
|
|
2803
|
+
sep: '/',
|
|
2804
|
+
delimiter: ':',
|
|
2805
|
+
win32: null,
|
|
2806
|
+
posix: null
|
|
2807
|
+
};
|
|
2808
|
+
|
|
2809
|
+
posix.posix = posix;
|
|
2810
|
+
|
|
2811
|
+
module.exports = posix;
|
|
2812
|
+
|
|
2813
|
+
|
|
2078
2814
|
/***/ })
|
|
2079
2815
|
|
|
2080
2816
|
/******/ });
|
|
@@ -2142,33 +2878,93 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
2142
2878
|
|
|
2143
2879
|
// EXPORTS
|
|
2144
2880
|
__webpack_require__.d(__webpack_exports__, {
|
|
2145
|
-
ColorSafeStringify: () => (/* reexport */ libs_ColorSafeStringify),
|
|
2146
|
-
TinyLevelUp: () => (/* reexport */ userLevel),
|
|
2147
|
-
TinyPromiseQueue: () => (/* reexport */ libs_TinyPromiseQueue),
|
|
2148
|
-
TinyRateLimiter: () => (/* reexport */ libs_TinyRateLimiter),
|
|
2149
2881
|
addAiMarkerShortcut: () => (/* reexport */ addAiMarkerShortcut),
|
|
2882
|
+
areHtmlElsColliding: () => (/* reexport */ areHtmlElsColliding),
|
|
2150
2883
|
arraySortPositions: () => (/* reexport */ arraySortPositions),
|
|
2151
2884
|
asyncReplace: () => (/* reexport */ asyncReplace),
|
|
2152
|
-
|
|
2885
|
+
backupFile: () => (/* reexport */ backupFile),
|
|
2886
|
+
clearDirectory: () => (/* reexport */ clearDirectory),
|
|
2153
2887
|
cloneObjTypeOrder: () => (/* reexport */ cloneObjTypeOrder),
|
|
2154
2888
|
countObj: () => (/* reexport */ countObj),
|
|
2889
|
+
dirExists: () => (/* reexport */ dirExists),
|
|
2890
|
+
dirSize: () => (/* reexport */ dirSize),
|
|
2891
|
+
documentIsFullScreen: () => (/* reexport */ documentIsFullScreen),
|
|
2892
|
+
ensureCopyFile: () => (/* reexport */ ensureCopyFile),
|
|
2893
|
+
ensureDirectory: () => (/* reexport */ ensureDirectory),
|
|
2894
|
+
exitFullScreen: () => (/* reexport */ exitFullScreen),
|
|
2155
2895
|
extendObjType: () => (/* reexport */ extendObjType),
|
|
2896
|
+
fetchJson: () => (/* reexport */ fetchJson),
|
|
2897
|
+
fileExists: () => (/* reexport */ fileExists),
|
|
2898
|
+
fileSize: () => (/* reexport */ fileSize),
|
|
2156
2899
|
formatBytes: () => (/* reexport */ formatBytes),
|
|
2157
2900
|
formatCustomTimer: () => (/* reexport */ formatCustomTimer),
|
|
2158
2901
|
formatDayTimer: () => (/* reexport */ formatDayTimer),
|
|
2159
2902
|
formatTimer: () => (/* reexport */ formatTimer),
|
|
2160
2903
|
getAge: () => (/* reexport */ getAge),
|
|
2904
|
+
getHtmlElBorders: () => (/* reexport */ getHtmlElBorders),
|
|
2905
|
+
getHtmlElBordersWidth: () => (/* reexport */ getHtmlElBordersWidth),
|
|
2906
|
+
getHtmlElMargin: () => (/* reexport */ getHtmlElMargin),
|
|
2907
|
+
getHtmlElPadding: () => (/* reexport */ getHtmlElPadding),
|
|
2908
|
+
getLatestBackupPath: () => (/* reexport */ getLatestBackupPath),
|
|
2161
2909
|
getSimplePerc: () => (/* reexport */ getSimplePerc),
|
|
2162
2910
|
getTimeDuration: () => (/* reexport */ getTimeDuration),
|
|
2911
|
+
isDirEmpty: () => (/* reexport */ isDirEmpty),
|
|
2912
|
+
isFullScreenMode: () => (/* reexport */ isFullScreenMode),
|
|
2163
2913
|
isJsonObject: () => (/* reexport */ isJsonObject),
|
|
2914
|
+
isScreenFilled: () => (/* reexport */ isScreenFilled),
|
|
2915
|
+
listDirs: () => (/* reexport */ listDirs),
|
|
2916
|
+
listFiles: () => (/* reexport */ listFiles),
|
|
2164
2917
|
objType: () => (/* reexport */ objType),
|
|
2918
|
+
offFullScreenChange: () => (/* reexport */ offFullScreenChange),
|
|
2919
|
+
onFullScreenChange: () => (/* reexport */ onFullScreenChange),
|
|
2920
|
+
readJsonBlob: () => (/* reexport */ readJsonBlob),
|
|
2921
|
+
readJsonFile: () => (/* reexport */ readJsonFile),
|
|
2922
|
+
renameFileAddPrefixSuffix: () => (/* reexport */ renameFileAddPrefixSuffix),
|
|
2923
|
+
renameFileBatch: () => (/* reexport */ renameFileBatch),
|
|
2924
|
+
renameFileNormalizeCase: () => (/* reexport */ renameFileNormalizeCase),
|
|
2925
|
+
renameFilePadNumbers: () => (/* reexport */ renameFilePadNumbers),
|
|
2926
|
+
renameFileRegex: () => (/* reexport */ renameFileRegex),
|
|
2165
2927
|
reorderObjTypeOrder: () => (/* reexport */ reorderObjTypeOrder),
|
|
2928
|
+
requestFullScreen: () => (/* reexport */ requestFullScreen),
|
|
2929
|
+
restoreLatestBackup: () => (/* reexport */ restoreLatestBackup),
|
|
2166
2930
|
ruleOfThree: () => (/* reexport */ ruleOfThree),
|
|
2931
|
+
saveJsonFile: () => (/* reexport */ saveJsonFile),
|
|
2167
2932
|
shuffleArray: () => (/* reexport */ shuffleArray),
|
|
2168
2933
|
toTitleCase: () => (/* reexport */ toTitleCase),
|
|
2169
|
-
toTitleCaseLowerFirst: () => (/* reexport */ toTitleCaseLowerFirst)
|
|
2934
|
+
toTitleCaseLowerFirst: () => (/* reexport */ toTitleCaseLowerFirst),
|
|
2935
|
+
tryDeleteFile: () => (/* reexport */ tryDeleteFile),
|
|
2936
|
+
writeJsonFile: () => (/* reexport */ writeJsonFile),
|
|
2937
|
+
writeTextFile: () => (/* reexport */ writeTextFile)
|
|
2170
2938
|
});
|
|
2171
2939
|
|
|
2940
|
+
;// ./src/legacy/libs/arraySortPositions.mjs
|
|
2941
|
+
/**
|
|
2942
|
+
* Generates a comparator function to sort an array of objects by a given key.
|
|
2943
|
+
*
|
|
2944
|
+
* @param {string} item - The object key to sort by.
|
|
2945
|
+
* @param {boolean} [isReverse=false] - If `true`, the sorting will be in descending order.
|
|
2946
|
+
* @returns {(a: Object<string|number, *>, b: Object<string|number, *>) => number} Comparator function compatible with Array.prototype.sort().
|
|
2947
|
+
*
|
|
2948
|
+
* @example
|
|
2949
|
+
* const arr = [{ pos: 2 }, { pos: 1 }, { pos: 3 }];
|
|
2950
|
+
* arr.sort(arraySortPositions('pos')); // Ascending: [{pos: 1}, {pos: 2}, {pos: 3}]
|
|
2951
|
+
*
|
|
2952
|
+
* @example
|
|
2953
|
+
* const arr = [{ pos: 2 }, { pos: 1 }, { pos: 3 }];
|
|
2954
|
+
* arr.sort(arraySortPositions('pos', true)); // Descending: [{pos: 3}, {pos: 2}, {pos: 1}]
|
|
2955
|
+
*/
|
|
2956
|
+
function arraySortPositions(item, isReverse = false) {
|
|
2957
|
+
if (!isReverse) {
|
|
2958
|
+
return function (a, b) {
|
|
2959
|
+
return a[item] < b[item] ? -1 : a[item] > b[item] ? 1 : 0;
|
|
2960
|
+
};
|
|
2961
|
+
} else {
|
|
2962
|
+
return function (a, b) {
|
|
2963
|
+
return a[item] > b[item] ? -1 : a[item] < b[item] ? 1 : 0;
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2172
2968
|
;// ./src/legacy/libs/replaceAsync.mjs
|
|
2173
2969
|
/**
|
|
2174
2970
|
* Asynchronously replaces matches in a string using a regex and an async function.
|
|
@@ -2202,348 +2998,68 @@ async function asyncReplace(str, regex, asyncFn) {
|
|
|
2202
2998
|
return str.replace(regex, () => data.shift());
|
|
2203
2999
|
}
|
|
2204
3000
|
|
|
2205
|
-
;// ./src/
|
|
3001
|
+
;// ./src/v1/basics/array.mjs
|
|
3002
|
+
// https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
|
3003
|
+
|
|
2206
3004
|
/**
|
|
2207
|
-
*
|
|
3005
|
+
* Randomly shuffles the elements of an array in place using the Fisher–Yates algorithm.
|
|
2208
3006
|
*
|
|
2209
|
-
*
|
|
2210
|
-
*
|
|
2211
|
-
*
|
|
2212
|
-
* @
|
|
3007
|
+
* This implementation ensures a uniform distribution of permutations.
|
|
3008
|
+
* Original algorithm source: StackOverflow (link above).
|
|
3009
|
+
*
|
|
3010
|
+
* @param {any[]} items - The array to shuffle.
|
|
3011
|
+
* @returns {any[]} The same array instance, now shuffled in place.
|
|
2213
3012
|
*/
|
|
3013
|
+
function shuffleArray(items) {
|
|
3014
|
+
let currentIndex = items.length,
|
|
3015
|
+
randomIndex;
|
|
3016
|
+
|
|
3017
|
+
// While there remain elements to shuffle...
|
|
3018
|
+
while (currentIndex !== 0) {
|
|
3019
|
+
// Pick a remaining element...
|
|
3020
|
+
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
3021
|
+
currentIndex--;
|
|
3022
|
+
|
|
3023
|
+
// And swap it with the current element.
|
|
3024
|
+
[items[currentIndex], items[randomIndex]] = [items[randomIndex], items[currentIndex]];
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
return items;
|
|
3028
|
+
}
|
|
2214
3029
|
|
|
3030
|
+
;// ./src/v1/basics/clock.mjs
|
|
2215
3031
|
/**
|
|
2216
|
-
*
|
|
3032
|
+
* Calculates the time duration between the current time and a given time offset.
|
|
3033
|
+
*
|
|
3034
|
+
* @param {Date} timeData - The target time as a Date object.
|
|
3035
|
+
* @param {string} [durationType='asSeconds'] - The type of duration to return. Can be 'asMilliseconds', 'asSeconds', 'asMinutes', 'asHours', 'asDays'.
|
|
3036
|
+
* @param {Date|null} [now=null] - The current time as a Date object. Defaults to the current date and time if not provided.
|
|
3037
|
+
* @returns {number|null} The calculated duration in the specified unit, or `null` if `timeData` is not provided.
|
|
2217
3038
|
*/
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
*/
|
|
2224
|
-
constructor(giveExp, expLevel) {
|
|
2225
|
-
if (typeof giveExp !== 'number' || Number.isNaN(giveExp))
|
|
2226
|
-
throw new Error('giveExp must be a valid number');
|
|
2227
|
-
if (typeof expLevel !== 'number' || Number.isNaN(expLevel))
|
|
2228
|
-
throw new Error('expLevel must be a valid number');
|
|
2229
|
-
this.giveExp = giveExp;
|
|
2230
|
-
this.expLevel = expLevel;
|
|
2231
|
-
}
|
|
3039
|
+
function getTimeDuration(timeData = new Date(), durationType = 'asSeconds', now = null) {
|
|
3040
|
+
if (timeData instanceof Date) {
|
|
3041
|
+
const currentTime = now instanceof Date ? now : new Date();
|
|
3042
|
+
/** @type {number} */
|
|
3043
|
+
const diffMs = timeData.getTime() - currentTime.getTime();
|
|
2232
3044
|
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
3045
|
+
switch (durationType) {
|
|
3046
|
+
case 'asMilliseconds':
|
|
3047
|
+
return diffMs;
|
|
3048
|
+
case 'asSeconds':
|
|
3049
|
+
return diffMs / 1000;
|
|
3050
|
+
case 'asMinutes':
|
|
3051
|
+
return diffMs / (1000 * 60);
|
|
3052
|
+
case 'asHours':
|
|
3053
|
+
return diffMs / (1000 * 60 * 60);
|
|
3054
|
+
case 'asDays':
|
|
3055
|
+
return diffMs / (1000 * 60 * 60 * 24);
|
|
3056
|
+
default:
|
|
3057
|
+
return diffMs / 1000; // default to seconds
|
|
3058
|
+
}
|
|
2243
3059
|
}
|
|
2244
3060
|
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
* Throws an error if any property is invalid.
|
|
2248
|
-
*
|
|
2249
|
-
* @param {UserEditor} user - The user object to validate.
|
|
2250
|
-
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2251
|
-
*/
|
|
2252
|
-
validateUser(user) {
|
|
2253
|
-
if (typeof user.exp !== 'number' || Number.isNaN(user.exp))
|
|
2254
|
-
throw new Error('exp must be a valid number');
|
|
2255
|
-
if (typeof user.level !== 'number' || Number.isNaN(user.level))
|
|
2256
|
-
throw new Error('level must be a valid number');
|
|
2257
|
-
if (user.level < 1) throw new Error('level must be at least 1');
|
|
2258
|
-
if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp))
|
|
2259
|
-
throw new Error('totalExp must be a valid number');
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
/**
|
|
2263
|
-
* Checks if the given user object is valid by verifying its numeric properties.
|
|
2264
|
-
*
|
|
2265
|
-
* @param {UserEditor} user - The user object to check.
|
|
2266
|
-
* @returns {boolean} `true` if all properties (exp, level, totalExp) are valid numbers; otherwise `false`.
|
|
2267
|
-
*/
|
|
2268
|
-
isValidUser(user) {
|
|
2269
|
-
if (typeof user.exp !== 'number' || Number.isNaN(user.exp)) return false;
|
|
2270
|
-
if (typeof user.level !== 'number' || Number.isNaN(user.level)) return false;
|
|
2271
|
-
if (user.level < 1) return false;
|
|
2272
|
-
if (typeof user.totalExp !== 'number' || Number.isNaN(user.totalExp)) return false;
|
|
2273
|
-
return true;
|
|
2274
|
-
}
|
|
2275
|
-
|
|
2276
|
-
/**
|
|
2277
|
-
* Returns the base experience value used for random experience generation.
|
|
2278
|
-
* Throws an error if the internal giveExp value is not a valid number.
|
|
2279
|
-
*
|
|
2280
|
-
* @returns {number} The base experience value.
|
|
2281
|
-
* @throws {Error} If giveExp is not a valid number.
|
|
2282
|
-
*/
|
|
2283
|
-
getGiveExpBase() {
|
|
2284
|
-
if (typeof this.giveExp !== 'number' || Number.isNaN(this.giveExp))
|
|
2285
|
-
throw new Error('giveExp must be a valid number');
|
|
2286
|
-
return this.giveExp;
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
/**
|
|
2290
|
-
* Returns the base experience required to level up.
|
|
2291
|
-
* Throws an error if the internal expLevel value is not a valid number.
|
|
2292
|
-
*
|
|
2293
|
-
* @returns {number} The base experience needed per level.
|
|
2294
|
-
* @throws {Error} If expLevel is not a valid number.
|
|
2295
|
-
*/
|
|
2296
|
-
getExpLevelBase() {
|
|
2297
|
-
if (typeof this.expLevel !== 'number' || Number.isNaN(this.expLevel))
|
|
2298
|
-
throw new Error('expLevel must be a valid number');
|
|
2299
|
-
return this.expLevel;
|
|
2300
|
-
}
|
|
2301
|
-
|
|
2302
|
-
/**
|
|
2303
|
-
* Validates and adjusts the user's level based on their current experience.
|
|
2304
|
-
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
2305
|
-
* @returns {UserEditor} The updated user object.
|
|
2306
|
-
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2307
|
-
*/
|
|
2308
|
-
expValidator(user) {
|
|
2309
|
-
const expLevel = this.getExpLevelBase();
|
|
2310
|
-
this.validateUser(user);
|
|
2311
|
-
|
|
2312
|
-
let extraValue = 0;
|
|
2313
|
-
const nextLevelExp = expLevel * user.level;
|
|
2314
|
-
|
|
2315
|
-
// Level Up
|
|
2316
|
-
if (user.exp >= nextLevelExp) {
|
|
2317
|
-
user.level++;
|
|
2318
|
-
extraValue = user.exp - nextLevelExp;
|
|
2319
|
-
user.exp = 0;
|
|
2320
|
-
|
|
2321
|
-
if (extraValue > 0) return this.give(user, extraValue, 'extra');
|
|
2322
|
-
}
|
|
2323
|
-
|
|
2324
|
-
// Level Down
|
|
2325
|
-
if (user.exp < 1 && user.level > 1) {
|
|
2326
|
-
user.level--;
|
|
2327
|
-
extraValue = Math.abs(user.exp);
|
|
2328
|
-
user.exp = expLevel * user.level;
|
|
2329
|
-
|
|
2330
|
-
if (extraValue > 0) return this.remove(user, extraValue, 'extra');
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
return user;
|
|
2334
|
-
}
|
|
2335
|
-
|
|
2336
|
-
/**
|
|
2337
|
-
* Calculates the total experience based on the user's level.
|
|
2338
|
-
* @param {UserEditor} user - The user object containing experience and level properties.
|
|
2339
|
-
* @returns {number} The total experience of the user.
|
|
2340
|
-
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2341
|
-
*/
|
|
2342
|
-
getTotalExp(user) {
|
|
2343
|
-
this.validateUser(user);
|
|
2344
|
-
let totalExp = 0;
|
|
2345
|
-
for (let p = 1; p <= user.level; p++) totalExp += this.getExpLevelBase() * p;
|
|
2346
|
-
totalExp += user.exp;
|
|
2347
|
-
return totalExp;
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
/**
|
|
2351
|
-
* Generates random experience points based on the configured multiplier.
|
|
2352
|
-
* @param {number} [multi] - A multiplier for the generated experience.
|
|
2353
|
-
* @returns {number} The generated experience points.
|
|
2354
|
-
*/
|
|
2355
|
-
expGenerator(multi = 1) {
|
|
2356
|
-
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
2357
|
-
throw new Error('multi must be a valid number');
|
|
2358
|
-
return Math.floor(Math.random() * this.getGiveExpBase()) * multi;
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
/**
|
|
2362
|
-
* Calculates how much experience is missing to next level.
|
|
2363
|
-
* @param {UserEditor} user
|
|
2364
|
-
* @returns {number}
|
|
2365
|
-
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2366
|
-
*/
|
|
2367
|
-
getMissingExp(user) {
|
|
2368
|
-
return this.getProgress(user) - user.exp;
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
/**
|
|
2372
|
-
* Gets the experience points required to reach the next level.
|
|
2373
|
-
* @param {UserEditor} user - The user object containing the level.
|
|
2374
|
-
* @returns {number} The experience required for the next level.
|
|
2375
|
-
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2376
|
-
*/
|
|
2377
|
-
progress(user) {
|
|
2378
|
-
return this.getProgress(user);
|
|
2379
|
-
}
|
|
2380
|
-
|
|
2381
|
-
/**
|
|
2382
|
-
* Gets the experience points required to reach the next level.
|
|
2383
|
-
* @param {UserEditor} user - The user object containing the level.
|
|
2384
|
-
* @returns {number} The experience required for the next level.
|
|
2385
|
-
* @throws {Error} If any property (exp, level, totalExp) is not a valid number.
|
|
2386
|
-
*/
|
|
2387
|
-
getProgress(user) {
|
|
2388
|
-
this.validateUser(user);
|
|
2389
|
-
return this.getExpLevelBase() * user.level;
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
/**
|
|
2393
|
-
* Sets the experience value for the user, adjusting their level if necessary.
|
|
2394
|
-
* @param {UserEditor} user - The user object.
|
|
2395
|
-
* @param {number} value - The new experience value to set.
|
|
2396
|
-
* @returns {UserEditor} The updated user object.
|
|
2397
|
-
*/
|
|
2398
|
-
set(user, value) {
|
|
2399
|
-
if (typeof value !== 'number' || Number.isNaN(value))
|
|
2400
|
-
throw new Error('value must be a valid number');
|
|
2401
|
-
|
|
2402
|
-
user.exp = value;
|
|
2403
|
-
this.expValidator(user);
|
|
2404
|
-
user.totalExp = this.getTotalExp(user);
|
|
2405
|
-
return user;
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
/**
|
|
2409
|
-
* Adds experience to the user, adjusting their level if necessary.
|
|
2410
|
-
* @param {UserEditor} user - The user object.
|
|
2411
|
-
* @param {number} [extraExp] - Additional experience to be added.
|
|
2412
|
-
* @param {'add' | 'extra'} [type] - Type of addition ('add' or 'extra').
|
|
2413
|
-
* @param {number} [multi] - Multiplier for experience generation.
|
|
2414
|
-
* @returns {UserEditor} The updated user object.
|
|
2415
|
-
*/
|
|
2416
|
-
give(user, extraExp = 0, type = 'add', multi = 1) {
|
|
2417
|
-
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
2418
|
-
throw new Error('multi must be a valid number');
|
|
2419
|
-
if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
|
|
2420
|
-
throw new Error('extraExp must be a valid number');
|
|
2421
|
-
if (typeof type !== 'string') throw new Error('type must be a valid string');
|
|
2422
|
-
|
|
2423
|
-
if (type === 'add') user.exp += this.expGenerator(multi) + extraExp;
|
|
2424
|
-
else if (type === 'extra') user.exp += extraExp;
|
|
2425
|
-
|
|
2426
|
-
this.expValidator(user);
|
|
2427
|
-
user.totalExp = this.getTotalExp(user);
|
|
2428
|
-
return user;
|
|
2429
|
-
}
|
|
2430
|
-
|
|
2431
|
-
/**
|
|
2432
|
-
* Removes experience from the user, adjusting their level if necessary.
|
|
2433
|
-
* @param {UserEditor} user - The user object.
|
|
2434
|
-
* @param {number} [extraExp] - Additional experience to remove.
|
|
2435
|
-
* @param {'add' | 'extra'} [type] - Type of removal ('add' or 'extra').
|
|
2436
|
-
* @param {number} [multi] - Multiplier for experience generation.
|
|
2437
|
-
* @returns {UserEditor} The updated user object.
|
|
2438
|
-
*/
|
|
2439
|
-
remove(user, extraExp = 0, type = 'add', multi = 1) {
|
|
2440
|
-
if (typeof multi !== 'number' || Number.isNaN(multi))
|
|
2441
|
-
throw new Error('multi must be a valid number');
|
|
2442
|
-
if (typeof extraExp !== 'number' || Number.isNaN(extraExp))
|
|
2443
|
-
throw new Error('extraExp must be a valid number');
|
|
2444
|
-
if (typeof type !== 'string') throw new Error('type must be a valid string');
|
|
2445
|
-
|
|
2446
|
-
if (type === 'add') user.exp -= this.expGenerator(multi) + extraExp;
|
|
2447
|
-
else if (type === 'extra') user.exp -= extraExp;
|
|
2448
|
-
|
|
2449
|
-
this.expValidator(user);
|
|
2450
|
-
user.totalExp = this.getTotalExp(user);
|
|
2451
|
-
return user;
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
|
-
/* harmony default export */ const userLevel = (TinyLevelUp);
|
|
2456
|
-
|
|
2457
|
-
;// ./src/legacy/libs/arraySortPositions.mjs
|
|
2458
|
-
/**
|
|
2459
|
-
* Generates a comparator function to sort an array of objects by a given key.
|
|
2460
|
-
*
|
|
2461
|
-
* @param {string} item - The object key to sort by.
|
|
2462
|
-
* @param {boolean} [isReverse=false] - If `true`, the sorting will be in descending order.
|
|
2463
|
-
* @returns {(a: Object<string|number, *>, b: Object<string|number, *>) => number} Comparator function compatible with Array.prototype.sort().
|
|
2464
|
-
*
|
|
2465
|
-
* @example
|
|
2466
|
-
* const arr = [{ pos: 2 }, { pos: 1 }, { pos: 3 }];
|
|
2467
|
-
* arr.sort(arraySortPositions('pos')); // Ascending: [{pos: 1}, {pos: 2}, {pos: 3}]
|
|
2468
|
-
*
|
|
2469
|
-
* @example
|
|
2470
|
-
* const arr = [{ pos: 2 }, { pos: 1 }, { pos: 3 }];
|
|
2471
|
-
* arr.sort(arraySortPositions('pos', true)); // Descending: [{pos: 3}, {pos: 2}, {pos: 1}]
|
|
2472
|
-
*/
|
|
2473
|
-
function arraySortPositions(item, isReverse = false) {
|
|
2474
|
-
if (!isReverse) {
|
|
2475
|
-
return function (a, b) {
|
|
2476
|
-
return a[item] < b[item] ? -1 : a[item] > b[item] ? 1 : 0;
|
|
2477
|
-
};
|
|
2478
|
-
} else {
|
|
2479
|
-
return function (a, b) {
|
|
2480
|
-
return a[item] > b[item] ? -1 : a[item] < b[item] ? 1 : 0;
|
|
2481
|
-
};
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
|
|
2485
|
-
;// ./src/v1/basics/array.mjs
|
|
2486
|
-
// https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
|
|
2487
|
-
|
|
2488
|
-
/**
|
|
2489
|
-
* Randomly shuffles the elements of an array in place using the Fisher–Yates algorithm.
|
|
2490
|
-
*
|
|
2491
|
-
* This implementation ensures a uniform distribution of permutations.
|
|
2492
|
-
* Original algorithm source: StackOverflow (link above).
|
|
2493
|
-
*
|
|
2494
|
-
* @param {any[]} items - The array to shuffle.
|
|
2495
|
-
* @returns {any[]} The same array instance, now shuffled in place.
|
|
2496
|
-
*/
|
|
2497
|
-
function shuffleArray(items) {
|
|
2498
|
-
let currentIndex = items.length,
|
|
2499
|
-
randomIndex;
|
|
2500
|
-
|
|
2501
|
-
// While there remain elements to shuffle...
|
|
2502
|
-
while (currentIndex !== 0) {
|
|
2503
|
-
// Pick a remaining element...
|
|
2504
|
-
randomIndex = Math.floor(Math.random() * currentIndex);
|
|
2505
|
-
currentIndex--;
|
|
2506
|
-
|
|
2507
|
-
// And swap it with the current element.
|
|
2508
|
-
[items[currentIndex], items[randomIndex]] = [items[randomIndex], items[currentIndex]];
|
|
2509
|
-
}
|
|
2510
|
-
|
|
2511
|
-
return items;
|
|
2512
|
-
}
|
|
2513
|
-
|
|
2514
|
-
;// ./src/v1/basics/clock.mjs
|
|
2515
|
-
/**
|
|
2516
|
-
* Calculates the time duration between the current time and a given time offset.
|
|
2517
|
-
*
|
|
2518
|
-
* @param {Date} timeData - The target time as a Date object.
|
|
2519
|
-
* @param {string} [durationType='asSeconds'] - The type of duration to return. Can be 'asMilliseconds', 'asSeconds', 'asMinutes', 'asHours', 'asDays'.
|
|
2520
|
-
* @param {Date|null} [now=null] - The current time as a Date object. Defaults to the current date and time if not provided.
|
|
2521
|
-
* @returns {number|null} The calculated duration in the specified unit, or `null` if `timeData` is not provided.
|
|
2522
|
-
*/
|
|
2523
|
-
function getTimeDuration(timeData = new Date(), durationType = 'asSeconds', now = null) {
|
|
2524
|
-
if (timeData instanceof Date) {
|
|
2525
|
-
const currentTime = now instanceof Date ? now : new Date();
|
|
2526
|
-
/** @type {number} */
|
|
2527
|
-
const diffMs = timeData.getTime() - currentTime.getTime();
|
|
2528
|
-
|
|
2529
|
-
switch (durationType) {
|
|
2530
|
-
case 'asMilliseconds':
|
|
2531
|
-
return diffMs;
|
|
2532
|
-
case 'asSeconds':
|
|
2533
|
-
return diffMs / 1000;
|
|
2534
|
-
case 'asMinutes':
|
|
2535
|
-
return diffMs / (1000 * 60);
|
|
2536
|
-
case 'asHours':
|
|
2537
|
-
return diffMs / (1000 * 60 * 60);
|
|
2538
|
-
case 'asDays':
|
|
2539
|
-
return diffMs / (1000 * 60 * 60 * 24);
|
|
2540
|
-
default:
|
|
2541
|
-
return diffMs / 1000; // default to seconds
|
|
2542
|
-
}
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
return null;
|
|
2546
|
-
}
|
|
3061
|
+
return null;
|
|
3062
|
+
}
|
|
2547
3063
|
|
|
2548
3064
|
/**
|
|
2549
3065
|
* Formats a custom timer string based on total seconds and a detail level.
|
|
@@ -3088,94 +3604,473 @@ extendObjType([
|
|
|
3088
3604
|
],
|
|
3089
3605
|
]);
|
|
3090
3606
|
|
|
3091
|
-
;// ./src/v1/basics/
|
|
3607
|
+
;// ./src/v1/basics/html.mjs
|
|
3608
|
+
|
|
3609
|
+
|
|
3092
3610
|
/**
|
|
3093
|
-
*
|
|
3094
|
-
*
|
|
3095
|
-
* @param {number} val1 - The first reference value (numerator in direct proportion, denominator in inverse).
|
|
3096
|
-
* @param {number} val2 - The second reference value (denominator in direct proportion, numerator in inverse).
|
|
3097
|
-
* @param {number} val3 - The third value (numerator in direct proportion, denominator in inverse).
|
|
3098
|
-
* @param {boolean} [inverse] - Whether the calculation should use inverse proportion (true for inverse, false for direct).
|
|
3099
|
-
* @returns {number} The result of the Rule of Three operation.
|
|
3100
|
-
*
|
|
3101
|
-
* Rule of Three Formula (Direct Proportion):
|
|
3102
|
-
* val1 / val2 = val3 / result
|
|
3103
|
-
*
|
|
3104
|
-
* For Inverse Proportion:
|
|
3105
|
-
* val1 / val3 = val2 / result
|
|
3106
|
-
*
|
|
3107
|
-
* Visual Representation:
|
|
3108
|
-
*
|
|
3109
|
-
* For Direct Proportion:
|
|
3110
|
-
* val1 val2
|
|
3111
|
-
* ----- = ------
|
|
3112
|
-
* val3 result
|
|
3113
|
-
*
|
|
3114
|
-
* For Inverse Proportion:
|
|
3115
|
-
* val1 val2
|
|
3116
|
-
* ----- = ------
|
|
3117
|
-
* val3 result
|
|
3118
|
-
*
|
|
3119
|
-
* @example
|
|
3120
|
-
* // Direct proportion:
|
|
3121
|
-
* ruleOfThree.execute(2, 6, 3, false); // → 9
|
|
3611
|
+
* Checks if two DOM elements are colliding on the screen.
|
|
3122
3612
|
*
|
|
3123
|
-
* @
|
|
3124
|
-
*
|
|
3125
|
-
*
|
|
3613
|
+
* @param {Element} elem1 - First DOM element.
|
|
3614
|
+
* @param {Element} elem2 - Second DOM element.
|
|
3615
|
+
* @returns {boolean} - Returns true if the elements are colliding.
|
|
3126
3616
|
*/
|
|
3127
|
-
function
|
|
3128
|
-
|
|
3617
|
+
function areHtmlElsColliding(elem1, elem2) {
|
|
3618
|
+
const rect1 = elem1.getBoundingClientRect();
|
|
3619
|
+
const rect2 = elem2.getBoundingClientRect();
|
|
3620
|
+
|
|
3621
|
+
return !(
|
|
3622
|
+
rect1.right < rect2.left ||
|
|
3623
|
+
rect1.left > rect2.right ||
|
|
3624
|
+
rect1.bottom < rect2.top ||
|
|
3625
|
+
rect1.top > rect2.bottom
|
|
3626
|
+
);
|
|
3129
3627
|
}
|
|
3130
3628
|
|
|
3131
3629
|
/**
|
|
3132
|
-
*
|
|
3133
|
-
*
|
|
3134
|
-
* @param {
|
|
3135
|
-
* @returns {
|
|
3136
|
-
*
|
|
3137
|
-
* @example
|
|
3138
|
-
* getSimplePerc(200, 15); // 30
|
|
3630
|
+
* Reads and parses a JSON data using FileReader.
|
|
3631
|
+
* Throws an error if the content is not valid JSON.
|
|
3632
|
+
* @param {File} file
|
|
3633
|
+
* @returns {Promise<any>}
|
|
3139
3634
|
*/
|
|
3140
|
-
function
|
|
3141
|
-
return
|
|
3635
|
+
function readJsonBlob(file) {
|
|
3636
|
+
return new Promise((resolve, reject) => {
|
|
3637
|
+
const reader = new FileReader();
|
|
3638
|
+
|
|
3639
|
+
reader.onload = () => {
|
|
3640
|
+
try {
|
|
3641
|
+
// @ts-ignore
|
|
3642
|
+
const result = JSON.parse(reader.result);
|
|
3643
|
+
resolve(result);
|
|
3644
|
+
} catch (error) {
|
|
3645
|
+
// @ts-ignore
|
|
3646
|
+
reject(new Error(`Invalid JSON in file: ${file.name}\n${error.message}`));
|
|
3647
|
+
}
|
|
3648
|
+
};
|
|
3649
|
+
|
|
3650
|
+
reader.onerror = () => {
|
|
3651
|
+
reject(new Error(`Error reading file: ${file.name}`));
|
|
3652
|
+
};
|
|
3653
|
+
|
|
3654
|
+
reader.readAsText(file);
|
|
3655
|
+
});
|
|
3142
3656
|
}
|
|
3143
3657
|
|
|
3144
3658
|
/**
|
|
3145
|
-
*
|
|
3659
|
+
* Saves a JSON object as a downloadable file.
|
|
3660
|
+
* @param {string} filename
|
|
3661
|
+
* @param {any} data
|
|
3662
|
+
* @param {number} [spaces=2]
|
|
3663
|
+
*/
|
|
3664
|
+
function saveJsonFile(filename, data, spaces = 2) {
|
|
3665
|
+
const json = JSON.stringify(data, null, spaces);
|
|
3666
|
+
const blob = new Blob([json], { type: 'application/json' });
|
|
3667
|
+
const url = URL.createObjectURL(blob);
|
|
3668
|
+
|
|
3669
|
+
const link = document.createElement('a');
|
|
3670
|
+
link.href = url;
|
|
3671
|
+
link.download = filename;
|
|
3672
|
+
|
|
3673
|
+
document.body.appendChild(link);
|
|
3674
|
+
link.click();
|
|
3675
|
+
document.body.removeChild(link);
|
|
3676
|
+
|
|
3677
|
+
URL.revokeObjectURL(url);
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
/**
|
|
3681
|
+
* Loads and parses a JSON from a remote URL using Fetch API.
|
|
3146
3682
|
*
|
|
3147
|
-
* @param {
|
|
3148
|
-
* @param {
|
|
3149
|
-
* @
|
|
3683
|
+
* @param {string} url - The full URL to fetch JSON from.
|
|
3684
|
+
* @param {Object} [options] - Optional settings.
|
|
3685
|
+
* @param {string} [options.method="GET"] - HTTP method to use (GET, POST, etc.).
|
|
3686
|
+
* @param {any} [options.body] - Request body (only for methods like POST, PUT).
|
|
3687
|
+
* @param {number} [options.timeout=0] - Timeout in milliseconds (ignored if signal is provided).
|
|
3688
|
+
* @param {number} [options.retries=0] - Number of retry attempts (ignored if signal is provided).
|
|
3689
|
+
* @param {Headers|Record<string, *>} [options.headers={}] - Additional headers.
|
|
3690
|
+
* @param {AbortSignal|null} [options.signal] - External AbortSignal; disables timeout and retries.
|
|
3691
|
+
* @returns {Promise<*>} Parsed JSON object.
|
|
3692
|
+
* @throws {Error} Throws if fetch fails, times out, or returns invalid JSON.
|
|
3150
3693
|
*/
|
|
3151
|
-
function
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3694
|
+
async function fetchJson(
|
|
3695
|
+
url,
|
|
3696
|
+
{ method = 'GET', body, timeout = 0, retries = 0, headers = {}, signal = null } = {},
|
|
3697
|
+
) {
|
|
3698
|
+
if (
|
|
3699
|
+
typeof url !== 'string' ||
|
|
3700
|
+
(!url.startsWith('../') &&
|
|
3701
|
+
!url.startsWith('./') &&
|
|
3702
|
+
!url.startsWith('/') &&
|
|
3703
|
+
!url.startsWith('https://') &&
|
|
3704
|
+
!url.startsWith('http://'))
|
|
3705
|
+
)
|
|
3706
|
+
throw new Error('Invalid URL: must be a valid http or https address.');
|
|
3155
3707
|
|
|
3156
|
-
|
|
3708
|
+
if (typeof method !== 'string' || !method.trim())
|
|
3709
|
+
throw new Error('Invalid method: must be a non-empty string.');
|
|
3157
3710
|
|
|
3158
|
-
|
|
3711
|
+
if (!signal) {
|
|
3712
|
+
if (
|
|
3713
|
+
typeof timeout !== 'number' ||
|
|
3714
|
+
!Number.isFinite(timeout) ||
|
|
3715
|
+
Number.isNaN(timeout) ||
|
|
3716
|
+
timeout < 0
|
|
3717
|
+
)
|
|
3718
|
+
throw new Error('Invalid timeout: must be a positive number.');
|
|
3159
3719
|
|
|
3160
|
-
|
|
3161
|
-
|
|
3720
|
+
if (
|
|
3721
|
+
typeof retries !== 'number' ||
|
|
3722
|
+
!Number.isFinite(retries) ||
|
|
3723
|
+
Number.isNaN(retries) ||
|
|
3724
|
+
retries < 0
|
|
3725
|
+
)
|
|
3726
|
+
throw new Error('Invalid retries: must be a positive number.');
|
|
3727
|
+
}
|
|
3162
3728
|
|
|
3163
|
-
|
|
3164
|
-
|
|
3729
|
+
const attempts = signal ? 1 : retries + 1;
|
|
3730
|
+
/** @type {Error|null} */
|
|
3731
|
+
let lastError = null;
|
|
3165
3732
|
|
|
3166
|
-
|
|
3167
|
-
|
|
3733
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
3734
|
+
const controller = signal ? null : new AbortController();
|
|
3735
|
+
const localSignal = signal || (controller?.signal ?? null);
|
|
3736
|
+
const timer =
|
|
3737
|
+
!signal && timeout && controller ? setTimeout(() => controller.abort(), timeout) : null;
|
|
3168
3738
|
|
|
3169
|
-
|
|
3739
|
+
try {
|
|
3740
|
+
const response = await fetch(url, {
|
|
3741
|
+
method: method.toUpperCase(),
|
|
3742
|
+
headers: {
|
|
3743
|
+
Accept: 'application/json',
|
|
3744
|
+
...headers,
|
|
3745
|
+
},
|
|
3746
|
+
body: body !== undefined ? (isJsonObject(body) ? JSON.stringify(body) : body) : undefined,
|
|
3747
|
+
signal: localSignal,
|
|
3748
|
+
});
|
|
3749
|
+
|
|
3750
|
+
if (timer) clearTimeout(timer);
|
|
3751
|
+
|
|
3752
|
+
if (!response.ok) throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
3753
|
+
|
|
3754
|
+
const contentType = response.headers.get('content-type') || '';
|
|
3755
|
+
if (!contentType.includes('application/json'))
|
|
3756
|
+
throw new Error(`Unexpected content-type: ${contentType}`);
|
|
3757
|
+
|
|
3758
|
+
const data = await response.json();
|
|
3759
|
+
|
|
3760
|
+
if (!isJsonObject(data)) throw new Error('Received invalid data instead of valid JSON.');
|
|
3761
|
+
|
|
3762
|
+
return data;
|
|
3763
|
+
} catch (err) {
|
|
3764
|
+
lastError = /** @type {Error} */ (err);
|
|
3765
|
+
if (signal) break; // if an external signal came, it does not retry
|
|
3766
|
+
if (attempt < retries)
|
|
3767
|
+
await new Promise((resolve) => setTimeout(resolve, 300 * (attempt + 1)));
|
|
3768
|
+
}
|
|
3170
3769
|
}
|
|
3171
3770
|
|
|
3172
|
-
|
|
3771
|
+
throw new Error(
|
|
3772
|
+
`Failed to fetch JSON from "${url}"${lastError ? `: ${lastError.message}` : '.'}`,
|
|
3773
|
+
);
|
|
3173
3774
|
}
|
|
3174
3775
|
|
|
3175
3776
|
/**
|
|
3176
|
-
* @typedef {Object}
|
|
3177
|
-
* @property {
|
|
3178
|
-
* @property {number
|
|
3777
|
+
* @typedef {Object} HtmlElBoxSides
|
|
3778
|
+
* @property {number} x - Total horizontal size (left + right)
|
|
3779
|
+
* @property {number} y - Total vertical size (top + bottom)
|
|
3780
|
+
* @property {number} left
|
|
3781
|
+
* @property {number} right
|
|
3782
|
+
* @property {number} top
|
|
3783
|
+
* @property {number} bottom
|
|
3784
|
+
*/
|
|
3785
|
+
|
|
3786
|
+
/**
|
|
3787
|
+
* Returns the total border width and individual sides from `border{Side}Width` CSS properties.
|
|
3788
|
+
*
|
|
3789
|
+
* @param {Element} el - The target DOM element.
|
|
3790
|
+
* @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border widths, and each side individually.
|
|
3791
|
+
*/
|
|
3792
|
+
const getHtmlElBordersWidth = (el) => {
|
|
3793
|
+
const styles = getComputedStyle(el);
|
|
3794
|
+
const left = parseFloat(styles.borderLeftWidth) || 0;
|
|
3795
|
+
const right = parseFloat(styles.borderRightWidth) || 0;
|
|
3796
|
+
const top = parseFloat(styles.borderTopWidth) || 0;
|
|
3797
|
+
const bottom = parseFloat(styles.borderBottomWidth) || 0;
|
|
3798
|
+
const x = left + right;
|
|
3799
|
+
const y = top + bottom;
|
|
3800
|
+
|
|
3801
|
+
return { x, y, left, right, top, bottom };
|
|
3802
|
+
};
|
|
3803
|
+
|
|
3804
|
+
/**
|
|
3805
|
+
* Returns the total border size and individual sides from `border{Side}` CSS properties.
|
|
3806
|
+
*
|
|
3807
|
+
* @param {Element} el - The target DOM element.
|
|
3808
|
+
* @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border sizes, and each side individually.
|
|
3809
|
+
*/
|
|
3810
|
+
const getHtmlElBorders = (el) => {
|
|
3811
|
+
const styles = getComputedStyle(el);
|
|
3812
|
+
const left = parseFloat(styles.borderLeft) || 0;
|
|
3813
|
+
const right = parseFloat(styles.borderRight) || 0;
|
|
3814
|
+
const top = parseFloat(styles.borderTop) || 0;
|
|
3815
|
+
const bottom = parseFloat(styles.borderBottom) || 0;
|
|
3816
|
+
const x = left + right;
|
|
3817
|
+
const y = top + bottom;
|
|
3818
|
+
|
|
3819
|
+
return { x, y, left, right, top, bottom };
|
|
3820
|
+
};
|
|
3821
|
+
|
|
3822
|
+
/**
|
|
3823
|
+
* Returns the total margin and individual sides from `margin{Side}` CSS properties.
|
|
3824
|
+
*
|
|
3825
|
+
* @param {Element} el - The target DOM element.
|
|
3826
|
+
* @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) margins, and each side individually.
|
|
3827
|
+
*/
|
|
3828
|
+
const getHtmlElMargin = (el) => {
|
|
3829
|
+
const styles = getComputedStyle(el);
|
|
3830
|
+
const left = parseFloat(styles.marginLeft) || 0;
|
|
3831
|
+
const right = parseFloat(styles.marginRight) || 0;
|
|
3832
|
+
const top = parseFloat(styles.marginTop) || 0;
|
|
3833
|
+
const bottom = parseFloat(styles.marginBottom) || 0;
|
|
3834
|
+
|
|
3835
|
+
const x = left + right;
|
|
3836
|
+
const y = top + bottom;
|
|
3837
|
+
|
|
3838
|
+
return { x, y, left, right, top, bottom };
|
|
3839
|
+
};
|
|
3840
|
+
|
|
3841
|
+
/**
|
|
3842
|
+
* Returns the total padding and individual sides from `padding{Side}` CSS properties.
|
|
3843
|
+
*
|
|
3844
|
+
* @param {Element} el - The target DOM element.
|
|
3845
|
+
* @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) paddings, and each side individually.
|
|
3846
|
+
*/
|
|
3847
|
+
const getHtmlElPadding = (el) => {
|
|
3848
|
+
const styles = getComputedStyle(el);
|
|
3849
|
+
const left = parseFloat(styles.paddingLeft) || 0;
|
|
3850
|
+
const right = parseFloat(styles.paddingRight) || 0;
|
|
3851
|
+
const top = parseFloat(styles.paddingTop) || 0;
|
|
3852
|
+
const bottom = parseFloat(styles.paddingBottom) || 0;
|
|
3853
|
+
|
|
3854
|
+
const x = left + right;
|
|
3855
|
+
const y = top + bottom;
|
|
3856
|
+
|
|
3857
|
+
return { x, y, left, right, top, bottom };
|
|
3858
|
+
};
|
|
3859
|
+
|
|
3860
|
+
;// ./src/v1/basics/fullScreen.mjs
|
|
3861
|
+
/**
|
|
3862
|
+
* Checks if the document is currently in fullscreen mode.
|
|
3863
|
+
*
|
|
3864
|
+
* @returns {boolean}
|
|
3865
|
+
*/
|
|
3866
|
+
const documentIsFullScreen = () =>
|
|
3867
|
+
!!(
|
|
3868
|
+
document.fullscreenElement ||
|
|
3869
|
+
// @ts-ignore
|
|
3870
|
+
document.webkitFullscreenElement ||
|
|
3871
|
+
// @ts-ignore
|
|
3872
|
+
document.mozFullScreenElement ||
|
|
3873
|
+
// @ts-ignore
|
|
3874
|
+
document.msFullscreenElement ||
|
|
3875
|
+
// @ts-ignore
|
|
3876
|
+
document.webkitIsFullScreen ||
|
|
3877
|
+
// @ts-ignore
|
|
3878
|
+
document.mozFullScreen
|
|
3879
|
+
);
|
|
3880
|
+
|
|
3881
|
+
/**
|
|
3882
|
+
* Checks if the window occupies the entire screen dimensions.
|
|
3883
|
+
*
|
|
3884
|
+
* @returns {boolean}
|
|
3885
|
+
*/
|
|
3886
|
+
const isScreenFilled = () =>
|
|
3887
|
+
window.innerHeight === screen.height && window.innerWidth === screen.width;
|
|
3888
|
+
|
|
3889
|
+
/**
|
|
3890
|
+
* Checks if fullscreen mode is active either via document API or by matching screen dimensions.
|
|
3891
|
+
*
|
|
3892
|
+
* @returns {boolean}
|
|
3893
|
+
*/
|
|
3894
|
+
const isFullScreenMode = () => documentIsFullScreen() || isScreenFilled();
|
|
3895
|
+
|
|
3896
|
+
/**
|
|
3897
|
+
* Requests fullscreen mode for the document.
|
|
3898
|
+
*
|
|
3899
|
+
* @param {FullscreenOptions} [ops]
|
|
3900
|
+
* @returns {Promise<void>}
|
|
3901
|
+
*/
|
|
3902
|
+
const requestFullScreen = (ops) =>
|
|
3903
|
+
new Promise(async (resolve, reject) => {
|
|
3904
|
+
const docElm = document.documentElement;
|
|
3905
|
+
try {
|
|
3906
|
+
if (docElm.requestFullscreen) {
|
|
3907
|
+
await docElm.requestFullscreen(ops);
|
|
3908
|
+
// @ts-ignore
|
|
3909
|
+
} else if (docElm.mozRequestFullScreen) {
|
|
3910
|
+
// @ts-ignore
|
|
3911
|
+
docElm.mozRequestFullScreen(ops);
|
|
3912
|
+
// @ts-ignore
|
|
3913
|
+
} else if (docElm.webkitRequestFullScreen) {
|
|
3914
|
+
// @ts-ignore
|
|
3915
|
+
docElm.webkitRequestFullScreen(ops);
|
|
3916
|
+
// @ts-ignore
|
|
3917
|
+
} else if (docElm.msRequestFullscreen) {
|
|
3918
|
+
// @ts-ignore
|
|
3919
|
+
docElm.msRequestFullscreen(ops);
|
|
3920
|
+
}
|
|
3921
|
+
resolve();
|
|
3922
|
+
} catch (err) {
|
|
3923
|
+
reject(err);
|
|
3924
|
+
}
|
|
3925
|
+
});
|
|
3926
|
+
|
|
3927
|
+
/**
|
|
3928
|
+
* Exits fullscreen mode.
|
|
3929
|
+
*
|
|
3930
|
+
* @returns {Promise<void>}
|
|
3931
|
+
*/
|
|
3932
|
+
const exitFullScreen = () =>
|
|
3933
|
+
new Promise((resolve, reject) => {
|
|
3934
|
+
if (document.exitFullscreen) {
|
|
3935
|
+
document.exitFullscreen().then(resolve).catch(reject);
|
|
3936
|
+
} else {
|
|
3937
|
+
try {
|
|
3938
|
+
// @ts-ignore
|
|
3939
|
+
if (document.mozCancelFullScreen) document.mozCancelFullScreen();
|
|
3940
|
+
// @ts-ignore
|
|
3941
|
+
else if (document.webkitCancelFullScreen) document.webkitCancelFullScreen();
|
|
3942
|
+
// @ts-ignore
|
|
3943
|
+
else if (document.msExitFullscreen) document.msExitFullscreen();
|
|
3944
|
+
else throw new Error('Fullscreen API is not supported');
|
|
3945
|
+
resolve();
|
|
3946
|
+
} catch (err) {
|
|
3947
|
+
reject(err);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
});
|
|
3951
|
+
|
|
3952
|
+
/** @type {readonly string[]} */
|
|
3953
|
+
const fullScreenEvents = [
|
|
3954
|
+
'fullscreenchange',
|
|
3955
|
+
'webkitfullscreenchange',
|
|
3956
|
+
'mozfullscreenchange',
|
|
3957
|
+
'MSFullscreenChange',
|
|
3958
|
+
];
|
|
3959
|
+
|
|
3960
|
+
/**
|
|
3961
|
+
* Attaches a listener to fullscreen change events.
|
|
3962
|
+
*
|
|
3963
|
+
* @param {EventListenerOrEventListenerObject} listener
|
|
3964
|
+
* @param {boolean|AddEventListenerOptions} [ops]
|
|
3965
|
+
* @returns {void}
|
|
3966
|
+
*/
|
|
3967
|
+
const onFullScreenChange = (listener, ops) => {
|
|
3968
|
+
fullScreenEvents.forEach((event) => {
|
|
3969
|
+
document.addEventListener(event, listener, ops);
|
|
3970
|
+
});
|
|
3971
|
+
};
|
|
3972
|
+
|
|
3973
|
+
/**
|
|
3974
|
+
* Removes a listener from fullscreen change events.
|
|
3975
|
+
*
|
|
3976
|
+
* @param {EventListenerOrEventListenerObject} listener
|
|
3977
|
+
* @param {boolean|AddEventListenerOptions} [ops]
|
|
3978
|
+
* @returns {void}
|
|
3979
|
+
*/
|
|
3980
|
+
const offFullScreenChange = (listener, ops) => {
|
|
3981
|
+
fullScreenEvents.forEach((event) => {
|
|
3982
|
+
document.removeEventListener(event, listener, ops);
|
|
3983
|
+
});
|
|
3984
|
+
};
|
|
3985
|
+
|
|
3986
|
+
;// ./src/v1/basics/simpleMath.mjs
|
|
3987
|
+
/**
|
|
3988
|
+
* Executes a Rule of Three calculation.
|
|
3989
|
+
*
|
|
3990
|
+
* @param {number} val1 - The first reference value (numerator in direct proportion, denominator in inverse).
|
|
3991
|
+
* @param {number} val2 - The second reference value (denominator in direct proportion, numerator in inverse).
|
|
3992
|
+
* @param {number} val3 - The third value (numerator in direct proportion, denominator in inverse).
|
|
3993
|
+
* @param {boolean} [inverse] - Whether the calculation should use inverse proportion (true for inverse, false for direct).
|
|
3994
|
+
* @returns {number} The result of the Rule of Three operation.
|
|
3995
|
+
*
|
|
3996
|
+
* Rule of Three Formula (Direct Proportion):
|
|
3997
|
+
* val1 / val2 = val3 / result
|
|
3998
|
+
*
|
|
3999
|
+
* For Inverse Proportion:
|
|
4000
|
+
* val1 / val3 = val2 / result
|
|
4001
|
+
*
|
|
4002
|
+
* Visual Representation:
|
|
4003
|
+
*
|
|
4004
|
+
* For Direct Proportion:
|
|
4005
|
+
* val1 val2
|
|
4006
|
+
* ----- = ------
|
|
4007
|
+
* val3 result
|
|
4008
|
+
*
|
|
4009
|
+
* For Inverse Proportion:
|
|
4010
|
+
* val1 val2
|
|
4011
|
+
* ----- = ------
|
|
4012
|
+
* val3 result
|
|
4013
|
+
*
|
|
4014
|
+
* @example
|
|
4015
|
+
* // Direct proportion:
|
|
4016
|
+
* ruleOfThree.execute(2, 6, 3, false); // → 9
|
|
4017
|
+
*
|
|
4018
|
+
* @example
|
|
4019
|
+
* // Inverse proportion:
|
|
4020
|
+
* ruleOfThree.execute(2, 6, 3, true); // → 4
|
|
4021
|
+
*/
|
|
4022
|
+
function ruleOfThree(val1, val2, val3, inverse = false) {
|
|
4023
|
+
return inverse ? Number(val1 * val2) / val3 : Number(val3 * val2) / val1;
|
|
4024
|
+
}
|
|
4025
|
+
|
|
4026
|
+
/**
|
|
4027
|
+
* Calculates a percentage of a given base value.
|
|
4028
|
+
* @param {number} price - The base value.
|
|
4029
|
+
* @param {number} percentage - The percentage to apply.
|
|
4030
|
+
* @returns {number} The result of the percentage calculation.
|
|
4031
|
+
*
|
|
4032
|
+
* @example
|
|
4033
|
+
* getSimplePerc(200, 15); // 30
|
|
4034
|
+
*/
|
|
4035
|
+
function getSimplePerc(price, percentage) {
|
|
4036
|
+
return price * (percentage / 100);
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
/**
|
|
4040
|
+
* Calculates the age based on the given date.
|
|
4041
|
+
*
|
|
4042
|
+
* @param {number|string|Date} timeData - The birth date (can be a timestamp, ISO string, or Date object).
|
|
4043
|
+
* @param {Date|null} [now=null] - The Date object representing the current date. Defaults to the current date and time if not provided.
|
|
4044
|
+
* @returns {number|null} The age in years, or null if `timeData` is not provided or invalid.
|
|
4045
|
+
*/
|
|
4046
|
+
function getAge(timeData = 0, now = null) {
|
|
4047
|
+
if (typeof timeData !== 'undefined' && timeData !== null && timeData !== 0) {
|
|
4048
|
+
const birthDate = new Date(timeData);
|
|
4049
|
+
if (Number.isNaN(birthDate.getTime())) return null;
|
|
4050
|
+
|
|
4051
|
+
const currentDate = now instanceof Date ? now : new Date();
|
|
4052
|
+
|
|
4053
|
+
let age = currentDate.getFullYear() - birthDate.getFullYear();
|
|
4054
|
+
|
|
4055
|
+
const currentMonth = currentDate.getMonth();
|
|
4056
|
+
const birthMonth = birthDate.getMonth();
|
|
4057
|
+
|
|
4058
|
+
const currentDay = currentDate.getDate();
|
|
4059
|
+
const birthDay = birthDate.getDate();
|
|
4060
|
+
|
|
4061
|
+
// Adjust if birthday hasn't occurred yet this year
|
|
4062
|
+
if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) age--;
|
|
4063
|
+
|
|
4064
|
+
return Math.abs(age);
|
|
4065
|
+
}
|
|
4066
|
+
|
|
4067
|
+
return null;
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
/**
|
|
4071
|
+
* @typedef {Object} FormattedByteResult
|
|
4072
|
+
* @property {string|null} unit - The resulting unit (e.g., 'MB', 'GB') or null if input is invalid.
|
|
4073
|
+
* @property {number|null} value - The numerical value in the chosen unit, or null if input is invalid.
|
|
3179
4074
|
*/
|
|
3180
4075
|
|
|
3181
4076
|
/**
|
|
@@ -3304,1035 +4199,504 @@ function KeyPressHandler() {
|
|
|
3304
4199
|
export default KeyPressHandler;
|
|
3305
4200
|
*/
|
|
3306
4201
|
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
4202
|
+
// EXTERNAL MODULE: fs (ignored)
|
|
4203
|
+
var fs_ignored_ = __webpack_require__(41);
|
|
4204
|
+
// EXTERNAL MODULE: ./node_modules/path-browserify/index.js
|
|
4205
|
+
var path_browserify = __webpack_require__(975);
|
|
4206
|
+
;// ./src/v1/basics/fileManager.mjs
|
|
3312
4207
|
|
|
3313
|
-
class ColorSafeStringify {
|
|
3314
|
-
/**
|
|
3315
|
-
* Currently active color configuration.
|
|
3316
|
-
* @type {ColorsList}
|
|
3317
|
-
*/
|
|
3318
|
-
#colors;
|
|
3319
4208
|
|
|
3320
|
-
/**
|
|
3321
|
-
* Preset collections (internal and user-defined).
|
|
3322
|
-
* @type {Record<string, ColorsList>}
|
|
3323
|
-
* @static
|
|
3324
|
-
*/
|
|
3325
|
-
static #PRESETS = {
|
|
3326
|
-
default: {
|
|
3327
|
-
reset: '\x1b[0m',
|
|
3328
|
-
key: '\x1b[36m', // Cyan (object keys)
|
|
3329
|
-
string: '\x1b[32m', // Green (regular strings)
|
|
3330
|
-
string_url: '\x1b[34m', // Blue (URLs)
|
|
3331
|
-
string_bool: '\x1b[35m', // Magenta (boolean/null in string form)
|
|
3332
|
-
string_number: '\x1b[33m', // Yellow (numbers in string form)
|
|
3333
|
-
number: '\x1b[33m', // Yellow (raw numbers)
|
|
3334
|
-
boolean: '\x1b[35m', // Magenta (true/false)
|
|
3335
|
-
null: '\x1b[1;30m', // Gray (null)
|
|
3336
|
-
special: '\x1b[31m', // Red (e.g., [Circular], [undefined])
|
|
3337
|
-
func: '\x1b[90m', // Dim (function string representations)
|
|
3338
|
-
},
|
|
3339
|
-
solarized: {
|
|
3340
|
-
reset: '\x1b[0m',
|
|
3341
|
-
key: '\x1b[38;5;37m',
|
|
3342
|
-
string: '\x1b[38;5;136m',
|
|
3343
|
-
string_url: '\x1b[38;5;33m',
|
|
3344
|
-
string_bool: '\x1b[38;5;166m',
|
|
3345
|
-
string_number: '\x1b[38;5;136m',
|
|
3346
|
-
number: '\x1b[38;5;136m',
|
|
3347
|
-
boolean: '\x1b[38;5;166m',
|
|
3348
|
-
null: '\x1b[38;5;241m',
|
|
3349
|
-
special: '\x1b[38;5;160m',
|
|
3350
|
-
func: '\x1b[38;5;244m',
|
|
3351
|
-
},
|
|
3352
|
-
monokai: {
|
|
3353
|
-
reset: '\x1b[0m',
|
|
3354
|
-
key: '\x1b[38;5;81m',
|
|
3355
|
-
string: '\x1b[38;5;114m',
|
|
3356
|
-
string_url: '\x1b[38;5;75m',
|
|
3357
|
-
string_bool: '\x1b[38;5;204m',
|
|
3358
|
-
string_number: '\x1b[38;5;221m',
|
|
3359
|
-
number: '\x1b[38;5;221m',
|
|
3360
|
-
boolean: '\x1b[38;5;204m',
|
|
3361
|
-
null: '\x1b[38;5;241m',
|
|
3362
|
-
special: '\x1b[38;5;160m',
|
|
3363
|
-
func: '\x1b[38;5;102m',
|
|
3364
|
-
},
|
|
3365
|
-
};
|
|
3366
4209
|
|
|
3367
|
-
/**
|
|
3368
|
-
* Constructs a new instance with an optional base preset or custom override.
|
|
3369
|
-
* @param {ColorsList} [defaultColors] - Optional override for the default color scheme.
|
|
3370
|
-
*/
|
|
3371
|
-
constructor(defaultColors = {}) {
|
|
3372
|
-
this.#colors = { ...ColorSafeStringify.#PRESETS.default, ...defaultColors };
|
|
3373
|
-
}
|
|
3374
4210
|
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
* @param {ColorsList} colors - ANSI color mapping to be applied to each JSON element type.
|
|
3379
|
-
* @returns {string} Colorized JSON string.
|
|
3380
|
-
*/
|
|
3381
|
-
#colorizeJSON(str, colors) {
|
|
3382
|
-
/** @type {{ marker: string, key: string }[]} */
|
|
3383
|
-
const keyMatches = [];
|
|
3384
|
-
|
|
3385
|
-
// Colorize numeric values
|
|
3386
|
-
str = str.replace(
|
|
3387
|
-
/(?<!")\b(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\b(?!")/g,
|
|
3388
|
-
`${colors.number}$1${colors.reset}`,
|
|
3389
|
-
);
|
|
4211
|
+
/*========================*
|
|
4212
|
+
* JSON Operations
|
|
4213
|
+
*========================*/
|
|
3390
4214
|
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
4215
|
+
/**
|
|
4216
|
+
* Reads and parses a JSON file.
|
|
4217
|
+
* Throws an error if the file content is not valid JSON.
|
|
4218
|
+
* @param {string} filePath
|
|
4219
|
+
* @returns {any}
|
|
4220
|
+
*/
|
|
4221
|
+
function readJsonFile(filePath) {
|
|
4222
|
+
if (!fs_ignored_.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
4223
|
+
const content = fs_ignored_.readFileSync(filePath, 'utf-8');
|
|
4224
|
+
return JSON.parse(content);
|
|
4225
|
+
}
|
|
3397
4226
|
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
4227
|
+
/**
|
|
4228
|
+
* Saves an object as JSON to a file.
|
|
4229
|
+
* Automatically creates the directory if it does not exist.
|
|
4230
|
+
* @param {string} filePath
|
|
4231
|
+
* @param {any} data
|
|
4232
|
+
* @param {number} [spaces=2]
|
|
4233
|
+
*/
|
|
4234
|
+
function writeJsonFile(filePath, data, spaces = 2) {
|
|
4235
|
+
const json = JSON.stringify(data, null, spaces);
|
|
4236
|
+
fs_ignored_.writeFileSync(filePath, json, 'utf-8');
|
|
4237
|
+
}
|
|
3401
4238
|
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
4239
|
+
/**
|
|
4240
|
+
* Reads and parses a JSON file.
|
|
4241
|
+
* Throws an error if the file content is not valid JSON.
|
|
4242
|
+
* @param {string} filePath
|
|
4243
|
+
* @returns {Promise<any>}
|
|
4244
|
+
*/
|
|
4245
|
+
async function readJsonFileAsync(filePath) {
|
|
4246
|
+
if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
4247
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
4248
|
+
return JSON.parse(content);
|
|
4249
|
+
}
|
|
3405
4250
|
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
4251
|
+
/**
|
|
4252
|
+
* Saves an object as JSON to a file.
|
|
4253
|
+
* Automatically creates the directory if it does not exist.
|
|
4254
|
+
* @param {string} filePath
|
|
4255
|
+
* @param {any} data
|
|
4256
|
+
* @param {number} [spaces=2]
|
|
4257
|
+
* @returns {Promise<void>}
|
|
4258
|
+
*/
|
|
4259
|
+
function writeJsonFileAsync(filePath, data, spaces = 2) {
|
|
4260
|
+
const json = JSON.stringify(data, null, spaces);
|
|
4261
|
+
return fs.promises.writeFile(filePath, json, 'utf-8');
|
|
4262
|
+
}
|
|
3409
4263
|
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
4264
|
+
/*========================*
|
|
4265
|
+
* Directory Management
|
|
4266
|
+
*========================*/
|
|
3413
4267
|
|
|
3414
|
-
|
|
3415
|
-
|
|
4268
|
+
/**
|
|
4269
|
+
* Ensures that the directory exists, creating it recursively if needed.
|
|
4270
|
+
* @param {string} dirPath
|
|
4271
|
+
*/
|
|
4272
|
+
function ensureDirectory(dirPath) {
|
|
4273
|
+
if (!fs_ignored_.existsSync(dirPath)) {
|
|
4274
|
+
fs_ignored_.mkdirSync(dirPath, { recursive: true });
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
3416
4277
|
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
4278
|
+
/**
|
|
4279
|
+
* Clears all contents inside a directory but keeps the directory.
|
|
4280
|
+
* @param {string} dirPath
|
|
4281
|
+
*/
|
|
4282
|
+
function clearDirectory(dirPath) {
|
|
4283
|
+
if (!fs_ignored_.existsSync(dirPath)) return;
|
|
4284
|
+
const files = fs_ignored_.readdirSync(dirPath);
|
|
4285
|
+
for (const file of files) {
|
|
4286
|
+
const fullPath = path_browserify.join(dirPath, file);
|
|
4287
|
+
const stat = fs_ignored_.lstatSync(fullPath);
|
|
4288
|
+
if (stat.isDirectory()) {
|
|
4289
|
+
fs_ignored_.rmSync(fullPath, { recursive: true, force: true });
|
|
4290
|
+
} else {
|
|
4291
|
+
fs_ignored_.unlinkSync(fullPath);
|
|
3421
4292
|
}
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
3422
4295
|
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
// Colorize null values
|
|
3427
|
-
str = str.replace(/(?<!")\bnull\b(?!")/g, `${colors.null}null${colors.reset}`);
|
|
3428
|
-
|
|
3429
|
-
// Highlight special placeholder values
|
|
3430
|
-
str = str.replace(/\[Circular\]/g, `${colors.special}[Circular]${colors.reset}`);
|
|
3431
|
-
str = str.replace(/\[undefined\]/g, `${colors.special}[undefined]${colors.reset}`);
|
|
4296
|
+
/*========================*
|
|
4297
|
+
* File Checks
|
|
4298
|
+
*========================*/
|
|
3432
4299
|
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
4300
|
+
/**
|
|
4301
|
+
* Checks whether a file exists.
|
|
4302
|
+
* @param {string} filePath
|
|
4303
|
+
* @returns {boolean}
|
|
4304
|
+
*/
|
|
4305
|
+
function fileExists(filePath) {
|
|
4306
|
+
return fs_ignored_.existsSync(filePath) && fs_ignored_.lstatSync(filePath).isFile();
|
|
4307
|
+
}
|
|
3437
4308
|
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
return this.#colorizeJSON(json, colors);
|
|
3447
|
-
}
|
|
4309
|
+
/**
|
|
4310
|
+
* Checks whether a directory exists.
|
|
4311
|
+
* @param {string} dirPath
|
|
4312
|
+
* @returns {boolean}
|
|
4313
|
+
*/
|
|
4314
|
+
function dirExists(dirPath) {
|
|
4315
|
+
return fs_ignored_.existsSync(dirPath) && fs_ignored_.lstatSync(dirPath).isDirectory();
|
|
4316
|
+
}
|
|
3448
4317
|
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
4318
|
+
/**
|
|
4319
|
+
* Checks whether a directory is empty.
|
|
4320
|
+
* @param {string} dirPath
|
|
4321
|
+
* @returns {boolean}
|
|
4322
|
+
*/
|
|
4323
|
+
function isDirEmpty(dirPath) {
|
|
4324
|
+
return fs_ignored_.existsSync(dirPath) && fs_ignored_.readdirSync(dirPath).length === 0;
|
|
4325
|
+
}
|
|
3456
4326
|
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
*/
|
|
3461
|
-
updateColors(newColors) {
|
|
3462
|
-
Object.assign(this.#colors, newColors);
|
|
3463
|
-
}
|
|
4327
|
+
/*========================*
|
|
4328
|
+
* File Operations
|
|
4329
|
+
*========================*/
|
|
3464
4330
|
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
4331
|
+
/**
|
|
4332
|
+
* Copies a file to a destination.
|
|
4333
|
+
* @param {string} src
|
|
4334
|
+
* @param {string} dest
|
|
4335
|
+
* @param {number} [mode]
|
|
4336
|
+
*/
|
|
4337
|
+
function ensureCopyFile(src, dest, mode) {
|
|
4338
|
+
ensureDirectory(path_browserify.dirname(dest));
|
|
4339
|
+
fs_ignored_.copyFileSync(src, dest, mode);
|
|
4340
|
+
}
|
|
3471
4341
|
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
4342
|
+
/**
|
|
4343
|
+
* Deletes a file (If the file exists).
|
|
4344
|
+
* @param {string} filePath
|
|
4345
|
+
* @returns {boolean}
|
|
4346
|
+
*/
|
|
4347
|
+
function tryDeleteFile(filePath) {
|
|
4348
|
+
if (fileExists(filePath)) {
|
|
4349
|
+
fs_ignored_.unlinkSync(filePath);
|
|
4350
|
+
return true;
|
|
3481
4351
|
}
|
|
4352
|
+
return false;
|
|
4353
|
+
}
|
|
3482
4354
|
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
4355
|
+
/**
|
|
4356
|
+
* Copies a file to a destination.
|
|
4357
|
+
* @param {string} src
|
|
4358
|
+
* @param {string} dest
|
|
4359
|
+
* @param {number} [mode]
|
|
4360
|
+
* @returns {Promise<void>}
|
|
4361
|
+
*/
|
|
4362
|
+
function ensureCopyFileAsync(src, dest, mode) {
|
|
4363
|
+
ensureDirectory(path.dirname(dest));
|
|
4364
|
+
return fs.promises.copyFile(src, dest, mode);
|
|
4365
|
+
}
|
|
3491
4366
|
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
4367
|
+
/**
|
|
4368
|
+
* Deletes a file (If the file exists).
|
|
4369
|
+
* @param {string} filePath
|
|
4370
|
+
* @returns {Promise<boolean>}
|
|
4371
|
+
*/
|
|
4372
|
+
async function tryDeleteFileAsync(filePath) {
|
|
4373
|
+
if (fileExists(filePath)) {
|
|
4374
|
+
await fs.promises.unlink(filePath);
|
|
4375
|
+
return true;
|
|
3498
4376
|
}
|
|
4377
|
+
return false;
|
|
3499
4378
|
}
|
|
3500
4379
|
|
|
3501
|
-
|
|
4380
|
+
/*========================*
|
|
4381
|
+
* Text Operations
|
|
4382
|
+
*========================*/
|
|
3502
4383
|
|
|
3503
|
-
;// ./src/v1/libs/TinyPromiseQueue.mjs
|
|
3504
4384
|
/**
|
|
3505
|
-
*
|
|
3506
|
-
* @
|
|
3507
|
-
* @
|
|
3508
|
-
* @
|
|
3509
|
-
* @property {string|undefined} [id] - Optional identifier for the task.
|
|
3510
|
-
* @property {string|null|undefined} [marker] - Optional marker for the task.
|
|
3511
|
-
* @property {number|null|undefined} [delay] - Optional delay (in ms) before the task is executed.
|
|
4385
|
+
* Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
|
|
4386
|
+
* @param {string} filePath
|
|
4387
|
+
* @param {string} content
|
|
4388
|
+
* @param {fs.WriteFileOptions} [ops='utf-8']
|
|
3512
4389
|
*/
|
|
4390
|
+
function writeTextFile(filePath, content, ops = 'utf-8') {
|
|
4391
|
+
const dir = path_browserify.dirname(filePath);
|
|
4392
|
+
ensureDirectory(dir);
|
|
4393
|
+
fs_ignored_.writeFileSync(filePath, content, ops);
|
|
4394
|
+
}
|
|
3513
4395
|
|
|
3514
4396
|
/**
|
|
3515
|
-
*
|
|
3516
|
-
*
|
|
3517
|
-
*
|
|
3518
|
-
*
|
|
3519
|
-
*
|
|
3520
|
-
* @class
|
|
4397
|
+
* Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
|
|
4398
|
+
* @param {string} filePath
|
|
4399
|
+
* @param {string} content
|
|
4400
|
+
* @param {fs.WriteFileOptions} [ops='utf-8']
|
|
4401
|
+
* @returns {Promise<void>}
|
|
3521
4402
|
*/
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
#timeouts = {};
|
|
3528
|
-
/** @type {Set<string>} */
|
|
3529
|
-
#blacklist = new Set();
|
|
4403
|
+
function writeTextFileAsync(filePath, content, ops = 'utf-8') {
|
|
4404
|
+
const dir = path.dirname(filePath);
|
|
4405
|
+
ensureDirectory(dir);
|
|
4406
|
+
return fs.promises.writeFile(filePath, content, ops);
|
|
4407
|
+
}
|
|
3530
4408
|
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
* @returns {boolean}
|
|
3535
|
-
*/
|
|
3536
|
-
isRunning() {
|
|
3537
|
-
return this.#running;
|
|
3538
|
-
}
|
|
3539
|
-
|
|
3540
|
-
/**
|
|
3541
|
-
* Processes the a normal task.
|
|
3542
|
-
*
|
|
3543
|
-
* @param {QueuedTask} data
|
|
3544
|
-
*
|
|
3545
|
-
* @returns {Promise<void>}
|
|
3546
|
-
*/
|
|
3547
|
-
async #normalProcessQueue(data) {
|
|
3548
|
-
if (
|
|
3549
|
-
data &&
|
|
3550
|
-
typeof data.task === 'function' &&
|
|
3551
|
-
typeof data.resolve === 'function' &&
|
|
3552
|
-
typeof data.reject === 'function'
|
|
3553
|
-
) {
|
|
3554
|
-
const { task, resolve, reject, delay, id } = data;
|
|
3555
|
-
try {
|
|
3556
|
-
if (id && this.#blacklist.has(id)) {
|
|
3557
|
-
reject(new Error('The function was canceled on TinyPromiseQueue.'));
|
|
3558
|
-
this.#blacklist.delete(id);
|
|
3559
|
-
this.#running = false;
|
|
3560
|
-
this.#processQueue();
|
|
3561
|
-
return;
|
|
3562
|
-
}
|
|
3563
|
-
|
|
3564
|
-
if (delay && id) {
|
|
3565
|
-
await new Promise((resolveDelay) => {
|
|
3566
|
-
const timeoutId = setTimeout(() => {
|
|
3567
|
-
delete this.#timeouts[id];
|
|
3568
|
-
resolveDelay(null);
|
|
3569
|
-
}, delay);
|
|
3570
|
-
this.#timeouts[id] = timeoutId;
|
|
3571
|
-
});
|
|
3572
|
-
}
|
|
3573
|
-
|
|
3574
|
-
const result = await task();
|
|
3575
|
-
resolve(result);
|
|
3576
|
-
} catch (error) {
|
|
3577
|
-
reject(error);
|
|
3578
|
-
} finally {
|
|
3579
|
-
this.#running = false;
|
|
3580
|
-
this.#processQueue();
|
|
3581
|
-
}
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
|
|
3585
|
-
/**
|
|
3586
|
-
* Processes a group task.
|
|
3587
|
-
*
|
|
3588
|
-
* @returns {Promise<void>}
|
|
3589
|
-
*/
|
|
3590
|
-
async #groupProcessQueue() {
|
|
3591
|
-
/** @type {Array<QueuedTask>} */
|
|
3592
|
-
const grouped = [];
|
|
3593
|
-
while (this.#queue.length && this.#queue[0]?.marker === 'POINT_MARKER') {
|
|
3594
|
-
// @ts-ignore
|
|
3595
|
-
grouped.push(this.#queue.shift());
|
|
3596
|
-
}
|
|
3597
|
-
|
|
3598
|
-
if (grouped.length === 0) {
|
|
3599
|
-
this.#running = false;
|
|
3600
|
-
this.#processQueue();
|
|
3601
|
-
return;
|
|
3602
|
-
}
|
|
3603
|
-
|
|
3604
|
-
await Promise.all(
|
|
3605
|
-
grouped.map(
|
|
3606
|
-
({ task, resolve, reject, id }) =>
|
|
3607
|
-
new Promise(async (pResolve) => {
|
|
3608
|
-
if (id && this.#blacklist.has(id)) {
|
|
3609
|
-
this.#blacklist.delete(id);
|
|
3610
|
-
reject(new Error('The function was canceled on TinyPromiseQueue.'));
|
|
3611
|
-
pResolve(true);
|
|
3612
|
-
return;
|
|
3613
|
-
}
|
|
3614
|
-
await task().then(resolve).catch(reject);
|
|
3615
|
-
pResolve(true);
|
|
3616
|
-
}),
|
|
3617
|
-
),
|
|
3618
|
-
);
|
|
3619
|
-
|
|
3620
|
-
this.#running = false;
|
|
3621
|
-
this.#processQueue();
|
|
3622
|
-
}
|
|
3623
|
-
|
|
3624
|
-
/**
|
|
3625
|
-
* Processes the next task in the queue if not already running.
|
|
3626
|
-
* Ensures tasks are executed in order, one at a time.
|
|
3627
|
-
*
|
|
3628
|
-
* @returns {Promise<void>}
|
|
3629
|
-
*/
|
|
3630
|
-
async #processQueue() {
|
|
3631
|
-
if (this.#running || this.#queue.length === 0) return;
|
|
3632
|
-
this.#running = true;
|
|
3633
|
-
if (typeof this.#queue[0]?.marker !== 'string' || this.#queue[0]?.marker !== 'POINT_MARKER') {
|
|
3634
|
-
const data = this.#queue.shift();
|
|
3635
|
-
// @ts-ignore
|
|
3636
|
-
this.#normalProcessQueue(data);
|
|
3637
|
-
} else this.#groupProcessQueue();
|
|
3638
|
-
}
|
|
3639
|
-
|
|
3640
|
-
/**
|
|
3641
|
-
* Returns the index of a task by its ID.
|
|
3642
|
-
*
|
|
3643
|
-
* @param {string} id The ID of the task to locate.
|
|
3644
|
-
* @returns {number} The index of the task in the queue, or -1 if not found.
|
|
3645
|
-
*/
|
|
3646
|
-
getIndexById(id) {
|
|
3647
|
-
return this.#queue.findIndex((item) => item.id === id);
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
/**
|
|
3651
|
-
* Returns a list of IDs for all tasks currently in the queue.
|
|
3652
|
-
*
|
|
3653
|
-
* @returns {{ index: number, id: string }[]} An array of task IDs currently queued.
|
|
3654
|
-
*/
|
|
3655
|
-
getQueuedIds() {
|
|
3656
|
-
// @ts-ignore
|
|
3657
|
-
return this.#queue
|
|
3658
|
-
.map((item, index) => ({ index, id: item.id }))
|
|
3659
|
-
.filter((entry) => typeof entry.id === 'string');
|
|
3660
|
-
}
|
|
3661
|
-
|
|
3662
|
-
/**
|
|
3663
|
-
* Reorders a task in the queue from one index to another.
|
|
3664
|
-
*
|
|
3665
|
-
* @param {number} fromIndex The current index of the task to move.
|
|
3666
|
-
* @param {number} toIndex The index where the task should be placed.
|
|
3667
|
-
*/
|
|
3668
|
-
reorderQueue(fromIndex, toIndex) {
|
|
3669
|
-
if (
|
|
3670
|
-
typeof fromIndex !== 'number' ||
|
|
3671
|
-
typeof toIndex !== 'number' ||
|
|
3672
|
-
fromIndex < 0 ||
|
|
3673
|
-
toIndex < 0 ||
|
|
3674
|
-
fromIndex >= this.#queue.length ||
|
|
3675
|
-
toIndex >= this.#queue.length
|
|
3676
|
-
)
|
|
3677
|
-
return;
|
|
3678
|
-
const [item] = this.#queue.splice(fromIndex, 1);
|
|
3679
|
-
this.#queue.splice(toIndex, 0, item);
|
|
3680
|
-
}
|
|
3681
|
-
|
|
3682
|
-
/**
|
|
3683
|
-
* Inserts a point in the queue where subsequent tasks will be grouped and executed together in a Promise.all.
|
|
3684
|
-
* If the queue is currently empty, behaves like a regular promise.
|
|
3685
|
-
*
|
|
3686
|
-
* @param {(...args: any[]) => Promise<any>|Promise<any>} task A function that returns a Promise.
|
|
3687
|
-
* @param {string} [id] Optional ID to identify the task in the queue.
|
|
3688
|
-
* @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
|
|
3689
|
-
* @throws {Error} Throws if param is invalid.
|
|
3690
|
-
*/
|
|
3691
|
-
async enqueuePoint(task, id) {
|
|
3692
|
-
if (typeof task !== 'function')
|
|
3693
|
-
return Promise.reject(new Error('Task must be a function returning a Promise.'));
|
|
3694
|
-
if (typeof id !== 'undefined' && typeof id !== 'string')
|
|
3695
|
-
throw new Error('The "id" parameter must be a string.');
|
|
3696
|
-
if (!this.#running) return task();
|
|
3697
|
-
return new Promise((resolve, reject) => {
|
|
3698
|
-
this.#queue.push({ marker: 'POINT_MARKER', task, resolve, reject, id });
|
|
3699
|
-
this.#processQueue();
|
|
3700
|
-
});
|
|
3701
|
-
}
|
|
3702
|
-
|
|
3703
|
-
/**
|
|
3704
|
-
* Adds a new async task to the queue and ensures it runs in order after previous tasks.
|
|
3705
|
-
* Optionally, a delay can be added before the task is executed.
|
|
3706
|
-
*
|
|
3707
|
-
* If the task is canceled before execution, it will be rejected with the message:
|
|
3708
|
-
* "The function was canceled on TinyPromiseQueue."
|
|
3709
|
-
*
|
|
3710
|
-
* @param {(...args: any[]) => Promise<any>|Promise<any>} task A function that returns a Promise to be executed sequentially.
|
|
3711
|
-
* @param {number|null} [delay] Optional delay (in ms) before the task is executed.
|
|
3712
|
-
* @param {string} [id] Optional ID to identify the task in the queue.
|
|
3713
|
-
* @returns {Promise<any>} A Promise that resolves or rejects with the result of the task once it's processed.
|
|
3714
|
-
* @throws {Error} Throws if param is invalid.
|
|
3715
|
-
*/
|
|
3716
|
-
enqueue(task, delay, id) {
|
|
3717
|
-
if (typeof task !== 'function')
|
|
3718
|
-
return Promise.reject(new Error('Task must be a function returning a Promise.'));
|
|
3719
|
-
if (typeof delay !== 'undefined' && (typeof delay !== 'number' || delay < 0))
|
|
3720
|
-
return Promise.reject(new Error('Delay must be a positive number or undefined.'));
|
|
3721
|
-
if (typeof id !== 'undefined' && typeof id !== 'string')
|
|
3722
|
-
throw new Error('The "id" parameter must be a string.');
|
|
3723
|
-
|
|
3724
|
-
return new Promise((resolve, reject) => {
|
|
3725
|
-
this.#queue.push({ task, resolve, reject, id, delay });
|
|
3726
|
-
this.#processQueue();
|
|
3727
|
-
});
|
|
3728
|
-
}
|
|
3729
|
-
|
|
3730
|
-
/**
|
|
3731
|
-
* Cancels a scheduled delay and removes the task from the queue.
|
|
3732
|
-
* Adds the ID to a blacklist so the task is skipped if already being processed.
|
|
3733
|
-
*
|
|
3734
|
-
* @param {string} id The ID of the task to cancel.
|
|
3735
|
-
* @returns {boolean} True if a delay was cancelled and the task was removed.
|
|
3736
|
-
* @throws {Error} Throws if `id` is not a string.
|
|
3737
|
-
*/
|
|
3738
|
-
cancelTask(id) {
|
|
3739
|
-
if (typeof id !== 'string') throw new Error('The "id" parameter must be a string.');
|
|
3740
|
-
let cancelled = false;
|
|
3741
|
-
|
|
3742
|
-
if (id in this.#timeouts) {
|
|
3743
|
-
clearTimeout(this.#timeouts[id]);
|
|
3744
|
-
delete this.#timeouts[id];
|
|
3745
|
-
cancelled = true;
|
|
3746
|
-
}
|
|
3747
|
-
|
|
3748
|
-
const index = this.getIndexById(id);
|
|
3749
|
-
if (index !== -1) {
|
|
3750
|
-
const [removed] = this.#queue.splice(index, 1);
|
|
3751
|
-
removed?.reject?.(new Error('The function was canceled on TinyPromiseQueue.'));
|
|
3752
|
-
cancelled = true;
|
|
3753
|
-
}
|
|
3754
|
-
|
|
3755
|
-
if (cancelled) this.#blacklist.add(id);
|
|
3756
|
-
|
|
3757
|
-
return cancelled;
|
|
3758
|
-
}
|
|
3759
|
-
}
|
|
3760
|
-
|
|
3761
|
-
/* harmony default export */ const libs_TinyPromiseQueue = (TinyPromiseQueue);
|
|
3762
|
-
|
|
3763
|
-
;// ./src/v1/libs/TinyRateLimiter.mjs
|
|
3764
|
-
/** @typedef {(groupId: string) => void} OnMemoryExceeded */
|
|
3765
|
-
|
|
3766
|
-
/** @typedef {(groupId: string) => void} OnGroupExpired */
|
|
4409
|
+
/*========================*
|
|
4410
|
+
* Directory Listings
|
|
4411
|
+
*========================*/
|
|
3767
4412
|
|
|
3768
4413
|
/**
|
|
3769
|
-
*
|
|
3770
|
-
*
|
|
3771
|
-
*
|
|
3772
|
-
*
|
|
3773
|
-
* rate limits.
|
|
4414
|
+
* Lists all files and dirs in a directory (optionally recursive).
|
|
4415
|
+
* @param {string} dirPath
|
|
4416
|
+
* @param {boolean} [recursive=false]
|
|
4417
|
+
* @returns {{ files: string[]; dirs: string[] }}
|
|
3774
4418
|
*/
|
|
3775
|
-
|
|
3776
|
-
/** @type {
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
/** @type {number|null|undefined} */
|
|
3792
|
-
#maxIdle = null;
|
|
3793
|
-
|
|
3794
|
-
/** @type {Map<string, number[]>} */
|
|
3795
|
-
groupData = new Map(); // groupId -> timestamps[]
|
|
3796
|
-
|
|
3797
|
-
/** @type {Map<string, number>} */
|
|
3798
|
-
lastSeen = new Map(); // groupId -> timestamp
|
|
3799
|
-
|
|
3800
|
-
/** @type {Map<string, string>} */
|
|
3801
|
-
userToGroup = new Map(); // userId -> groupId
|
|
3802
|
-
|
|
3803
|
-
/** @type {Map<string, boolean>} */
|
|
3804
|
-
groupFlags = new Map(); // groupId -> boolean
|
|
3805
|
-
|
|
3806
|
-
/**
|
|
3807
|
-
* @type {Map<string, number>}
|
|
3808
|
-
* Stores TTL (in ms) for each groupId individually
|
|
3809
|
-
*/
|
|
3810
|
-
groupTTL = new Map();
|
|
3811
|
-
|
|
3812
|
-
/**
|
|
3813
|
-
* @type {null|OnMemoryExceeded}
|
|
3814
|
-
*/
|
|
3815
|
-
#onMemoryExceeded = null;
|
|
3816
|
-
|
|
3817
|
-
/**
|
|
3818
|
-
* Set the callback to be triggered when a group exceeds its limit
|
|
3819
|
-
* @param {OnMemoryExceeded} callback
|
|
3820
|
-
*/
|
|
3821
|
-
setOnMemoryExceeded(callback) {
|
|
3822
|
-
if (typeof callback !== 'function') throw new Error('onMemoryExceeded must be a function');
|
|
3823
|
-
this.#onMemoryExceeded = callback;
|
|
3824
|
-
}
|
|
3825
|
-
|
|
3826
|
-
/**
|
|
3827
|
-
* Clear the onMemoryExceeded callback
|
|
3828
|
-
*/
|
|
3829
|
-
clearOnMemoryExceeded() {
|
|
3830
|
-
this.#onMemoryExceeded = null;
|
|
3831
|
-
}
|
|
3832
|
-
|
|
3833
|
-
/**
|
|
3834
|
-
* @type {null|OnGroupExpired}
|
|
3835
|
-
*/
|
|
3836
|
-
#onGroupExpired = null;
|
|
3837
|
-
|
|
3838
|
-
/**
|
|
3839
|
-
* Set the callback to be triggered when a group expires and is removed.
|
|
3840
|
-
*
|
|
3841
|
-
* This callback is called automatically during cleanup when a group
|
|
3842
|
-
* becomes inactive for longer than its TTL.
|
|
3843
|
-
*
|
|
3844
|
-
* @param {OnGroupExpired} callback - A function that receives the expired groupId.
|
|
3845
|
-
*/
|
|
3846
|
-
setOnGroupExpired(callback) {
|
|
3847
|
-
if (typeof callback !== 'function') throw new Error('onGroupExpired must be a function');
|
|
3848
|
-
this.#onGroupExpired = callback;
|
|
3849
|
-
}
|
|
3850
|
-
|
|
3851
|
-
/**
|
|
3852
|
-
* Clear the onGroupExpired callback
|
|
3853
|
-
*/
|
|
3854
|
-
clearOnGroupExpired() {
|
|
3855
|
-
this.#onGroupExpired = null;
|
|
3856
|
-
}
|
|
3857
|
-
|
|
3858
|
-
/**
|
|
3859
|
-
* @param {Object} options
|
|
3860
|
-
* @param {number|null} [options.maxMemory] - Max memory allowed
|
|
3861
|
-
* @param {number} [options.maxHits] - Max interactions allowed
|
|
3862
|
-
* @param {number} [options.interval] - Time window in milliseconds
|
|
3863
|
-
* @param {number} [options.cleanupInterval] - Interval for automatic cleanup (ms)
|
|
3864
|
-
* @param {number} [options.maxIdle=300000] - Max idle time for a user before being cleaned (ms)
|
|
3865
|
-
*/
|
|
3866
|
-
constructor({ maxHits, interval, cleanupInterval, maxIdle = 300000, maxMemory = 100000 }) {
|
|
3867
|
-
/** @param {number|undefined} val */
|
|
3868
|
-
const isPositiveInteger = (val) =>
|
|
3869
|
-
typeof val === 'number' && Number.isFinite(val) && val >= 1 && Number.isInteger(val);
|
|
3870
|
-
|
|
3871
|
-
const isMaxHitsValid = isPositiveInteger(maxHits);
|
|
3872
|
-
const isIntervalValid = isPositiveInteger(interval);
|
|
3873
|
-
const isCleanupValid = isPositiveInteger(cleanupInterval);
|
|
3874
|
-
const isMaxIdleValid = isPositiveInteger(maxIdle);
|
|
3875
|
-
|
|
3876
|
-
if (!isMaxHitsValid && !isIntervalValid)
|
|
3877
|
-
throw new Error("RateLimiter requires at least one valid option: 'maxHits' or 'interval'.");
|
|
3878
|
-
if (maxHits !== undefined && !isMaxHitsValid)
|
|
3879
|
-
throw new Error("'maxHits' must be a positive integer if defined.");
|
|
3880
|
-
if (interval !== undefined && !isIntervalValid)
|
|
3881
|
-
throw new Error("'interval' must be a positive integer in milliseconds if defined.");
|
|
3882
|
-
if (cleanupInterval !== undefined && !isCleanupValid)
|
|
3883
|
-
throw new Error("'cleanupInterval' must be a positive integer in milliseconds if defined.");
|
|
3884
|
-
if (!isMaxIdleValid) throw new Error("'maxIdle' must be a positive integer in milliseconds.");
|
|
3885
|
-
|
|
3886
|
-
if (typeof maxMemory === 'number' && Number.isFinite(maxMemory) && maxMemory > 0) {
|
|
3887
|
-
this.#maxMemory = Math.floor(maxMemory);
|
|
3888
|
-
} else if (maxMemory === null || maxMemory === undefined) {
|
|
3889
|
-
this.#maxMemory = null;
|
|
3890
|
-
} else {
|
|
3891
|
-
throw new Error('maxMemory must be a positive number or null');
|
|
3892
|
-
}
|
|
3893
|
-
|
|
3894
|
-
this.#maxHits = isMaxHitsValid ? maxHits : null;
|
|
3895
|
-
this.#interval = isIntervalValid ? interval : null;
|
|
3896
|
-
this.#cleanupInterval = isCleanupValid ? cleanupInterval : null;
|
|
3897
|
-
this.#maxIdle = maxIdle;
|
|
3898
|
-
|
|
3899
|
-
// Start automatic cleanup only if cleanupInterval is valid
|
|
3900
|
-
if (this.#cleanupInterval !== null)
|
|
3901
|
-
this.#cleanupTimer = setInterval(() => this._cleanup(), this.#cleanupInterval);
|
|
3902
|
-
}
|
|
3903
|
-
|
|
3904
|
-
/**
|
|
3905
|
-
* Check if a given ID is a groupId (not a userId)
|
|
3906
|
-
* @param {string} id
|
|
3907
|
-
* @returns {boolean}
|
|
3908
|
-
*/
|
|
3909
|
-
isGroupId(id) {
|
|
3910
|
-
const result = this.groupFlags.get(id);
|
|
3911
|
-
return typeof result === 'boolean' ? result : false;
|
|
3912
|
-
}
|
|
3913
|
-
|
|
3914
|
-
/**
|
|
3915
|
-
* Get all user IDs that belong to a given group.
|
|
3916
|
-
* @param {string} groupId
|
|
3917
|
-
* @returns {string[]}
|
|
3918
|
-
*/
|
|
3919
|
-
getUsersInGroup(groupId) {
|
|
3920
|
-
const users = [];
|
|
3921
|
-
for (const [userId, assignedGroup] of this.userToGroup.entries()) {
|
|
3922
|
-
if (assignedGroup === groupId) {
|
|
3923
|
-
users.push(userId);
|
|
4419
|
+
function listFiles(dirPath, recursive = false) {
|
|
4420
|
+
/** @type {{ files: string[]; dirs: string[] }} */
|
|
4421
|
+
const results = { files: [], dirs: [] };
|
|
4422
|
+
if (!dirExists(dirPath)) return results;
|
|
4423
|
+
|
|
4424
|
+
const entries = fs_ignored_.readdirSync(dirPath);
|
|
4425
|
+
for (const entry of entries) {
|
|
4426
|
+
const fullPath = path_browserify.join(dirPath, entry);
|
|
4427
|
+
const stat = fs_ignored_.lstatSync(fullPath);
|
|
4428
|
+
if (stat.isDirectory()) {
|
|
4429
|
+
results.dirs.push(fullPath);
|
|
4430
|
+
if (recursive) {
|
|
4431
|
+
const results2 = listFiles(fullPath, true);
|
|
4432
|
+
results.files.push(...results2.files);
|
|
4433
|
+
results.dirs.push(...results2.dirs);
|
|
3924
4434
|
}
|
|
3925
|
-
}
|
|
3926
|
-
|
|
3927
|
-
return users;
|
|
3928
|
-
}
|
|
3929
|
-
|
|
3930
|
-
/**
|
|
3931
|
-
* Set TTL (in milliseconds) for a specific group
|
|
3932
|
-
* @param {string} groupId
|
|
3933
|
-
* @param {number} ttl
|
|
3934
|
-
*/
|
|
3935
|
-
setGroupTTL(groupId, ttl) {
|
|
3936
|
-
if (typeof ttl !== 'number' || !Number.isFinite(ttl) || ttl <= 0)
|
|
3937
|
-
throw new Error('TTL must be a positive number in milliseconds');
|
|
3938
|
-
this.groupTTL.set(groupId, ttl);
|
|
3939
|
-
}
|
|
3940
|
-
|
|
3941
|
-
/**
|
|
3942
|
-
* Get TTL (in ms) for a specific group.
|
|
3943
|
-
* @param {string} groupId
|
|
3944
|
-
* @returns {number|null}
|
|
3945
|
-
*/
|
|
3946
|
-
getGroupTTL(groupId) {
|
|
3947
|
-
return this.groupTTL.get(groupId) ?? null;
|
|
3948
|
-
}
|
|
3949
|
-
|
|
3950
|
-
/**
|
|
3951
|
-
* Delete the TTL setting for a specific group
|
|
3952
|
-
* @param {string} groupId
|
|
3953
|
-
*/
|
|
3954
|
-
deleteGroupTTL(groupId) {
|
|
3955
|
-
this.groupTTL.delete(groupId);
|
|
3956
|
-
}
|
|
3957
|
-
|
|
3958
|
-
/**
|
|
3959
|
-
* Assign a userId to a groupId, with merge if user has existing data.
|
|
3960
|
-
* @param {string} userId
|
|
3961
|
-
* @param {string} groupId
|
|
3962
|
-
* @throws {Error} If userId is already assigned to a different group
|
|
3963
|
-
*/
|
|
3964
|
-
assignToGroup(userId, groupId) {
|
|
3965
|
-
const existingGroup = this.userToGroup.get(userId);
|
|
3966
|
-
if (existingGroup && existingGroup !== groupId)
|
|
3967
|
-
throw new Error(`User ${userId} is already assigned to group ${existingGroup}`);
|
|
3968
|
-
|
|
3969
|
-
// If the user is already in the group, nothing needs to be done
|
|
3970
|
-
if (existingGroup === groupId) return;
|
|
3971
|
-
const userData = this.groupData.get(userId);
|
|
3972
|
-
|
|
3973
|
-
// Associates the user to the group
|
|
3974
|
-
if (this.isGroupId(userId)) {
|
|
3975
|
-
for (const [uid, gId] of this.userToGroup.entries())
|
|
3976
|
-
if (gId === userId) this.userToGroup.set(uid, groupId);
|
|
3977
|
-
this.userToGroup.delete(userId);
|
|
3978
|
-
} else this.userToGroup.set(userId, groupId);
|
|
3979
|
-
|
|
3980
|
-
// If the user has no data, nothing needs to be done
|
|
3981
|
-
if (!userData) return;
|
|
3982
|
-
|
|
3983
|
-
const groupData = this.groupData.get(groupId);
|
|
3984
|
-
if (groupData) {
|
|
3985
|
-
for (const item of userData) groupData.push(item);
|
|
3986
4435
|
} else {
|
|
3987
|
-
|
|
3988
|
-
for (const item of userData) newData.push(item);
|
|
3989
|
-
this.groupData.set(groupId, newData);
|
|
4436
|
+
results.files.push(fullPath);
|
|
3990
4437
|
}
|
|
3991
|
-
|
|
3992
|
-
this.lastSeen.set(groupId, Date.now());
|
|
3993
|
-
|
|
3994
|
-
// Removes individual data as they are now in the group
|
|
3995
|
-
this.groupFlags.delete(userId);
|
|
3996
|
-
this.groupData.delete(userId);
|
|
3997
|
-
this.lastSeen.delete(userId);
|
|
3998
|
-
this.groupTTL.delete(userId);
|
|
3999
|
-
this.groupFlags.set(groupId, true);
|
|
4000
|
-
}
|
|
4001
|
-
|
|
4002
|
-
/**
|
|
4003
|
-
* Get the groupId for a given userId
|
|
4004
|
-
* @param {string} userId
|
|
4005
|
-
* @returns {string}
|
|
4006
|
-
*/
|
|
4007
|
-
getGroupId(userId) {
|
|
4008
|
-
return this.userToGroup.get(userId) || userId; // fallback: use userId as own group
|
|
4009
4438
|
}
|
|
4439
|
+
return results;
|
|
4440
|
+
}
|
|
4010
4441
|
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
const
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
// Clean up old entries
|
|
4031
|
-
if (this.#interval !== null) {
|
|
4032
|
-
const interval = this.getInterval();
|
|
4033
|
-
const cutoff = now - interval;
|
|
4034
|
-
while (history.length && history[0] < cutoff) {
|
|
4035
|
-
history.shift();
|
|
4036
|
-
}
|
|
4037
|
-
}
|
|
4038
|
-
|
|
4039
|
-
// Optional: keep only the last N entries for memory optimization
|
|
4040
|
-
if (this.#maxMemory !== null && typeof this.#maxMemory === 'number') {
|
|
4041
|
-
if (history.length > this.#maxMemory) {
|
|
4042
|
-
history.splice(0, history.length - this.#maxMemory);
|
|
4043
|
-
if (typeof this.#onMemoryExceeded === 'function') this.#onMemoryExceeded(groupId);
|
|
4442
|
+
/**
|
|
4443
|
+
* Lists all directories in a directory (optionally recursive).
|
|
4444
|
+
* @param {string} dirPath
|
|
4445
|
+
* @param {boolean} [recursive=false]
|
|
4446
|
+
* @returns {string[]}
|
|
4447
|
+
*/
|
|
4448
|
+
function listDirs(dirPath, recursive = false) {
|
|
4449
|
+
/** @type {string[]} */
|
|
4450
|
+
const results = [];
|
|
4451
|
+
if (!dirExists(dirPath)) return results;
|
|
4452
|
+
|
|
4453
|
+
const entries = fs_ignored_.readdirSync(dirPath);
|
|
4454
|
+
for (const entry of entries) {
|
|
4455
|
+
const fullPath = path_browserify.join(dirPath, entry);
|
|
4456
|
+
const stat = fs_ignored_.lstatSync(fullPath);
|
|
4457
|
+
if (stat.isDirectory()) {
|
|
4458
|
+
results.push(fullPath);
|
|
4459
|
+
if (recursive) {
|
|
4460
|
+
results.push(...listDirs(fullPath, true));
|
|
4044
4461
|
}
|
|
4045
4462
|
}
|
|
4046
4463
|
}
|
|
4464
|
+
return results;
|
|
4465
|
+
}
|
|
4047
4466
|
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
* @returns {boolean}
|
|
4052
|
-
*/
|
|
4053
|
-
isRateLimited(userId) {
|
|
4054
|
-
const groupId = this.getGroupId(userId);
|
|
4055
|
-
if (!this.groupData.has(groupId)) return false;
|
|
4056
|
-
|
|
4057
|
-
const history = this.groupData.get(groupId);
|
|
4058
|
-
if (!history) throw new Error(`No data found for groupId: ${groupId}`);
|
|
4059
|
-
|
|
4060
|
-
if (this.#interval !== null) {
|
|
4061
|
-
const now = Date.now();
|
|
4062
|
-
const interval = this.getInterval();
|
|
4063
|
-
const cutoff = now - interval;
|
|
4064
|
-
let count = 0;
|
|
4065
|
-
for (let i = history.length - 1; i >= 0; i--) {
|
|
4066
|
-
if (history[i] > cutoff) count++;
|
|
4067
|
-
else break;
|
|
4068
|
-
}
|
|
4069
|
-
if (this.#maxHits !== null) return count > this.getMaxHits();
|
|
4070
|
-
return count > 0;
|
|
4071
|
-
}
|
|
4467
|
+
/*========================*
|
|
4468
|
+
* File Info
|
|
4469
|
+
*========================*/
|
|
4072
4470
|
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4471
|
+
/**
|
|
4472
|
+
* Returns the size of a file in bytes.
|
|
4473
|
+
* @param {string} filePath
|
|
4474
|
+
* @returns {number}
|
|
4475
|
+
*/
|
|
4476
|
+
function fileSize(filePath) {
|
|
4477
|
+
if (!fileExists(filePath)) return 0;
|
|
4478
|
+
const stats = fs_ignored_.statSync(filePath);
|
|
4479
|
+
return stats.size;
|
|
4480
|
+
}
|
|
4076
4481
|
|
|
4077
|
-
|
|
4482
|
+
/**
|
|
4483
|
+
* Returns the total size of a directory in bytes.
|
|
4484
|
+
* @param {string} dirPath
|
|
4485
|
+
* @returns {number}
|
|
4486
|
+
*/
|
|
4487
|
+
function dirSize(dirPath) {
|
|
4488
|
+
let total = 0;
|
|
4489
|
+
const files = listFiles(dirPath, true).files;
|
|
4490
|
+
for (const file of files) {
|
|
4491
|
+
total += fileSize(file);
|
|
4078
4492
|
}
|
|
4493
|
+
return total;
|
|
4494
|
+
}
|
|
4079
4495
|
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
*/
|
|
4084
|
-
resetGroup(groupId) {
|
|
4085
|
-
this.groupFlags.delete(groupId);
|
|
4086
|
-
this.groupData.delete(groupId);
|
|
4087
|
-
this.lastSeen.delete(groupId);
|
|
4088
|
-
this.groupTTL.delete(groupId);
|
|
4089
|
-
}
|
|
4496
|
+
/*========================*
|
|
4497
|
+
* Backup Utilities
|
|
4498
|
+
*========================*/
|
|
4090
4499
|
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
}
|
|
4103
|
-
|
|
4104
|
-
/**
|
|
4105
|
-
* Manually reset a user mapping
|
|
4106
|
-
* @param {string} userId
|
|
4107
|
-
*/
|
|
4108
|
-
resetUserGroup(userId) {
|
|
4109
|
-
this.userToGroup.delete(userId);
|
|
4110
|
-
}
|
|
4500
|
+
/**
|
|
4501
|
+
* Creates a backup copy of a file with .bak timestamp suffix.
|
|
4502
|
+
* @param {string} filePath
|
|
4503
|
+
* @param {string} [ext='bak']
|
|
4504
|
+
*/
|
|
4505
|
+
function backupFile(filePath, ext = 'bak') {
|
|
4506
|
+
if (!fileExists(filePath)) return;
|
|
4507
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
4508
|
+
const backupPath = `${filePath}.${ext}.${timestamp}`;
|
|
4509
|
+
ensureCopyFile(filePath, backupPath);
|
|
4510
|
+
}
|
|
4111
4511
|
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
this.groupData.set(groupId, timestamps);
|
|
4126
|
-
this.lastSeen.set(groupId, Date.now());
|
|
4127
|
-
}
|
|
4512
|
+
/**
|
|
4513
|
+
* Returns the most recent backup file path for a given file.
|
|
4514
|
+
* @param {string} filePath
|
|
4515
|
+
* @param {string} [ext='bak']
|
|
4516
|
+
* @returns {string} Full path to the most recent backup
|
|
4517
|
+
*/
|
|
4518
|
+
function getLatestBackupPath(filePath, ext = 'bak') {
|
|
4519
|
+
const dir = path_browserify.dirname(filePath);
|
|
4520
|
+
const baseName = path_browserify.basename(filePath);
|
|
4521
|
+
const backups = fs_ignored_.readdirSync(dir)
|
|
4522
|
+
.filter((name) => name.startsWith(`${baseName}.${ext}.`))
|
|
4523
|
+
.sort()
|
|
4524
|
+
.reverse();
|
|
4128
4525
|
|
|
4129
|
-
|
|
4130
|
-
* Check if a group has data
|
|
4131
|
-
* @param {string} groupId
|
|
4132
|
-
* @returns {boolean}
|
|
4133
|
-
*/
|
|
4134
|
-
hasData(groupId) {
|
|
4135
|
-
return this.groupData.has(groupId);
|
|
4136
|
-
}
|
|
4526
|
+
if (backups.length === 0) throw new Error(`No backups found for ${filePath}`);
|
|
4137
4527
|
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
* @param {string} groupId
|
|
4141
|
-
* @returns {number[]}
|
|
4142
|
-
*/
|
|
4143
|
-
getData(groupId) {
|
|
4144
|
-
return this.groupData.get(groupId) || [];
|
|
4145
|
-
}
|
|
4528
|
+
return path_browserify.join(dir, backups[0]);
|
|
4529
|
+
}
|
|
4146
4530
|
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
}
|
|
4531
|
+
/**
|
|
4532
|
+
* Restores the most recent backup of a file.
|
|
4533
|
+
* @param {string} filePath
|
|
4534
|
+
* @param {string} [ext='bak']
|
|
4535
|
+
*/
|
|
4536
|
+
function restoreLatestBackup(filePath, ext = 'bak') {
|
|
4537
|
+
const latestBackup = getLatestBackupPath(filePath, ext);
|
|
4538
|
+
ensureCopyFile(latestBackup, filePath);
|
|
4539
|
+
}
|
|
4157
4540
|
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4541
|
+
/**
|
|
4542
|
+
* Restores the most recent backup of a file.
|
|
4543
|
+
* @param {string} filePath
|
|
4544
|
+
* @param {string} [ext='bak']
|
|
4545
|
+
* @returns {Promise<void>}
|
|
4546
|
+
*/
|
|
4547
|
+
function restoreLatestBackupAsync(filePath, ext = 'bak') {
|
|
4548
|
+
const latestBackup = getLatestBackupPath(filePath, ext);
|
|
4549
|
+
return ensureCopyFileAsync(latestBackup, filePath);
|
|
4550
|
+
}
|
|
4168
4551
|
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
this.groupTTL.delete(groupId);
|
|
4182
|
-
|
|
4183
|
-
// Notify subclass or external binding
|
|
4184
|
-
if (typeof this.#onGroupExpired === 'function') {
|
|
4185
|
-
this.#onGroupExpired(groupId);
|
|
4186
|
-
}
|
|
4187
|
-
}
|
|
4188
|
-
}
|
|
4189
|
-
}
|
|
4552
|
+
/**
|
|
4553
|
+
* Creates a backup copy of a file with .bak timestamp suffix.
|
|
4554
|
+
* @param {string} filePath
|
|
4555
|
+
* @param {string} [ext='bak']
|
|
4556
|
+
* @returns {Promise<void>}
|
|
4557
|
+
*/
|
|
4558
|
+
async function backupFileAsync(filePath, ext = 'bak') {
|
|
4559
|
+
if (!fileExists(filePath)) return;
|
|
4560
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
4561
|
+
const backupPath = `${filePath}.${ext}.${timestamp}`;
|
|
4562
|
+
return ensureCopyFileAsync(filePath, backupPath);
|
|
4563
|
+
}
|
|
4190
4564
|
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
*/
|
|
4195
|
-
getActiveGroups() {
|
|
4196
|
-
return Array.from(this.groupData.keys());
|
|
4197
|
-
}
|
|
4565
|
+
/*========================*
|
|
4566
|
+
* Rename Utilities
|
|
4567
|
+
*========================*/
|
|
4198
4568
|
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4569
|
+
/**
|
|
4570
|
+
* Renames multiple files in a directory using a rename function.
|
|
4571
|
+
* @param {string} dirPath - The target directory.
|
|
4572
|
+
* @param {(original: string, index: number) => string} renameFn - Function that returns new filename.
|
|
4573
|
+
* @param {string[]} [extensions] - Optional: Only rename files with these extensions.
|
|
4574
|
+
*
|
|
4575
|
+
* @throws {TypeError} If any argument has an invalid type.
|
|
4576
|
+
* @throws {Error} If the directory does not exist or contains invalid files.
|
|
4577
|
+
*/
|
|
4578
|
+
function renameFileBatch(dirPath, renameFn, extensions = []) {
|
|
4579
|
+
// Validate types
|
|
4580
|
+
if (typeof dirPath !== 'string') throw new TypeError('dirPath must be a string');
|
|
4581
|
+
if (typeof renameFn !== 'function') throw new TypeError('renameFn must be a function');
|
|
4582
|
+
if (!Array.isArray(extensions)) throw new TypeError('extensions must be an array of strings');
|
|
4583
|
+
if (!fs_ignored_.existsSync(dirPath) || !fs_ignored_.statSync(dirPath).isDirectory())
|
|
4584
|
+
throw new Error(`Directory not found or invalid: ${dirPath}`);
|
|
4585
|
+
for (const ext of extensions) {
|
|
4586
|
+
if (typeof ext !== 'string' || !ext.startsWith('.'))
|
|
4587
|
+
throw new TypeError(`Invalid extension: ${ext}`);
|
|
4205
4588
|
}
|
|
4206
4589
|
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
* @returns {number}
|
|
4210
|
-
*/
|
|
4211
|
-
getInterval() {
|
|
4212
|
-
if (typeof this.#interval !== 'number' || !Number.isFinite(this.#interval)) {
|
|
4213
|
-
throw new Error("'interval' is not a valid finite number.");
|
|
4214
|
-
}
|
|
4215
|
-
return this.#interval;
|
|
4216
|
-
}
|
|
4590
|
+
const files = listFiles(dirPath).files;
|
|
4591
|
+
let index = 0;
|
|
4217
4592
|
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
*/
|
|
4222
|
-
getMaxHits() {
|
|
4223
|
-
if (typeof this.#maxHits !== 'number' || !Number.isFinite(this.#maxHits)) {
|
|
4224
|
-
throw new Error("'maxHits' is not a valid finite number.");
|
|
4225
|
-
}
|
|
4226
|
-
return this.#maxHits;
|
|
4227
|
-
}
|
|
4593
|
+
for (const file of files) {
|
|
4594
|
+
const ext = path_browserify.extname(file);
|
|
4595
|
+
if (extensions.length && !extensions.includes(ext)) continue;
|
|
4228
4596
|
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
* @returns {number}
|
|
4233
|
-
*/
|
|
4234
|
-
getTotalHits(groupId) {
|
|
4235
|
-
const history = this.groupData.get(groupId);
|
|
4236
|
-
return Array.isArray(history) ? history.length : 0;
|
|
4237
|
-
}
|
|
4597
|
+
const originalName = path_browserify.basename(file);
|
|
4598
|
+
const newName = renameFn(originalName, index++);
|
|
4599
|
+
const newPath = path_browserify.join(dirPath, newName);
|
|
4238
4600
|
|
|
4239
|
-
|
|
4240
|
-
* Get the timestamp of the last hit for a group.
|
|
4241
|
-
* @param {string} groupId
|
|
4242
|
-
* @returns {number|null}
|
|
4243
|
-
*/
|
|
4244
|
-
getLastHit(groupId) {
|
|
4245
|
-
const history = this.groupData.get(groupId);
|
|
4246
|
-
return history?.length ? history[history.length - 1] : null;
|
|
4247
|
-
}
|
|
4601
|
+
if (originalName === newName) continue;
|
|
4248
4602
|
|
|
4249
|
-
|
|
4250
|
-
* Get milliseconds since the last hit for a group.
|
|
4251
|
-
* @param {string} groupId
|
|
4252
|
-
* @returns {number|null}
|
|
4253
|
-
*/
|
|
4254
|
-
getTimeSinceLastHit(groupId) {
|
|
4255
|
-
const last = this.getLastHit(groupId);
|
|
4256
|
-
return last !== null ? Date.now() - last : null;
|
|
4603
|
+
fs_ignored_.renameSync(file, newPath);
|
|
4257
4604
|
}
|
|
4605
|
+
}
|
|
4258
4606
|
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4607
|
+
/**
|
|
4608
|
+
* Renames files using regex replacement.
|
|
4609
|
+
* @param {string} dirPath
|
|
4610
|
+
* @param {RegExp} pattern - Regex to match in the filename.
|
|
4611
|
+
* @param {string} replacement - Replacement string.
|
|
4612
|
+
* @param {string[]} [extensions]
|
|
4613
|
+
*/
|
|
4614
|
+
function renameFileRegex(dirPath, pattern, replacement, extensions = []) {
|
|
4615
|
+
renameFileBatch(
|
|
4616
|
+
dirPath,
|
|
4617
|
+
(filename) => {
|
|
4618
|
+
const ext = path_browserify.extname(filename);
|
|
4619
|
+
const name = path_browserify.basename(filename, ext).replace(pattern, replacement);
|
|
4620
|
+
return `${name}${ext}`;
|
|
4621
|
+
},
|
|
4622
|
+
extensions,
|
|
4623
|
+
);
|
|
4624
|
+
}
|
|
4273
4625
|
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4626
|
+
/**
|
|
4627
|
+
* Adds a prefix or suffix to filenames.
|
|
4628
|
+
* @param {string} dirPath
|
|
4629
|
+
* @param {{ prefix?: string, suffix?: string }} options
|
|
4630
|
+
* @param {string[]} [extensions]
|
|
4631
|
+
*/
|
|
4632
|
+
function renameFileAddPrefixSuffix(dirPath, { prefix = '', suffix = '' }, extensions = []) {
|
|
4633
|
+
renameFileBatch(
|
|
4634
|
+
dirPath,
|
|
4635
|
+
(filename) => {
|
|
4636
|
+
const ext = path_browserify.extname(filename);
|
|
4637
|
+
const name = path_browserify.basename(filename, ext);
|
|
4638
|
+
return `${prefix}${name}${suffix}${ext}`;
|
|
4639
|
+
},
|
|
4640
|
+
extensions,
|
|
4641
|
+
);
|
|
4642
|
+
}
|
|
4282
4643
|
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4644
|
+
/**
|
|
4645
|
+
* Normalizes all filenames to lowercase (or uppercase).
|
|
4646
|
+
* @param {string} dirPath
|
|
4647
|
+
* @param {'lower' | 'upper' | 'title'} mode
|
|
4648
|
+
* @param {string[]} [extensions]
|
|
4649
|
+
* @param {boolean} [normalizeExt=false] - Whether to apply case change to file extensions too.
|
|
4650
|
+
* @throws {Error} If mode is invalid.
|
|
4651
|
+
*/
|
|
4652
|
+
function renameFileNormalizeCase(
|
|
4653
|
+
dirPath,
|
|
4654
|
+
mode = 'lower',
|
|
4655
|
+
extensions = [],
|
|
4656
|
+
normalizeExt = false,
|
|
4657
|
+
) {
|
|
4658
|
+
if (typeof mode !== 'string' || !['lower', 'upper', 'title'].includes(mode))
|
|
4659
|
+
throw new Error(`Invalid mode "${mode}". Must be 'lower', 'upper' or 'title'.`);
|
|
4660
|
+
renameFileBatch(
|
|
4661
|
+
dirPath,
|
|
4662
|
+
(filename) => {
|
|
4663
|
+
/**
|
|
4664
|
+
* @param {string} text
|
|
4665
|
+
* @returns {string}
|
|
4666
|
+
*/
|
|
4667
|
+
const changeToMode = (text) => {
|
|
4668
|
+
if (mode === 'lower') return text.toLowerCase();
|
|
4669
|
+
else if (mode === 'upper') return text.toUpperCase();
|
|
4670
|
+
else if (mode === 'title') return toTitleCase(text);
|
|
4671
|
+
else return text;
|
|
4302
4672
|
};
|
|
4303
|
-
}
|
|
4304
4673
|
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
lastHit,
|
|
4313
|
-
timeSinceLastHit,
|
|
4314
|
-
averageHitSpacing,
|
|
4315
|
-
};
|
|
4316
|
-
}
|
|
4317
|
-
|
|
4318
|
-
/**
|
|
4319
|
-
* Destroy the rate limiter, stopping cleanup and clearing data
|
|
4320
|
-
*/
|
|
4321
|
-
destroy() {
|
|
4322
|
-
if (this.#cleanupTimer) clearInterval(this.#cleanupTimer);
|
|
4323
|
-
this._cleanup();
|
|
4324
|
-
this.groupData.clear();
|
|
4325
|
-
this.lastSeen.clear();
|
|
4326
|
-
this.userToGroup.clear();
|
|
4327
|
-
this.groupTTL.clear();
|
|
4328
|
-
this.groupFlags.clear();
|
|
4329
|
-
}
|
|
4674
|
+
const rawExt = path_browserify.extname(filename);
|
|
4675
|
+
const ext = normalizeExt ? changeToMode(rawExt) : rawExt;
|
|
4676
|
+
const name = changeToMode(path_browserify.basename(filename, rawExt));
|
|
4677
|
+
return `${name}${ext}`;
|
|
4678
|
+
},
|
|
4679
|
+
extensions,
|
|
4680
|
+
);
|
|
4330
4681
|
}
|
|
4331
4682
|
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4683
|
+
/**
|
|
4684
|
+
* Pads numbers in filenames (e.g., "img1.jpg" -> "img001.jpg").
|
|
4685
|
+
* @param {string} dirPath
|
|
4686
|
+
* @param {number} totalDigits
|
|
4687
|
+
* @param {string[]} [extensions]
|
|
4688
|
+
*/
|
|
4689
|
+
function renameFilePadNumbers(dirPath, totalDigits = 3, extensions = []) {
|
|
4690
|
+
renameFileBatch(
|
|
4691
|
+
dirPath,
|
|
4692
|
+
(filename) => {
|
|
4693
|
+
return filename.replace(/\d+/, (match) => match.padStart(totalDigits, '0'));
|
|
4694
|
+
},
|
|
4695
|
+
extensions,
|
|
4696
|
+
);
|
|
4697
|
+
}
|
|
4335
4698
|
|
|
4699
|
+
;// ./src/v1/basics/index.mjs
|
|
4336
4700
|
|
|
4337
4701
|
|
|
4338
4702
|
|
|
@@ -4348,6 +4712,6 @@ class TinyRateLimiter {
|
|
|
4348
4712
|
|
|
4349
4713
|
})();
|
|
4350
4714
|
|
|
4351
|
-
window.
|
|
4715
|
+
window.TinyBasicsEs = __webpack_exports__;
|
|
4352
4716
|
/******/ })()
|
|
4353
4717
|
;
|