@esposter/shared 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/dist/index.d.ts +0 -2387
- package/dist/index.js +0 -1918
package/dist/index.js
DELETED
|
@@ -1,1918 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { ResultAsync, fromThrowable } from "neverthrow";
|
|
3
|
-
import { parse } from "node-html-parser";
|
|
4
|
-
import mimeDatabase from "mime-db/db.json" with { type: "json" };
|
|
5
|
-
import { defaultTreeAdapter, html as html$1, parseFragment } from "parse5";
|
|
6
|
-
//#region src/models/error/ForbiddenError.ts
|
|
7
|
-
var ForbiddenError = class extends Error {
|
|
8
|
-
constructor(message) {
|
|
9
|
-
super(message);
|
|
10
|
-
this.name = "ForbiddenError";
|
|
11
|
-
}
|
|
12
|
-
};
|
|
13
|
-
//#endregion
|
|
14
|
-
//#region src/models/error/InvalidOperationError.ts
|
|
15
|
-
var InvalidOperationError = class extends Error {
|
|
16
|
-
constructor(operation, name, message) {
|
|
17
|
-
super(`Invalid operation: ${operation}, name: ${name}, ${message}`);
|
|
18
|
-
this.name = "InvalidOperationError";
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/models/error/NotFoundError.ts
|
|
23
|
-
var NotFoundError = class extends Error {
|
|
24
|
-
constructor(name, id) {
|
|
25
|
-
super(`${name} is not found for id: ${id}`);
|
|
26
|
-
this.name = "NotFoundError";
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
//#endregion
|
|
30
|
-
//#region src/models/error/NotInitializedError.ts
|
|
31
|
-
var NotInitializedError = class extends Error {
|
|
32
|
-
constructor(name) {
|
|
33
|
-
super(`${name} is not initialized`);
|
|
34
|
-
this.name = "NotInitializedError";
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
//#endregion
|
|
38
|
-
//#region src/models/error/WordFilteredError.ts
|
|
39
|
-
var WordFilteredError = class extends Error {
|
|
40
|
-
constructor(message) {
|
|
41
|
-
super(message);
|
|
42
|
-
this.name = "WordFilteredError";
|
|
43
|
-
}
|
|
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
|
|
93
|
-
//#region src/models/message/MentionType.ts
|
|
94
|
-
let MentionType = /* @__PURE__ */ function(MentionType) {
|
|
95
|
-
MentionType["Role"] = "role";
|
|
96
|
-
MentionType["User"] = "user";
|
|
97
|
-
return MentionType;
|
|
98
|
-
}({});
|
|
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
|
|
107
|
-
//#region src/services/app/constants.ts
|
|
108
|
-
const MAX_READ_LIMIT = 1e3;
|
|
109
|
-
const SITE_NAME = "Esposter";
|
|
110
|
-
const KIBIBYTE = 1024;
|
|
111
|
-
const GIBIBYTE = KIBIBYTE ** 2 * KIBIBYTE;
|
|
112
|
-
//#endregion
|
|
113
|
-
//#region src/models/router/RoutePath.ts
|
|
114
|
-
const RoutePath = {
|
|
115
|
-
About: "/about",
|
|
116
|
-
Achievements: "/achievements",
|
|
117
|
-
Anime: "/anime",
|
|
118
|
-
Calls: (id) => `/calls/${id}`,
|
|
119
|
-
CallsIndex: "/calls",
|
|
120
|
-
Clicker: "/clicker",
|
|
121
|
-
Docs: "/docs",
|
|
122
|
-
Dungeons: "/dungeons",
|
|
123
|
-
FluidSimulator: "/fluid-simulator",
|
|
124
|
-
Genshin: "/genshin",
|
|
125
|
-
Github: `https://github.com/${SITE_NAME}/${SITE_NAME}`,
|
|
126
|
-
Index: "/",
|
|
127
|
-
Login: "/login",
|
|
128
|
-
Messages: (id) => `/messages/${id}`,
|
|
129
|
-
MessagesDraftsAndSent: "/messages/draftsandsent",
|
|
130
|
-
MessagesFriends: "/messages/friends",
|
|
131
|
-
MessagesIndex: "/messages",
|
|
132
|
-
MessagesInvite: (code) => `/messages/invite/${code}`,
|
|
133
|
-
MessagesMessage: (id, rowKey) => `/messages/${id}/${rowKey}`,
|
|
134
|
-
MessagesThread: (id, rowKey) => `/messages/${id}/thread/${rowKey}`,
|
|
135
|
-
Post: (id) => `/post/${id}`,
|
|
136
|
-
PostCreate: "/post/create",
|
|
137
|
-
PostUpdate: (id) => `/post/update/${id}`,
|
|
138
|
-
PrivacyPolicy: "/privacy-policy",
|
|
139
|
-
Resource: (id) => `/resource-explorer/${id}`,
|
|
140
|
-
ResourceExplorer: "/resource-explorer",
|
|
141
|
-
ResourceExplorerAll: "/resource-explorer/all",
|
|
142
|
-
ResourceExplorerCreate: "/resource-explorer/create",
|
|
143
|
-
ResourceExplorerCreateType: (type) => `/resource-explorer/create/${type}`,
|
|
144
|
-
ResourceExplorerFavorites: "/resource-explorer/favorites",
|
|
145
|
-
ResourceExplorerRecents: "/resource-explorer/recents",
|
|
146
|
-
ResourceExplorerRecycleBin: "/resource-explorer/recycle-bin",
|
|
147
|
-
ResourceExplorerTags: "/resource-explorer/tags",
|
|
148
|
-
ResourceItems: (id) => `/resource-explorer/${id}/items`,
|
|
149
|
-
User: (id) => `/user/${id}`,
|
|
150
|
-
UserSettings: "/user/settings",
|
|
151
|
-
View: (type, id) => `/view/${type}/${id}`
|
|
152
|
-
};
|
|
153
|
-
//#endregion
|
|
154
|
-
//#region src/models/shared/ItemEntityType.ts
|
|
155
|
-
const createItemEntityTypeSchema = (schema) => z.object({ type: schema });
|
|
156
|
-
//#endregion
|
|
157
|
-
//#region src/models/shared/ItemMetadata.ts
|
|
158
|
-
var ItemMetadata = class {
|
|
159
|
-
createdAt = /* @__PURE__ */ new Date();
|
|
160
|
-
deletedAt = null;
|
|
161
|
-
updatedAt = /* @__PURE__ */ new Date();
|
|
162
|
-
};
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region src/util/reactivity/getRawData.ts
|
|
165
|
-
const getRawData = (data) => {
|
|
166
|
-
const raw = data && data.__v_raw;
|
|
167
|
-
return raw ? getRawData(raw) : data;
|
|
168
|
-
};
|
|
169
|
-
//#endregion
|
|
170
|
-
//#region src/util/reactivity/toRawDeep.ts
|
|
171
|
-
const toRawDeep = (data) => {
|
|
172
|
-
const rawData = getRawData(data);
|
|
173
|
-
for (const key in rawData) if (Object.hasOwn(rawData, key)) {
|
|
174
|
-
const value = rawData[key];
|
|
175
|
-
if (value !== null && typeof value === "object") rawData[key] = toRawDeep(value);
|
|
176
|
-
}
|
|
177
|
-
return rawData;
|
|
178
|
-
};
|
|
179
|
-
//#endregion
|
|
180
|
-
//#region src/models/shared/Serializable.ts
|
|
181
|
-
var Serializable = class {
|
|
182
|
-
toJSON() {
|
|
183
|
-
return structuredClone(toRawDeep(this));
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
//#endregion
|
|
187
|
-
//#region src/services/error/toAppError.ts
|
|
188
|
-
const toAppError = (error) => {
|
|
189
|
-
if (error instanceof Error) return error;
|
|
190
|
-
else if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") return new Error(error.message, { cause: error });
|
|
191
|
-
else return new Error(String(error), { cause: error });
|
|
192
|
-
};
|
|
193
|
-
//#endregion
|
|
194
|
-
//#region src/services/error/getResult.ts
|
|
195
|
-
const getResult = (callback) => fromThrowable(callback, toAppError)();
|
|
196
|
-
//#endregion
|
|
197
|
-
//#region src/services/error/getResultAsync.ts
|
|
198
|
-
const getResultAsync = (callback) => ResultAsync.fromThrowable(callback, toAppError)();
|
|
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
|
|
209
|
-
//#region src/util/function/noop.ts
|
|
210
|
-
const noop = () => {};
|
|
211
|
-
//#endregion
|
|
212
|
-
//#region src/services/error/withFinalizer.ts
|
|
213
|
-
const withFinalizer = (callback, finalizer) => {
|
|
214
|
-
const callbackResult = getResult(callback);
|
|
215
|
-
if (finalizer) getResult(finalizer).match(noop, (error) => {
|
|
216
|
-
reportFinalizerError(callbackResult, error);
|
|
217
|
-
});
|
|
218
|
-
return callbackResult.match((value) => value, (error) => {
|
|
219
|
-
throw error;
|
|
220
|
-
});
|
|
221
|
-
};
|
|
222
|
-
//#endregion
|
|
223
|
-
//#region src/services/error/withFinalizerAsync.ts
|
|
224
|
-
const withFinalizerAsync = async (callback, finalizer) => {
|
|
225
|
-
const callbackResult = await getResultAsync(async () => callback());
|
|
226
|
-
if (finalizer) await getResultAsync(async () => finalizer()).match(noop, (error) => {
|
|
227
|
-
reportFinalizerError(callbackResult, error);
|
|
228
|
-
});
|
|
229
|
-
return callbackResult.match((value) => value, (error) => {
|
|
230
|
-
throw error;
|
|
231
|
-
});
|
|
232
|
-
};
|
|
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
|
|
363
|
-
//#region src/services/message/constants.ts
|
|
364
|
-
const MENTION_ID_ATTRIBUTE = "data-id";
|
|
365
|
-
const MENTION_ITEM_TYPE_ATTRIBUTE = "data-mention-type";
|
|
366
|
-
const MENTION_LABEL_ATTRIBUTE = "data-label";
|
|
367
|
-
const MENTION_TYPE_ATTRIBUTE = "data-type";
|
|
368
|
-
const MENTION_TYPE = "mention";
|
|
369
|
-
const MENTION_SELECTOR = `span[${MENTION_TYPE_ATTRIBUTE}='${MENTION_TYPE}']`;
|
|
370
|
-
const MENTION_HERE_ID = "@here";
|
|
371
|
-
const MENTION_EVERYONE_ID = "@everyone";
|
|
372
|
-
const CUSTOM_EMOJI_ID_ATTRIBUTE = "data-custom-emoji-id";
|
|
373
|
-
const CUSTOM_EMOJI_NAME_ATTRIBUTE = "data-custom-emoji-name";
|
|
374
|
-
const SPOILER_ATTRIBUTE = "data-spoiler";
|
|
375
|
-
const SPOILER_SELECTOR = `span[${SPOILER_ATTRIBUTE}]`;
|
|
376
|
-
//#endregion
|
|
377
|
-
//#region src/services/message/getMentions.ts
|
|
378
|
-
const getMentions = (message) => parse(message).querySelectorAll(MENTION_SELECTOR);
|
|
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
|
|
478
|
-
//#region src/services/prettier/css.ts
|
|
479
|
-
const css = String.raw;
|
|
480
|
-
//#endregion
|
|
481
|
-
//#region src/services/prettier/html.ts
|
|
482
|
-
const html = String.raw;
|
|
483
|
-
//#endregion
|
|
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(" "));
|
|
1184
|
-
}
|
|
1185
|
-
return candidates.length > 0 ? candidates.join(", ") : void 0;
|
|
1186
|
-
};
|
|
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;")
|
|
1273
|
-
});
|
|
1274
|
-
const TEXT_ALIGN_REGEXES = [/^(?:left|right|center|justify)$/u];
|
|
1275
|
-
const TABLE_ALLOWED_STYLES = {
|
|
1276
|
-
table: {
|
|
1277
|
-
"border-collapse": [/^collapse$/u],
|
|
1278
|
-
width: [/^100%$/u]
|
|
1279
|
-
},
|
|
1280
|
-
td: { "text-align": TEXT_ALIGN_REGEXES },
|
|
1281
|
-
th: { "text-align": TEXT_ALIGN_REGEXES }
|
|
1282
|
-
};
|
|
1283
|
-
const sanitizeHtml = (html, options) => filterHtml(html, {
|
|
1284
|
-
allowedAttributes: {
|
|
1285
|
-
...options?.allowedAttributes ?? DEFAULT_ALLOWED_ATTRIBUTES,
|
|
1286
|
-
a: [.../* @__PURE__ */ new Set([
|
|
1287
|
-
...options?.allowedAttributes?.a ?? DEFAULT_LINK_ATTRIBUTES,
|
|
1288
|
-
"href",
|
|
1289
|
-
"rel",
|
|
1290
|
-
"target"
|
|
1291
|
-
])],
|
|
1292
|
-
table: ["style"],
|
|
1293
|
-
td: ["style"],
|
|
1294
|
-
th: ["style"]
|
|
1295
|
-
},
|
|
1296
|
-
allowedClasses: options?.allowedClasses ?? {},
|
|
1297
|
-
allowedSchemes: DEFAULT_ALLOWED_SCHEMES,
|
|
1298
|
-
allowedStyles: {
|
|
1299
|
-
...options?.allowedStyles,
|
|
1300
|
-
...TABLE_ALLOWED_STYLES
|
|
1301
|
-
},
|
|
1302
|
-
allowedTags: options?.allowedTags ?? DEFAULT_ALLOWED_TAGS,
|
|
1303
|
-
transformAttributes: {
|
|
1304
|
-
...options?.transformAttributes,
|
|
1305
|
-
a: transformLink,
|
|
1306
|
-
table: transformTable,
|
|
1307
|
-
td: transformCellAlign,
|
|
1308
|
-
th: transformCellAlign
|
|
1309
|
-
}
|
|
1310
|
-
});
|
|
1311
|
-
//#endregion
|
|
1312
|
-
//#region src/services/sanitizeHtml/sanitizeTextHtml.ts
|
|
1313
|
-
const COLOR_REGEXES = [
|
|
1314
|
-
/^#[\da-fA-F]{3,8}$/u,
|
|
1315
|
-
/^rgba?\(\d{1,3},\s*\d{1,3},\s*\d{1,3}(?:,\s*[\d.]+)?\)$/u,
|
|
1316
|
-
/^[a-z]+$/iu
|
|
1317
|
-
];
|
|
1318
|
-
const sanitizeTextHtml = (html) => sanitizeHtml(html, {
|
|
1319
|
-
allowedAttributes: {
|
|
1320
|
-
input: [
|
|
1321
|
-
"checked",
|
|
1322
|
-
"disabled",
|
|
1323
|
-
"type"
|
|
1324
|
-
],
|
|
1325
|
-
li: ["data-checked", "data-type"],
|
|
1326
|
-
span: [
|
|
1327
|
-
CUSTOM_EMOJI_ID_ATTRIBUTE,
|
|
1328
|
-
CUSTOM_EMOJI_NAME_ATTRIBUTE,
|
|
1329
|
-
MENTION_ID_ATTRIBUTE,
|
|
1330
|
-
MENTION_ITEM_TYPE_ATTRIBUTE,
|
|
1331
|
-
MENTION_LABEL_ATTRIBUTE,
|
|
1332
|
-
MENTION_TYPE_ATTRIBUTE,
|
|
1333
|
-
SPOILER_ATTRIBUTE,
|
|
1334
|
-
"style"
|
|
1335
|
-
],
|
|
1336
|
-
ul: ["data-type"]
|
|
1337
|
-
},
|
|
1338
|
-
allowedClasses: {
|
|
1339
|
-
code: ["language-*"],
|
|
1340
|
-
pre: ["language-*"],
|
|
1341
|
-
span: ["hljs*"]
|
|
1342
|
-
},
|
|
1343
|
-
allowedStyles: { span: {
|
|
1344
|
-
"background-color": COLOR_REGEXES,
|
|
1345
|
-
"border-radius": [/^[\d.]+(?<unit>px|em|rem|%)$/u],
|
|
1346
|
-
color: COLOR_REGEXES
|
|
1347
|
-
} },
|
|
1348
|
-
allowedTags: [
|
|
1349
|
-
...DEFAULT_ALLOWED_TAGS,
|
|
1350
|
-
"input",
|
|
1351
|
-
"label"
|
|
1352
|
-
],
|
|
1353
|
-
transformAttributes: { input: ({ checked }) => ({
|
|
1354
|
-
...checked === void 0 ? {} : { checked },
|
|
1355
|
-
disabled: "",
|
|
1356
|
-
type: "checkbox"
|
|
1357
|
-
}) }
|
|
1358
|
-
});
|
|
1359
|
-
//#endregion
|
|
1360
|
-
//#region src/services/shared/applyItemMetadataMixin.ts
|
|
1361
|
-
const applyItemMetadataMixin = (Base) => class ItemWithMetadata extends Base {
|
|
1362
|
-
createdAt = /* @__PURE__ */ new Date();
|
|
1363
|
-
deletedAt = null;
|
|
1364
|
-
updatedAt = /* @__PURE__ */ new Date();
|
|
1365
|
-
};
|
|
1366
|
-
//#endregion
|
|
1367
|
-
//#region src/util/text/normalizeString.ts
|
|
1368
|
-
const normalizeString = (value) => value?.trim() ?? "";
|
|
1369
|
-
//#endregion
|
|
1370
|
-
//#region src/services/zod/createNormalizedStringSchema.ts
|
|
1371
|
-
const createNormalizedStringSchema = (maxLength, schema = z.string()) => schema.transform(normalizeString).pipe(z.string().max(maxLength));
|
|
1372
|
-
//#endregion
|
|
1373
|
-
//#region src/services/zod/createUniqueArraySchema.ts
|
|
1374
|
-
const createUniqueArraySchema = (schema, key) => schema.array().refine((array) => new Set(key === void 0 ? array : array.map((item) => item[key])).size === array.length, "Array items must be unique");
|
|
1375
|
-
//#endregion
|
|
1376
|
-
//#region src/services/zstd/constants.ts
|
|
1377
|
-
const DEFAULT_COMPRESSION_LEVEL = 12;
|
|
1378
|
-
const MIN_WINDOW_LOG = 10;
|
|
1379
|
-
const MAX_WINDOW_LOG = 27;
|
|
1380
|
-
//#endregion
|
|
1381
|
-
//#region src/services/zstd/getWindowLog.ts
|
|
1382
|
-
const getWindowLog = (dictionaryByteCount, inputByteCount) => Math.min(27, Math.max(10, Math.ceil(Math.log2(dictionaryByteCount + inputByteCount + 1))));
|
|
1383
|
-
//#endregion
|
|
1384
|
-
//#region src/test/constants.ts
|
|
1385
|
-
const ALL_SPECIAL_VALUES = [
|
|
1386
|
-
{
|
|
1387
|
-
isPlainObject: true,
|
|
1388
|
-
value: {}
|
|
1389
|
-
},
|
|
1390
|
-
{
|
|
1391
|
-
isPlainObject: true,
|
|
1392
|
-
value: Object.create(null)
|
|
1393
|
-
},
|
|
1394
|
-
{
|
|
1395
|
-
isPlainObject: false,
|
|
1396
|
-
value: []
|
|
1397
|
-
},
|
|
1398
|
-
{
|
|
1399
|
-
isPlainObject: false,
|
|
1400
|
-
value: /* @__PURE__ */ new Date()
|
|
1401
|
-
},
|
|
1402
|
-
{
|
|
1403
|
-
isPlainObject: false,
|
|
1404
|
-
value: null
|
|
1405
|
-
},
|
|
1406
|
-
{
|
|
1407
|
-
isPlainObject: false,
|
|
1408
|
-
value: void 0
|
|
1409
|
-
},
|
|
1410
|
-
{
|
|
1411
|
-
isPlainObject: false,
|
|
1412
|
-
value: ""
|
|
1413
|
-
},
|
|
1414
|
-
{
|
|
1415
|
-
isPlainObject: false,
|
|
1416
|
-
value: 0
|
|
1417
|
-
},
|
|
1418
|
-
{
|
|
1419
|
-
isPlainObject: false,
|
|
1420
|
-
value: Symbol("")
|
|
1421
|
-
},
|
|
1422
|
-
{
|
|
1423
|
-
isPlainObject: false,
|
|
1424
|
-
value: noop
|
|
1425
|
-
},
|
|
1426
|
-
{
|
|
1427
|
-
isPlainObject: false,
|
|
1428
|
-
value: /(?:)/u
|
|
1429
|
-
}
|
|
1430
|
-
];
|
|
1431
|
-
//#endregion
|
|
1432
|
-
//#region src/util/array/chunk.ts
|
|
1433
|
-
const chunk = (array, size) => {
|
|
1434
|
-
const chunks = [];
|
|
1435
|
-
for (let index = 0; index < array.length; index += size) chunks.push(array.slice(index, index + size));
|
|
1436
|
-
return chunks;
|
|
1437
|
-
};
|
|
1438
|
-
//#endregion
|
|
1439
|
-
//#region src/util/array/chunkBySerializedSize.ts
|
|
1440
|
-
const ARRAY_SERIALIZATION_BYTES = 1;
|
|
1441
|
-
const chunkBySerializedSize = (items, maxBytes, maxCount) => {
|
|
1442
|
-
const textEncoder = new TextEncoder();
|
|
1443
|
-
const chunks = [];
|
|
1444
|
-
let currentChunk = [];
|
|
1445
|
-
let currentBytes = ARRAY_SERIALIZATION_BYTES;
|
|
1446
|
-
for (const item of items) {
|
|
1447
|
-
const itemBytes = textEncoder.encode(JSON.stringify(item)).length + 1;
|
|
1448
|
-
if (currentChunk.length > 0 && (currentChunk.length === maxCount || currentBytes + itemBytes > maxBytes)) {
|
|
1449
|
-
chunks.push(currentChunk);
|
|
1450
|
-
currentChunk = [];
|
|
1451
|
-
currentBytes = ARRAY_SERIALIZATION_BYTES;
|
|
1452
|
-
}
|
|
1453
|
-
currentChunk.push(item);
|
|
1454
|
-
currentBytes += itemBytes;
|
|
1455
|
-
}
|
|
1456
|
-
if (currentChunk.length > 0) chunks.push(currentChunk);
|
|
1457
|
-
return chunks;
|
|
1458
|
-
};
|
|
1459
|
-
//#endregion
|
|
1460
|
-
//#region src/util/date/getZonedDateTime.ts
|
|
1461
|
-
const getZonedDateTime = (date) => Temporal.Instant.fromEpochMilliseconds(date.getTime()).toZonedDateTimeISO(Temporal.Now.timeZoneId());
|
|
1462
|
-
//#endregion
|
|
1463
|
-
//#region src/util/date/getEndOfDay.ts
|
|
1464
|
-
const getEndOfDay = (date) => /* @__PURE__ */ new Date(getZonedDateTime(date).startOfDay().add({ days: 1 }).epochMilliseconds - 1);
|
|
1465
|
-
//#endregion
|
|
1466
|
-
//#region src/util/dom/checkIsNestedInteraction.ts
|
|
1467
|
-
const NESTED_INTERACTION_SELECTOR = [
|
|
1468
|
-
"a[href]",
|
|
1469
|
-
"button",
|
|
1470
|
-
"dialog",
|
|
1471
|
-
"input",
|
|
1472
|
-
"label",
|
|
1473
|
-
"select",
|
|
1474
|
-
"summary",
|
|
1475
|
-
"textarea",
|
|
1476
|
-
"[contenteditable='']",
|
|
1477
|
-
"[contenteditable='plaintext-only']",
|
|
1478
|
-
"[contenteditable='true']",
|
|
1479
|
-
"[data-nested-interaction]",
|
|
1480
|
-
"[role='button']",
|
|
1481
|
-
"[role='checkbox']",
|
|
1482
|
-
"[role='dialog']",
|
|
1483
|
-
"[role='link']",
|
|
1484
|
-
"[role='menu']",
|
|
1485
|
-
"[role='menuitem']",
|
|
1486
|
-
"[role='option']",
|
|
1487
|
-
"[role='switch']",
|
|
1488
|
-
"[role='tab']"
|
|
1489
|
-
].join(", ");
|
|
1490
|
-
const checkIsNestedInteraction = (event) => {
|
|
1491
|
-
const { currentTarget, target } = event;
|
|
1492
|
-
if (!(currentTarget instanceof Element) || !(target instanceof Element)) return false;
|
|
1493
|
-
const interactiveElement = target.closest(NESTED_INTERACTION_SELECTOR);
|
|
1494
|
-
if (interactiveElement && interactiveElement !== currentTarget && currentTarget.contains(interactiveElement)) return true;
|
|
1495
|
-
else return Boolean(window.getSelection()?.toString());
|
|
1496
|
-
};
|
|
1497
|
-
//#endregion
|
|
1498
|
-
//#region src/util/environment/checkIsServer.ts
|
|
1499
|
-
const checkIsServer = () => typeof window === "undefined";
|
|
1500
|
-
//#endregion
|
|
1501
|
-
//#region src/util/function/getSynchronizedFunction.ts
|
|
1502
|
-
const pendingPromises = /* @__PURE__ */ new Set();
|
|
1503
|
-
const getSynchronizedFunction = (asyncFunction) => (...args) => {
|
|
1504
|
-
const promise = asyncFunction(...args).finally(() => {
|
|
1505
|
-
pendingPromises.delete(promise);
|
|
1506
|
-
});
|
|
1507
|
-
pendingPromises.add(promise);
|
|
1508
|
-
};
|
|
1509
|
-
const waitForSynchronizedFunctions = async () => {
|
|
1510
|
-
await Promise.allSettled(pendingPromises);
|
|
1511
|
-
};
|
|
1512
|
-
//#endregion
|
|
1513
|
-
//#region src/util/id/constants.ts
|
|
1514
|
-
const ID_SEPARATOR = "|";
|
|
1515
|
-
//#endregion
|
|
1516
|
-
//#region src/util/id/uuid/constants.ts
|
|
1517
|
-
const UUIDV4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/iu;
|
|
1518
|
-
//#endregion
|
|
1519
|
-
//#region src/util/map/getOrCreate.ts
|
|
1520
|
-
const getOrCreate = (map, key, create) => {
|
|
1521
|
-
if (map.has(key)) return map.get(key);
|
|
1522
|
-
const value = create();
|
|
1523
|
-
map.set(key, value);
|
|
1524
|
-
return value;
|
|
1525
|
-
};
|
|
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
|
|
1578
|
-
//#region src/util/object/checkIsPlainObject.ts
|
|
1579
|
-
const checkIsPlainObject = (data) => {
|
|
1580
|
-
if (typeof data !== "object" || data === null) return false;
|
|
1581
|
-
const prototype = Object.getPrototypeOf(data);
|
|
1582
|
-
return prototype === null || prototype === Object.prototype;
|
|
1583
|
-
};
|
|
1584
|
-
//#endregion
|
|
1585
|
-
//#region src/util/object/getPropertyNames.ts
|
|
1586
|
-
const getPropertyNames = () => new Proxy({}, { get: (_target, property) => property });
|
|
1587
|
-
//#endregion
|
|
1588
|
-
//#region src/util/object/jsonDateParse.ts
|
|
1589
|
-
const ISO_DATE_REGEX = /^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})T(?<hours>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2}(?:\.{0,1}\d*))(?:Z|(?<sign>\+|-)(?<offset>[\d|:]*))?$/u;
|
|
1590
|
-
const MS_AJAX_DATE_REGEX = /^\/Date\((?<timestamp>-?\d+(?:[-+]\d+)?)\)[/\\]$/u;
|
|
1591
|
-
const jsonDateParse = (text) => JSON.parse(text, (_key, value) => {
|
|
1592
|
-
if (typeof value !== "string") return value;
|
|
1593
|
-
else if (ISO_DATE_REGEX.test(value)) return new Date(value);
|
|
1594
|
-
const msAjaxDateMatch = MS_AJAX_DATE_REGEX.exec(value);
|
|
1595
|
-
if (!msAjaxDateMatch) return value;
|
|
1596
|
-
const timestampParts = takeOne(msAjaxDateMatch, 1).split(/[-+,.]/u);
|
|
1597
|
-
return new Date(timestampParts[0] ? Number(timestampParts[0]) : 0 - Number(timestampParts[1]));
|
|
1598
|
-
});
|
|
1599
|
-
//#endregion
|
|
1600
|
-
//#region src/util/object/mergeObjectsStrict.ts
|
|
1601
|
-
const mergeObjectsStrict = (...objects) => Object.assign({}, ...objects);
|
|
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
|
|
1628
|
-
//#region src/util/promise/settleAll.ts
|
|
1629
|
-
const settleAll = async (tasks, concurrencyLimit = tasks.length) => {
|
|
1630
|
-
if (tasks.length === 0) return [];
|
|
1631
|
-
const values = [];
|
|
1632
|
-
for (const tasksChunk of chunk(tasks, concurrencyLimit)) {
|
|
1633
|
-
const results = await Promise.allSettled(tasksChunk.map((task) => Promise.try(task)));
|
|
1634
|
-
const reasons = [];
|
|
1635
|
-
for (const result of results) if (result.status === "rejected") reasons.push(result.reason);
|
|
1636
|
-
else values.push(result.value);
|
|
1637
|
-
if (reasons.length === 1) throw takeOne(reasons);
|
|
1638
|
-
else if (reasons.length > 1) throw new AggregateError(reasons, toAppError(takeOne(reasons)).message);
|
|
1639
|
-
}
|
|
1640
|
-
return values;
|
|
1641
|
-
};
|
|
1642
|
-
//#endregion
|
|
1643
|
-
//#region src/util/text/capitalize.ts
|
|
1644
|
-
const capitalize = (string) => `${string.charAt(0).toUpperCase()}${string.slice(1)}`;
|
|
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
|
|
1846
|
-
//#region src/util/text/encodeUrlSubDelimiters.ts
|
|
1847
|
-
const EncodedSubDelimiterMap = {
|
|
1848
|
-
"!": "%21",
|
|
1849
|
-
"'": "%27",
|
|
1850
|
-
"(": "%28",
|
|
1851
|
-
")": "%29",
|
|
1852
|
-
"*": "%2A"
|
|
1853
|
-
};
|
|
1854
|
-
const encodeUrlSubDelimiters = (value) => Object.entries(EncodedSubDelimiterMap).reduce((encoded, [subDelimiter, encodedSubDelimiter]) => encoded.replaceAll(subDelimiter, encodedSubDelimiter), value);
|
|
1855
|
-
//#endregion
|
|
1856
|
-
//#region src/util/text/getDecodedUriComponent.ts
|
|
1857
|
-
const getDecodedUriComponent = (value, fallback) => getResult(() => decodeURIComponent(value)).unwrapOr(fallback);
|
|
1858
|
-
//#endregion
|
|
1859
|
-
//#region src/util/text/streamToText.ts
|
|
1860
|
-
const streamToText = async (readable) => {
|
|
1861
|
-
readable.setEncoding("utf8");
|
|
1862
|
-
let data = "";
|
|
1863
|
-
for await (const chunk of readable) data += chunk.toString();
|
|
1864
|
-
return data;
|
|
1865
|
-
};
|
|
1866
|
-
//#endregion
|
|
1867
|
-
//#region src/util/text/toKebabCase.ts
|
|
1868
|
-
const toKebabCase = (string) => string.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/gu)?.map((x) => x.toLowerCase()).join("-") ?? "";
|
|
1869
|
-
//#endregion
|
|
1870
|
-
//#region src/util/text/truncate.ts
|
|
1871
|
-
const SUFFIX = "...";
|
|
1872
|
-
const truncate = (string, length) => {
|
|
1873
|
-
if (string.length <= length) return string;
|
|
1874
|
-
else if (length < 3) return string.slice(0, length);
|
|
1875
|
-
else return `${string.slice(0, length - 3)}${SUFFIX}`;
|
|
1876
|
-
};
|
|
1877
|
-
//#endregion
|
|
1878
|
-
//#region src/util/text/uncapitalize.ts
|
|
1879
|
-
const uncapitalize = (string) => `${string.charAt(0).toLowerCase()}${string.slice(1)}`;
|
|
1880
|
-
//#endregion
|
|
1881
|
-
//#region src/util/time/hrtime.ts
|
|
1882
|
-
const hrtime = (previousHrTime) => {
|
|
1883
|
-
if (checkIsServer()) return process.hrtime(previousHrTime);
|
|
1884
|
-
const clocktime = performance.now() * .001;
|
|
1885
|
-
let seconds = Math.floor(clocktime);
|
|
1886
|
-
let nanoseconds = Math.floor(clocktime % 1 * 1e9);
|
|
1887
|
-
if (previousHrTime) {
|
|
1888
|
-
seconds -= previousHrTime[0];
|
|
1889
|
-
nanoseconds -= previousHrTime[1];
|
|
1890
|
-
if (nanoseconds < 0) {
|
|
1891
|
-
seconds--;
|
|
1892
|
-
nanoseconds += 1e9;
|
|
1893
|
-
}
|
|
1894
|
-
}
|
|
1895
|
-
return [seconds, nanoseconds];
|
|
1896
|
-
};
|
|
1897
|
-
//#endregion
|
|
1898
|
-
//#region src/util/time/now.ts
|
|
1899
|
-
const loadNanoseconds = hrtime();
|
|
1900
|
-
const loadMilliseconds = Date.now();
|
|
1901
|
-
const now = () => {
|
|
1902
|
-
const [seconds, nanoseconds] = hrtime(loadNanoseconds);
|
|
1903
|
-
return (BigInt(loadMilliseconds) * 10n ** 6n + (BigInt(seconds) * 10n ** 9n + BigInt(nanoseconds))).toString();
|
|
1904
|
-
};
|
|
1905
|
-
//#endregion
|
|
1906
|
-
//#region src/util/time/sleep.ts
|
|
1907
|
-
const sleep = (durationMs) => new Promise((resolve) => {
|
|
1908
|
-
setTimeout(() => {
|
|
1909
|
-
resolve();
|
|
1910
|
-
}, durationMs);
|
|
1911
|
-
});
|
|
1912
|
-
//#endregion
|
|
1913
|
-
//#region src/util/validation/exhaustiveGuard.ts
|
|
1914
|
-
const exhaustiveGuard = (value) => {
|
|
1915
|
-
throw new InvalidOperationError("Read", exhaustiveGuard.name, JSON.stringify(value));
|
|
1916
|
-
};
|
|
1917
|
-
//#endregion
|
|
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 };
|