641 lines
20 KiB
JavaScript
641 lines
20 KiB
JavaScript
|
function asArray$1(value) {
|
||
|
return Array.isArray(value) ? value : [value];
|
||
|
}
|
||
|
|
||
|
const SelfClosingTags = ["meta", "link", "base"];
|
||
|
const TagsWithInnerContent = ["title", "titleTemplate", "script", "style", "noscript"];
|
||
|
const HasElementTags = [
|
||
|
"base",
|
||
|
"meta",
|
||
|
"link",
|
||
|
"style",
|
||
|
"script",
|
||
|
"noscript"
|
||
|
];
|
||
|
const ValidHeadTags = [
|
||
|
"title",
|
||
|
"titleTemplate",
|
||
|
"templateParams",
|
||
|
"base",
|
||
|
"htmlAttrs",
|
||
|
"bodyAttrs",
|
||
|
"meta",
|
||
|
"link",
|
||
|
"style",
|
||
|
"script",
|
||
|
"noscript"
|
||
|
];
|
||
|
const UniqueTags = ["base", "title", "titleTemplate", "bodyAttrs", "htmlAttrs", "templateParams"];
|
||
|
const TagConfigKeys = ["tagPosition", "tagPriority", "tagDuplicateStrategy", "children", "innerHTML", "textContent", "processTemplateParams"];
|
||
|
const IsBrowser = typeof window !== "undefined";
|
||
|
const composableNames = [
|
||
|
"getActiveHead",
|
||
|
"useHead",
|
||
|
"useSeoMeta",
|
||
|
"useHeadSafe",
|
||
|
"useServerHead",
|
||
|
"useServerSeoMeta",
|
||
|
"useServerHeadSafe"
|
||
|
];
|
||
|
|
||
|
function defineHeadPlugin(plugin) {
|
||
|
return plugin;
|
||
|
}
|
||
|
|
||
|
function hashCode(s) {
|
||
|
let h = 9;
|
||
|
for (let i = 0; i < s.length; )
|
||
|
h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9);
|
||
|
return ((h ^ h >>> 9) + 65536).toString(16).substring(1, 8).toLowerCase();
|
||
|
}
|
||
|
function hashTag(tag) {
|
||
|
return tag._h || hashCode(tag._d ? tag._d : `${tag.tag}:${tag.textContent || tag.innerHTML || ""}:${Object.entries(tag.props).map(([key, value]) => `${key}:${String(value)}`).join(",")}`);
|
||
|
}
|
||
|
|
||
|
function tagDedupeKey(tag, fn) {
|
||
|
const { props, tag: tagName } = tag;
|
||
|
if (UniqueTags.includes(tagName))
|
||
|
return tagName;
|
||
|
if (tagName === "link" && props.rel === "canonical")
|
||
|
return "canonical";
|
||
|
if (props.charset)
|
||
|
return "charset";
|
||
|
const name = ["id"];
|
||
|
if (tagName === "meta")
|
||
|
name.push(...["name", "property", "http-equiv"]);
|
||
|
for (const n of name) {
|
||
|
if (typeof props[n] !== "undefined") {
|
||
|
const val = String(props[n]);
|
||
|
if (fn && !fn(val))
|
||
|
return false;
|
||
|
return `${tagName}:${n}:${val}`;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function resolveTitleTemplate(template, title) {
|
||
|
if (template == null)
|
||
|
return title || null;
|
||
|
if (typeof template === "function")
|
||
|
return template(title);
|
||
|
return template;
|
||
|
}
|
||
|
|
||
|
function asArray(input) {
|
||
|
return Array.isArray(input) ? input : [input];
|
||
|
}
|
||
|
const InternalKeySymbol = "_$key";
|
||
|
function packObject(input, options) {
|
||
|
const keys = Object.keys(input);
|
||
|
let [k, v] = keys;
|
||
|
options = options || {};
|
||
|
options.key = options.key || k;
|
||
|
options.value = options.value || v;
|
||
|
options.resolveKey = options.resolveKey || ((k2) => k2);
|
||
|
const resolveKey = (index) => {
|
||
|
const arr = asArray(options?.[index]);
|
||
|
return arr.find((k2) => {
|
||
|
if (typeof k2 === "string" && k2.includes(".")) {
|
||
|
return k2;
|
||
|
}
|
||
|
return k2 && keys.includes(k2);
|
||
|
});
|
||
|
};
|
||
|
const resolveValue = (k2, input2) => {
|
||
|
if (k2.includes(".")) {
|
||
|
const paths = k2.split(".");
|
||
|
let val = input2;
|
||
|
for (const path of paths)
|
||
|
val = val[path];
|
||
|
return val;
|
||
|
}
|
||
|
return input2[k2];
|
||
|
};
|
||
|
k = resolveKey("key") || k;
|
||
|
v = resolveKey("value") || v;
|
||
|
const dedupeKeyPrefix = input.key ? `${InternalKeySymbol}${input.key}-` : "";
|
||
|
let keyValue = resolveValue(k, input);
|
||
|
keyValue = options.resolveKey(keyValue);
|
||
|
return {
|
||
|
[`${dedupeKeyPrefix}${keyValue}`]: resolveValue(v, input)
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function packArray(input, options) {
|
||
|
const packed = {};
|
||
|
for (const i of input) {
|
||
|
const packedObj = packObject(i, options);
|
||
|
const pKey = Object.keys(packedObj)[0];
|
||
|
const isDedupeKey = pKey.startsWith(InternalKeySymbol);
|
||
|
if (!isDedupeKey && packed[pKey]) {
|
||
|
packed[pKey] = Array.isArray(packed[pKey]) ? packed[pKey] : [packed[pKey]];
|
||
|
packed[pKey].push(Object.values(packedObj)[0]);
|
||
|
} else {
|
||
|
packed[isDedupeKey ? pKey.split("-").slice(1).join("-") || pKey : pKey] = packedObj[pKey];
|
||
|
}
|
||
|
}
|
||
|
return packed;
|
||
|
}
|
||
|
|
||
|
function unpackToArray(input, options) {
|
||
|
const unpacked = [];
|
||
|
const kFn = options.resolveKeyData || ((ctx) => ctx.key);
|
||
|
const vFn = options.resolveValueData || ((ctx) => ctx.value);
|
||
|
for (const [k, v] of Object.entries(input)) {
|
||
|
unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
|
||
|
const ctx = { key: k, value: i };
|
||
|
const val = vFn(ctx);
|
||
|
if (typeof val === "object")
|
||
|
return unpackToArray(val, options);
|
||
|
if (Array.isArray(val))
|
||
|
return val;
|
||
|
return {
|
||
|
[typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
|
||
|
[typeof options.value === "function" ? options.value(ctx) : options.value]: val
|
||
|
};
|
||
|
}).flat());
|
||
|
}
|
||
|
return unpacked;
|
||
|
}
|
||
|
|
||
|
function unpackToString(value, options) {
|
||
|
return Object.entries(value).map(([key, value2]) => {
|
||
|
if (typeof value2 === "object")
|
||
|
value2 = unpackToString(value2, options);
|
||
|
if (options.resolve) {
|
||
|
const resolved = options.resolve({ key, value: value2 });
|
||
|
if (resolved)
|
||
|
return resolved;
|
||
|
}
|
||
|
if (typeof value2 === "number")
|
||
|
value2 = value2.toString();
|
||
|
if (typeof value2 === "string" && options.wrapValue) {
|
||
|
value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
|
||
|
value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
|
||
|
}
|
||
|
return `${key}${options.keyValueSeparator || ""}${value2}`;
|
||
|
}).join(options.entrySeparator || "");
|
||
|
}
|
||
|
|
||
|
const p = (p2) => ({ keyValue: p2, metaKey: "property" });
|
||
|
const k = (p2) => ({ keyValue: p2 });
|
||
|
const MetaPackingSchema = {
|
||
|
appleItunesApp: {
|
||
|
unpack: {
|
||
|
entrySeparator: ", ",
|
||
|
resolve({ key, value }) {
|
||
|
return `${fixKeyCase(key)}=${value}`;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
articleExpirationTime: p("article:expiration_time"),
|
||
|
articleModifiedTime: p("article:modified_time"),
|
||
|
articlePublishedTime: p("article:published_time"),
|
||
|
bookReleaseDate: p("book:release_date"),
|
||
|
charset: {
|
||
|
metaKey: "charset"
|
||
|
},
|
||
|
contentSecurityPolicy: {
|
||
|
unpack: {
|
||
|
entrySeparator: "; ",
|
||
|
resolve({ key, value }) {
|
||
|
return `${fixKeyCase(key)} ${value}`;
|
||
|
}
|
||
|
},
|
||
|
metaKey: "http-equiv"
|
||
|
},
|
||
|
contentType: {
|
||
|
metaKey: "http-equiv"
|
||
|
},
|
||
|
defaultStyle: {
|
||
|
metaKey: "http-equiv"
|
||
|
},
|
||
|
fbAppId: p("fb:app_id"),
|
||
|
msapplicationConfig: k("msapplication-Config"),
|
||
|
msapplicationTileColor: k("msapplication-TileColor"),
|
||
|
msapplicationTileImage: k("msapplication-TileImage"),
|
||
|
ogAudioSecureUrl: p("og:audio:secure_url"),
|
||
|
ogAudioUrl: p("og:audio"),
|
||
|
ogImageSecureUrl: p("og:image:secure_url"),
|
||
|
ogImageUrl: p("og:image"),
|
||
|
ogSiteName: p("og:site_name"),
|
||
|
ogVideoSecureUrl: p("og:video:secure_url"),
|
||
|
ogVideoUrl: p("og:video"),
|
||
|
profileFirstName: p("profile:first_name"),
|
||
|
profileLastName: p("profile:last_name"),
|
||
|
profileUsername: p("profile:username"),
|
||
|
refresh: {
|
||
|
metaKey: "http-equiv",
|
||
|
unpack: {
|
||
|
entrySeparator: ";",
|
||
|
resolve({ key, value }) {
|
||
|
if (key === "seconds")
|
||
|
return `${value}`;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
robots: {
|
||
|
unpack: {
|
||
|
entrySeparator: ", ",
|
||
|
resolve({ key, value }) {
|
||
|
if (typeof value === "boolean")
|
||
|
return `${fixKeyCase(key)}`;
|
||
|
else
|
||
|
return `${fixKeyCase(key)}:${value}`;
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
xUaCompatible: {
|
||
|
metaKey: "http-equiv"
|
||
|
}
|
||
|
};
|
||
|
const openGraphNamespaces = [
|
||
|
"og",
|
||
|
"book",
|
||
|
"article",
|
||
|
"profile"
|
||
|
];
|
||
|
function resolveMetaKeyType(key) {
|
||
|
const fKey = fixKeyCase(key).split(":")[0];
|
||
|
if (openGraphNamespaces.includes(fKey))
|
||
|
return "property";
|
||
|
return MetaPackingSchema[key]?.metaKey || "name";
|
||
|
}
|
||
|
function resolveMetaKeyValue(key) {
|
||
|
return MetaPackingSchema[key]?.keyValue || fixKeyCase(key);
|
||
|
}
|
||
|
function fixKeyCase(key) {
|
||
|
const updated = key.replace(/([A-Z])/g, "-$1").toLowerCase();
|
||
|
const fKey = updated.split("-")[0];
|
||
|
if (openGraphNamespaces.includes(fKey) || fKey === "twitter")
|
||
|
return key.replace(/([A-Z])/g, ":$1").toLowerCase();
|
||
|
return updated;
|
||
|
}
|
||
|
function changeKeyCasingDeep(input) {
|
||
|
if (Array.isArray(input)) {
|
||
|
return input.map((entry) => changeKeyCasingDeep(entry));
|
||
|
}
|
||
|
if (typeof input !== "object" || Array.isArray(input))
|
||
|
return input;
|
||
|
const output = {};
|
||
|
for (const [key, value] of Object.entries(input))
|
||
|
output[fixKeyCase(key)] = changeKeyCasingDeep(value);
|
||
|
return output;
|
||
|
}
|
||
|
function resolvePackedMetaObjectValue(value, key) {
|
||
|
const definition = MetaPackingSchema[key];
|
||
|
if (key === "refresh")
|
||
|
return `${value.seconds};url=${value.url}`;
|
||
|
return unpackToString(
|
||
|
changeKeyCasingDeep(value),
|
||
|
{
|
||
|
keyValueSeparator: "=",
|
||
|
entrySeparator: ", ",
|
||
|
resolve({ value: value2, key: key2 }) {
|
||
|
if (value2 === null)
|
||
|
return "";
|
||
|
if (typeof value2 === "boolean")
|
||
|
return `${key2}`;
|
||
|
},
|
||
|
...definition?.unpack
|
||
|
}
|
||
|
);
|
||
|
}
|
||
|
const ObjectArrayEntries = ["og:image", "og:video", "og:audio", "twitter:image"];
|
||
|
function sanitize(input) {
|
||
|
const out = {};
|
||
|
Object.entries(input).forEach(([k2, v]) => {
|
||
|
if (String(v) !== "false" && k2)
|
||
|
out[k2] = v;
|
||
|
});
|
||
|
return out;
|
||
|
}
|
||
|
function handleObjectEntry(key, v) {
|
||
|
const value = sanitize(v);
|
||
|
const fKey = fixKeyCase(key);
|
||
|
const attr = resolveMetaKeyType(fKey);
|
||
|
if (ObjectArrayEntries.includes(fKey)) {
|
||
|
const input = {};
|
||
|
Object.entries(value).forEach(([k2, v2]) => {
|
||
|
input[`${key}${k2 === "url" ? "" : `${k2.charAt(0).toUpperCase()}${k2.slice(1)}`}`] = v2;
|
||
|
});
|
||
|
return unpackMeta(input).sort((a, b) => (a[attr]?.length || 0) - (b[attr]?.length || 0));
|
||
|
}
|
||
|
return [{ [attr]: fKey, ...value }];
|
||
|
}
|
||
|
function unpackMeta(input) {
|
||
|
const extras = [];
|
||
|
const primitives = {};
|
||
|
Object.entries(input).forEach(([key, value]) => {
|
||
|
if (!Array.isArray(value)) {
|
||
|
if (typeof value === "object" && value) {
|
||
|
if (ObjectArrayEntries.includes(fixKeyCase(key))) {
|
||
|
extras.push(...handleObjectEntry(key, value));
|
||
|
return;
|
||
|
}
|
||
|
primitives[key] = sanitize(value);
|
||
|
} else {
|
||
|
primitives[key] = value;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
value.forEach((v) => {
|
||
|
extras.push(...typeof v === "string" ? unpackMeta({ [key]: v }) : handleObjectEntry(key, v));
|
||
|
});
|
||
|
});
|
||
|
const meta = unpackToArray(primitives, {
|
||
|
key({ key }) {
|
||
|
return resolveMetaKeyType(key);
|
||
|
},
|
||
|
value({ key }) {
|
||
|
return key === "charset" ? "charset" : "content";
|
||
|
},
|
||
|
resolveKeyData({ key }) {
|
||
|
return resolveMetaKeyValue(key);
|
||
|
},
|
||
|
resolveValueData({ value, key }) {
|
||
|
if (value === null)
|
||
|
return "_null";
|
||
|
if (typeof value === "object")
|
||
|
return resolvePackedMetaObjectValue(value, key);
|
||
|
return typeof value === "number" ? value.toString() : value;
|
||
|
}
|
||
|
});
|
||
|
return [...extras, ...meta].map((m) => {
|
||
|
if (m.content === "_null")
|
||
|
m.content = null;
|
||
|
return m;
|
||
|
});
|
||
|
}
|
||
|
function packMeta(inputs) {
|
||
|
const mappedPackingSchema = Object.entries(MetaPackingSchema).map(([key, value]) => [key, value.keyValue]);
|
||
|
return packArray(inputs, {
|
||
|
key: ["name", "property", "httpEquiv", "http-equiv", "charset"],
|
||
|
value: ["content", "charset"],
|
||
|
resolveKey(k2) {
|
||
|
let key = mappedPackingSchema.filter((sk) => sk[1] === k2)?.[0]?.[0] || k2;
|
||
|
const replacer = (_, letter) => letter?.toUpperCase();
|
||
|
key = key.replace(/:([a-z])/g, replacer).replace(/-([a-z])/g, replacer);
|
||
|
return key;
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
const WhitelistAttributes = {
|
||
|
htmlAttrs: ["id", "class", "lang", "dir"],
|
||
|
bodyAttrs: ["id", "class"],
|
||
|
meta: ["id", "name", "property", "charset", "content"],
|
||
|
noscript: ["id", "textContent"],
|
||
|
script: ["id", "type", "textContent"],
|
||
|
link: ["id", "color", "crossorigin", "fetchpriority", "href", "hreflang", "imagesrcset", "imagesizes", "integrity", "media", "referrerpolicy", "rel", "sizes", "type"]
|
||
|
};
|
||
|
function acceptDataAttrs(value) {
|
||
|
const filtered = {};
|
||
|
Object.keys(value || {}).filter((a) => a.startsWith("data-")).forEach((a) => {
|
||
|
filtered[a] = value[a];
|
||
|
});
|
||
|
return filtered;
|
||
|
}
|
||
|
function whitelistSafeInput(input) {
|
||
|
const filtered = {};
|
||
|
Object.keys(input).forEach((key) => {
|
||
|
const tagValue = input[key];
|
||
|
if (!tagValue)
|
||
|
return;
|
||
|
switch (key) {
|
||
|
case "title":
|
||
|
case "titleTemplate":
|
||
|
case "templateParams":
|
||
|
filtered[key] = tagValue;
|
||
|
break;
|
||
|
case "htmlAttrs":
|
||
|
case "bodyAttrs":
|
||
|
filtered[key] = acceptDataAttrs(tagValue);
|
||
|
WhitelistAttributes[key].forEach((a) => {
|
||
|
if (tagValue[a])
|
||
|
filtered[key][a] = tagValue[a];
|
||
|
});
|
||
|
break;
|
||
|
case "meta":
|
||
|
if (Array.isArray(tagValue)) {
|
||
|
filtered[key] = tagValue.map((meta) => {
|
||
|
const safeMeta = acceptDataAttrs(meta);
|
||
|
WhitelistAttributes.meta.forEach((key2) => {
|
||
|
if (meta[key2])
|
||
|
safeMeta[key2] = meta[key2];
|
||
|
});
|
||
|
return safeMeta;
|
||
|
}).filter((meta) => Object.keys(meta).length > 0);
|
||
|
}
|
||
|
break;
|
||
|
case "link":
|
||
|
if (Array.isArray(tagValue)) {
|
||
|
filtered[key] = tagValue.map((meta) => {
|
||
|
const link = acceptDataAttrs(meta);
|
||
|
WhitelistAttributes.link.forEach((key2) => {
|
||
|
const val = meta[key2];
|
||
|
if (key2 === "rel" && ["stylesheet", "canonical", "modulepreload", "prerender", "preload", "prefetch"].includes(val))
|
||
|
return;
|
||
|
if (key2 === "href") {
|
||
|
if (val.includes("javascript:") || val.includes("data:"))
|
||
|
return;
|
||
|
link[key2] = val;
|
||
|
} else if (val) {
|
||
|
link[key2] = val;
|
||
|
}
|
||
|
});
|
||
|
return link;
|
||
|
}).filter((link) => Object.keys(link).length > 1 && !!link.rel);
|
||
|
}
|
||
|
break;
|
||
|
case "noscript":
|
||
|
if (Array.isArray(tagValue)) {
|
||
|
filtered[key] = tagValue.map((meta) => {
|
||
|
const noscript = acceptDataAttrs(meta);
|
||
|
WhitelistAttributes.noscript.forEach((key2) => {
|
||
|
if (meta[key2])
|
||
|
noscript[key2] = meta[key2];
|
||
|
});
|
||
|
return noscript;
|
||
|
}).filter((meta) => Object.keys(meta).length > 0);
|
||
|
}
|
||
|
break;
|
||
|
case "script":
|
||
|
if (Array.isArray(tagValue)) {
|
||
|
filtered[key] = tagValue.map((script) => {
|
||
|
const safeScript = acceptDataAttrs(script);
|
||
|
WhitelistAttributes.script.forEach((s) => {
|
||
|
if (script[s]) {
|
||
|
if (s === "textContent") {
|
||
|
try {
|
||
|
const jsonVal = typeof script[s] === "string" ? JSON.parse(script[s]) : script[s];
|
||
|
safeScript[s] = JSON.stringify(jsonVal, null, 0);
|
||
|
} catch (e) {
|
||
|
}
|
||
|
} else {
|
||
|
safeScript[s] = script[s];
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
return safeScript;
|
||
|
}).filter((meta) => Object.keys(meta).length > 0);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
});
|
||
|
return filtered;
|
||
|
}
|
||
|
|
||
|
async function normaliseTag(tagName, input, e) {
|
||
|
const tag = {
|
||
|
tag: tagName,
|
||
|
props: await normaliseProps(
|
||
|
// explicitly check for an object
|
||
|
// @ts-expect-error untyped
|
||
|
typeof input === "object" && typeof input !== "function" && !(input instanceof Promise) ? { ...input } : { [["script", "noscript", "style"].includes(tagName) ? "innerHTML" : "textContent"]: input },
|
||
|
["templateParams", "titleTemplate"].includes(tagName)
|
||
|
)
|
||
|
};
|
||
|
TagConfigKeys.forEach((k) => {
|
||
|
const val = typeof tag.props[k] !== "undefined" ? tag.props[k] : e[k];
|
||
|
if (typeof val !== "undefined") {
|
||
|
if (!["innerHTML", "textContent", "children"].includes(k) || TagsWithInnerContent.includes(tag.tag)) {
|
||
|
tag[k === "children" ? "innerHTML" : k] = val;
|
||
|
}
|
||
|
delete tag.props[k];
|
||
|
}
|
||
|
});
|
||
|
if (tag.props.body) {
|
||
|
tag.tagPosition = "bodyClose";
|
||
|
delete tag.props.body;
|
||
|
}
|
||
|
if (tag.tag === "script") {
|
||
|
if (typeof tag.innerHTML === "object") {
|
||
|
tag.innerHTML = JSON.stringify(tag.innerHTML);
|
||
|
tag.props.type = tag.props.type || "application/json";
|
||
|
}
|
||
|
}
|
||
|
return Array.isArray(tag.props.content) ? tag.props.content.map((v) => ({ ...tag, props: { ...tag.props, content: v } })) : tag;
|
||
|
}
|
||
|
function normaliseClassProp(v) {
|
||
|
if (typeof v === "object" && !Array.isArray(v)) {
|
||
|
v = Object.keys(v).filter((k) => v[k]);
|
||
|
}
|
||
|
return (Array.isArray(v) ? v.join(" ") : v).split(" ").filter((c) => c.trim()).filter(Boolean).join(" ");
|
||
|
}
|
||
|
async function normaliseProps(props, virtual) {
|
||
|
for (const k of Object.keys(props)) {
|
||
|
if (k === "class") {
|
||
|
props[k] = normaliseClassProp(props[k]);
|
||
|
continue;
|
||
|
}
|
||
|
if (props[k] instanceof Promise)
|
||
|
props[k] = await props[k];
|
||
|
if (!virtual && !TagConfigKeys.includes(k)) {
|
||
|
const v = String(props[k]);
|
||
|
const isDataKey = k.startsWith("data-");
|
||
|
if (v === "true" || v === "") {
|
||
|
props[k] = isDataKey ? "true" : true;
|
||
|
} else if (!props[k]) {
|
||
|
if (isDataKey && v === "false")
|
||
|
props[k] = "false";
|
||
|
else
|
||
|
delete props[k];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return props;
|
||
|
}
|
||
|
const TagEntityBits = 10;
|
||
|
async function normaliseEntryTags(e) {
|
||
|
const tagPromises = [];
|
||
|
Object.entries(e.resolvedInput).filter(([k, v]) => typeof v !== "undefined" && ValidHeadTags.includes(k)).forEach(([k, value]) => {
|
||
|
const v = asArray$1(value);
|
||
|
tagPromises.push(...v.map((props) => normaliseTag(k, props, e)).flat());
|
||
|
});
|
||
|
return (await Promise.all(tagPromises)).flat().filter(Boolean).map((t, i) => {
|
||
|
t._e = e._i;
|
||
|
e.mode && (t._m = e.mode);
|
||
|
t._p = (e._i << TagEntityBits) + i;
|
||
|
return t;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
const TAG_WEIGHTS = {
|
||
|
// tags
|
||
|
base: -10,
|
||
|
title: 10
|
||
|
};
|
||
|
const TAG_ALIASES = {
|
||
|
// relative scores to their default values
|
||
|
critical: -80,
|
||
|
high: -10,
|
||
|
low: 20
|
||
|
};
|
||
|
function tagWeight(tag) {
|
||
|
let weight = 100;
|
||
|
const priority = tag.tagPriority;
|
||
|
if (typeof priority === "number")
|
||
|
return priority;
|
||
|
if (tag.tag === "meta") {
|
||
|
if (tag.props["http-equiv"] === "content-security-policy")
|
||
|
weight = -30;
|
||
|
if (tag.props.charset)
|
||
|
weight = -20;
|
||
|
if (tag.props.name === "viewport")
|
||
|
weight = -15;
|
||
|
} else if (tag.tag === "link" && tag.props.rel === "preconnect") {
|
||
|
weight = 20;
|
||
|
} else if (tag.tag in TAG_WEIGHTS) {
|
||
|
weight = TAG_WEIGHTS[tag.tag];
|
||
|
}
|
||
|
if (typeof priority === "string" && priority in TAG_ALIASES) {
|
||
|
return weight + TAG_ALIASES[priority];
|
||
|
}
|
||
|
return weight;
|
||
|
}
|
||
|
const SortModifiers = [{ prefix: "before:", offset: -1 }, { prefix: "after:", offset: 1 }];
|
||
|
|
||
|
const NetworkEvents = ["onload", "onerror", "onabort", "onprogress", "onloadstart"];
|
||
|
|
||
|
const sepSub = "%separator";
|
||
|
function processTemplateParams(s, p, sep) {
|
||
|
if (typeof s !== "string" || !s.includes("%"))
|
||
|
return s;
|
||
|
function sub(token) {
|
||
|
let val;
|
||
|
if (["s", "pageTitle"].includes(token)) {
|
||
|
val = p.pageTitle;
|
||
|
} else if (token.includes(".")) {
|
||
|
val = token.split(".").reduce((acc, key) => acc ? acc[key] || void 0 : void 0, p);
|
||
|
} else {
|
||
|
val = p[token];
|
||
|
}
|
||
|
return typeof val !== "undefined" ? (val || "").replace(/"/g, '\\"') : false;
|
||
|
}
|
||
|
let decoded = s;
|
||
|
try {
|
||
|
decoded = decodeURI(s);
|
||
|
} catch {
|
||
|
}
|
||
|
const tokens = (decoded.match(/%(\w+\.+\w+)|%(\w+)/g) || []).sort().reverse();
|
||
|
tokens.forEach((token) => {
|
||
|
const re = sub(token.slice(1));
|
||
|
if (typeof re === "string") {
|
||
|
s = s.replace(new RegExp(`\\${token}(\\W|$)`, "g"), (_, args) => `${re}${args}`).trim();
|
||
|
}
|
||
|
});
|
||
|
if (s.includes(sepSub)) {
|
||
|
if (s.endsWith(sepSub))
|
||
|
s = s.slice(0, -sepSub.length).trim();
|
||
|
if (s.startsWith(sepSub))
|
||
|
s = s.slice(sepSub.length).trim();
|
||
|
s = s.replace(new RegExp(`\\${sepSub}\\s*\\${sepSub}`, "g"), sepSub);
|
||
|
s = processTemplateParams(s, { separator: sep }, sep);
|
||
|
}
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
export { HasElementTags, IsBrowser, NetworkEvents, SelfClosingTags, SortModifiers, TAG_ALIASES, TAG_WEIGHTS, TagConfigKeys, TagEntityBits, TagsWithInnerContent, UniqueTags, ValidHeadTags, asArray$1 as asArray, composableNames, defineHeadPlugin, hashCode, hashTag, normaliseClassProp, normaliseEntryTags, normaliseProps, normaliseTag, packMeta, processTemplateParams, resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue, resolveTitleTemplate, tagDedupeKey, tagWeight, unpackMeta, whitelistSafeInput };
|