mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-03 10:13:47 +10:00
feat(resume): add per-section heading visibility (#3477)
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
# Plan 21 implementation evidence
|
||||
|
||||
## Revision and scope
|
||||
|
||||
- Worktree: `issue-3060-section-heading-visibility`
|
||||
- Base: current `origin/main` at dispatch, `2a4a1583b` (`fix(pdf): restore Gengar skill rating order (#3473)`)
|
||||
- Product decisions applied: Q1 explicit Show heading toggle; Q2 Move-to continuations default visible; Q3 visual omission in preview/PDF/DOCX with accessible outline labels retained.
|
||||
- No `.codegraph/` directory exists in this worktree, so CodeGraph was skipped after the required presence check.
|
||||
- Intent discovery ran before edits; no matching local skill was available for this schema/PDF/DOCX/web change.
|
||||
|
||||
## Implementation
|
||||
|
||||
- Added backward-compatible `showHeading` section data for summary, built-ins, and custom sections. `parseResumeData` normalizes absent legacy values to `true`; explicit `false` survives round trips.
|
||||
- Added heading toggles to built-in/summary and custom section menus. Toggle mutations use `useUpdateResumeData`, preserving undo/autosave/save/reload behavior; legacy absent values are treated as visible. Existing lock fieldset remains authoritative.
|
||||
- Move-to-created custom sections explicitly set `showHeading: true`, independent of source heading state or copied title.
|
||||
- `SectionShell` omits complete heading/icon/decoration output when disabled in both icon and no-icon branches. Empty titles still resolve localized defaults.
|
||||
- DOCX section renderers omit visible heading paragraphs for summary, built-in, and custom sections while retaining content. Screen-reader mirror continues to expose section labels regardless of visual setting.
|
||||
- Added characterization for Semantic CSS `section[id="..."] section-heading { display: none; }`; body remains while heading is omitted.
|
||||
- Updated default/sample fixtures, generated schema references, recovery hashes, and compatibility tests; existing Gengar renderer/order changes remain untouched.
|
||||
|
||||
## Verification
|
||||
|
||||
- `pnpm --filter @reactive-resume/schema test`: 9 files, 132 tests passed.
|
||||
- `pnpm --filter @reactive-resume/pdf test`: 81 files, 1059 tests passed.
|
||||
- `pnpm --filter @reactive-resume/docx test`: 9 files, 76 tests passed.
|
||||
- `pnpm --filter web test`: 135 files, 942 tests passed.
|
||||
- `pnpm test`: full Turborepo suite passed (19 successful tasks; 10 cache hits).
|
||||
- Affected typechecks passed: schema, PDF, DOCX, web.
|
||||
- Focused menu, Move-to, accessible-outline, schema, PDF semantic, and DOCX renderer tests passed.
|
||||
- `pnpm exec turbo boundaries`: passed (1108 files, 20 packages).
|
||||
- Read-only `pnpm exec biome check` on 22 changed source/test files: passed; no write-capable `pnpm check` run.
|
||||
- `git diff --check`: passed.
|
||||
- Final diff review completed; local commit follows.
|
||||
@@ -39,6 +39,7 @@ const defaultValues: FormValues = {
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
|
||||
@@ -38,3 +38,28 @@ it.each([false, true])("exposes keyword list semantics for custom=%s", (custom)
|
||||
if (!list) throw new Error("Missing keyword list");
|
||||
expect(within(list).getAllByRole("listitem")).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("retains section labels when visual heading is disabled", () => {
|
||||
const data = structuredClone(defaultResumeData);
|
||||
data.sections.skills.showHeading = false;
|
||||
data.sections.skills.items = [
|
||||
{
|
||||
id: "skill",
|
||||
name: "TypeScript",
|
||||
hidden: false,
|
||||
icon: "",
|
||||
iconColor: "",
|
||||
proficiency: "Expert",
|
||||
level: 3,
|
||||
keywords: [],
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<I18nProvider i18n={i18n}>
|
||||
<ResumeAccessibleText data={data} />
|
||||
</I18nProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 2, name: "Skills" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -213,6 +213,7 @@ describe("createCustomSectionWithItem", () => {
|
||||
|
||||
expect(result.customSections).toHaveLength(1);
|
||||
expect(result.customSections[0]?.title).toBe("My Section");
|
||||
expect(result.customSections[0]?.showHeading).toBe(true);
|
||||
expect(result.customSections[0]?.items).toHaveLength(1);
|
||||
expect(result.metadata.layout.pages[0]?.main).toContain(newSectionId);
|
||||
});
|
||||
|
||||
@@ -204,6 +204,7 @@ function makeCustomSection(id: string, type: CustomSectionType, title: string, i
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [item as never],
|
||||
|
||||
@@ -225,6 +225,7 @@ function CustomSectionDropdownMenu({ section }: CustomSectionDropdownMenuProps)
|
||||
const confirm = useConfirm();
|
||||
const { openDialog } = useDialogStore();
|
||||
const updateResumeData = useUpdateResumeData();
|
||||
const showHeading = section.showHeading !== false;
|
||||
|
||||
const onToggleSectionVisibility = () => {
|
||||
updateResumeData((draft) => {
|
||||
@@ -234,6 +235,14 @@ function CustomSectionDropdownMenu({ section }: CustomSectionDropdownMenuProps)
|
||||
});
|
||||
};
|
||||
|
||||
const onToggleHeading = () => {
|
||||
updateResumeData((draft) => {
|
||||
const sectionIndex = draft.customSections.findIndex((_section) => _section.id === section.id);
|
||||
if (sectionIndex === -1) return;
|
||||
draft.customSections[sectionIndex].showHeading = !(draft.customSections[sectionIndex].showHeading !== false);
|
||||
});
|
||||
};
|
||||
|
||||
const onUpdateSection = () => {
|
||||
openDialog("resume.sections.custom.update", section);
|
||||
};
|
||||
@@ -287,6 +296,11 @@ function CustomSectionDropdownMenu({ section }: CustomSectionDropdownMenuProps)
|
||||
{section.hidden ? <Trans>Show</Trans> : <Trans>Hide</Trans>}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onToggleHeading}>
|
||||
{showHeading ? <EyeClosedIcon /> : <EyeIcon />}
|
||||
{showHeading ? <Trans>Hide heading</Trans> : <Trans>Show heading</Trans>}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onUpdateSection}>
|
||||
<PencilSimpleLineIcon />
|
||||
<Trans>Update</Trans>
|
||||
|
||||
@@ -170,6 +170,27 @@ describe("SkillsSectionDropdownMenu", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("heading visibility", () => {
|
||||
it("toggles heading visibility without changing section visibility", async () => {
|
||||
render(
|
||||
<I18nProvider i18n={i18n}>
|
||||
<SectionDropdownMenu type="experience" />
|
||||
</I18nProvider>,
|
||||
);
|
||||
|
||||
await openSectionOptions();
|
||||
fireEvent.click(await screen.findByRole("menuitem", { name: "Hide heading" }));
|
||||
|
||||
expect(mocks.updateResumeData).toHaveBeenCalledTimes(1);
|
||||
const mutation = mocks.updateResumeData.mock.calls[0]?.[0] as (draft: Resume["data"]) => void;
|
||||
const draft = structuredClone(mocks.resume.value.data);
|
||||
mutation(draft);
|
||||
|
||||
expect(draft.sections.experience.showHeading).toBe(false);
|
||||
expect(draft.sections.experience.hidden).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("chronological section sorting", () => {
|
||||
it("sorts Experience once through one draft mutation and names only unresolved items", async () => {
|
||||
render(
|
||||
|
||||
@@ -45,6 +45,7 @@ export function SectionDropdownMenu({ type }: Props) {
|
||||
const updateResumeData = useUpdateResumeData();
|
||||
const resume = useCurrentResume();
|
||||
const section = type === "summary" ? resume.data.summary : resume.data.sections[type];
|
||||
const showHeading = section.showHeading !== false;
|
||||
const dropDownValue =
|
||||
type === "skills" && resume.data.sections[type].layout === "inline" ? "inline" : section.columns.toString();
|
||||
|
||||
@@ -93,6 +94,16 @@ export function SectionDropdownMenu({ type }: Props) {
|
||||
});
|
||||
};
|
||||
|
||||
const onToggleHeading = () => {
|
||||
updateResumeData((draft) => {
|
||||
if (type === "summary") {
|
||||
draft.summary.showHeading = !(draft.summary.showHeading !== false);
|
||||
} else {
|
||||
draft.sections[type].showHeading = !(draft.sections[type].showHeading !== false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onRenameSection = async () => {
|
||||
const newTitle = await prompt(t`What do you want to rename this section to?`, {
|
||||
description: t`Leave empty to reset the title to the original.`,
|
||||
@@ -190,6 +201,11 @@ export function SectionDropdownMenu({ type }: Props) {
|
||||
{section.hidden ? <Trans>Show</Trans> : <Trans>Hide</Trans>}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onToggleHeading}>
|
||||
{showHeading ? <EyeClosedIcon /> : <EyeIcon />}
|
||||
{showHeading ? <Trans>Hide heading</Trans> : <Trans>Show heading</Trans>}
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem onClick={onRenameSection}>
|
||||
<PencilSimpleLineIcon />
|
||||
<Trans>Rename</Trans>
|
||||
|
||||
@@ -338,6 +338,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the summary from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the summary heading, icon, and decoration while retaining summary content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the summary is kept on a single page instead of splitting across a page break.",
|
||||
@@ -390,6 +395,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -508,6 +518,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -663,6 +678,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -795,6 +815,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -908,6 +933,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1030,6 +1060,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1115,6 +1150,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1207,6 +1247,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1324,6 +1369,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1441,6 +1491,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1558,6 +1613,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1675,6 +1735,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1813,6 +1878,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1901,6 +1971,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -1984,6 +2059,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -2114,6 +2194,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -2281,6 +2366,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -2425,6 +2515,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -2549,6 +2644,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -2674,6 +2774,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -2771,6 +2876,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -2875,6 +2985,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -3004,6 +3119,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -3133,6 +3253,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -3262,6 +3387,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
@@ -3391,6 +3521,11 @@ The full JSON Schema for Reactive Resume follows. You can also fetch the latest
|
||||
"type": "boolean",
|
||||
"description": "Whether to hide the section from the resume."
|
||||
},
|
||||
"showHeading": {
|
||||
"default": true,
|
||||
"description": "Whether to show the section heading, icon, and decoration while retaining section content.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keepTogether": {
|
||||
"default": false,
|
||||
"description": "If true, the section is kept on a single page instead of splitting across a page break.",
|
||||
|
||||
@@ -25,6 +25,7 @@ describe("renderSummary", () => {
|
||||
icon: getDefaultSectionIconName("summary"),
|
||||
content: "<p>Hello</p>",
|
||||
hidden: true,
|
||||
showHeading: true,
|
||||
columns: 1,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
@@ -38,6 +39,7 @@ describe("renderSummary", () => {
|
||||
icon: getDefaultSectionIconName("summary"),
|
||||
content: "",
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
columns: 1,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
@@ -51,6 +53,7 @@ describe("renderSummary", () => {
|
||||
icon: getDefaultSectionIconName("summary"),
|
||||
content: "<p>Hello world</p>",
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
columns: 1,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
@@ -60,12 +63,30 @@ describe("renderSummary", () => {
|
||||
expect(paragraphs.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("omits a disabled heading while retaining summary content", () => {
|
||||
const summary: ResumeData["summary"] = {
|
||||
title: "Summary",
|
||||
icon: getDefaultSectionIconName("summary"),
|
||||
content: "<p>Hello world</p>",
|
||||
hidden: false,
|
||||
showHeading: false,
|
||||
columns: 1,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
};
|
||||
|
||||
const paragraphs = renderSummary(summary, HEX);
|
||||
|
||||
expect(paragraphs).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("omits the heading when title is empty but still renders content", () => {
|
||||
const summary: ResumeData["summary"] = {
|
||||
title: "",
|
||||
icon: getDefaultSectionIconName("summary"),
|
||||
content: "<p>Hello world</p>",
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
columns: 1,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
@@ -81,6 +102,7 @@ const emptySection = <T extends SectionType>(type: T): ResumeData["sections"][T]
|
||||
icon: getDefaultSectionIconName(type),
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
@@ -110,6 +132,7 @@ describe("renderCustomSection", () => {
|
||||
icon: getDefaultSectionIconName("summary"),
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
@@ -147,6 +170,16 @@ describe("renderCustomSection", () => {
|
||||
expect(paragraphs.length).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("omits a disabled custom section heading while retaining content", () => {
|
||||
const section: CustomSection = {
|
||||
...baseCustom,
|
||||
showHeading: false,
|
||||
items: [{ id: "x", hidden: false, content: "<p>Hello</p>" } as never],
|
||||
};
|
||||
|
||||
expect(renderCustomSection(section, HEX)).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders recipient + content for a cover-letter custom section", () => {
|
||||
const section: CustomSection = {
|
||||
...baseCustom,
|
||||
|
||||
@@ -78,6 +78,14 @@ function sectionHeading(title: string, colorHex: string): Paragraph {
|
||||
});
|
||||
}
|
||||
|
||||
function sectionHeadingParagraph<T extends { showHeading?: boolean | undefined }>(
|
||||
section: T,
|
||||
title: string,
|
||||
colorHex: string,
|
||||
): Paragraph[] {
|
||||
return section.showHeading !== false && title ? [sectionHeading(title, colorHex)] : [];
|
||||
}
|
||||
|
||||
function titleAndSubtitle(primary: string, secondary: string, rightText?: string, bold = true): Paragraph {
|
||||
const baseRun = getBaseRun();
|
||||
const children: (TextRun | ExternalHyperlink)[] = [new TextRun({ text: primary, bold, ...baseRun })];
|
||||
@@ -144,9 +152,7 @@ export function renderSummary(summary: ResumeData["summary"], colorHex: string):
|
||||
if (summary.hidden || !summary.content) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [];
|
||||
if (summary.title) {
|
||||
paragraphs.push(sectionHeading(summary.title, colorHex));
|
||||
}
|
||||
paragraphs.push(...sectionHeadingParagraph(summary, summary.title, colorHex));
|
||||
paragraphs.push(...htmlToParagraphs(summary.content, getHtmlStyle()));
|
||||
return paragraphs;
|
||||
}
|
||||
@@ -155,7 +161,7 @@ function renderExperience(section: Sections["experience"], colorHex: string): Pa
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
const baseRun = getBaseRun();
|
||||
|
||||
for (const item of items) {
|
||||
@@ -201,7 +207,7 @@ function renderEducation(section: Sections["education"], colorHex: string): Para
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
const baseRun = getBaseRun();
|
||||
|
||||
for (const item of items) {
|
||||
@@ -234,7 +240,7 @@ function renderProjects(section: Sections["projects"], colorHex: string): Paragr
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
|
||||
for (const item of items) {
|
||||
paragraphs.push(titleAndSubtitle(item.name, "", item.period));
|
||||
@@ -254,7 +260,7 @@ function renderSkills(section: Sections["skills"], colorHex: string): Paragraph[
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
const baseRun = getBaseRun();
|
||||
|
||||
for (const item of items) {
|
||||
@@ -285,7 +291,7 @@ function renderLanguages(section: Sections["languages"], colorHex: string): Para
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
const baseRun = getBaseRun();
|
||||
|
||||
for (const item of items) {
|
||||
@@ -305,7 +311,7 @@ function renderInterests(section: Sections["interests"], colorHex: string): Para
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
const baseRun = getBaseRun();
|
||||
|
||||
for (const item of items) {
|
||||
@@ -325,7 +331,7 @@ function renderAwards(section: Sections["awards"], colorHex: string): Paragraph[
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
|
||||
for (const item of items) {
|
||||
paragraphs.push(titleAndSubtitle(item.title, item.awarder, item.date, false));
|
||||
@@ -345,7 +351,7 @@ function renderCertifications(section: Sections["certifications"], colorHex: str
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
|
||||
for (const item of items) {
|
||||
paragraphs.push(titleAndSubtitle(item.title, item.issuer, item.date));
|
||||
@@ -365,7 +371,7 @@ function renderPublications(section: Sections["publications"], colorHex: string)
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
|
||||
for (const item of items) {
|
||||
paragraphs.push(titleAndSubtitle(item.title, item.publisher, item.date));
|
||||
@@ -385,7 +391,7 @@ function renderVolunteer(section: Sections["volunteer"], colorHex: string): Para
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
|
||||
for (const item of items) {
|
||||
paragraphs.push(titleAndSubtitle(item.organization, "", item.period));
|
||||
@@ -408,7 +414,7 @@ function renderReferences(section: Sections["references"], colorHex: string): Pa
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
const baseRun = getBaseRun();
|
||||
|
||||
for (const item of items) {
|
||||
@@ -443,7 +449,7 @@ function renderProfiles(section: Sections["profiles"], colorHex: string): Paragr
|
||||
const items = section.items.filter((item) => !item.hidden);
|
||||
if (section.hidden || items.length === 0) return [];
|
||||
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
const baseRun = getBaseRun();
|
||||
|
||||
for (const item of items) {
|
||||
@@ -502,7 +508,7 @@ export function renderCustomSection(section: CustomSection, colorHex: string): P
|
||||
|
||||
// Summary-type custom sections render HTML content
|
||||
if (sectionType === "summary") {
|
||||
const paragraphs: Paragraph[] = [sectionHeading(section.title, colorHex)];
|
||||
const paragraphs: Paragraph[] = sectionHeadingParagraph(section, section.title, colorHex);
|
||||
for (const item of visibleItems) {
|
||||
if ("content" in item && item.content) {
|
||||
paragraphs.push(...htmlToParagraphs(item.content, getHtmlStyle()));
|
||||
@@ -533,6 +539,7 @@ export function renderCustomSection(section: CustomSection, colorHex: string): P
|
||||
title: section.title,
|
||||
columns: section.columns,
|
||||
hidden: false,
|
||||
showHeading: section.showHeading,
|
||||
items: visibleItems,
|
||||
} as Sections[SectionType];
|
||||
|
||||
|
||||
@@ -109,6 +109,7 @@ describe("getResumeSectionIcon", () => {
|
||||
icon: "lightning",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
@@ -129,6 +130,7 @@ describe("getResumeSectionIcon", () => {
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
@@ -149,6 +151,7 @@ describe("getResumeSectionIcon", () => {
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
@@ -169,6 +172,7 @@ describe("getResumeSectionIcon", () => {
|
||||
icon: "none",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
|
||||
@@ -112,6 +112,7 @@ describe("getResumeSectionTitle", () => {
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
@@ -132,6 +133,7 @@ describe("getResumeSectionTitle", () => {
|
||||
icon: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
|
||||
@@ -239,6 +239,7 @@ describe("semantic binding inventory", () => {
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
keepTogether: false,
|
||||
showHeading: true,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
{
|
||||
|
||||
@@ -242,6 +242,34 @@ describe("semantic issue fixtures", () => {
|
||||
expect(containsStyle(document, "opacity", 0.2)).toBe(true);
|
||||
});
|
||||
|
||||
it("hides section heading decoration without hiding section content", async () => {
|
||||
const data = buildIssueFixture();
|
||||
data.sections.experience.showHeading = false;
|
||||
const element = createElement(ResumeDocument, { data, template: "onyx" }) as unknown as Parameters<typeof pdf>[0];
|
||||
const instance = pdf(element);
|
||||
await vi.waitFor(() => expect(instance.container.document).not.toBeNull());
|
||||
const document = instance.container.document as HostNode;
|
||||
|
||||
expect(findText(document, "Experience")).toBeUndefined();
|
||||
expect(findText(document, "Analytical Engines")).toBeDefined();
|
||||
});
|
||||
|
||||
it("characterizes section-heading display:none as a CSS alternative", async () => {
|
||||
const data = buildIssueFixture();
|
||||
data.metadata.page.hideSectionIcons = false;
|
||||
data.metadata.stylesheet = {
|
||||
mode: "semantic",
|
||||
source: source('@version 1; section[id="experience"] section-heading { display: none; }'),
|
||||
};
|
||||
const element = createElement(ResumeDocument, { data, template: "onyx" }) as unknown as Parameters<typeof pdf>[0];
|
||||
const instance = pdf(element);
|
||||
await vi.waitFor(() => expect(instance.container.document).not.toBeNull());
|
||||
const document = instance.container.document as HostNode;
|
||||
|
||||
expect(findText(document, "Experience")).toBeUndefined();
|
||||
expect(findText(document, "Analytical Engines")).toBeDefined();
|
||||
});
|
||||
|
||||
it("unbolds only the final skill-name primitive and preserves the experience title weight (#2223)", async () => {
|
||||
const data = buildIssueFixture();
|
||||
const stylesheet = source(`
|
||||
|
||||
@@ -133,6 +133,7 @@ const buildFixture = (rules: StyleRule[]): ResumeData => {
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
keepTogether: false,
|
||||
showHeading: true,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
{
|
||||
|
||||
@@ -126,6 +126,7 @@ const buildFixture = (rules: StyleRule[]): ResumeData => {
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
keepTogether: false,
|
||||
showHeading: true,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
{
|
||||
|
||||
@@ -316,6 +316,7 @@ const buildCompleteFixture = (): ResumeData => {
|
||||
icon: "star",
|
||||
columns: 1,
|
||||
hidden: true,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
|
||||
@@ -320,6 +320,11 @@ const SectionShell = ({ sectionId, title, showHeading = true, children }: Sectio
|
||||
const sectionHeadingContainerStyle = useTemplateStyle("sectionHeadingContainer");
|
||||
const sectionHeadingRuleStyle = useSectionStyleRule("heading");
|
||||
const sectionTitle = getResumeSectionTitle(data, sectionId, title);
|
||||
const sectionHeadingEnabled = (() => {
|
||||
if (sectionId === "summary") return data.summary.showHeading !== false;
|
||||
if (sectionId in data.sections) return data.sections[sectionId as SectionType].showHeading !== false;
|
||||
return data.customSections.find((section) => section.id === sectionId)?.showHeading !== false;
|
||||
})();
|
||||
const sectionHeadingNodeKey = semanticNodeKeys.sectionHeading(sectionNodeKey);
|
||||
const sectionHeadingResolved = useResolvedNode(sectionHeadingNodeKey);
|
||||
const sectionHeadingVisible = useSemanticNodeVisible(sectionHeadingNodeKey);
|
||||
@@ -343,7 +348,7 @@ const SectionShell = ({ sectionId, title, showHeading = true, children }: Sectio
|
||||
return (
|
||||
<SemanticNodeKeyProvider nodeKey={sectionNodeKey}>
|
||||
<View style={resolvedSectionStyle} {...flowProps}>
|
||||
{showHeading && (
|
||||
{showHeading && sectionHeadingEnabled && (
|
||||
<Heading style={composeStyles(sectionHeadingStyle, sectionHeadingRuleStyle)}>{sectionTitle}</Heading>
|
||||
)}
|
||||
{children}
|
||||
@@ -356,7 +361,7 @@ const SectionShell = ({ sectionId, title, showHeading = true, children }: Sectio
|
||||
return (
|
||||
<SemanticNodeKeyProvider nodeKey={sectionNodeKey}>
|
||||
<View style={resolvedSectionStyle} {...flowProps}>
|
||||
{showHeading && sectionHeadingVisible && (
|
||||
{showHeading && sectionHeadingEnabled && sectionHeadingVisible && (
|
||||
<View
|
||||
{...resolvedPdfFlowProps(sectionHeadingResolved)}
|
||||
style={composeStyles(
|
||||
|
||||
@@ -103,6 +103,42 @@ const customSectionFixture = (type: CustomSectionType, item: Record<string, unkn
|
||||
});
|
||||
|
||||
describe("resumeDataSchema", () => {
|
||||
it("defaults heading visibility to true for legacy summary and section data", () => {
|
||||
const legacySummary = {
|
||||
title: "",
|
||||
icon: "article",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
content: "<p>Summary</p>",
|
||||
};
|
||||
const legacySection = {
|
||||
title: "",
|
||||
icon: "briefcase",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [],
|
||||
};
|
||||
|
||||
expect(
|
||||
parseResumeData({
|
||||
...defaultResumeData,
|
||||
summary: legacySummary,
|
||||
sections: { ...defaultResumeData.sections, experience: legacySection },
|
||||
}).summary.showHeading,
|
||||
).toBe(true);
|
||||
expect(
|
||||
parseResumeData({
|
||||
...defaultResumeData,
|
||||
summary: legacySummary,
|
||||
sections: { ...defaultResumeData.sections, experience: legacySection },
|
||||
}).sections.experience.showHeading,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("validates the default resume", () => {
|
||||
expect(resumeDataSchema.safeParse(defaultResumeData).success).toBe(true);
|
||||
});
|
||||
|
||||
@@ -107,6 +107,11 @@ export const summarySchema = z.object({
|
||||
),
|
||||
columns: z.number().int().min(1).max(6).catch(1).describe("The number of columns the summary should span across."),
|
||||
hidden: z.boolean().describe("Whether to hide the summary from the resume."),
|
||||
showHeading: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.catch(true)
|
||||
.describe("Whether to show the summary heading, icon, and decoration while retaining summary content."),
|
||||
keepTogether: z
|
||||
.boolean()
|
||||
.catch(false)
|
||||
@@ -295,6 +300,11 @@ export const baseSectionSchema = z.object({
|
||||
),
|
||||
columns: z.number().int().min(1).max(6).catch(1).describe("The number of columns the section should span across."),
|
||||
hidden: z.boolean().describe("Whether to hide the section from the resume."),
|
||||
showHeading: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.catch(true)
|
||||
.describe("Whether to show the section heading, icon, and decoration while retaining section content."),
|
||||
keepTogether: z
|
||||
.boolean()
|
||||
.catch(false)
|
||||
@@ -694,7 +704,13 @@ export const resumeDataSchema = z.looseObject({
|
||||
|
||||
export type ResumeData = z.infer<typeof resumeDataSchema>;
|
||||
|
||||
export const parseResumeData = (data: unknown): ResumeData => resumeDataSchema.parse(data);
|
||||
export const parseResumeData = (data: unknown): ResumeData => {
|
||||
const parsed = resumeDataSchema.parse(data);
|
||||
parsed.summary.showHeading ??= true;
|
||||
for (const section of Object.values(parsed.sections)) section.showHeading ??= true;
|
||||
for (const section of parsed.customSections) section.showHeading ??= true;
|
||||
return parsed;
|
||||
};
|
||||
|
||||
export type LayoutPage = z.infer<typeof pageLayoutSchema>;
|
||||
export type Typography = z.infer<typeof typographySchema>;
|
||||
|
||||
@@ -6,6 +6,7 @@ const section = (icon: string) => ({
|
||||
icon,
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [] as never[],
|
||||
@@ -39,6 +40,7 @@ export const defaultResumeData: ResumeData = {
|
||||
icon: "article",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
content: "",
|
||||
|
||||
@@ -44,6 +44,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "article",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
content:
|
||||
@@ -55,6 +56,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "messenger-logo",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -91,6 +93,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "briefcase",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -117,6 +120,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "graduation-cap",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -144,6 +148,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "code-simple",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -195,6 +200,7 @@ export const sampleResumeData: ResumeData = {
|
||||
layout: "default",
|
||||
keywordLayout: "inline",
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -265,6 +271,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "translate",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -289,6 +296,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "football",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -331,6 +339,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "trophy",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -369,6 +378,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "certificate",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -405,6 +415,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "books",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -443,6 +454,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "hand-heart",
|
||||
columns: 2,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -481,6 +493,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "phone",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
items: [
|
||||
@@ -506,6 +519,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "briefcase",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
id: "019becaf-0b87-769d-98a6-46ccf558c0e8",
|
||||
@@ -550,6 +564,7 @@ export const sampleResumeData: ResumeData = {
|
||||
icon: "envelope-simple",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
showHeading: true,
|
||||
keepTogether: false,
|
||||
startOnNewPage: false,
|
||||
id: "019bef5b-0b3d-7e2a-8a7c-12d9e23a4f6b",
|
||||
|
||||
@@ -65,6 +65,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `summary.icon` | `string` | yes | default: "" | Phosphor icon name to display before the summary section title in the PDF output. Empty string uses the default summary icon; 'none' hides the icon. |
|
||||
| `summary.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the summary should span across. |
|
||||
| `summary.hidden` | `boolean` | yes | — | Whether to hide the summary from the resume. |
|
||||
| `summary.showHeading` | `boolean` | no | default: true | Whether to show the summary heading, icon, and decoration while retaining summary content. |
|
||||
| `summary.keepTogether` | `boolean` | yes | default: false | If true, the summary is kept on a single page instead of splitting across a page break. |
|
||||
| `summary.startOnNewPage` | `boolean` | yes | default: false | If true, the summary always begins on a new page. |
|
||||
| `summary.content` | `string` | yes | — | The content of the summary of the resume. This should be a HTML-formatted string. |
|
||||
@@ -74,6 +75,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.profiles.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.profiles.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.profiles.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.profiles.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.profiles.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.profiles.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.profiles.items` | `array` | yes | — | The items to display in the profiles section. |
|
||||
@@ -93,6 +95,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.experience.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.experience.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.experience.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.experience.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.experience.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.experience.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.experience.items` | `array` | yes | — | The items to display in the experience section. |
|
||||
@@ -119,6 +122,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.education.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.education.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.education.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.education.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.education.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.education.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.education.items` | `array` | yes | — | The items to display in the education section. |
|
||||
@@ -141,6 +145,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.projects.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.projects.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.projects.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.projects.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.projects.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.projects.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.projects.items` | `array` | yes | — | The items to display in the projects section. |
|
||||
@@ -159,6 +164,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.skills.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.skills.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.skills.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.skills.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.skills.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.skills.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.skills.items` | `array` | yes | — | The items to display in the skills section. |
|
||||
@@ -179,6 +185,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.languages.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.languages.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.languages.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.languages.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.languages.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.languages.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.languages.items` | `array` | yes | — | The items to display in the languages section. |
|
||||
@@ -193,6 +200,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.interests.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.interests.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.interests.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.interests.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.interests.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.interests.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.interests.items` | `array` | yes | — | The items to display in the interests section. |
|
||||
@@ -209,6 +217,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.awards.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.awards.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.awards.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.awards.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.awards.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.awards.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.awards.items` | `array` | yes | — | The items to display in the awards section. |
|
||||
@@ -228,6 +237,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.certifications.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.certifications.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.certifications.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.certifications.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.certifications.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.certifications.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.certifications.items` | `array` | yes | — | The items to display in the certifications section. |
|
||||
@@ -247,6 +257,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.publications.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.publications.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.publications.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.publications.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.publications.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.publications.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.publications.items` | `array` | yes | — | The items to display in the publications section. |
|
||||
@@ -266,6 +277,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.volunteer.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.volunteer.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.volunteer.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.volunteer.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.volunteer.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.volunteer.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.volunteer.items` | `array` | yes | — | The items to display in the volunteer section. |
|
||||
@@ -285,6 +297,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `sections.references.icon` | `string` | yes | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `sections.references.columns` | `integer` | yes | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `sections.references.hidden` | `boolean` | yes | — | Whether to hide the section from the resume. |
|
||||
| `sections.references.showHeading` | `boolean` | no | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `sections.references.keepTogether` | `boolean` | yes | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `sections.references.startOnNewPage` | `boolean` | yes | default: false | If true, the section always begins on a new page. |
|
||||
| `sections.references.items` | `array` | yes | — | The items to display in the references section. |
|
||||
@@ -306,6 +319,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type summary, schema summaryItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type summary, schema summaryItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type summary, schema summaryItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type summary, schema summaryItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type summary, schema summaryItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type summary, schema summaryItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type summary, schema summaryItemSchema at customSections[]) | — | — |
|
||||
@@ -321,6 +335,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type profiles, schema profileItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type profiles, schema profileItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type profiles, schema profileItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type profiles, schema profileItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type profiles, schema profileItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type profiles, schema profileItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type profiles, schema profileItemSchema at customSections[]) | — | — |
|
||||
@@ -343,6 +358,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type experience, schema experienceItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type experience, schema experienceItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type experience, schema experienceItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type experience, schema experienceItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type experience, schema experienceItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type experience, schema experienceItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type experience, schema experienceItemSchema at customSections[]) | — | — |
|
||||
@@ -372,6 +388,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type education, schema educationItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type education, schema educationItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type education, schema educationItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type education, schema educationItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type education, schema educationItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type education, schema educationItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type education, schema educationItemSchema at customSections[]) | — | — |
|
||||
@@ -397,6 +414,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type projects, schema projectItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type projects, schema projectItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type projects, schema projectItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type projects, schema projectItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type projects, schema projectItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type projects, schema projectItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type projects, schema projectItemSchema at customSections[]) | — | — |
|
||||
@@ -418,6 +436,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type skills, schema skillItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type skills, schema skillItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type skills, schema skillItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type skills, schema skillItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type skills, schema skillItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type skills, schema skillItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `string` | no (type skills, schema skillItemSchema at customSections[]) | enum: ["inline","list"]; default: "inline" | How skill keywords are displayed: inline separated by commas, or one bullet per keyword. |
|
||||
@@ -439,6 +458,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type languages, schema languageItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type languages, schema languageItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type languages, schema languageItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type languages, schema languageItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type languages, schema languageItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type languages, schema languageItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type languages, schema languageItemSchema at customSections[]) | — | — |
|
||||
@@ -456,6 +476,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type interests, schema interestItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type interests, schema interestItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type interests, schema interestItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type interests, schema interestItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type interests, schema interestItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type interests, schema interestItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type interests, schema interestItemSchema at customSections[]) | — | — |
|
||||
@@ -475,6 +496,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type awards, schema awardItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type awards, schema awardItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type awards, schema awardItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type awards, schema awardItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type awards, schema awardItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type awards, schema awardItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type awards, schema awardItemSchema at customSections[]) | — | — |
|
||||
@@ -497,6 +519,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type certifications, schema certificationItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type certifications, schema certificationItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type certifications, schema certificationItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type certifications, schema certificationItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type certifications, schema certificationItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type certifications, schema certificationItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type certifications, schema certificationItemSchema at customSections[]) | — | — |
|
||||
@@ -519,6 +542,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type publications, schema publicationItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type publications, schema publicationItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type publications, schema publicationItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type publications, schema publicationItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type publications, schema publicationItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type publications, schema publicationItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type publications, schema publicationItemSchema at customSections[]) | — | — |
|
||||
@@ -541,6 +565,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type volunteer, schema volunteerItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type volunteer, schema volunteerItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type volunteer, schema volunteerItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type volunteer, schema volunteerItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type volunteer, schema volunteerItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type volunteer, schema volunteerItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type volunteer, schema volunteerItemSchema at customSections[]) | — | — |
|
||||
@@ -563,6 +588,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type references, schema referenceItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type references, schema referenceItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type references, schema referenceItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type references, schema referenceItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type references, schema referenceItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type references, schema referenceItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type references, schema referenceItemSchema at customSections[]) | — | — |
|
||||
@@ -585,6 +611,7 @@ Choose one coherent shape for each union value. Required fields are local to tha
|
||||
| `customSections[].icon` | `string` | yes (type cover-letter, schema coverLetterItemSchema at customSections[]) | default: "" | Phosphor icon name to display before the section title in the PDF output. Empty string uses the default section icon; 'none' hides the icon. |
|
||||
| `customSections[].columns` | `integer` | yes (type cover-letter, schema coverLetterItemSchema at customSections[]) | minimum: 1; maximum: 6; default: 1 | The number of columns the section should span across. |
|
||||
| `customSections[].hidden` | `boolean` | yes (type cover-letter, schema coverLetterItemSchema at customSections[]) | — | Whether to hide the section from the resume. |
|
||||
| `customSections[].showHeading` | `boolean` | no (type cover-letter, schema coverLetterItemSchema at customSections[]) | default: true | Whether to show the section heading, icon, and decoration while retaining section content. |
|
||||
| `customSections[].keepTogether` | `boolean` | yes (type cover-letter, schema coverLetterItemSchema at customSections[]) | default: false | If true, the section is kept on a single page instead of splitting across a page break. |
|
||||
| `customSections[].startOnNewPage` | `boolean` | yes (type cover-letter, schema coverLetterItemSchema at customSections[]) | default: false | If true, the section always begins on a new page. |
|
||||
| `customSections[].keywordLayout` | `any` | no (type cover-letter, schema coverLetterItemSchema at customSections[]) | — | — |
|
||||
|
||||
@@ -2,10 +2,10 @@ import { describe, expect, it } from "vitest";
|
||||
import { defaultResumeData } from "@reactive-resume/schema/resume/default";
|
||||
import { compareResumeRecovery } from "./compare-resume";
|
||||
|
||||
const SYNTHETIC_SOURCE_HASH = "2fc24960657492304068db58c4f4f8b0861ef4937bf12aadeced12ed03856d41";
|
||||
const RECOVERED_COPY_HASH = "87b3c6293158e14591387969b804dda0233ae83df79e59043b340c9f0180bf6d";
|
||||
const CURRENT_COPY_HASH = "701ee9fde9161c974ee6325e169132249ccb77adb31d875541ab70dd20835017";
|
||||
const DEFAULT_RESUME_HASH = "bb0b5937876766c5a0d5014538257870396f30902d86a2d32d2c901f644ffb1f";
|
||||
const SYNTHETIC_SOURCE_HASH = "33bd2901474d077a37ed73f0646ac2812a0fbb6ca8ca7bd5825c56ec4c598b8c";
|
||||
const RECOVERED_COPY_HASH = "1c0e1eefac8375d15b5d0d5fc040a970d1f10242c31a224f3c2a0ea5750c719e";
|
||||
const CURRENT_COPY_HASH = "0e8a2e90ccb44068f500a92bc0d312b4290269f1b68ba925fdfe6109a15e739e";
|
||||
const DEFAULT_RESUME_HASH = "1870f56666738b8748ac2769f5c79fe7b19863efecc07142a34c425254d871d8";
|
||||
|
||||
const FORMAT_CHARACTERS = [
|
||||
["zero-width space (U+200B)", "\u200B"],
|
||||
|
||||
Reference in New Issue
Block a user