@esposter/shared 4.0.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +326 -126
- package/dist/index.js +1378 -74
- package/package.json +12 -11
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ResultAsync, fromThrowable } from "neverthrow";
|
|
3
3
|
import { parse } from "node-html-parser";
|
|
4
|
-
import
|
|
4
|
+
import mimeDatabase from "mime-db/db.json" with { type: "json" };
|
|
5
|
+
import { defaultTreeAdapter, html as html$1, parseFragment } from "parse5";
|
|
5
6
|
//#region src/models/error/ForbiddenError.ts
|
|
6
7
|
var ForbiddenError = class extends Error {
|
|
7
8
|
constructor(message) {
|
|
@@ -42,6 +43,53 @@ var WordFilteredError = class extends Error {
|
|
|
42
43
|
}
|
|
43
44
|
};
|
|
44
45
|
//#endregion
|
|
46
|
+
//#region src/models/shared/Operation.ts
|
|
47
|
+
let Operation = /* @__PURE__ */ function(Operation) {
|
|
48
|
+
Operation["Create"] = "Create";
|
|
49
|
+
Operation["Delete"] = "Delete";
|
|
50
|
+
Operation["Push"] = "Push";
|
|
51
|
+
Operation["Read"] = "Read";
|
|
52
|
+
Operation["Unshift"] = "Unshift";
|
|
53
|
+
Operation["Update"] = "Update";
|
|
54
|
+
return Operation;
|
|
55
|
+
}({});
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/models/eventEmitter/TypedEventEmitter.ts
|
|
58
|
+
var TypedEventEmitter = class {
|
|
59
|
+
#entriesMap = /* @__PURE__ */ new Map();
|
|
60
|
+
emit(event, ...args) {
|
|
61
|
+
const entries = this.#entriesMap.get(event) ?? [];
|
|
62
|
+
if (entries.length === 0) return false;
|
|
63
|
+
this.#setEntries(event, entries.filter((entry) => !entry.isOnce));
|
|
64
|
+
for (const { listener } of entries) listener(...args);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
off(event, listener) {
|
|
68
|
+
this.#setEntries(event, (this.#entriesMap.get(event) ?? []).filter((entry) => entry.listener !== listener));
|
|
69
|
+
}
|
|
70
|
+
on(event, listener) {
|
|
71
|
+
this.#addEntry(event, listener, false);
|
|
72
|
+
}
|
|
73
|
+
once(event, listener) {
|
|
74
|
+
this.#addEntry(event, listener, true);
|
|
75
|
+
}
|
|
76
|
+
removeAllListeners(event) {
|
|
77
|
+
if (event === void 0) this.#entriesMap.clear();
|
|
78
|
+
else this.#entriesMap.delete(event);
|
|
79
|
+
}
|
|
80
|
+
#addEntry(event, listener, isOnce) {
|
|
81
|
+
if (typeof listener !== "function") throw new InvalidOperationError("Create", String(event), "The listener must be a function");
|
|
82
|
+
this.#setEntries(event, [...this.#entriesMap.get(event) ?? [], {
|
|
83
|
+
isOnce,
|
|
84
|
+
listener
|
|
85
|
+
}]);
|
|
86
|
+
}
|
|
87
|
+
#setEntries(event, entries) {
|
|
88
|
+
if (entries.length === 0) this.#entriesMap.delete(event);
|
|
89
|
+
else this.#entriesMap.set(event, entries);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
//#endregion
|
|
45
93
|
//#region src/models/message/MentionType.ts
|
|
46
94
|
let MentionType = /* @__PURE__ */ function(MentionType) {
|
|
47
95
|
MentionType["Role"] = "role";
|
|
@@ -49,6 +97,13 @@ let MentionType = /* @__PURE__ */ function(MentionType) {
|
|
|
49
97
|
return MentionType;
|
|
50
98
|
}({});
|
|
51
99
|
//#endregion
|
|
100
|
+
//#region src/models/object/ObjectMethod.ts
|
|
101
|
+
let ObjectMethod = /* @__PURE__ */ function(ObjectMethod) {
|
|
102
|
+
ObjectMethod["ToString"] = "toString";
|
|
103
|
+
ObjectMethod["ValueOf"] = "valueOf";
|
|
104
|
+
return ObjectMethod;
|
|
105
|
+
}({});
|
|
106
|
+
//#endregion
|
|
52
107
|
//#region src/services/app/constants.ts
|
|
53
108
|
const MAX_READ_LIMIT = 1e3;
|
|
54
109
|
const SITE_NAME = "Esposter";
|
|
@@ -106,17 +161,6 @@ var ItemMetadata = class {
|
|
|
106
161
|
updatedAt = /* @__PURE__ */ new Date();
|
|
107
162
|
};
|
|
108
163
|
//#endregion
|
|
109
|
-
//#region src/models/shared/Operation.ts
|
|
110
|
-
let Operation = /* @__PURE__ */ function(Operation) {
|
|
111
|
-
Operation["Create"] = "Create";
|
|
112
|
-
Operation["Delete"] = "Delete";
|
|
113
|
-
Operation["Push"] = "Push";
|
|
114
|
-
Operation["Read"] = "Read";
|
|
115
|
-
Operation["Unshift"] = "Unshift";
|
|
116
|
-
Operation["Update"] = "Update";
|
|
117
|
-
return Operation;
|
|
118
|
-
}({});
|
|
119
|
-
//#endregion
|
|
120
164
|
//#region src/util/reactivity/getRawData.ts
|
|
121
165
|
const getRawData = (data) => {
|
|
122
166
|
const raw = data && data.__v_raw;
|
|
@@ -153,6 +197,15 @@ const getResult = (callback) => fromThrowable(callback, toAppError)();
|
|
|
153
197
|
//#region src/services/error/getResultAsync.ts
|
|
154
198
|
const getResultAsync = (callback) => ResultAsync.fromThrowable(callback, toAppError)();
|
|
155
199
|
//#endregion
|
|
200
|
+
//#region src/services/error/reportFinalizerError.ts
|
|
201
|
+
const reportFinalizerError = (callbackResult, finalizerError) => {
|
|
202
|
+
callbackResult.match(() => {
|
|
203
|
+
throw finalizerError;
|
|
204
|
+
}, () => {
|
|
205
|
+
console.error(finalizerError);
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
//#endregion
|
|
156
209
|
//#region src/util/function/noop.ts
|
|
157
210
|
const noop = () => {};
|
|
158
211
|
//#endregion
|
|
@@ -160,11 +213,7 @@ const noop = () => {};
|
|
|
160
213
|
const withFinalizer = (callback, finalizer) => {
|
|
161
214
|
const callbackResult = getResult(callback);
|
|
162
215
|
if (finalizer) getResult(finalizer).match(noop, (error) => {
|
|
163
|
-
callbackResult
|
|
164
|
-
throw error;
|
|
165
|
-
}, () => {
|
|
166
|
-
console.error(error);
|
|
167
|
-
});
|
|
216
|
+
reportFinalizerError(callbackResult, error);
|
|
168
217
|
});
|
|
169
218
|
return callbackResult.match((value) => value, (error) => {
|
|
170
219
|
throw error;
|
|
@@ -175,17 +224,142 @@ const withFinalizer = (callback, finalizer) => {
|
|
|
175
224
|
const withFinalizerAsync = async (callback, finalizer) => {
|
|
176
225
|
const callbackResult = await getResultAsync(async () => callback());
|
|
177
226
|
if (finalizer) await getResultAsync(async () => finalizer()).match(noop, (error) => {
|
|
178
|
-
callbackResult
|
|
179
|
-
throw error;
|
|
180
|
-
}, () => {
|
|
181
|
-
console.error(error);
|
|
182
|
-
});
|
|
227
|
+
reportFinalizerError(callbackResult, error);
|
|
183
228
|
});
|
|
184
229
|
return callbackResult.match((value) => value, (error) => {
|
|
185
230
|
throw error;
|
|
186
231
|
});
|
|
187
232
|
};
|
|
188
233
|
//#endregion
|
|
234
|
+
//#region src/services/indexedDb/getRequestError.ts
|
|
235
|
+
const getRequestError = (request) => request.error ?? new DOMException("The request failed without an error", "UnknownError");
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/services/indexedDb/getTransactionDone.ts
|
|
238
|
+
const getTransactionDone = (transaction) => new Promise((resolve, reject) => {
|
|
239
|
+
transaction.addEventListener("complete", () => {
|
|
240
|
+
resolve();
|
|
241
|
+
});
|
|
242
|
+
transaction.addEventListener("abort", () => {
|
|
243
|
+
reject(transaction.error ?? new DOMException("The transaction was aborted", "AbortError"));
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/services/indexedDb/requestToPromise.ts
|
|
248
|
+
const requestToPromise = (request) => new Promise((resolve, reject) => {
|
|
249
|
+
request.addEventListener("success", () => {
|
|
250
|
+
resolve(request.result);
|
|
251
|
+
});
|
|
252
|
+
request.addEventListener("error", () => {
|
|
253
|
+
reject(getRequestError(request));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/services/indexedDb/wrapIndex.ts
|
|
258
|
+
const wrapIndex = (index) => ({
|
|
259
|
+
getAll: (query, count) => requestToPromise(index.getAll(query, count)),
|
|
260
|
+
getAllKeys: (query, count) => requestToPromise(index.getAllKeys(query, count))
|
|
261
|
+
});
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/services/indexedDb/wrapObjectStore.ts
|
|
264
|
+
const wrapObjectStore = (objectStore) => {
|
|
265
|
+
const reader = {
|
|
266
|
+
get: (key) => requestToPromise(objectStore.get(key)),
|
|
267
|
+
getAll: (query, count) => requestToPromise(objectStore.getAll(query, count)),
|
|
268
|
+
getAllKeys: (query, count) => requestToPromise(objectStore.getAllKeys(query, count)),
|
|
269
|
+
index: (name) => wrapIndex(objectStore.index(name))
|
|
270
|
+
};
|
|
271
|
+
const writer = {
|
|
272
|
+
delete: (query) => requestToPromise(objectStore.delete(query)),
|
|
273
|
+
put: (value, key) => requestToPromise(objectStore.put(value, key))
|
|
274
|
+
};
|
|
275
|
+
const creator = { createIndex: (name, keyPath, options) => {
|
|
276
|
+
objectStore.createIndex(name, keyPath, options);
|
|
277
|
+
} };
|
|
278
|
+
return {
|
|
279
|
+
...reader,
|
|
280
|
+
...writer,
|
|
281
|
+
...creator
|
|
282
|
+
};
|
|
283
|
+
};
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/services/indexedDb/getTransactionObjectStore.ts
|
|
286
|
+
const getTransactionObjectStore = (transaction) => (name) => wrapObjectStore(transaction.objectStore(name));
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/services/indexedDb/wrapTransaction.ts
|
|
289
|
+
const wrapTransaction = (transaction) => ({
|
|
290
|
+
done: getTransactionDone(transaction),
|
|
291
|
+
objectStore: getTransactionObjectStore(transaction)
|
|
292
|
+
});
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/services/indexedDb/wrapDatabase.ts
|
|
295
|
+
const wrapDatabase = (database) => {
|
|
296
|
+
const getReadonlyObjectStore = (storeName) => wrapObjectStore(database.transaction(storeName, "readonly").objectStore(storeName));
|
|
297
|
+
const getReadwriteObjectStore = (storeName) => wrapObjectStore(database.transaction(storeName, "readwrite").objectStore(storeName));
|
|
298
|
+
const transaction = (storeNames, mode, options) => {
|
|
299
|
+
const names = typeof storeNames === "string" ? storeNames : [...storeNames];
|
|
300
|
+
return wrapTransaction(database.transaction(names, mode, options));
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
close: () => {
|
|
304
|
+
database.close();
|
|
305
|
+
},
|
|
306
|
+
delete: (storeName, query) => getReadwriteObjectStore(storeName).delete(query),
|
|
307
|
+
get: (storeName, key) => getReadonlyObjectStore(storeName).get(key),
|
|
308
|
+
getAll: (storeName, query, count) => getReadonlyObjectStore(storeName).getAll(query, count),
|
|
309
|
+
getAllKeys: (storeName, query, count) => getReadonlyObjectStore(storeName).getAllKeys(query, count),
|
|
310
|
+
name: database.name,
|
|
311
|
+
put: (storeName, value, key) => getReadwriteObjectStore(storeName).put(value, key),
|
|
312
|
+
transaction
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/services/indexedDb/wrapUpgradeDatabase.ts
|
|
317
|
+
const wrapUpgradeDatabase = (database) => ({
|
|
318
|
+
...wrapDatabase(database),
|
|
319
|
+
createObjectStore: (name, options) => wrapObjectStore(database.createObjectStore(name, options))
|
|
320
|
+
});
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/services/indexedDb/wrapUpgradeTransaction.ts
|
|
323
|
+
const wrapUpgradeTransaction = (transaction) => {
|
|
324
|
+
const done = getTransactionDone(transaction);
|
|
325
|
+
getResultAsync(() => done).match(noop, console.error);
|
|
326
|
+
return {
|
|
327
|
+
get done() {
|
|
328
|
+
return done;
|
|
329
|
+
},
|
|
330
|
+
objectStore: getTransactionObjectStore(transaction)
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/services/indexedDb/openDatabase.ts
|
|
335
|
+
const openDatabase = (name, version, options = {}) => new Promise((resolve, reject) => {
|
|
336
|
+
const request = indexedDB.open(name, version);
|
|
337
|
+
request.addEventListener("blocked", (event) => {
|
|
338
|
+
options.blocked?.(event.oldVersion, event.newVersion);
|
|
339
|
+
});
|
|
340
|
+
request.addEventListener("upgradeneeded", (event) => {
|
|
341
|
+
const { transaction } = request;
|
|
342
|
+
if (!transaction) return;
|
|
343
|
+
getResult(() => options.upgrade?.(wrapUpgradeDatabase(request.result), event.oldVersion, event.newVersion, wrapUpgradeTransaction(transaction))).match(noop, (error) => {
|
|
344
|
+
console.error(error);
|
|
345
|
+
transaction.abort();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
request.addEventListener("success", () => {
|
|
349
|
+
const database = request.result;
|
|
350
|
+
database.addEventListener("close", () => {
|
|
351
|
+
options.terminated?.();
|
|
352
|
+
});
|
|
353
|
+
database.addEventListener("versionchange", (event) => {
|
|
354
|
+
options.blocking?.(event.oldVersion, event.newVersion);
|
|
355
|
+
});
|
|
356
|
+
resolve(wrapDatabase(database));
|
|
357
|
+
});
|
|
358
|
+
request.addEventListener("error", () => {
|
|
359
|
+
reject(getRequestError(request));
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
//#endregion
|
|
189
363
|
//#region src/services/message/constants.ts
|
|
190
364
|
const MENTION_ID_ATTRIBUTE = "data-id";
|
|
191
365
|
const MENTION_ITEM_TYPE_ATTRIBUTE = "data-mention-type";
|
|
@@ -197,35 +371,905 @@ const MENTION_HERE_ID = "@here";
|
|
|
197
371
|
const MENTION_EVERYONE_ID = "@everyone";
|
|
198
372
|
const CUSTOM_EMOJI_ID_ATTRIBUTE = "data-custom-emoji-id";
|
|
199
373
|
const CUSTOM_EMOJI_NAME_ATTRIBUTE = "data-custom-emoji-name";
|
|
374
|
+
const SPOILER_ATTRIBUTE = "data-spoiler";
|
|
375
|
+
const SPOILER_SELECTOR = `span[${SPOILER_ATTRIBUTE}]`;
|
|
200
376
|
//#endregion
|
|
201
377
|
//#region src/services/message/getMentions.ts
|
|
202
378
|
const getMentions = (message) => parse(message).querySelectorAll(MENTION_SELECTOR);
|
|
203
379
|
//#endregion
|
|
380
|
+
//#region src/services/mime/constants.ts
|
|
381
|
+
const MEDIA_TYPE_REGEX = /^\s*(?<mediaType>[^;\s]*)(?:;|\s|$)/u;
|
|
382
|
+
const PATH_SEPARATOR_REGEX = /[/\\]/u;
|
|
383
|
+
const TEXT_MEDIA_TYPE_REGEX = /^text\//iu;
|
|
384
|
+
const CHARSET_PARAMETER_REGEX = /;\s*charset=/iu;
|
|
385
|
+
const DEFAULT_TEXT_CHARSET = "UTF-8";
|
|
386
|
+
const FacetScoreMap = /* @__PURE__ */ new Map([
|
|
387
|
+
["prs.", 100],
|
|
388
|
+
["vnd.", 400],
|
|
389
|
+
["x-", 200],
|
|
390
|
+
["x.", 300]
|
|
391
|
+
]);
|
|
392
|
+
const DEFAULT_FACET_SCORE = 900;
|
|
393
|
+
const SourceScoreMap = /* @__PURE__ */ new Map([
|
|
394
|
+
["apache", 20],
|
|
395
|
+
["iana", 40],
|
|
396
|
+
["nginx", 10]
|
|
397
|
+
]);
|
|
398
|
+
const DEFAULT_SOURCE_SCORE = 30;
|
|
399
|
+
const TypeScoreMap = /* @__PURE__ */ new Map([
|
|
400
|
+
["application", 1],
|
|
401
|
+
["audio", 2],
|
|
402
|
+
["font", 2],
|
|
403
|
+
["video", 3]
|
|
404
|
+
]);
|
|
405
|
+
const DEFAULT_TYPE_SCORE = 0;
|
|
406
|
+
//#endregion
|
|
407
|
+
//#region src/services/mime/getMediaType.ts
|
|
408
|
+
const getMediaType = (value) => MEDIA_TYPE_REGEX.exec(value)?.groups?.mediaType ?? "";
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/services/mime/getMimeTypeEntryMap.ts
|
|
411
|
+
let mimeTypeEntryMap;
|
|
412
|
+
const getMimeTypeEntryMap = () => {
|
|
413
|
+
mimeTypeEntryMap ??= new Map(Object.entries(mimeDatabase));
|
|
414
|
+
return mimeTypeEntryMap;
|
|
415
|
+
};
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region src/services/mime/charset.ts
|
|
418
|
+
const charset = (type) => {
|
|
419
|
+
const mediaType = getMediaType(type);
|
|
420
|
+
const dataCharset = getMimeTypeEntryMap().get(mediaType.toLowerCase())?.charset;
|
|
421
|
+
if (dataCharset) return dataCharset;
|
|
422
|
+
return TEXT_MEDIA_TYPE_REGEX.test(mediaType) ? DEFAULT_TEXT_CHARSET : false;
|
|
423
|
+
};
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/services/mime/getMimeScore.ts
|
|
426
|
+
const getMimeScore = (mimeType, source = "") => {
|
|
427
|
+
if (mimeType === "application/octet-stream") return 0;
|
|
428
|
+
const [type = "", subtype = ""] = mimeType.split("/");
|
|
429
|
+
const facet = subtype.replace(/(?<prefix>\.|x-).*/u, "$<prefix>");
|
|
430
|
+
const facetScore = FacetScoreMap.get(facet) ?? 900;
|
|
431
|
+
const sourceScore = SourceScoreMap.get(source) ?? 30;
|
|
432
|
+
const typeScore = TypeScoreMap.get(type) ?? 0;
|
|
433
|
+
const lengthScore = 1 - mimeType.length / 100;
|
|
434
|
+
return facetScore + sourceScore + typeScore + lengthScore;
|
|
435
|
+
};
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/services/mime/getExtensionMimeTypeMap.ts
|
|
438
|
+
let extensionMimeTypeMap;
|
|
439
|
+
const getPreferredMimeType = (currentMimeType, candidateMimeType) => {
|
|
440
|
+
if (currentMimeType === void 0) return candidateMimeType;
|
|
441
|
+
const entryMap = getMimeTypeEntryMap();
|
|
442
|
+
return getMimeScore(currentMimeType, entryMap.get(currentMimeType)?.source) > getMimeScore(candidateMimeType, entryMap.get(candidateMimeType)?.source) ? currentMimeType : candidateMimeType;
|
|
443
|
+
};
|
|
444
|
+
const getExtensionMimeTypeMap = () => {
|
|
445
|
+
if (!extensionMimeTypeMap) {
|
|
446
|
+
const map = /* @__PURE__ */ new Map();
|
|
447
|
+
for (const [mimeType, { extensions = [] }] of getMimeTypeEntryMap()) for (const extension of extensions) map.set(extension, getPreferredMimeType(map.get(extension), mimeType));
|
|
448
|
+
extensionMimeTypeMap = map;
|
|
449
|
+
}
|
|
450
|
+
return extensionMimeTypeMap;
|
|
451
|
+
};
|
|
452
|
+
//#endregion
|
|
453
|
+
//#region src/services/mime/lookup.ts
|
|
454
|
+
const getExtension = (path) => {
|
|
455
|
+
const name = path.split(PATH_SEPARATOR_REGEX).at(-1) ?? "";
|
|
456
|
+
const dotIndex = name.lastIndexOf(".");
|
|
457
|
+
if (path === name) return name.slice(dotIndex + 1);
|
|
458
|
+
return dotIndex > 0 ? name.slice(dotIndex + 1) : "";
|
|
459
|
+
};
|
|
460
|
+
const lookup = (path) => {
|
|
461
|
+
const extension = getExtension(path).toLowerCase();
|
|
462
|
+
if (!extension) return false;
|
|
463
|
+
return getExtensionMimeTypeMap().get(extension) ?? false;
|
|
464
|
+
};
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/services/mime/contentType.ts
|
|
467
|
+
const contentType = (value) => {
|
|
468
|
+
const mediaType = value.includes("/") ? value : lookup(value);
|
|
469
|
+
if (!mediaType) return false;
|
|
470
|
+
if (CHARSET_PARAMETER_REGEX.test(mediaType)) return mediaType;
|
|
471
|
+
const mediaCharset = charset(mediaType);
|
|
472
|
+
return mediaCharset ? `${mediaType}; charset=${mediaCharset.toLowerCase()}` : mediaType;
|
|
473
|
+
};
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/services/mime/extension.ts
|
|
476
|
+
const extension = (type) => getMimeTypeEntryMap().get(getMediaType(type).toLowerCase())?.extensions?.[0] ?? false;
|
|
477
|
+
//#endregion
|
|
204
478
|
//#region src/services/prettier/css.ts
|
|
205
479
|
const css = String.raw;
|
|
206
480
|
//#endregion
|
|
207
481
|
//#region src/services/prettier/html.ts
|
|
208
482
|
const html = String.raw;
|
|
209
483
|
//#endregion
|
|
210
|
-
//#region src/services/
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
484
|
+
//#region src/services/profanity/englishProfaneWords.ts
|
|
485
|
+
const englishProfaneWords = [
|
|
486
|
+
"4r5e",
|
|
487
|
+
"5h1t",
|
|
488
|
+
"5hit",
|
|
489
|
+
"a55",
|
|
490
|
+
"anal",
|
|
491
|
+
"anus",
|
|
492
|
+
"ar5e",
|
|
493
|
+
"arrse",
|
|
494
|
+
"arse",
|
|
495
|
+
"arses",
|
|
496
|
+
"ass",
|
|
497
|
+
"ass-fucker",
|
|
498
|
+
"asses",
|
|
499
|
+
"assfucker",
|
|
500
|
+
"assfukka",
|
|
501
|
+
"asshole",
|
|
502
|
+
"assholes",
|
|
503
|
+
"asswhole",
|
|
504
|
+
"a_s_s",
|
|
505
|
+
"a$$",
|
|
506
|
+
"as$",
|
|
507
|
+
"a$s",
|
|
508
|
+
"b!tch",
|
|
509
|
+
"b00bs",
|
|
510
|
+
"b17ch",
|
|
511
|
+
"b1tch",
|
|
512
|
+
"ballbag",
|
|
513
|
+
"balls",
|
|
514
|
+
"ballsack",
|
|
515
|
+
"bastard",
|
|
516
|
+
"beastial",
|
|
517
|
+
"beastiality",
|
|
518
|
+
"bellend",
|
|
519
|
+
"bestial",
|
|
520
|
+
"bestiality",
|
|
521
|
+
"bi+ch",
|
|
522
|
+
"biatch",
|
|
523
|
+
"bitch",
|
|
524
|
+
"bitchboy",
|
|
525
|
+
"bitcher",
|
|
526
|
+
"bitchers",
|
|
527
|
+
"bitches",
|
|
528
|
+
"bitchin",
|
|
529
|
+
"bitching",
|
|
530
|
+
"bloody",
|
|
531
|
+
"blow job",
|
|
532
|
+
"blowjob",
|
|
533
|
+
"blowjobs",
|
|
534
|
+
"boiolas",
|
|
535
|
+
"bollock",
|
|
536
|
+
"bollok",
|
|
537
|
+
"boner",
|
|
538
|
+
"boob",
|
|
539
|
+
"boobs",
|
|
540
|
+
"booobs",
|
|
541
|
+
"boooobs",
|
|
542
|
+
"booooobs",
|
|
543
|
+
"booooooobs",
|
|
544
|
+
"breasts",
|
|
545
|
+
"buceta",
|
|
546
|
+
"bugger",
|
|
547
|
+
"bullshit",
|
|
548
|
+
"bum",
|
|
549
|
+
"butt",
|
|
550
|
+
"butts",
|
|
551
|
+
"butthole",
|
|
552
|
+
"buttmuch",
|
|
553
|
+
"buttplug",
|
|
554
|
+
"c0ck",
|
|
555
|
+
"c0cksucker",
|
|
556
|
+
"carpet muncher",
|
|
557
|
+
"cawk",
|
|
558
|
+
"chink",
|
|
559
|
+
"cipa",
|
|
560
|
+
"cl1t",
|
|
561
|
+
"clit",
|
|
562
|
+
"clitoris",
|
|
563
|
+
"clits",
|
|
564
|
+
"cnut",
|
|
565
|
+
"cock",
|
|
566
|
+
"cock-sucker",
|
|
567
|
+
"cockface",
|
|
568
|
+
"cockhead",
|
|
569
|
+
"cockmunch",
|
|
570
|
+
"cockmuncher",
|
|
571
|
+
"cocks",
|
|
572
|
+
"cocksuck",
|
|
573
|
+
"cocksucked",
|
|
574
|
+
"cocksucker",
|
|
575
|
+
"cocksucking",
|
|
576
|
+
"cocksucks",
|
|
577
|
+
"cocksuka",
|
|
578
|
+
"cocksukka",
|
|
579
|
+
"cok",
|
|
580
|
+
"cokmuncher",
|
|
581
|
+
"coksucka",
|
|
582
|
+
"coon",
|
|
583
|
+
"cox",
|
|
584
|
+
"crap",
|
|
585
|
+
"cum",
|
|
586
|
+
"cummer",
|
|
587
|
+
"cumming",
|
|
588
|
+
"cums",
|
|
589
|
+
"cumshot",
|
|
590
|
+
"cunilingus",
|
|
591
|
+
"cunillingus",
|
|
592
|
+
"cunnilingus",
|
|
593
|
+
"cunt",
|
|
594
|
+
"cuntlick",
|
|
595
|
+
"cuntlicker",
|
|
596
|
+
"cuntlicking",
|
|
597
|
+
"cunts",
|
|
598
|
+
"cyalis",
|
|
599
|
+
"cyberfuc",
|
|
600
|
+
"cyberfuck",
|
|
601
|
+
"cyberfucked",
|
|
602
|
+
"cyberfucker",
|
|
603
|
+
"cyberfuckers",
|
|
604
|
+
"cyberfucking",
|
|
605
|
+
"d1ck",
|
|
606
|
+
"damn",
|
|
607
|
+
"dick",
|
|
608
|
+
"dickhead",
|
|
609
|
+
"dildo",
|
|
610
|
+
"dildos",
|
|
611
|
+
"dink",
|
|
612
|
+
"dinks",
|
|
613
|
+
"dirsa",
|
|
614
|
+
"dlck",
|
|
615
|
+
"dog-fucker",
|
|
616
|
+
"doggin",
|
|
617
|
+
"dogging",
|
|
618
|
+
"donkeyribber",
|
|
619
|
+
"doosh",
|
|
620
|
+
"duche",
|
|
621
|
+
"dyke",
|
|
622
|
+
"ejaculate",
|
|
623
|
+
"ejaculated",
|
|
624
|
+
"ejaculates",
|
|
625
|
+
"ejaculating",
|
|
626
|
+
"ejaculatings",
|
|
627
|
+
"ejaculation",
|
|
628
|
+
"ejakulate",
|
|
629
|
+
"f u c k",
|
|
630
|
+
"f u c k e r",
|
|
631
|
+
"f4nny",
|
|
632
|
+
"fag",
|
|
633
|
+
"fagging",
|
|
634
|
+
"faggitt",
|
|
635
|
+
"faggot",
|
|
636
|
+
"faggs",
|
|
637
|
+
"fagot",
|
|
638
|
+
"fagots",
|
|
639
|
+
"fags",
|
|
640
|
+
"fanny",
|
|
641
|
+
"fannyflaps",
|
|
642
|
+
"fannyfucker",
|
|
643
|
+
"fanyy",
|
|
644
|
+
"fatass",
|
|
645
|
+
"fcuk",
|
|
646
|
+
"fcuker",
|
|
647
|
+
"fcuking",
|
|
648
|
+
"feck",
|
|
649
|
+
"fecker",
|
|
650
|
+
"felching",
|
|
651
|
+
"fellate",
|
|
652
|
+
"fellatio",
|
|
653
|
+
"fingerfuck",
|
|
654
|
+
"fingerfucked",
|
|
655
|
+
"fingerfucker",
|
|
656
|
+
"fingerfuckers",
|
|
657
|
+
"fingerfucking",
|
|
658
|
+
"fingerfucks",
|
|
659
|
+
"fistfuck",
|
|
660
|
+
"fistfucked",
|
|
661
|
+
"fistfucker",
|
|
662
|
+
"fistfuckers",
|
|
663
|
+
"fistfucking",
|
|
664
|
+
"fistfuckings",
|
|
665
|
+
"fistfucks",
|
|
666
|
+
"flange",
|
|
667
|
+
"fook",
|
|
668
|
+
"fooker",
|
|
669
|
+
"fuck",
|
|
670
|
+
"fucka",
|
|
671
|
+
"fucked",
|
|
672
|
+
"fucker",
|
|
673
|
+
"fuckers",
|
|
674
|
+
"fuckhead",
|
|
675
|
+
"fuckheads",
|
|
676
|
+
"fuckin",
|
|
677
|
+
"fucking",
|
|
678
|
+
"fuckings",
|
|
679
|
+
"fuckingshitmotherfucker",
|
|
680
|
+
"fuckme",
|
|
681
|
+
"fucks",
|
|
682
|
+
"fuckwhit",
|
|
683
|
+
"fuckwit",
|
|
684
|
+
"fudge packer",
|
|
685
|
+
"fudgepacker",
|
|
686
|
+
"fuk",
|
|
687
|
+
"fuker",
|
|
688
|
+
"fukker",
|
|
689
|
+
"fukkin",
|
|
690
|
+
"fuks",
|
|
691
|
+
"fukwhit",
|
|
692
|
+
"fukwit",
|
|
693
|
+
"fux",
|
|
694
|
+
"fux0r",
|
|
695
|
+
"f_u_c_k",
|
|
696
|
+
"gangbang",
|
|
697
|
+
"gangbanged",
|
|
698
|
+
"gangbangs",
|
|
699
|
+
"gaylord",
|
|
700
|
+
"gaysex",
|
|
701
|
+
"goatse",
|
|
702
|
+
"god-dam",
|
|
703
|
+
"god-damned",
|
|
704
|
+
"goddamn",
|
|
705
|
+
"goddamned",
|
|
706
|
+
"hardcoresex",
|
|
707
|
+
"headass",
|
|
708
|
+
"hoar",
|
|
709
|
+
"hoare",
|
|
710
|
+
"hoer",
|
|
711
|
+
"hoes",
|
|
712
|
+
"homo",
|
|
713
|
+
"hore",
|
|
714
|
+
"horniest",
|
|
715
|
+
"horny",
|
|
716
|
+
"hotsex",
|
|
717
|
+
"jack-off",
|
|
718
|
+
"jackoff",
|
|
719
|
+
"jap",
|
|
720
|
+
"jerk-off",
|
|
721
|
+
"jism",
|
|
722
|
+
"jiz",
|
|
723
|
+
"jizm",
|
|
724
|
+
"jizz",
|
|
725
|
+
"kawk",
|
|
726
|
+
"knobead",
|
|
727
|
+
"knobed",
|
|
728
|
+
"knobend",
|
|
729
|
+
"knobhead",
|
|
730
|
+
"knobjocky",
|
|
731
|
+
"knobjokey",
|
|
732
|
+
"kock",
|
|
733
|
+
"kondum",
|
|
734
|
+
"kondums",
|
|
735
|
+
"kum",
|
|
736
|
+
"kummer",
|
|
737
|
+
"kumming",
|
|
738
|
+
"kums",
|
|
739
|
+
"kunilingus",
|
|
740
|
+
"l3i+ch",
|
|
741
|
+
"l3itch",
|
|
742
|
+
"labia",
|
|
743
|
+
"lust",
|
|
744
|
+
"lusting",
|
|
745
|
+
"m0f0",
|
|
746
|
+
"m0fo",
|
|
747
|
+
"m45terbate",
|
|
748
|
+
"ma5terb8",
|
|
749
|
+
"ma5terbate",
|
|
750
|
+
"masochist",
|
|
751
|
+
"master-bate",
|
|
752
|
+
"masterb8",
|
|
753
|
+
"masterbat*",
|
|
754
|
+
"masterbat3",
|
|
755
|
+
"masterbate",
|
|
756
|
+
"masterbation",
|
|
757
|
+
"masterbations",
|
|
758
|
+
"masturbate",
|
|
759
|
+
"mo-fo",
|
|
760
|
+
"mof0",
|
|
761
|
+
"mofo",
|
|
762
|
+
"mothafuck",
|
|
763
|
+
"mothafucka",
|
|
764
|
+
"mothafuckas",
|
|
765
|
+
"mothafuckaz",
|
|
766
|
+
"mothafucked",
|
|
767
|
+
"mothafucker",
|
|
768
|
+
"mothafuckers",
|
|
769
|
+
"mothafuckin",
|
|
770
|
+
"mothafucking",
|
|
771
|
+
"mothafuckings",
|
|
772
|
+
"mothafucks",
|
|
773
|
+
"motherfuck",
|
|
774
|
+
"motherfucked",
|
|
775
|
+
"motherfucker",
|
|
776
|
+
"motherfuckers",
|
|
777
|
+
"motherfuckin",
|
|
778
|
+
"motherfucking",
|
|
779
|
+
"motherfuckings",
|
|
780
|
+
"motherfuckka",
|
|
781
|
+
"motherfucks",
|
|
782
|
+
"muff",
|
|
783
|
+
"muthafecker",
|
|
784
|
+
"muthafuckker",
|
|
785
|
+
"mutherfucker",
|
|
786
|
+
"n1gga",
|
|
787
|
+
"n1gger",
|
|
788
|
+
"nazi",
|
|
789
|
+
"nigg3r",
|
|
790
|
+
"nigg4h",
|
|
791
|
+
"nigga",
|
|
792
|
+
"niggah",
|
|
793
|
+
"niggas",
|
|
794
|
+
"niggaz",
|
|
795
|
+
"nigger",
|
|
796
|
+
"niggers",
|
|
797
|
+
"nob",
|
|
798
|
+
"nob jokey",
|
|
799
|
+
"nobhead",
|
|
800
|
+
"nobjocky",
|
|
801
|
+
"nobjokey",
|
|
802
|
+
"numbnuts",
|
|
803
|
+
"nutsack",
|
|
804
|
+
"orgasim",
|
|
805
|
+
"orgasims",
|
|
806
|
+
"orgasm",
|
|
807
|
+
"orgasms",
|
|
808
|
+
"p0rn",
|
|
809
|
+
"pawn",
|
|
810
|
+
"pecker",
|
|
811
|
+
"penis",
|
|
812
|
+
"penisfucker",
|
|
813
|
+
"phonesex",
|
|
814
|
+
"phuck",
|
|
815
|
+
"phuk",
|
|
816
|
+
"phuked",
|
|
817
|
+
"phuking",
|
|
818
|
+
"phukked",
|
|
819
|
+
"phukking",
|
|
820
|
+
"phuks",
|
|
821
|
+
"phuq",
|
|
822
|
+
"pigfucker",
|
|
823
|
+
"pimpis",
|
|
824
|
+
"piss",
|
|
825
|
+
"pissed",
|
|
826
|
+
"pisser",
|
|
827
|
+
"pissers",
|
|
828
|
+
"pisses",
|
|
829
|
+
"pissflaps",
|
|
830
|
+
"pissin",
|
|
831
|
+
"pissing",
|
|
832
|
+
"pissoff",
|
|
833
|
+
"poop",
|
|
834
|
+
"porn",
|
|
835
|
+
"porno",
|
|
836
|
+
"pornography",
|
|
837
|
+
"pornos",
|
|
838
|
+
"prick",
|
|
839
|
+
"pricks",
|
|
840
|
+
"pron",
|
|
841
|
+
"pube",
|
|
842
|
+
"pusse",
|
|
843
|
+
"pussi",
|
|
844
|
+
"pussies",
|
|
845
|
+
"pussy",
|
|
846
|
+
"pussys",
|
|
847
|
+
"rectum",
|
|
848
|
+
"retard",
|
|
849
|
+
"rimjaw",
|
|
850
|
+
"rimming",
|
|
851
|
+
"s hit",
|
|
852
|
+
"s.o.b.",
|
|
853
|
+
"sadist",
|
|
854
|
+
"schlong",
|
|
855
|
+
"screwing",
|
|
856
|
+
"scroat",
|
|
857
|
+
"scrote",
|
|
858
|
+
"scrotum",
|
|
859
|
+
"semen",
|
|
860
|
+
"sex",
|
|
861
|
+
"sh!+",
|
|
862
|
+
"sh!t",
|
|
863
|
+
"sh1t",
|
|
864
|
+
"shag",
|
|
865
|
+
"shagger",
|
|
866
|
+
"shaggin",
|
|
867
|
+
"shagging",
|
|
868
|
+
"shemale",
|
|
869
|
+
"shi+",
|
|
870
|
+
"shit",
|
|
871
|
+
"shitdick",
|
|
872
|
+
"shite",
|
|
873
|
+
"shited",
|
|
874
|
+
"shitey",
|
|
875
|
+
"shitfuck",
|
|
876
|
+
"shitfull",
|
|
877
|
+
"shithead",
|
|
878
|
+
"shiting",
|
|
879
|
+
"shitings",
|
|
880
|
+
"shits",
|
|
881
|
+
"shitted",
|
|
882
|
+
"shitter",
|
|
883
|
+
"shitters",
|
|
884
|
+
"shitting",
|
|
885
|
+
"shittings",
|
|
886
|
+
"shitty",
|
|
887
|
+
"skank",
|
|
888
|
+
"slut",
|
|
889
|
+
"sluts",
|
|
890
|
+
"smegma",
|
|
891
|
+
"smut",
|
|
892
|
+
"snatch",
|
|
893
|
+
"son-of-a-bitch",
|
|
894
|
+
"spac",
|
|
895
|
+
"spunk",
|
|
896
|
+
"s_h_i_t",
|
|
897
|
+
"t1tt1e5",
|
|
898
|
+
"t1tties",
|
|
899
|
+
"teets",
|
|
900
|
+
"teez",
|
|
901
|
+
"testical",
|
|
902
|
+
"testicle",
|
|
903
|
+
"tit",
|
|
904
|
+
"titfuck",
|
|
905
|
+
"tits",
|
|
906
|
+
"titt",
|
|
907
|
+
"tittie5",
|
|
908
|
+
"tittiefucker",
|
|
909
|
+
"titties",
|
|
910
|
+
"tittyfuck",
|
|
911
|
+
"tittywank",
|
|
912
|
+
"titwank",
|
|
913
|
+
"tosser",
|
|
914
|
+
"turd",
|
|
915
|
+
"tw4t",
|
|
916
|
+
"twat",
|
|
917
|
+
"twathead",
|
|
918
|
+
"twatty",
|
|
919
|
+
"twunt",
|
|
920
|
+
"twunter",
|
|
921
|
+
"v14gra",
|
|
922
|
+
"v1gra",
|
|
923
|
+
"vagina",
|
|
924
|
+
"viagra",
|
|
925
|
+
"vulva",
|
|
926
|
+
"w00se",
|
|
927
|
+
"wang",
|
|
928
|
+
"wank",
|
|
929
|
+
"wanker",
|
|
930
|
+
"wanky",
|
|
931
|
+
"whoar",
|
|
932
|
+
"whore",
|
|
933
|
+
"willies",
|
|
934
|
+
"willy"
|
|
935
|
+
];
|
|
936
|
+
//#endregion
|
|
937
|
+
//#region src/services/profanity/checkContainsProfanity.ts
|
|
938
|
+
const WORD_CHARACTER_CLASS = String.raw`\p{L}\p{N}\p{M}`;
|
|
939
|
+
const PROFANE_WORD_ALTERNATION = englishProfaneWords.map((word) => word.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`)).join("|");
|
|
940
|
+
const WHOLE_WORD_PROFANITY_REGEX = new RegExp(`(?<![${WORD_CHARACTER_CLASS}])(?:${PROFANE_WORD_ALTERNATION})(?![${WORD_CHARACTER_CLASS}])`, "iu");
|
|
941
|
+
const SUBSTRING_PROFANITY_REGEX = new RegExp(PROFANE_WORD_ALTERNATION, "iu");
|
|
942
|
+
const checkContainsProfanity = (text, { isWholeWord = true } = {}) => {
|
|
943
|
+
if (typeof text !== "string") return false;
|
|
944
|
+
return (isWholeWord ? WHOLE_WORD_PROFANITY_REGEX : SUBSTRING_PROFANITY_REGEX).test(text);
|
|
945
|
+
};
|
|
946
|
+
//#endregion
|
|
947
|
+
//#region src/services/sanitizeHtml/checkIsWhitespaceCharacter.ts
|
|
948
|
+
const checkIsWhitespaceCharacter = (character) => character.trim() === "";
|
|
949
|
+
//#endregion
|
|
950
|
+
//#region src/services/sanitizeHtml/checkIsUnsafeUrlCharacter.ts
|
|
951
|
+
const FIRST_PRINTABLE_CODE_POINT = 32;
|
|
952
|
+
const DELETE_CODE_POINT = 127;
|
|
953
|
+
const LAST_C1_CONTROL_CODE_POINT = 159;
|
|
954
|
+
const GRAVE_ACCENT = "`";
|
|
955
|
+
const checkIsUnsafeUrlCharacter = (character) => {
|
|
956
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
957
|
+
return checkIsWhitespaceCharacter(character) || codePoint < FIRST_PRINTABLE_CODE_POINT || codePoint >= DELETE_CODE_POINT && codePoint <= LAST_C1_CONTROL_CODE_POINT || character === GRAVE_ACCENT;
|
|
958
|
+
};
|
|
959
|
+
//#endregion
|
|
960
|
+
//#region src/util/array/takeOne.ts
|
|
961
|
+
const takeOne = (values, index = 0) => {
|
|
962
|
+
return values[index];
|
|
963
|
+
};
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/services/sanitizeHtml/checkIsAllowedUrl.ts
|
|
966
|
+
const PATH_DELIMITER_REGEX = /[/?#]/u;
|
|
967
|
+
const NON_SCHEME_CHARACTERS_REGEX = /[^a-z\d+.-]/giu;
|
|
968
|
+
const SCHEME_SEPARATOR = ":";
|
|
969
|
+
const checkIsAllowedUrl = (url, allowedSchemes) => {
|
|
970
|
+
const normalizedUrl = url.split("").filter((character) => !checkIsUnsafeUrlCharacter(character)).join("");
|
|
971
|
+
const beforePath = takeOne(normalizedUrl.split(PATH_DELIMITER_REGEX), 0);
|
|
972
|
+
const separatorIndex = beforePath.indexOf(SCHEME_SEPARATOR);
|
|
973
|
+
if (separatorIndex === -1) return true;
|
|
974
|
+
const scheme = beforePath.slice(0, separatorIndex).replace(NON_SCHEME_CHARACTERS_REGEX, "").toLowerCase();
|
|
975
|
+
return allowedSchemes.includes(scheme);
|
|
976
|
+
};
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region src/services/sanitizeHtml/constants.ts
|
|
979
|
+
const HTML_NAMESPACE = html$1.NS.HTML;
|
|
980
|
+
const DEFAULT_ALLOWED_TAGS = [
|
|
981
|
+
"address",
|
|
982
|
+
"article",
|
|
983
|
+
"aside",
|
|
984
|
+
"footer",
|
|
985
|
+
"header",
|
|
986
|
+
"h1",
|
|
987
|
+
"h2",
|
|
988
|
+
"h3",
|
|
989
|
+
"h4",
|
|
990
|
+
"h5",
|
|
991
|
+
"h6",
|
|
992
|
+
"hgroup",
|
|
993
|
+
"main",
|
|
994
|
+
"nav",
|
|
995
|
+
"section",
|
|
996
|
+
"blockquote",
|
|
997
|
+
"dd",
|
|
998
|
+
"div",
|
|
999
|
+
"dl",
|
|
1000
|
+
"dt",
|
|
1001
|
+
"figcaption",
|
|
1002
|
+
"figure",
|
|
1003
|
+
"hr",
|
|
1004
|
+
"li",
|
|
1005
|
+
"menu",
|
|
1006
|
+
"ol",
|
|
1007
|
+
"p",
|
|
1008
|
+
"pre",
|
|
1009
|
+
"ul",
|
|
1010
|
+
"a",
|
|
1011
|
+
"abbr",
|
|
1012
|
+
"b",
|
|
1013
|
+
"bdi",
|
|
1014
|
+
"bdo",
|
|
1015
|
+
"br",
|
|
1016
|
+
"cite",
|
|
1017
|
+
"code",
|
|
1018
|
+
"data",
|
|
1019
|
+
"dfn",
|
|
1020
|
+
"em",
|
|
1021
|
+
"i",
|
|
1022
|
+
"kbd",
|
|
1023
|
+
"mark",
|
|
1024
|
+
"q",
|
|
1025
|
+
"rb",
|
|
1026
|
+
"rp",
|
|
1027
|
+
"rt",
|
|
1028
|
+
"rtc",
|
|
1029
|
+
"ruby",
|
|
1030
|
+
"s",
|
|
1031
|
+
"samp",
|
|
1032
|
+
"small",
|
|
1033
|
+
"span",
|
|
1034
|
+
"strong",
|
|
1035
|
+
"sub",
|
|
1036
|
+
"sup",
|
|
1037
|
+
"time",
|
|
1038
|
+
"u",
|
|
1039
|
+
"var",
|
|
1040
|
+
"wbr",
|
|
1041
|
+
"caption",
|
|
1042
|
+
"col",
|
|
1043
|
+
"colgroup",
|
|
1044
|
+
"table",
|
|
1045
|
+
"tbody",
|
|
1046
|
+
"td",
|
|
1047
|
+
"tfoot",
|
|
1048
|
+
"th",
|
|
1049
|
+
"thead",
|
|
1050
|
+
"tr"
|
|
1051
|
+
];
|
|
1052
|
+
const DEFAULT_LINK_ATTRIBUTES = [
|
|
1053
|
+
"href",
|
|
1054
|
+
"name",
|
|
1055
|
+
"target"
|
|
1056
|
+
];
|
|
1057
|
+
const DEFAULT_ALLOWED_ATTRIBUTES = { a: DEFAULT_LINK_ATTRIBUTES };
|
|
1058
|
+
const DEFAULT_ALLOWED_SCHEMES = [
|
|
1059
|
+
"http",
|
|
1060
|
+
"https",
|
|
1061
|
+
"ftp",
|
|
1062
|
+
"mailto",
|
|
1063
|
+
"tel"
|
|
1064
|
+
];
|
|
1065
|
+
const NON_TEXT_TAGS = [
|
|
1066
|
+
"iframe",
|
|
1067
|
+
"noembed",
|
|
1068
|
+
"noframes",
|
|
1069
|
+
"noscript",
|
|
1070
|
+
"option",
|
|
1071
|
+
"plaintext",
|
|
1072
|
+
"script",
|
|
1073
|
+
"style",
|
|
1074
|
+
"textarea",
|
|
1075
|
+
"title",
|
|
1076
|
+
"xmp"
|
|
1077
|
+
];
|
|
1078
|
+
const VOID_TAGS = [
|
|
1079
|
+
"area",
|
|
1080
|
+
"base",
|
|
1081
|
+
"br",
|
|
1082
|
+
"col",
|
|
1083
|
+
"embed",
|
|
1084
|
+
"hr",
|
|
1085
|
+
"img",
|
|
1086
|
+
"input",
|
|
1087
|
+
"link",
|
|
1088
|
+
"meta",
|
|
1089
|
+
"source",
|
|
1090
|
+
"track",
|
|
1091
|
+
"wbr"
|
|
1092
|
+
];
|
|
1093
|
+
const URL_ATTRIBUTES = [
|
|
1094
|
+
"action",
|
|
1095
|
+
"background",
|
|
1096
|
+
"cite",
|
|
1097
|
+
"data",
|
|
1098
|
+
"formaction",
|
|
1099
|
+
"href",
|
|
1100
|
+
"icon",
|
|
1101
|
+
"longdesc",
|
|
1102
|
+
"manifest",
|
|
1103
|
+
"poster",
|
|
1104
|
+
"src"
|
|
1105
|
+
];
|
|
1106
|
+
const SRCSET_ATTRIBUTE = "srcset";
|
|
1107
|
+
const IMPORTANT_REGEX = /!\s*important$/iu;
|
|
1108
|
+
const MAX_NESTING_DEPTH = 256;
|
|
1109
|
+
const SRCSET_DESCRIPTOR_REGEX = /^(?:\d+w|\d*\.?\d+x|\d+h)$/u;
|
|
1110
|
+
const ESCAPED_TEXT_CHARACTERS_REGEX = /[&<>]/gu;
|
|
1111
|
+
const ESCAPED_ATTRIBUTE_CHARACTERS_REGEX = /[&"<>]/gu;
|
|
1112
|
+
const SANITIZER_ADVISORY_WATCH_SINCE = "2026-10-11";
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/services/sanitizeHtml/getChildNodes.ts
|
|
1115
|
+
const getChildNodes = (element) => "content" in element ? element.content.childNodes : element.childNodes;
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/services/sanitizeHtml/collectText.ts
|
|
1118
|
+
const collectText = (nodes, depth) => depth > 256 ? "" : nodes.map((node) => {
|
|
1119
|
+
if ("value" in node) return node.value;
|
|
1120
|
+
if (!("tagName" in node) || NON_TEXT_TAGS.includes(node.tagName)) return "";
|
|
1121
|
+
return collectText(getChildNodes(node), depth + 1);
|
|
1122
|
+
}).join("");
|
|
1123
|
+
//#endregion
|
|
1124
|
+
//#region src/services/sanitizeHtml/escapeCharacters.ts
|
|
1125
|
+
const NON_BREAKING_SPACE = String.fromCodePoint(160);
|
|
1126
|
+
const escapeCharacters = (value, charactersRegex, entities) => value.replace(charactersRegex, (character) => entities[character] ?? character).replaceAll(NON_BREAKING_SPACE, " ");
|
|
1127
|
+
//#endregion
|
|
1128
|
+
//#region src/services/sanitizeHtml/escapeAttribute.ts
|
|
1129
|
+
const ESCAPED_ATTRIBUTE_CHARACTERS = {
|
|
1130
|
+
"\"": """,
|
|
1131
|
+
"&": "&",
|
|
1132
|
+
"<": "<",
|
|
1133
|
+
">": ">"
|
|
1134
|
+
};
|
|
1135
|
+
const escapeAttribute = (value) => escapeCharacters(value, ESCAPED_ATTRIBUTE_CHARACTERS_REGEX, ESCAPED_ATTRIBUTE_CHARACTERS);
|
|
1136
|
+
//#endregion
|
|
1137
|
+
//#region src/services/sanitizeHtml/escapeText.ts
|
|
1138
|
+
const ESCAPED_TEXT_CHARACTERS = {
|
|
1139
|
+
"&": "&",
|
|
1140
|
+
"<": "<",
|
|
1141
|
+
">": ">"
|
|
1142
|
+
};
|
|
1143
|
+
const escapeText = (text) => escapeCharacters(text, ESCAPED_TEXT_CHARACTERS_REGEX, ESCAPED_TEXT_CHARACTERS);
|
|
1144
|
+
//#endregion
|
|
1145
|
+
//#region src/services/sanitizeHtml/matchesClassPattern.ts
|
|
1146
|
+
const matchesClassPattern = (className, pattern) => pattern.endsWith("*") ? className.startsWith(pattern.slice(0, -1)) : className === pattern;
|
|
1147
|
+
//#endregion
|
|
1148
|
+
//#region src/services/sanitizeHtml/filterClasses.ts
|
|
1149
|
+
const filterClasses = (value, patterns) => {
|
|
1150
|
+
if (!patterns) return void 0;
|
|
1151
|
+
const classNames = value.split(/\s+/u).filter((className) => className && patterns.some((pattern) => matchesClassPattern(className, pattern)));
|
|
1152
|
+
return classNames.length > 0 ? classNames.join(" ") : void 0;
|
|
1153
|
+
};
|
|
1154
|
+
//#endregion
|
|
1155
|
+
//#region src/services/sanitizeHtml/filterSrcset.ts
|
|
1156
|
+
const COMMA = ",";
|
|
1157
|
+
const OPENING_PARENTHESIS = "(";
|
|
1158
|
+
const CLOSING_PARENTHESIS = ")";
|
|
1159
|
+
const filterSrcset = (value, allowedSchemes) => {
|
|
1160
|
+
const candidates = [];
|
|
1161
|
+
let position = 0;
|
|
1162
|
+
while (position < value.length) {
|
|
1163
|
+
while (position < value.length && (checkIsWhitespaceCharacter(value.charAt(position)) || value.charAt(position) === COMMA)) position++;
|
|
1164
|
+
const urlStart = position;
|
|
1165
|
+
while (position < value.length && !checkIsWhitespaceCharacter(value.charAt(position))) position++;
|
|
1166
|
+
let url = value.slice(urlStart, position);
|
|
1167
|
+
let descriptorText = "";
|
|
1168
|
+
if (url.endsWith(COMMA)) url = url.replace(/,+$/u, "");
|
|
1169
|
+
else {
|
|
1170
|
+
const descriptorStart = position;
|
|
1171
|
+
let isInParentheses = false;
|
|
1172
|
+
while (position < value.length && (isInParentheses || value.charAt(position) !== COMMA)) {
|
|
1173
|
+
if (value.charAt(position) === OPENING_PARENTHESIS) isInParentheses = true;
|
|
1174
|
+
else if (value.charAt(position) === CLOSING_PARENTHESIS) isInParentheses = false;
|
|
1175
|
+
position++;
|
|
1176
|
+
}
|
|
1177
|
+
descriptorText = value.slice(descriptorStart, position);
|
|
1178
|
+
position++;
|
|
1179
|
+
}
|
|
1180
|
+
const descriptors = descriptorText.trim().split(/\s+/u).filter(Boolean);
|
|
1181
|
+
if (!url || !checkIsAllowedUrl(url, allowedSchemes)) continue;
|
|
1182
|
+
if (descriptors.length > 1 || descriptors.some((descriptor) => !SRCSET_DESCRIPTOR_REGEX.test(descriptor))) continue;
|
|
1183
|
+
candidates.push([url, ...descriptors].join(" "));
|
|
216
1184
|
}
|
|
217
|
-
return
|
|
218
|
-
attribs,
|
|
219
|
-
tagName
|
|
220
|
-
};
|
|
1185
|
+
return candidates.length > 0 ? candidates.join(", ") : void 0;
|
|
221
1186
|
};
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
1187
|
+
//#endregion
|
|
1188
|
+
//#region src/services/sanitizeHtml/filterStyle.ts
|
|
1189
|
+
const DECLARATION_SEPARATOR = ";";
|
|
1190
|
+
const PROPERTY_SEPARATOR = ":";
|
|
1191
|
+
const IMPORTANT_SUFFIX = "!important";
|
|
1192
|
+
const filterStyle = (value, rules) => {
|
|
1193
|
+
if (!rules) return void 0;
|
|
1194
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
1195
|
+
for (const declaration of value.split(DECLARATION_SEPARATOR)) {
|
|
1196
|
+
const separatorIndex = declaration.indexOf(PROPERTY_SEPARATOR);
|
|
1197
|
+
if (separatorIndex === -1) continue;
|
|
1198
|
+
const property = declaration.slice(0, separatorIndex).trim().toLowerCase();
|
|
1199
|
+
if (!Object.hasOwn(rules, property)) continue;
|
|
1200
|
+
const rawValue = declaration.slice(separatorIndex + 1).trim();
|
|
1201
|
+
const important = IMPORTANT_REGEX.test(rawValue);
|
|
1202
|
+
const propertyValue = rawValue.replace(IMPORTANT_REGEX, "").trim();
|
|
1203
|
+
if (!(rules[property] ?? []).some((rule) => rule.test(propertyValue))) continue;
|
|
1204
|
+
if (declarations.get(property)?.important && !important) continue;
|
|
1205
|
+
declarations.set(property, {
|
|
1206
|
+
important,
|
|
1207
|
+
propertyValue
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
if (declarations.size === 0) return void 0;
|
|
1211
|
+
return Array.from(declarations, ([property, { important, propertyValue }]) => `${property}${PROPERTY_SEPARATOR}${propertyValue}${important ? IMPORTANT_SUFFIX : ""}`).join(DECLARATION_SEPARATOR);
|
|
1212
|
+
};
|
|
1213
|
+
//#endregion
|
|
1214
|
+
//#region src/services/sanitizeHtml/filterAttributes.ts
|
|
1215
|
+
const getTagPolicy = (tagPolicies, tagName) => Object.hasOwn(tagPolicies, tagName) ? tagPolicies[tagName] : void 0;
|
|
1216
|
+
const filterAttributes = (element, policy) => {
|
|
1217
|
+
const { tagName } = element;
|
|
1218
|
+
const allowedAttributes = getTagPolicy(policy.allowedAttributes, tagName) ?? [];
|
|
1219
|
+
const isClassAllowed = Object.hasOwn(policy.allowedClasses, tagName);
|
|
1220
|
+
const parsedAttributes = Object.fromEntries(element.attrs.map(({ name, value }) => [name, value]));
|
|
1221
|
+
const transform = getTagPolicy(policy.transformAttributes, tagName);
|
|
1222
|
+
const attributes = transform ? transform(parsedAttributes) : parsedAttributes;
|
|
1223
|
+
const kept = {};
|
|
1224
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
1225
|
+
if (name === "class" ? !isClassAllowed : !allowedAttributes.includes(name)) continue;
|
|
1226
|
+
const filteredValue = filterValue(tagName, name, value, policy);
|
|
1227
|
+
if (filteredValue !== void 0) kept[name] = filteredValue;
|
|
1228
|
+
}
|
|
1229
|
+
return kept;
|
|
1230
|
+
};
|
|
1231
|
+
const filterValue = (tagName, name, value, policy) => {
|
|
1232
|
+
if (name === "style") return filterStyle(value, getTagPolicy(policy.allowedStyles, tagName));
|
|
1233
|
+
if (name === "class") return filterClasses(value, getTagPolicy(policy.allowedClasses, tagName));
|
|
1234
|
+
if (name === "srcset") return filterSrcset(value, policy.allowedSchemes);
|
|
1235
|
+
if (URL_ATTRIBUTES.includes(name)) return checkIsAllowedUrl(value, policy.allowedSchemes) ? value : void 0;
|
|
1236
|
+
return value;
|
|
1237
|
+
};
|
|
1238
|
+
//#endregion
|
|
1239
|
+
//#region src/services/sanitizeHtml/renderNode.ts
|
|
1240
|
+
const renderNode = (node, policy, depth) => {
|
|
1241
|
+
if (node.nodeName === "#text") return escapeText(node.value);
|
|
1242
|
+
if (!("tagName" in node) || depth > 256) return "";
|
|
1243
|
+
const element = node;
|
|
1244
|
+
const children = getChildNodes(element).map((child) => renderNode(child, policy, depth + 1)).join("");
|
|
1245
|
+
if (!(element.namespaceURI === HTML_NAMESPACE && !NON_TEXT_TAGS.includes(element.tagName) && policy.allowedTags.includes(element.tagName))) return NON_TEXT_TAGS.includes(element.tagName) ? "" : children;
|
|
1246
|
+
const attributes = Object.entries(filterAttributes(element, policy)).map(([name, value]) => ` ${name}="${escapeAttribute(value)}"`).join("");
|
|
1247
|
+
const opening = `<${element.tagName}${attributes}>`;
|
|
1248
|
+
return VOID_TAGS.includes(element.tagName) ? opening : `${opening}${children}</${element.tagName}>`;
|
|
1249
|
+
};
|
|
1250
|
+
//#endregion
|
|
1251
|
+
//#region src/services/sanitizeHtml/filterHtml.ts
|
|
1252
|
+
const filterHtml = (html, policy) => {
|
|
1253
|
+
const context = defaultTreeAdapter.createElement("body", HTML_NAMESPACE, []);
|
|
1254
|
+
const childNodes = parseFragment(context, html, {}).childNodes;
|
|
1255
|
+
const output = childNodes.map((node) => renderNode(node, policy, 0)).join("");
|
|
1256
|
+
return parseFragment(context, output, {}).childNodes.map((node) => renderNode(node, policy, 0)).join("") === output ? output : escapeText(collectText(childNodes, 0));
|
|
1257
|
+
};
|
|
1258
|
+
//#endregion
|
|
1259
|
+
//#region src/services/sanitizeHtml/sanitizeHtml.ts
|
|
1260
|
+
const appendStyle = (style, declarations) => style ? `${style}; ${declarations}` : declarations;
|
|
1261
|
+
const transformCellAlign = ({ align, ...attributes }) => align ? {
|
|
1262
|
+
...attributes,
|
|
1263
|
+
style: appendStyle(attributes.style, `text-align:${align}`)
|
|
1264
|
+
} : attributes;
|
|
1265
|
+
const transformLink = (attributes) => ({
|
|
1266
|
+
...attributes,
|
|
1267
|
+
rel: "noopener noreferrer nofollow",
|
|
1268
|
+
target: "_blank"
|
|
1269
|
+
});
|
|
1270
|
+
const transformTable = (attributes) => ({
|
|
1271
|
+
...attributes,
|
|
1272
|
+
style: appendStyle(attributes.style, "width:100%; border-collapse: collapse;")
|
|
229
1273
|
});
|
|
230
1274
|
const TEXT_ALIGN_REGEXES = [/^(?:left|right|center|justify)$/u];
|
|
231
1275
|
const TABLE_ALLOWED_STYLES = {
|
|
@@ -236,33 +1280,30 @@ const TABLE_ALLOWED_STYLES = {
|
|
|
236
1280
|
td: { "text-align": TEXT_ALIGN_REGEXES },
|
|
237
1281
|
th: { "text-align": TEXT_ALIGN_REGEXES }
|
|
238
1282
|
};
|
|
239
|
-
const sanitizeHtml = (
|
|
240
|
-
...options,
|
|
1283
|
+
const sanitizeHtml = (html, options) => filterHtml(html, {
|
|
241
1284
|
allowedAttributes: {
|
|
242
|
-
...options?.allowedAttributes
|
|
243
|
-
a: [
|
|
1285
|
+
...options?.allowedAttributes ?? DEFAULT_ALLOWED_ATTRIBUTES,
|
|
1286
|
+
a: [.../* @__PURE__ */ new Set([
|
|
1287
|
+
...options?.allowedAttributes?.a ?? DEFAULT_LINK_ATTRIBUTES,
|
|
244
1288
|
"href",
|
|
245
1289
|
"rel",
|
|
246
1290
|
"target"
|
|
247
|
-
],
|
|
1291
|
+
])],
|
|
248
1292
|
table: ["style"],
|
|
249
1293
|
td: ["style"],
|
|
250
1294
|
th: ["style"]
|
|
251
1295
|
},
|
|
1296
|
+
allowedClasses: options?.allowedClasses ?? {},
|
|
1297
|
+
allowedSchemes: DEFAULT_ALLOWED_SCHEMES,
|
|
252
1298
|
allowedStyles: {
|
|
253
1299
|
...options?.allowedStyles,
|
|
254
1300
|
...TABLE_ALLOWED_STYLES
|
|
255
1301
|
},
|
|
256
|
-
|
|
257
|
-
|
|
1302
|
+
allowedTags: options?.allowedTags ?? DEFAULT_ALLOWED_TAGS,
|
|
1303
|
+
transformAttributes: {
|
|
1304
|
+
...options?.transformAttributes,
|
|
258
1305
|
a: transformLink,
|
|
259
|
-
table:
|
|
260
|
-
attribs: {
|
|
261
|
-
...attribs,
|
|
262
|
-
style: appendStyle(attribs.style, "width:100%; border-collapse: collapse;")
|
|
263
|
-
},
|
|
264
|
-
tagName
|
|
265
|
-
}),
|
|
1306
|
+
table: transformTable,
|
|
266
1307
|
td: transformCellAlign,
|
|
267
1308
|
th: transformCellAlign
|
|
268
1309
|
}
|
|
@@ -276,22 +1317,20 @@ const COLOR_REGEXES = [
|
|
|
276
1317
|
];
|
|
277
1318
|
const sanitizeTextHtml = (html) => sanitizeHtml(html, {
|
|
278
1319
|
allowedAttributes: {
|
|
279
|
-
code: ["class"],
|
|
280
1320
|
input: [
|
|
281
1321
|
"checked",
|
|
282
1322
|
"disabled",
|
|
283
1323
|
"type"
|
|
284
1324
|
],
|
|
285
1325
|
li: ["data-checked", "data-type"],
|
|
286
|
-
pre: ["class"],
|
|
287
1326
|
span: [
|
|
288
|
-
"class",
|
|
289
1327
|
CUSTOM_EMOJI_ID_ATTRIBUTE,
|
|
290
1328
|
CUSTOM_EMOJI_NAME_ATTRIBUTE,
|
|
291
1329
|
MENTION_ID_ATTRIBUTE,
|
|
292
1330
|
MENTION_ITEM_TYPE_ATTRIBUTE,
|
|
293
1331
|
MENTION_LABEL_ATTRIBUTE,
|
|
294
1332
|
MENTION_TYPE_ATTRIBUTE,
|
|
1333
|
+
SPOILER_ATTRIBUTE,
|
|
295
1334
|
"style"
|
|
296
1335
|
],
|
|
297
1336
|
ul: ["data-type"]
|
|
@@ -307,21 +1346,15 @@ const sanitizeTextHtml = (html) => sanitizeHtml(html, {
|
|
|
307
1346
|
color: COLOR_REGEXES
|
|
308
1347
|
} },
|
|
309
1348
|
allowedTags: [
|
|
310
|
-
...
|
|
1349
|
+
...DEFAULT_ALLOWED_TAGS,
|
|
311
1350
|
"input",
|
|
312
1351
|
"label"
|
|
313
1352
|
],
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (checked !== void 0) attribs.checked = checked;
|
|
320
|
-
return {
|
|
321
|
-
attribs,
|
|
322
|
-
tagName
|
|
323
|
-
};
|
|
324
|
-
} }
|
|
1353
|
+
transformAttributes: { input: ({ checked }) => ({
|
|
1354
|
+
...checked === void 0 ? {} : { checked },
|
|
1355
|
+
disabled: "",
|
|
1356
|
+
type: "checkbox"
|
|
1357
|
+
}) }
|
|
325
1358
|
});
|
|
326
1359
|
//#endregion
|
|
327
1360
|
//#region src/services/shared/applyItemMetadataMixin.ts
|
|
@@ -424,11 +1457,6 @@ const chunkBySerializedSize = (items, maxBytes, maxCount) => {
|
|
|
424
1457
|
return chunks;
|
|
425
1458
|
};
|
|
426
1459
|
//#endregion
|
|
427
|
-
//#region src/util/array/takeOne.ts
|
|
428
|
-
const takeOne = (values, index = 0) => {
|
|
429
|
-
return values[index];
|
|
430
|
-
};
|
|
431
|
-
//#endregion
|
|
432
1460
|
//#region src/util/date/getZonedDateTime.ts
|
|
433
1461
|
const getZonedDateTime = (date) => Temporal.Instant.fromEpochMilliseconds(date.getTime()).toZonedDateTimeISO(Temporal.Now.timeZoneId());
|
|
434
1462
|
//#endregion
|
|
@@ -496,6 +1524,57 @@ const getOrCreate = (map, key, create) => {
|
|
|
496
1524
|
return value;
|
|
497
1525
|
};
|
|
498
1526
|
//#endregion
|
|
1527
|
+
//#region src/util/object/checkIsCustomMethod.ts
|
|
1528
|
+
const checkIsCustomMethod = (value, methodName) => {
|
|
1529
|
+
const method = Reflect.get(value, methodName);
|
|
1530
|
+
return typeof method === "function" && method !== Object.prototype[methodName];
|
|
1531
|
+
};
|
|
1532
|
+
//#endregion
|
|
1533
|
+
//#region src/util/object/checkIsSameBytes.ts
|
|
1534
|
+
const toBytes = (view) => ArrayBuffer.isView(view) ? new Uint8Array(view.buffer, view.byteOffset, view.byteLength) : new Uint8Array(view);
|
|
1535
|
+
const checkIsSameBytes = (first, second) => {
|
|
1536
|
+
const firstBytes = toBytes(first);
|
|
1537
|
+
const secondBytes = toBytes(second);
|
|
1538
|
+
return firstBytes.length === secondBytes.length && firstBytes.every((byte, index) => byte === secondBytes[index]);
|
|
1539
|
+
};
|
|
1540
|
+
//#endregion
|
|
1541
|
+
//#region src/util/object/checkIsSameValueZero.ts
|
|
1542
|
+
const checkIsSameValueZero = (first, second) => first === second || Number.isNaN(first) && Number.isNaN(second);
|
|
1543
|
+
//#endregion
|
|
1544
|
+
//#region src/util/object/getEnumerableKeys.ts
|
|
1545
|
+
const getEnumerableKeys = (value) => Reflect.ownKeys(value).filter((key) => Object.prototype.propertyIsEnumerable.call(value, key));
|
|
1546
|
+
//#endregion
|
|
1547
|
+
//#region src/util/object/checkIsDeepEqual.ts
|
|
1548
|
+
const checkIsBinary = (value) => ArrayBuffer.isView(value) || value instanceof ArrayBuffer || typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer;
|
|
1549
|
+
const checkIsDeepEqual = (first, second) => {
|
|
1550
|
+
if (first === second) return true;
|
|
1551
|
+
if (typeof first !== "object" || typeof second !== "object" || first === null || second === null) return checkIsSameValueZero(first, second);
|
|
1552
|
+
if (Object.getPrototypeOf(first) !== Object.getPrototypeOf(second)) return false;
|
|
1553
|
+
if (Array.isArray(first) || Array.isArray(second)) {
|
|
1554
|
+
if (!Array.isArray(first) || !Array.isArray(second) || first.length !== second.length) return false;
|
|
1555
|
+
for (const [index, item] of first.entries()) if (!checkIsDeepEqual(item, second[index])) return false;
|
|
1556
|
+
return true;
|
|
1557
|
+
}
|
|
1558
|
+
if (checkIsBinary(first) && checkIsBinary(second)) return checkIsSameBytes(first, second);
|
|
1559
|
+
if (first instanceof RegExp && second instanceof RegExp) return first.source === second.source && first.flags === second.flags;
|
|
1560
|
+
if (first instanceof WeakMap || first instanceof WeakSet || first instanceof Promise || first instanceof Blob) return false;
|
|
1561
|
+
if (checkIsCustomMethod(first, "valueOf") && checkIsCustomMethod(second, "valueOf")) return checkIsSameValueZero(first.valueOf(), second.valueOf());
|
|
1562
|
+
if (checkIsCustomMethod(first, "toString") && checkIsCustomMethod(second, "toString")) return first.toString() === second.toString();
|
|
1563
|
+
if (first instanceof Map && second instanceof Map) {
|
|
1564
|
+
if (first.size !== second.size) return false;
|
|
1565
|
+
for (const [key, value] of first) if (!second.has(key) || !checkIsDeepEqual(value, second.get(key))) return false;
|
|
1566
|
+
return true;
|
|
1567
|
+
}
|
|
1568
|
+
if (first instanceof Set && second instanceof Set) {
|
|
1569
|
+
if (first.size !== second.size) return false;
|
|
1570
|
+
for (const value of first) if (!second.has(value)) return false;
|
|
1571
|
+
return true;
|
|
1572
|
+
}
|
|
1573
|
+
const firstKeys = getEnumerableKeys(first);
|
|
1574
|
+
if (firstKeys.length !== getEnumerableKeys(second).length) return false;
|
|
1575
|
+
return firstKeys.every((key) => Object.prototype.propertyIsEnumerable.call(second, key) && checkIsDeepEqual(Reflect.get(first, key), Reflect.get(second, key)));
|
|
1576
|
+
};
|
|
1577
|
+
//#endregion
|
|
499
1578
|
//#region src/util/object/checkIsPlainObject.ts
|
|
500
1579
|
const checkIsPlainObject = (data) => {
|
|
501
1580
|
if (typeof data !== "object" || data === null) return false;
|
|
@@ -521,6 +1600,31 @@ const jsonDateParse = (text) => JSON.parse(text, (_key, value) => {
|
|
|
521
1600
|
//#region src/util/object/mergeObjectsStrict.ts
|
|
522
1601
|
const mergeObjectsStrict = (...objects) => Object.assign({}, ...objects);
|
|
523
1602
|
//#endregion
|
|
1603
|
+
//#region src/util/object/omitDeep.ts
|
|
1604
|
+
const checkIsWalkable = (value) => Object.prototype.toString.call(value) === "[object Object]";
|
|
1605
|
+
const omitValue = (value, omittedKeys) => {
|
|
1606
|
+
if (Array.isArray(value)) return value.map((element) => omitValue(element, omittedKeys));
|
|
1607
|
+
if (!checkIsWalkable(value)) return value;
|
|
1608
|
+
const omittedValue = {};
|
|
1609
|
+
for (const [key, propertyValue] of Object.entries(value)) if (!omittedKeys.has(key)) omittedValue[key] = omitValue(propertyValue, omittedKeys);
|
|
1610
|
+
return omittedValue;
|
|
1611
|
+
};
|
|
1612
|
+
const omitDeep = (value, ...keys) => omitValue(value, new Set(keys));
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/util/promise/createEvictingPromiseCache.ts
|
|
1615
|
+
const createEvictingPromiseCache = (getKey, load) => {
|
|
1616
|
+
const promiseMap = /* @__PURE__ */ new Map();
|
|
1617
|
+
return (...args) => {
|
|
1618
|
+
const key = getKey(...args);
|
|
1619
|
+
const promise = promiseMap.get(key) ?? load(...args);
|
|
1620
|
+
promiseMap.set(key, promise);
|
|
1621
|
+
return getResultAsync(() => promise).match((value) => value, (error) => {
|
|
1622
|
+
if (promiseMap.get(key) === promise) promiseMap.delete(key);
|
|
1623
|
+
throw error;
|
|
1624
|
+
});
|
|
1625
|
+
};
|
|
1626
|
+
};
|
|
1627
|
+
//#endregion
|
|
524
1628
|
//#region src/util/promise/settleAll.ts
|
|
525
1629
|
const settleAll = async (tasks, concurrencyLimit = tasks.length) => {
|
|
526
1630
|
if (tasks.length === 0) return [];
|
|
@@ -539,6 +1643,206 @@ const settleAll = async (tasks, concurrencyLimit = tasks.length) => {
|
|
|
539
1643
|
//#region src/util/text/capitalize.ts
|
|
540
1644
|
const capitalize = (string) => `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
|
|
541
1645
|
//#endregion
|
|
1646
|
+
//#region src/util/text/dedent.ts
|
|
1647
|
+
const ESCAPE_CHARACTER = "\\";
|
|
1648
|
+
const LINE_FEED = "\n";
|
|
1649
|
+
const LINE_FEED_CHARACTER = {
|
|
1650
|
+
escapable: false,
|
|
1651
|
+
value: LINE_FEED
|
|
1652
|
+
};
|
|
1653
|
+
const HEXADECIMAL_REGEX = /^[\da-fA-F]$/u;
|
|
1654
|
+
const WHITESPACE_REGEX = /^\s$/u;
|
|
1655
|
+
const BRACED_CODE_POINT_MAXIMUM_DIGITS = 6;
|
|
1656
|
+
const CODE_POINT_MAXIMUM = 1114111;
|
|
1657
|
+
const HEXADECIMAL_BYTE_DIGITS = 2;
|
|
1658
|
+
const HEXADECIMAL_UNIT_DIGITS = 4;
|
|
1659
|
+
const escapeValueMap = {
|
|
1660
|
+
"0": "\0",
|
|
1661
|
+
$: "$",
|
|
1662
|
+
"\\": "\\",
|
|
1663
|
+
"`": "`",
|
|
1664
|
+
b: "\b",
|
|
1665
|
+
f: "\f",
|
|
1666
|
+
n: "\n",
|
|
1667
|
+
r: "\r",
|
|
1668
|
+
t: " ",
|
|
1669
|
+
v: "\v",
|
|
1670
|
+
"{": "{"
|
|
1671
|
+
};
|
|
1672
|
+
const toCharacters = (text, escapable) => text.split("").map((value) => ({
|
|
1673
|
+
escapable,
|
|
1674
|
+
value
|
|
1675
|
+
}));
|
|
1676
|
+
const isWhitespace = (character) => WHITESPACE_REGEX.test(character.value);
|
|
1677
|
+
const alignValue = (value, precedingText) => {
|
|
1678
|
+
const currentLine = precedingText.slice(precedingText.lastIndexOf(LINE_FEED) + 1);
|
|
1679
|
+
const indentation = /^\s+/u.exec(currentLine)?.[0];
|
|
1680
|
+
return indentation ? value.replaceAll(LINE_FEED, `${LINE_FEED}${indentation}`) : value;
|
|
1681
|
+
};
|
|
1682
|
+
const isEscapeStart = (characters, index) => {
|
|
1683
|
+
const character = characters[index];
|
|
1684
|
+
const next = characters[index + 1];
|
|
1685
|
+
return character?.escapable === true && character.value === ESCAPE_CHARACTER && next?.escapable === true;
|
|
1686
|
+
};
|
|
1687
|
+
const removeLineContinuations = (characters) => {
|
|
1688
|
+
const result = [];
|
|
1689
|
+
let index = 0;
|
|
1690
|
+
while (index < characters.length) {
|
|
1691
|
+
const character = characters[index];
|
|
1692
|
+
if (!character) break;
|
|
1693
|
+
if (isEscapeStart(characters, index)) {
|
|
1694
|
+
const next = characters[index + 1];
|
|
1695
|
+
if (next?.value === LINE_FEED) {
|
|
1696
|
+
index += 2;
|
|
1697
|
+
while (characters[index]?.escapable === true && (characters[index]?.value === " " || characters[index]?.value === " ")) index++;
|
|
1698
|
+
} else {
|
|
1699
|
+
result.push(character, ...next ? [next] : []);
|
|
1700
|
+
index += 2;
|
|
1701
|
+
}
|
|
1702
|
+
} else {
|
|
1703
|
+
result.push(character);
|
|
1704
|
+
index++;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return result;
|
|
1708
|
+
};
|
|
1709
|
+
const splitLines = (characters) => {
|
|
1710
|
+
const lines = [];
|
|
1711
|
+
let line = [];
|
|
1712
|
+
for (const character of characters) if (character.value === LINE_FEED) {
|
|
1713
|
+
lines.push(line);
|
|
1714
|
+
line = [];
|
|
1715
|
+
} else line.push(character);
|
|
1716
|
+
lines.push(line);
|
|
1717
|
+
return lines;
|
|
1718
|
+
};
|
|
1719
|
+
const getIndentation = (line) => {
|
|
1720
|
+
const indentation = line.findIndex((character) => !isWhitespace(character));
|
|
1721
|
+
return indentation > 0 ? indentation : void 0;
|
|
1722
|
+
};
|
|
1723
|
+
const dedentCharacters = (characters) => {
|
|
1724
|
+
const lines = splitLines(characters);
|
|
1725
|
+
const indentations = lines.map((line) => getIndentation(line)).filter((indentation) => indentation !== void 0);
|
|
1726
|
+
if (indentations.length === 0) return characters;
|
|
1727
|
+
const minimumIndentation = Math.min(...indentations);
|
|
1728
|
+
const result = [];
|
|
1729
|
+
for (const [index, line] of lines.entries()) {
|
|
1730
|
+
if (index > 0) result.push(LINE_FEED_CHARACTER);
|
|
1731
|
+
const first = line[0]?.value;
|
|
1732
|
+
result.push(...first === " " || first === " " ? line.slice(minimumIndentation) : line);
|
|
1733
|
+
}
|
|
1734
|
+
return result;
|
|
1735
|
+
};
|
|
1736
|
+
const trimCharacters = (characters) => {
|
|
1737
|
+
const start = characters.findIndex((character) => !isWhitespace(character));
|
|
1738
|
+
if (start === -1) return [];
|
|
1739
|
+
return characters.slice(start, characters.findLastIndex((character) => !isWhitespace(character)) + 1);
|
|
1740
|
+
};
|
|
1741
|
+
const readHexadecimal = (characters, start, digits) => {
|
|
1742
|
+
let hexadecimal = "";
|
|
1743
|
+
for (let index = start; index < start + digits; index++) {
|
|
1744
|
+
const character = characters[index];
|
|
1745
|
+
if (character?.escapable !== true || !HEXADECIMAL_REGEX.test(character.value)) return void 0;
|
|
1746
|
+
hexadecimal += character.value;
|
|
1747
|
+
}
|
|
1748
|
+
return hexadecimal;
|
|
1749
|
+
};
|
|
1750
|
+
const readBracedCodePoint = (characters, index) => {
|
|
1751
|
+
if (characters[index + 2]?.value !== "{") return void 0;
|
|
1752
|
+
let digits = "";
|
|
1753
|
+
let cursor = index + 3;
|
|
1754
|
+
while (digits.length < BRACED_CODE_POINT_MAXIMUM_DIGITS && HEXADECIMAL_REGEX.test(characters[cursor]?.value ?? "")) {
|
|
1755
|
+
digits += characters[cursor]?.value;
|
|
1756
|
+
cursor++;
|
|
1757
|
+
}
|
|
1758
|
+
const closing = characters[cursor];
|
|
1759
|
+
if (digits.length === 0 || closing?.escapable !== true || closing.value !== "}") return void 0;
|
|
1760
|
+
const codePoint = Number.parseInt(digits, 16);
|
|
1761
|
+
if (codePoint > CODE_POINT_MAXIMUM) return void 0;
|
|
1762
|
+
return {
|
|
1763
|
+
length: cursor - index + 1,
|
|
1764
|
+
text: String.fromCodePoint(codePoint)
|
|
1765
|
+
};
|
|
1766
|
+
};
|
|
1767
|
+
const fromHexadecimal = (hexadecimal) => String.fromCodePoint(Number.parseInt(hexadecimal, 16));
|
|
1768
|
+
const readEscape = (characters, index) => {
|
|
1769
|
+
const character = characters[index];
|
|
1770
|
+
if (!character) return {
|
|
1771
|
+
length: 0,
|
|
1772
|
+
text: ""
|
|
1773
|
+
};
|
|
1774
|
+
if (!isEscapeStart(characters, index)) return {
|
|
1775
|
+
length: 1,
|
|
1776
|
+
text: character.value
|
|
1777
|
+
};
|
|
1778
|
+
const next = characters[index + 1];
|
|
1779
|
+
const escapeValue = next ? escapeValueMap[next.value] : void 0;
|
|
1780
|
+
if (escapeValue !== void 0) return {
|
|
1781
|
+
length: 2,
|
|
1782
|
+
text: escapeValue
|
|
1783
|
+
};
|
|
1784
|
+
if (next?.value === "x") {
|
|
1785
|
+
const hexadecimal = readHexadecimal(characters, index + 2, HEXADECIMAL_BYTE_DIGITS);
|
|
1786
|
+
if (hexadecimal !== void 0) return {
|
|
1787
|
+
length: 4,
|
|
1788
|
+
text: fromHexadecimal(hexadecimal)
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
if (next?.value === "u") {
|
|
1792
|
+
const braced = readBracedCodePoint(characters, index);
|
|
1793
|
+
if (braced) return braced;
|
|
1794
|
+
const hexadecimal = readHexadecimal(characters, index + 2, HEXADECIMAL_UNIT_DIGITS);
|
|
1795
|
+
if (hexadecimal !== void 0) return {
|
|
1796
|
+
length: 6,
|
|
1797
|
+
text: fromHexadecimal(hexadecimal)
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
return {
|
|
1801
|
+
length: 1,
|
|
1802
|
+
text: character.value
|
|
1803
|
+
};
|
|
1804
|
+
};
|
|
1805
|
+
const cookCharacters = (characters) => {
|
|
1806
|
+
let result = "";
|
|
1807
|
+
let index = 0;
|
|
1808
|
+
while (index < characters.length) {
|
|
1809
|
+
const cooked = readEscape(characters, index);
|
|
1810
|
+
result += cooked.text;
|
|
1811
|
+
index += cooked.length;
|
|
1812
|
+
}
|
|
1813
|
+
return result;
|
|
1814
|
+
};
|
|
1815
|
+
const createDedent = (options) => {
|
|
1816
|
+
const dedent = (strings, ...values) => {
|
|
1817
|
+
const isTemplate = typeof strings !== "string";
|
|
1818
|
+
const raws = isTemplate ? strings.raw : [strings];
|
|
1819
|
+
const { alignValues = false, escapeSpecialCharacters = isTemplate, trimWhitespace = true } = options;
|
|
1820
|
+
let precedingText = "";
|
|
1821
|
+
const parts = [];
|
|
1822
|
+
for (const [index, raw] of raws.entries()) {
|
|
1823
|
+
parts.push(toCharacters(raw, true));
|
|
1824
|
+
precedingText += raw;
|
|
1825
|
+
if (index < values.length) {
|
|
1826
|
+
const value = String(values[index]);
|
|
1827
|
+
const alignedValue = alignValues ? alignValue(value, precedingText) : value;
|
|
1828
|
+
parts.push(toCharacters(alignedValue, false));
|
|
1829
|
+
precedingText += alignedValue;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
let characters = parts.flat();
|
|
1833
|
+
if (escapeSpecialCharacters) characters = removeLineContinuations(characters);
|
|
1834
|
+
characters = dedentCharacters(characters);
|
|
1835
|
+
if (trimWhitespace) characters = trimCharacters(characters);
|
|
1836
|
+
return escapeSpecialCharacters ? cookCharacters(characters) : characters.map((character) => character.value).join("");
|
|
1837
|
+
};
|
|
1838
|
+
dedent.withOptions = (newOptions) => createDedent({
|
|
1839
|
+
...options,
|
|
1840
|
+
...newOptions
|
|
1841
|
+
});
|
|
1842
|
+
return dedent;
|
|
1843
|
+
};
|
|
1844
|
+
const dedent = createDedent({});
|
|
1845
|
+
//#endregion
|
|
542
1846
|
//#region src/util/text/encodeUrlSubDelimiters.ts
|
|
543
1847
|
const EncodedSubDelimiterMap = {
|
|
544
1848
|
"!": "%21",
|
|
@@ -611,4 +1915,4 @@ const exhaustiveGuard = (value) => {
|
|
|
611
1915
|
throw new InvalidOperationError("Read", exhaustiveGuard.name, JSON.stringify(value));
|
|
612
1916
|
};
|
|
613
1917
|
//#endregion
|
|
614
|
-
export { ALL_SPECIAL_VALUES, CUSTOM_EMOJI_ID_ATTRIBUTE, CUSTOM_EMOJI_NAME_ATTRIBUTE, DEFAULT_COMPRESSION_LEVEL, ForbiddenError, GIBIBYTE, ID_SEPARATOR, InvalidOperationError, ItemMetadata, KIBIBYTE, MAX_READ_LIMIT, MAX_WINDOW_LOG, MENTION_EVERYONE_ID, MENTION_HERE_ID, MENTION_ID_ATTRIBUTE, MENTION_ITEM_TYPE_ATTRIBUTE, MENTION_LABEL_ATTRIBUTE, MENTION_SELECTOR, MENTION_TYPE, MENTION_TYPE_ATTRIBUTE, MIN_WINDOW_LOG, MentionType, NotFoundError, NotInitializedError, Operation, RoutePath, SITE_NAME, Serializable, UUIDV4_REGEX, WordFilteredError, applyItemMetadataMixin, capitalize, checkIsNestedInteraction, checkIsPlainObject, checkIsServer, chunk, chunkBySerializedSize, createItemEntityTypeSchema, createNormalizedStringSchema, createUniqueArraySchema, css, encodeUrlSubDelimiters, exhaustiveGuard, getDecodedUriComponent, getEndOfDay, getMentions, getOrCreate, getPropertyNames, getRawData, getResult, getResultAsync, getSynchronizedFunction, getWindowLog, getZonedDateTime, hrtime, html, jsonDateParse, mergeObjectsStrict, noop, normalizeString, now, sanitizeHtml, sanitizeTextHtml, settleAll, sleep, streamToText, takeOne, toAppError, toKebabCase, toRawDeep, truncate, uncapitalize, waitForSynchronizedFunctions, withFinalizer, withFinalizerAsync };
|
|
1918
|
+
export { ALL_SPECIAL_VALUES, CHARSET_PARAMETER_REGEX, CUSTOM_EMOJI_ID_ATTRIBUTE, CUSTOM_EMOJI_NAME_ATTRIBUTE, DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_SCHEMES, DEFAULT_ALLOWED_TAGS, DEFAULT_COMPRESSION_LEVEL, DEFAULT_FACET_SCORE, DEFAULT_LINK_ATTRIBUTES, DEFAULT_SOURCE_SCORE, DEFAULT_TEXT_CHARSET, DEFAULT_TYPE_SCORE, ESCAPED_ATTRIBUTE_CHARACTERS_REGEX, ESCAPED_TEXT_CHARACTERS_REGEX, FacetScoreMap, ForbiddenError, GIBIBYTE, HTML_NAMESPACE, ID_SEPARATOR, IMPORTANT_REGEX, InvalidOperationError, ItemMetadata, KIBIBYTE, MAX_NESTING_DEPTH, MAX_READ_LIMIT, MAX_WINDOW_LOG, MEDIA_TYPE_REGEX, MENTION_EVERYONE_ID, MENTION_HERE_ID, MENTION_ID_ATTRIBUTE, MENTION_ITEM_TYPE_ATTRIBUTE, MENTION_LABEL_ATTRIBUTE, MENTION_SELECTOR, MENTION_TYPE, MENTION_TYPE_ATTRIBUTE, MIN_WINDOW_LOG, MentionType, NON_TEXT_TAGS, NotFoundError, NotInitializedError, ObjectMethod, Operation, PATH_SEPARATOR_REGEX, RoutePath, SANITIZER_ADVISORY_WATCH_SINCE, SITE_NAME, SPOILER_ATTRIBUTE, SPOILER_SELECTOR, SRCSET_ATTRIBUTE, SRCSET_DESCRIPTOR_REGEX, Serializable, SourceScoreMap, TEXT_MEDIA_TYPE_REGEX, TypeScoreMap, TypedEventEmitter, URL_ATTRIBUTES, UUIDV4_REGEX, VOID_TAGS, WordFilteredError, applyItemMetadataMixin, capitalize, charset, checkContainsProfanity, checkIsAllowedUrl, checkIsCustomMethod, checkIsDeepEqual, checkIsNestedInteraction, checkIsPlainObject, checkIsSameBytes, checkIsSameValueZero, checkIsServer, checkIsUnsafeUrlCharacter, checkIsWhitespaceCharacter, chunk, chunkBySerializedSize, collectText, contentType, createEvictingPromiseCache, createItemEntityTypeSchema, createNormalizedStringSchema, createUniqueArraySchema, css, dedent, encodeUrlSubDelimiters, englishProfaneWords, escapeAttribute, escapeCharacters, escapeText, exhaustiveGuard, extension, filterAttributes, filterClasses, filterHtml, filterSrcset, filterStyle, getChildNodes, getDecodedUriComponent, getEndOfDay, getEnumerableKeys, getExtensionMimeTypeMap, getMediaType, getMentions, getMimeScore, getMimeTypeEntryMap, getOrCreate, getPropertyNames, getRawData, getRequestError, getResult, getResultAsync, getSynchronizedFunction, getTransactionDone, getTransactionObjectStore, getWindowLog, getZonedDateTime, hrtime, html, jsonDateParse, lookup, matchesClassPattern, mergeObjectsStrict, noop, normalizeString, now, omitDeep, openDatabase, renderNode, reportFinalizerError, requestToPromise, sanitizeHtml, sanitizeTextHtml, settleAll, sleep, streamToText, takeOne, toAppError, toKebabCase, toRawDeep, truncate, uncapitalize, waitForSynchronizedFunctions, withFinalizer, withFinalizerAsync, wrapDatabase, wrapIndex, wrapObjectStore, wrapTransaction, wrapUpgradeDatabase, wrapUpgradeTransaction };
|