mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-08-24 23:32:19 +10:00
feat: add semantic CSS stylesheets (#3274)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor Agent
parent
4ac19f81b3
commit
d2ffbf9618
@@ -0,0 +1,145 @@
|
||||
import type { ResolveStylesheetResult, SemanticCssDiagnostic, SemanticNode } from "@reactive-resume/resume/stylesheet";
|
||||
import type { ResumeData } from "@reactive-resume/schema/resume/data";
|
||||
import type { StylesheetMode, StylesheetSource } from "@reactive-resume/schema/resume/stylesheet";
|
||||
import type { Template } from "@reactive-resume/schema/templates";
|
||||
import type { ResolvedResumePresentation } from "./context";
|
||||
import { compileStylesheet, resolveStylesheet } from "@reactive-resume/resume/stylesheet";
|
||||
import { EMPTY_SEMANTIC_CSS_SOURCE } from "@reactive-resume/schema/resume/stylesheet";
|
||||
import { shouldShowResumeHeader } from "../templates/shared/cover-letter";
|
||||
import { getTemplatePageSize } from "../templates/shared/page-size";
|
||||
import { adaptResolvedPdfNode } from "./adapter";
|
||||
import { buildPdfBaseStyles } from "./base-styles";
|
||||
import { createBindingInventory } from "./binding-inventory";
|
||||
import { semanticNodeKeys } from "./node-keys";
|
||||
import { getTemplateSemanticBindingRegistry } from "./template-manifest";
|
||||
import { buildSemanticTree } from "./tree";
|
||||
|
||||
export type ResolveResumePresentationInput = {
|
||||
data: ResumeData;
|
||||
template: Template;
|
||||
applied?: StylesheetSource;
|
||||
mode: StylesheetMode;
|
||||
};
|
||||
|
||||
export type ResolvedResumeRuntime = {
|
||||
presentation: ResolvedResumePresentation;
|
||||
sourceTree: SemanticNode;
|
||||
renderTree: SemanticNode;
|
||||
diagnostics: readonly SemanticCssDiagnostic[];
|
||||
};
|
||||
|
||||
const EMPTY_PRESENTATION = Object.freeze({}) satisfies ResolvedResumePresentation;
|
||||
|
||||
const mergeAuthoredPageTrees = (data: ResumeData, template: Template): SemanticNode => {
|
||||
const pageTrees = data.metadata.layout.pages.map((page, index) =>
|
||||
buildSemanticTree({
|
||||
data,
|
||||
template,
|
||||
page,
|
||||
pageNumber: index + 1,
|
||||
showHeader: shouldShowResumeHeader(data, index),
|
||||
}),
|
||||
);
|
||||
const root = pageTrees[0];
|
||||
|
||||
return {
|
||||
key: semanticNodeKeys.resume(),
|
||||
kind: "resume",
|
||||
attributes: { template },
|
||||
roles: [],
|
||||
children: pageTrees.flatMap((tree) => tree.children),
|
||||
...(root?.id === undefined ? {} : { id: root.id }),
|
||||
};
|
||||
};
|
||||
|
||||
const authoredPageDimensions = (data: ResumeData) => {
|
||||
const size = getTemplatePageSize(data.metadata.page.format);
|
||||
const dimensions =
|
||||
size === "LETTER"
|
||||
? { width: 612, height: 792 }
|
||||
: typeof size === "object"
|
||||
? { width: size.width, height: 841.89 }
|
||||
: { width: 595.28, height: 841.89 };
|
||||
|
||||
return data.metadata.layout.pages.map((_page, index) => ({
|
||||
pageKey: semanticNodeKeys.page(index + 1),
|
||||
...dimensions,
|
||||
}));
|
||||
};
|
||||
|
||||
const toPresentation = (
|
||||
resolved: ResolveStylesheetResult["nodes"],
|
||||
base: ReturnType<typeof buildPdfBaseStyles>,
|
||||
): ResolvedResumePresentation => {
|
||||
return Object.freeze(
|
||||
Object.fromEntries(
|
||||
Object.entries(resolved).map(([nodeKey, node]) => [nodeKey, adaptResolvedPdfNode(node, base[nodeKey])]),
|
||||
),
|
||||
) as ResolvedResumePresentation;
|
||||
};
|
||||
|
||||
export function resolveStylesheetMode(data: ResumeData): StylesheetMode {
|
||||
return data.metadata.stylesheet?.mode ?? "legacy";
|
||||
}
|
||||
|
||||
export function resolveResumeRuntime({
|
||||
data,
|
||||
template,
|
||||
applied,
|
||||
mode,
|
||||
}: ResolveResumePresentationInput): ResolvedResumeRuntime {
|
||||
const sourceTree = mergeAuthoredPageTrees(data, template);
|
||||
if (mode !== "semantic") {
|
||||
return { presentation: EMPTY_PRESENTATION, sourceTree, renderTree: sourceTree, diagnostics: [] };
|
||||
}
|
||||
|
||||
const source = applied ??
|
||||
data.metadata.stylesheet?.applied ?? {
|
||||
languageVersion: 1,
|
||||
text: EMPTY_SEMANTIC_CSS_SOURCE,
|
||||
};
|
||||
const compiled = compileStylesheet(source);
|
||||
if (!compiled.program) {
|
||||
return { presentation: EMPTY_PRESENTATION, sourceTree, renderTree: sourceTree, diagnostics: compiled.diagnostics };
|
||||
}
|
||||
|
||||
const baseStyles = buildPdfBaseStyles({ data, template, tree: sourceTree });
|
||||
const inventory = createBindingInventory(sourceTree, getTemplateSemanticBindingRegistry(template));
|
||||
const aliases: Record<string, string[]> = {};
|
||||
for (const [aliasKey, binding] of Object.entries(inventory.bindings)) {
|
||||
if (binding.type !== "alias") continue;
|
||||
aliases[binding.canonicalNodeKey] = [...(aliases[binding.canonicalNodeKey] ?? []), aliasKey];
|
||||
}
|
||||
const resolved = resolveStylesheet(compiled.program, sourceTree, {
|
||||
baseStyles,
|
||||
baseSettings: {
|
||||
picture: data.picture,
|
||||
template,
|
||||
design: data.metadata.design,
|
||||
typography: data.metadata.typography,
|
||||
page: data.metadata.page,
|
||||
layout: { sidebarWidth: data.metadata.layout.sidebarWidth },
|
||||
},
|
||||
pages: authoredPageDimensions(data),
|
||||
aliases,
|
||||
});
|
||||
if (resolved.diagnostics.some(({ severity }) => severity === "error")) {
|
||||
return {
|
||||
presentation: EMPTY_PRESENTATION,
|
||||
sourceTree,
|
||||
renderTree: sourceTree,
|
||||
diagnostics: [...compiled.diagnostics, ...resolved.diagnostics],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
presentation: toPresentation(resolved.nodes, baseStyles),
|
||||
sourceTree,
|
||||
renderTree: resolved.renderTree,
|
||||
diagnostics: [...compiled.diagnostics, ...resolved.diagnostics],
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveResumePresentation(input: ResolveResumePresentationInput): ResolvedResumePresentation {
|
||||
return resolveResumeRuntime(input).presentation;
|
||||
}
|
||||
Reference in New Issue
Block a user