@jsenv/terminal-recorder
Version:
Record terminal output as .svg, .gif, .webm, .mp4
168 lines (152 loc) • 5.34 kB
JavaScript
import he from "he";
export const encodeTextContent = (content) => {
return he.encode(content, {
decimal: false,
});
};
export const createXmlGenerator = ({
rootNodeName,
canSelfCloseNames = [],
canReceiveChildNames = [],
canReceiveContentNames = [],
canInjectWhitespacesAroundContentNames = [],
}) => {
const createNode = (name, attributes = {}) => {
const canSelfClose = canSelfCloseNames.includes(name);
const canReceiveChild = canReceiveChildNames.includes(name);
const canReceiveContent = canReceiveContentNames.includes(name);
const canInjectWhitespacesAroundContent =
canInjectWhitespacesAroundContentNames.includes(name);
const children = [];
const node = {
name,
content: "",
contentIsSafe: false,
children,
attributes,
canSelfClose,
createNode,
appendChild: (childNode) => {
if (!canReceiveChild) {
throw new Error(`cannot appendChild into ${name}`);
}
children.push(childNode);
return childNode;
},
setContent: (value, isSafe = false) => {
if (!canReceiveContent) {
throw new Error(`cannot setContent on ${name}`);
}
node.content = value;
node.contentIsSafe = isSafe;
},
renderAsString: () => {
const renderNode = (node, { depth }) => {
let nodeString = "";
nodeString += `<${node.name}`;
write_attributes: {
const attributeNames = Object.keys(node.attributes);
if (attributeNames.length) {
let attributesSingleLine = "";
let attributesMultiLine = "";
for (const attributeName of attributeNames) {
let attributeValue = node.attributes[attributeName];
if (attributeValue === undefined) {
continue;
}
if (typeof attributeValue === "number") {
attributeValue = round(attributeValue);
}
if (attributeName === "viewBox") {
attributeValue = attributeValue
.split(" ")
.map((v) => round(parseFloat(v.trim())))
.join(" ");
}
attributesSingleLine += ` ${attributeName}="${attributeValue}"`;
attributesMultiLine += `\n `;
attributesMultiLine += " ".repeat(depth);
attributesMultiLine += `${attributeName}="${attributeValue}"`;
}
attributesMultiLine += "\n";
attributesMultiLine += " ".repeat(depth);
if (attributesSingleLine.length < 100) {
nodeString += attributesSingleLine;
} else {
nodeString += attributesMultiLine;
}
}
}
let innerHTML = "";
if (node.content) {
if (canInjectWhitespacesAroundContent) {
innerHTML += "\n ";
innerHTML += " ".repeat(depth);
}
if (node.contentIsSafe) {
innerHTML += node.content;
} else {
const contentEncoded = encodeTextContent(node.content);
innerHTML += contentEncoded;
}
if (canInjectWhitespacesAroundContent) {
innerHTML += "\n";
innerHTML += " ".repeat(depth);
}
}
write_children: {
if (node.children.length > 0) {
for (const child of node.children) {
innerHTML += "\n ";
innerHTML += " ".repeat(depth);
const childHtml = renderNode(child, { depth: depth + 1 });
innerHTML += childHtml;
}
innerHTML += "\n";
innerHTML += " ".repeat(depth);
}
}
if (innerHTML === "") {
if (node.canSelfClose) {
nodeString += `/>`;
} else {
nodeString += `></${node.name}>`;
}
} else {
nodeString += `>`;
nodeString += innerHTML;
nodeString += `</${node.name}>`;
}
return nodeString;
};
return renderNode(node, {
depth: 0,
});
},
};
return node;
};
return (rootNodeAttributes) => createNode(rootNodeName, rootNodeAttributes);
};
export const createSvgRootNode = createXmlGenerator({
rootNodeName: "svg",
canSelfCloseNames: ["path", "rect", "circle"],
canReceiveChildNames: ["svg", "foreignObject", "g"],
canReceiveContentNames: ["text", "tspan", "style"],
canInjectWhitespacesAroundContentNames: ["style"],
});
// not used for now and needs to configure canReceiveChildNames and so on...
export const createXmlRootNode = createXmlGenerator({
rootNodeName: "xml",
});
// not used for now and needs to configure canReceiveChildNames and so on...
export const createHtmlRootNode = createXmlGenerator({
rootNodeName: "html",
});
// Round: Make number values smaller in output
// Eg: 14.23734 becomes 14.24
// Credit @Chris Martin: https://stackoverflow.com/a/43012696/2816869
const round = (x) => {
const rounded = Number(`${Math.round(`${x}e2`)}e-2`);
return rounded;
};