// Copyright Epic Games, Inc. All Rights Reserved. "use strict"; import { ZenPage } from "./page.js" import { marked, Renderer } from "../thirdparty/marked.esm.js" import { Fetcher } from "../util/fetcher.js" function slugify(text) { return text.toLowerCase().replace(/[^\w]+/g, "-").replace(/^-|-$/g, ""); } const renderer = new Renderer(); renderer.heading = function({ text, depth }) { const id = slugify(text); if (depth === 1) { return `
Loading\u2026
"; try { const md = await new Fetcher().resource("/dashboard/data/" + entry.path).text(); this._content.innerHTML = marked.parse(md, { renderer }); const source_link = this._content.querySelector(".docs-source-link"); if (source_link) { source_link.href = "/dashboard/data/" + entry.path; } const github_link = this._content.querySelector(".docs-github-link"); if (github_link) { github_link.href = "https://github.com/EpicGames/zen/blob/main/docs/" + entry.path; } // Render mermaid diagrams await this._render_mermaid(); // Re-apply filter to the newly rendered content if (this._filter) { this._apply_filter_to_content(); } // Scroll to fragment if specified const target_fragment = fragment || window.location.hash.slice(1); if (target_fragment) { this._scroll_to_fragment(target_fragment); } else { window.scrollTo(0, 0); } } catch (e) { this._content.innerHTML = "Failed to load document.
"; } } _scroll_to_fragment(id) { if (!id) { return; } const target = this._content.querySelector("#" + CSS.escape(id)); if (target) { target.scrollIntoView({ behavior: "smooth" }); } } _apply_filter(query) { this._filter = query.toLowerCase().trim(); // Filter sidebar entries based on cached doc content const sidebar_links = this._sidebar.children; for (let i = 0; i < this._docs_index.length; i++) { const entry = this._docs_index[i]; const link = sidebar_links[i]; if (!this._filter) { link.style.display = ""; continue; } const cached = this._docs_cache[entry.path]; const matches = !cached || cached.includes(this._filter) || entry.title.toLowerCase().includes(this._filter); link.style.display = matches ? "" : "none"; } this._apply_filter_to_content(); } _apply_filter_to_content() { // Remove existing highlights for (const mark of this._content.querySelectorAll("mark.docs-highlight")) { const parent = mark.parentNode; parent.replaceChild(document.createTextNode(mark.textContent), mark); parent.normalize(); } const sections = this._content.querySelectorAll("details.docs-section"); for (const section of sections) { if (!this._filter) { section.style.display = ""; section.open = true; continue; } const matches = section.textContent.toLowerCase().includes(this._filter); section.style.display = matches ? "" : "none"; if (matches) { section.open = true; this._highlight_element(section); } } } _get_mermaid_theme() { const attr = document.documentElement.getAttribute("data-theme"); const is_dark = attr ? attr === "dark" : window.matchMedia("(prefers-color-scheme: dark)").matches; return is_dark ? "dark" : "default"; } async _load_mermaid() { if (window.mermaid) { return window.mermaid; } return new Promise((resolve, reject) => { const script = document.createElement("script"); script.src = "/dashboard/thirdparty/mermaid.min.js"; script.onload = () => { window.mermaid.initialize({ startOnLoad: false, theme: this._get_mermaid_theme(), themeVariables: { background: "transparent", }, }); resolve(window.mermaid); }; script.onerror = reject; document.head.appendChild(script); }); } async _render_mermaid() { // marked renders ```mermaid blocks as inside
const code_blocks = this._content.querySelectorAll("pre > code.language-mermaid");
if (code_blocks.length === 0)
{
return;
}
try
{
const mermaid = await this._load_mermaid();
for (let i = 0; i < code_blocks.length; i++)
{
const code = code_blocks[i];
const pre = code.parentElement;
const definition = code.textContent;
const { svg } = await mermaid.render(`mermaid-${Date.now()}-${i}`, definition);
const container = document.createElement("div");
container.className = "docs-mermaid";
container.dataset.definition = definition;
container.innerHTML = svg;
pre.replaceWith(container);
}
this._watch_theme();
}
catch (e)
{
// Mermaid failed to load or render — leave code blocks as-is
}
}
_watch_theme()
{
if (this._theme_observer)
{
return;
}
this._theme_observer = new MutationObserver(() => this._rerender_mermaid());
this._theme_observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
}
async _rerender_mermaid()
{
const containers = this._content.querySelectorAll(".docs-mermaid[data-definition]");
if (containers.length === 0)
{
return;
}
try
{
const mermaid = await this._load_mermaid();
mermaid.initialize({
startOnLoad: false,
theme: this._get_mermaid_theme(),
themeVariables: {
background: "transparent",
},
});
for (let i = 0; i < containers.length; i++)
{
const container = containers[i];
const definition = container.dataset.definition;
const { svg } = await mermaid.render(`mermaid-${Date.now()}-${i}`, definition);
container.innerHTML = svg;
}
}
catch (e)
{
// Mermaid failed to re-render — leave existing SVGs as-is
}
}
_highlight_element(root)
{
const filter = this._filter;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
const matches = [];
let node;
while ((node = walker.nextNode()))
{
if (node.parentElement && node.parentElement.closest("pre"))
{
continue;
}
const text = node.textContent.toLowerCase();
let pos = 0;
while ((pos = text.indexOf(filter, pos)) !== -1)
{
matches.push({ node, pos, len: filter.length });
pos += filter.length;
}
}
for (let i = matches.length - 1; i >= 0; i--)
{
const { node: text_node, pos, len } = matches[i];
const after = text_node.splitText(pos);
after.splitText(len);
const mark = document.createElement("mark");
mark.className = "docs-highlight";
mark.textContent = after.textContent;
after.parentNode.replaceChild(mark, after);
}
}
}