Files
SnowLuma/.pnpm-store/v11/files/93/5423b060491d7d6606289ecde113748f7aed6a23a33d9cb2437acd6969564a92942c66a978a1a1cb530244d1f2793e36042babbe2d9439cfbe35ae89530fec
TanTan 2a69b23b47
Dev Build / cleanup (push) Canceled after 0s
Dev Build / typecheck (push) Canceled after 0s
Dev Build / build (linux-arm64, ubuntu-24.04-arm, linux-arm64) (push) Canceled after 0s
Dev Build / build (linux-x64, ubuntu-latest, linux-x64) (push) Canceled after 0s
Dev Build / build (win-x64, windows-latest, win32-x64) (push) Canceled after 0s
Dev Build / dispatch-dev-docker (push) Skipped
docs: 仓库链接指向本镜像仓库
2026-11-13 09:27:07 +08:00

104 lines
2.7 KiB
Plaintext

//#region src/utils/html.ts
/**
* @module
* HTML utility.
*/
const HtmlEscapedCallbackPhase = {
Stringify: 1,
BeforeStream: 2,
Stream: 3
};
const raw = (value, callbacks) => {
const escapedString = new String(value);
escapedString.isEscaped = true;
escapedString.callbacks = callbacks;
return escapedString;
};
const escapeRe = /[&<>'"]/;
const stringBufferToString = async (buffer, callbacks) => {
let str = "";
callbacks ||= [];
const resolvedBuffer = await Promise.all(buffer);
for (let i = resolvedBuffer.length - 1;; i--) {
str += resolvedBuffer[i];
i--;
if (i < 0) break;
let r = resolvedBuffer[i];
if (typeof r === "object") callbacks.push(...r.callbacks || []);
const isEscaped = r.isEscaped;
r = await (typeof r === "object" ? r.toString() : r);
if (typeof r === "object") callbacks.push(...r.callbacks || []);
if (r.isEscaped ?? isEscaped) str += r;
else {
const buf = [str];
escapeToBuffer(r, buf);
str = buf[0];
}
}
return raw(str, callbacks);
};
const escapeToBuffer = (str, buffer) => {
const match = str.search(escapeRe);
if (match === -1) {
buffer[0] += str;
return;
}
let escape;
let index;
let lastIndex = 0;
for (index = match; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34:
escape = "&quot;";
break;
case 39:
escape = "&#39;";
break;
case 38:
escape = "&amp;";
break;
case 60:
escape = "&lt;";
break;
case 62:
escape = "&gt;";
break;
default: continue;
}
buffer[0] += str.substring(lastIndex, index) + escape;
lastIndex = index + 1;
}
buffer[0] += str.substring(lastIndex, index);
};
const resolveCallbackSync = (str) => {
const callbacks = str.callbacks;
if (!callbacks?.length) return str;
const buffer = [str];
const context = {};
callbacks.forEach((c) => c({
phase: HtmlEscapedCallbackPhase.Stringify,
buffer,
context
}));
return buffer[0];
};
const resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
if (typeof str === "object" && !(str instanceof String)) {
if (!(str instanceof Promise)) str = str.toString();
if (str instanceof Promise) str = await str;
}
const callbacks = str.callbacks;
if (!callbacks?.length) return Promise.resolve(str);
if (buffer) buffer[0] += str;
else buffer = [str];
const resStr = Promise.all(callbacks.map((c) => c({
phase,
buffer,
context
}))).then((res) => Promise.all(res.filter(Boolean).map((str) => resolveCallback(str, phase, false, context, buffer))).then(() => buffer[0]));
if (preserveCallbacks) return raw(await resStr, callbacks);
else return resStr;
};
//#endregion
export { HtmlEscapedCallbackPhase, escapeToBuffer, raw, resolveCallback, resolveCallbackSync, stringBufferToString };