typedoc
Version:
Create api documentation for TypeScript projects.
187 lines (186 loc) • 4.9 kB
JavaScript
/**
* Custom JSX module designed specifically for TypeDoc's needs.
* When overriding a default TypeDoc theme output, your implementation must create valid {@link Element}
* instances, which can be most easily done by using TypeDoc's JSX implementation. To use it, set up
* your tsconfig with the following compiler options:
* ```json
* {
* "jsx": "react",
* "jsxFactory": "JSX.createElement",
* "jsxFragmentFactory": "JSX.Fragment"
* }
* ```
* @summary Custom JSX module designed specifically for TypeDoc's needs.
* @module
*/
import { escapeHtml } from "./html.js";
import { JsxFragment } from "./jsx.elements.js";
export { JsxFragment as Fragment } from "./jsx.elements.js";
/**
* Used to inject HTML directly into the document.
*/
export function Raw(_props) {
// This is handled specially by the renderElement function. Instead of being
// called, the tag is compared to this function and the `html` prop will be
// returned directly.
return null;
}
const voidElements = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr",
]);
const blockElements = new Set([
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"div",
"section",
"nav",
"details",
"p",
"ul",
"ol",
"li",
]);
/**
* JSX factory function to create an "element" that can later be rendered with {@link renderElement}
* @param tag
* @param props
* @param children
*/
export function createElement(tag, props, ...children) {
return { tag, props, children };
}
let renderPretty = true;
export function setRenderSettings(options) {
renderPretty = options.pretty;
}
export function renderElement(element) {
if (!element || typeof element === "boolean") {
return "";
}
const { tag, props, children } = element;
let html = "";
if (typeof tag === "function") {
if (tag === Raw) {
return String(props.html);
}
if (tag === JsxFragment) {
renderChildren(children);
return html;
}
return renderElement(tag(Object.assign({ children }, props)));
}
if (blockElements.has(tag) && renderPretty && html) {
html += "\n";
}
html += "<";
html += tag;
for (const [key, val] of Object.entries(props ?? {})) {
if (val == null)
continue;
if (typeof val == "boolean") {
if (val) {
html += " ";
html += key;
}
}
else {
html += " ";
html += key;
html += '="';
html += (typeof val === "string" ? val : JSON.stringify(val)).replaceAll('"', """);
html += '"';
}
}
if (children.length) {
html += ">";
renderChildren(children);
html += "</";
html += tag;
html += ">";
}
else {
if (voidElements.has(tag)) {
html += "/>";
}
else {
html += "></";
html += tag;
html += ">";
}
}
return html;
function renderChildren(children) {
for (const child of children) {
if (!child)
continue;
if (Array.isArray(child)) {
renderChildren(child);
}
else if (typeof child === "string" || typeof child === "number") {
html += escapeHtml(child.toString());
}
else {
html += renderElement(child);
}
}
}
}
/**
* Render an element to text, stripping out any HTML tags.
* This is roughly equivalent to getting `innerText` on a rendered element.
* @internal
*/
export function renderElementToText(element) {
if (!element || typeof element === "boolean") {
return "";
}
const { tag, props, children } = element;
let html = "";
if (typeof tag === "function") {
if (tag === Raw) {
return String(props.html);
}
if (tag === JsxFragment) {
renderChildren(children);
return html;
}
return renderElementToText(tag(Object.assign({ children }, props)));
}
else if (tag === "br") {
return "\n";
}
renderChildren(children);
return html;
function renderChildren(children) {
for (const child of children) {
if (!child)
continue;
if (Array.isArray(child)) {
renderChildren(child);
}
else if (typeof child === "string" || typeof child === "number") {
html += child.toString().replaceAll("\u00A0", " ");
}
else {
html += renderElementToText(child);
}
}
}
}