128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
const CHART_HEADING_RE = /^(?:D\d{1,3}\b|Moon Chart)/;
|
|
|
|
type HastProperties = {
|
|
className?: string | string[] | null;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
type HastNode = {
|
|
type: string;
|
|
tagName?: string;
|
|
value?: string;
|
|
properties?: HastProperties;
|
|
children?: HastNode[];
|
|
};
|
|
|
|
type HastRoot = {
|
|
type: "root";
|
|
children: HastNode[];
|
|
};
|
|
|
|
function classNames(node: HastNode | undefined): string[] {
|
|
const value = node?.properties?.className;
|
|
if (Array.isArray(value)) return value.map(String);
|
|
if (typeof value === "string") return value.split(/\s+/).filter(Boolean);
|
|
return [];
|
|
}
|
|
|
|
function hasClass(node: HastNode | undefined, name: string): boolean {
|
|
return classNames(node).includes(name);
|
|
}
|
|
|
|
function isWhitespace(node: HastNode | undefined): boolean {
|
|
return Boolean(node && node.type === "text" && !String(node.value ?? "").trim());
|
|
}
|
|
|
|
function flattenText(node: HastNode | undefined): string {
|
|
if (!node) return "";
|
|
if (node.type === "text") return String(node.value ?? "");
|
|
if (!Array.isArray(node.children)) return "";
|
|
return node.children.map(flattenText).join("");
|
|
}
|
|
|
|
function isElement(node: HastNode | undefined, tagName: string): node is HastNode {
|
|
return Boolean(node && node.type === "element" && node.tagName === tagName);
|
|
}
|
|
|
|
function isChartHeading(node: HastNode | undefined): boolean {
|
|
return isElement(node, "h4") && CHART_HEADING_RE.test(flattenText(node).trim());
|
|
}
|
|
|
|
function isChartPre(node: HastNode | undefined): boolean {
|
|
if (!isElement(node, "pre") || !Array.isArray(node.children)) return false;
|
|
const code = node.children.find((child) => isElement(child, "code"));
|
|
return hasClass(code, "language-jyotish-chart") || hasClass(code, "jyotish-chart");
|
|
}
|
|
|
|
function skipWhitespace(nodes: readonly HastNode[], index: number): number {
|
|
let cursor = index;
|
|
while (cursor < nodes.length && isWhitespace(nodes[cursor])) cursor += 1;
|
|
return cursor;
|
|
}
|
|
|
|
function element(tagName: string, className: string[], children: HastNode[]): HastNode {
|
|
return {
|
|
type: "element",
|
|
tagName,
|
|
properties: { className },
|
|
children,
|
|
};
|
|
}
|
|
|
|
function parseCard(nodes: readonly HastNode[], index: number): { card: HastNode; next: number } | null {
|
|
const heading = nodes[index];
|
|
if (!isChartHeading(heading)) return null;
|
|
const children: HastNode[] = [heading];
|
|
let cursor = skipWhitespace(nodes, index + 1);
|
|
if (isElement(nodes[cursor], "p")) {
|
|
const afterParagraph = skipWhitespace(nodes, cursor + 1);
|
|
if (isChartPre(nodes[afterParagraph])) {
|
|
children.push(nodes[cursor] as HastNode);
|
|
cursor = afterParagraph;
|
|
}
|
|
}
|
|
if (!isChartPre(nodes[cursor])) return null;
|
|
children.push(nodes[cursor] as HastNode);
|
|
return {
|
|
card: element("div", ["personal-report-chart-card"], children),
|
|
next: cursor + 1,
|
|
};
|
|
}
|
|
|
|
export function groupReportChartNodes(nodes: readonly HastNode[]): HastNode[] {
|
|
const grouped: HastNode[] = [];
|
|
let index = 0;
|
|
while (index < nodes.length) {
|
|
const first = parseCard(nodes, index);
|
|
if (!first) {
|
|
grouped.push(nodes[index] as HastNode);
|
|
index += 1;
|
|
continue;
|
|
}
|
|
const cards = [first.card];
|
|
index = first.next;
|
|
while (index < nodes.length) {
|
|
const peeked = skipWhitespace(nodes, index);
|
|
const next = parseCard(nodes, peeked);
|
|
if (!next) break;
|
|
cards.push(next.card);
|
|
index = next.next;
|
|
}
|
|
grouped.push(element(
|
|
"div",
|
|
cards.length === 1
|
|
? ["personal-report-chart-grid", "is-single"]
|
|
: ["personal-report-chart-grid"],
|
|
cards,
|
|
));
|
|
}
|
|
return grouped;
|
|
}
|
|
|
|
export function reportChartGrid() {
|
|
return function groupReportCharts(tree: HastRoot) {
|
|
if (tree?.type !== "root" || !Array.isArray(tree.children)) return;
|
|
tree.children = groupReportChartNodes(tree.children);
|
|
};
|
|
}
|