import { TagsWithInnerContent, SelfClosingTags } from '@unhead/shared'; function encodeAttribute(value) { return String(value).replace(/"/g, """); } function propsToString(props) { const attrs = []; for (const [key, value] of Object.entries(props)) { if (value !== false && value !== null) attrs.push(value === true ? key : `${key}="${encodeAttribute(value)}"`); } return `${attrs.length > 0 ? " " : ""}${attrs.join(" ")}`; } function escapeHtml(str) { return str.replace(/[&<>"'/]/g, (char) => { switch (char) { case "&": return "&"; case "<": return "<"; case ">": return ">"; case '"': return """; case "'": return "'"; case "/": return "/"; default: return char; } }); } function tagToString(tag) { const attrs = propsToString(tag.props); const openTag = `<${tag.tag}${attrs}>`; if (!TagsWithInnerContent.includes(tag.tag)) return SelfClosingTags.includes(tag.tag) ? openTag : `${openTag}`; let content = String(tag.innerHTML || ""); if (tag.textContent) content = escapeHtml(String(tag.textContent)); return SelfClosingTags.includes(tag.tag) ? openTag : `${openTag}${content}`; } function ssrRenderTags(tags) { const schema = { htmlAttrs: {}, bodyAttrs: {}, tags: { head: [], bodyClose: [], bodyOpen: [] } }; for (const tag of tags) { if (tag.tag === "htmlAttrs" || tag.tag === "bodyAttrs") { schema[tag.tag] = { ...schema[tag.tag], ...tag.props }; continue; } schema.tags[tag.tagPosition || "head"].push(tagToString(tag)); } return { headTags: schema.tags.head.join("\n"), bodyTags: schema.tags.bodyClose.join("\n"), bodyTagsOpen: schema.tags.bodyOpen.join("\n"), htmlAttrs: propsToString(schema.htmlAttrs), bodyAttrs: propsToString(schema.bodyAttrs) }; } async function renderSSRHead(head) { const beforeRenderCtx = { shouldRender: true }; await head.hooks.callHook("ssr:beforeRender", beforeRenderCtx); if (!beforeRenderCtx.shouldRender) { return { headTags: "", bodyTags: "", bodyTagsOpen: "", htmlAttrs: "", bodyAttrs: "" }; } const ctx = { tags: await head.resolveTags() }; await head.hooks.callHook("ssr:render", ctx); const html = ssrRenderTags(ctx.tags); const renderCtx = { tags: ctx.tags, html }; await head.hooks.callHook("ssr:rendered", renderCtx); return renderCtx.html; } export { escapeHtml, propsToString, renderSSRHead, ssrRenderTags, tagToString };