jsonresume-theme-academic 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs.map +1 -1
- package/examples/preview.html +492 -492
- package/examples/resume.json +303 -303
- package/package.json +95 -85
package/README.md
CHANGED
|
@@ -1,97 +1,105 @@
|
|
|
1
|
-
# jsonresume-theme-academic
|
|
2
|
-
|
|
3
|
-
> Academic serif theme for [JSON Resume](https://jsonresume.org) with EB Garamond typography, small caps headings, and gold accents. Designed for print and PDF.
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/jsonresume-theme-academic)
|
|
6
|
-
[ with EB Garamond typography, small caps headings, and gold accents. Designed for print and PDF.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/jsonresume-theme-academic)
|
|
6
|
+
[](https://www.npmjs.com/package/jsonresume-theme-academic)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- Academic serif typography (EB Garamond)
|
|
15
|
+
- A4 print layout with precise margins
|
|
16
|
+
- Built in HTML sanitization (XSS safe)
|
|
17
|
+
- Customizable section headings via `meta.headings`
|
|
18
|
+
- Zero runtime dependencies
|
|
19
|
+
- ESM + CJS + UMD builds with TypeScript declarations
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### With resumed (recommended)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install jsonresume-theme-academic
|
|
27
|
+
npx resumed render resume.json --theme jsonresume-theme-academic
|
|
28
|
+
# → resume.html ready
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### With resume-cli
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g resume-cli jsonresume-theme-academic
|
|
35
|
+
resume export resume.pdf --theme academic
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Programmatic Usage
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { render, pdfRenderOptions } from 'jsonresume-theme-academic';
|
|
42
|
+
|
|
43
|
+
const html = render(resumeJson);
|
|
44
|
+
// html is a self-contained HTML document
|
|
45
|
+
|
|
46
|
+
// For Puppeteer PDF rendering:
|
|
47
|
+
await page.pdf(pdfRenderOptions);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Customization
|
|
51
|
+
|
|
52
|
+
### Section Headings
|
|
53
|
+
|
|
54
|
+
Override default headings via `meta.headings`:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"meta": {
|
|
59
|
+
"headings": {
|
|
60
|
+
"experience": "Professional Experience",
|
|
61
|
+
"skills": "Technical Skills",
|
|
62
|
+
"education": "Academic Background"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Available keys: `skills`, `experience`, `projects`, `education`, `volunteer`, `certifications`, `additional`
|
|
69
|
+
|
|
70
|
+
### PDF Rendering Options
|
|
71
|
+
|
|
72
|
+
The exported `pdfRenderOptions` provides optimized settings for Puppeteer/Gotenberg:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
{
|
|
76
|
+
mediaType: 'print',
|
|
77
|
+
format: 'A4',
|
|
78
|
+
margin: { top: '12mm', right: '14mm', bottom: '12mm', left: '14mm' }
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Development
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/ebenezer-isaac/jsonresume-theme-academic.git
|
|
86
|
+
cd jsonresume-theme-academic
|
|
87
|
+
npm install
|
|
88
|
+
npm run dev # Start dev server
|
|
89
|
+
npm test # Run tests
|
|
90
|
+
npm run build # Build ESM + CJS + UMD
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Community
|
|
94
|
+
|
|
95
|
+
This theme is part of the open-source toolkit from [LLM Conveyors](https://llmconveyors.com), an AI-powered career platform that uses this theme for generating polished, print-ready resumes. We open-sourced it so anyone building with JSON Resume can have a production-quality academic theme out of the box.
|
|
96
|
+
|
|
97
|
+
If you're using this theme in your project, we'd love to hear about it — open an issue or start a discussion!
|
|
98
|
+
|
|
99
|
+
## Contributing
|
|
100
|
+
|
|
101
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/constants.ts","../src/utils/escape.ts","../src/utils/dates.ts","../src/utils/icons.ts","../src/utils/sanitize.ts","../src/utils/text.ts","../src/sections/header.ts","../src/sections/shared.ts","../src/sections/summary.ts","../src/sections/skills.ts","../src/sections/work.ts","../src/sections/projects.ts","../src/sections/education.ts","../src/sections/volunteer.ts","../src/sections/certificates.ts","../src/sections/additional.ts","../src/render.ts","../src/index.ts"],"sourcesContent":["export const MONTHS: readonly string[] = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\n\nexport const COUNTRY_NAMES: Readonly<Record<string, string>> = {\n GB: 'United Kingdom',\n US: 'United States',\n CA: 'Canada',\n AU: 'Australia',\n DE: 'Germany',\n FR: 'France',\n IN: 'India',\n JP: 'Japan',\n CN: 'China',\n BR: 'Brazil',\n NL: 'Netherlands',\n IT: 'Italy',\n ES: 'Spain',\n SE: 'Sweden',\n CH: 'Switzerland',\n SG: 'Singapore',\n NZ: 'New Zealand',\n IE: 'Ireland',\n AE: 'UAE',\n};\n\nexport const DEFAULT_HEADINGS: Readonly<Record<string, string>> = {\n summary: 'Summary',\n skills: 'Core Skills',\n experience: 'Experience',\n projects: 'Selected Projects',\n education: 'Education',\n volunteer: 'Leadership & Volunteering',\n certifications: 'Credentials',\n additional: 'Additional',\n};\n","/** Decode common HTML entities to literal characters. */\nexport function decodeEntities(text: string): string {\n return text\n .replace(/&#(\\d+);/g, (_, c) => String.fromCharCode(Number(c)))\n .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCharCode(parseInt(h, 16)))\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ')\n .replace(/—/g, '\\u2014')\n .replace(/–/g, '\\u2013')\n .replace(/…/g, '\\u2026');\n}\n\n/** HTML-escape for safe output (plain text fields like names, dates). */\nexport function esc(text: string | null | undefined): string {\n if (text == null) return '';\n return String(text)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","import { MONTHS, COUNTRY_NAMES } from '../constants.js';\n\nexport function formatDate(dateStr: string | null | undefined): string {\n if (!dateStr) return 'Present';\n if (dateStr.toLowerCase() === 'present') return 'Present';\n const parts = String(dateStr).split('-');\n if (parts.length === 1) return parts[0];\n const year = parts[0];\n const month = MONTHS[parseInt(parts[1], 10) - 1] || '';\n return `${month} ${year}`;\n}\n\nexport function dateRange(start: string | null | undefined, end: string | null | undefined): string {\n const s = formatDate(start);\n const e = formatDate(end);\n if (!s || s === 'Present') return ''; // start date should not be empty or present\n if (!e) return s;\n return `${s} to ${e}`;\n}\n\nexport function regionName(code: string | null | undefined): string {\n if (!code) return '';\n return COUNTRY_NAMES[code.toUpperCase()] || code;\n}\n","export function profileIcon(network: string | null | undefined): string {\n if (!network) return '<i class=\"fa-solid fa-link\"></i>';\n const n = network.toLowerCase();\n if (n === 'linkedin') return '<i class=\"fa-brands fa-linkedin-in\"></i>';\n if (n === 'github') return '<i class=\"fa-brands fa-github\"></i>';\n if (n === 'twitter' || n === 'x') return '<i class=\"fa-brands fa-x-twitter\"></i>';\n if (n === 'stackoverflow') return '<i class=\"fa-brands fa-stack-overflow\"></i>';\n return '<i class=\"fa-solid fa-link\"></i>';\n}\n","/** Tags allowed through sanitization — safe inline/block formatting. */\nconst BLOCK_TAGS = new Set(['p', 'ul', 'ol', 'li', 'blockquote']);\nconst INLINE_TAGS = new Set([\n 'br', 'b', 'strong', 'i', 'em', 'u', 's', 'mark', 'a', 'sub', 'sup', 'code',\n]);\nconst ALLOWED_TAGS = new Set([...BLOCK_TAGS, ...INLINE_TAGS]);\n\n/** URL schemes safe for <a href>. Everything else (javascript:, data:, vbscript:) is stripped. */\nconst SAFE_URL_SCHEMES = /^(?:https?|mailto|tel):/i;\nconst RELATIVE_URL = /^[#/?.]/;\n\nfunction isSafeUrl(href: string): boolean {\n const trimmed = href.trim();\n if (!trimmed) return false;\n return SAFE_URL_SCHEMES.test(trimmed) || RELATIVE_URL.test(trimmed);\n}\n\ninterface SanitizeOptions {\n readonly inline?: boolean;\n}\n\n/**\n * Sanitize HTML — keep allowed formatting tags, strip everything else.\n * Attributes are stripped from all tags except <a href> (safe schemes only) and <mark class>.\n * If inline=true, block tags (p, ul, ol, li, blockquote) are also stripped.\n */\nexport function sanitizeHtml(html: string | null | undefined, { inline = false }: SanitizeOptions = {}): string {\n if (!html) return '';\n const allowed = inline ? INLINE_TAGS : ALLOWED_TAGS;\n\n return String(html)\n .replace(/<a\\s+[^>]*href=\"([^\"]*)\"[^>]*>/gi, (_, href: string) => {\n if (!isSafeUrl(href)) return '';\n return `<a href=\"${href.replace(/\"/g, '"')}\" target=\"_blank\" rel=\"noopener\">`;\n })\n .replace(/<mark\\s+[^>]*class=\"([^\"]*)\"[^>]*>/gi, (_, cls: string) =>\n `<mark class=\"${cls.replace(/\"/g, '"')}\">`,\n )\n .replace(/<(\\/?)([a-z][a-z0-9]*)\\s*[^>]*?(\\/?)>/gi, (match, slash: string, tag: string, selfClose: string) => {\n const lower = tag.toLowerCase();\n if (lower === 'a' && !slash && match.includes('href=')) return match;\n if (lower === 'mark' && !slash && match.includes('class=')) return match;\n if (allowed.has(lower)) return `<${slash}${lower}${selfClose}>`;\n if (inline && BLOCK_TAGS.has(lower)) return slash ? ' ' : '';\n return '';\n })\n .replace(/<p>\\s*<\\/p>/gi, '')\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n","import { decodeEntities, esc } from './escape.js';\nimport { sanitizeHtml } from './sanitize.js';\n\n/** Strip HTML tags → plain text (for fields parsed as data, not displayed). */\nexport function stripHtml(text: string | null | undefined): string {\n if (!text) return '';\n return decodeEntities(\n String(text)\n .replace(/<br\\s*\\/?>/gi, ' ')\n .replace(/<\\/?(p|div|li)[^>]*>/gi, ' ')\n .replace(/<[^>]+>/g, ''),\n )\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n\nconst isHtml = (text: string): boolean => /<[a-z][\\s\\S]*>/i.test(text);\nconst hasEntities = (text: string): boolean => /&(?:amp|lt|gt|quot|#\\d+|#x[0-9a-f]+);/i.test(text);\n\n/**\n * Render user-provided rich text → safe HTML output.\n * block=true: preserve <p>, <ul>, <ol> etc.\n * block=false (default): inline mode — strip block tags, keep only inline formatting.\n */\nexport function richText(text: string | null | undefined, { block = false }: { block?: boolean } = {}): string {\n if (text == null) return '';\n const s = String(text);\n if (isHtml(s)) return sanitizeHtml(s, { inline: !block });\n if (hasEntities(s)) return esc(decodeEntities(s));\n return esc(s);\n}\n\n/** Check if an array is non-empty. */\nexport function has<T>(arr: readonly T[] | null | undefined): arr is readonly T[] & { length: number } {\n return Array.isArray(arr) && arr.length > 0;\n}\n","import type { ResumeBasics } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { regionName } from '../utils/dates.js';\nimport { profileIcon } from '../utils/icons.js';\nimport { has } from '../utils/text.js';\n\ninterface ContactLine {\n readonly icon: string;\n readonly text: string;\n}\n\nexport function renderHeader(basics: ResumeBasics | undefined): string {\n if (!basics) return '';\n\n const location = basics.location\n ? [basics.location.city, regionName(basics.location.region)].filter(Boolean).join(', ')\n : '';\n\n const lines: ContactLine[] = [];\n if (location)\n lines.push({ icon: '<i class=\"fa-solid fa-location-dot\"></i>', text: location });\n if (basics.phone)\n lines.push({ icon: '<i class=\"fa-solid fa-square-phone\"></i>', text: basics.phone });\n if (basics.email)\n lines.push({ icon: '<i class=\"fa-solid fa-envelope\"></i>', text: basics.email });\n if (basics.url) {\n const display = basics.url.replace(/^https?:\\/\\//, '');\n lines.push({ icon: '<i class=\"fa-solid fa-globe\"></i>', text: display });\n }\n if (has(basics.profiles)) {\n for (const p of basics.profiles) {\n lines.push({ icon: profileIcon(p.network), text: p.username || p.url || '' });\n }\n }\n\n return `\n <header class=\"header\">\n <div class=\"header-left\">\n <h1 class=\"name\">${esc(basics.name)}</h1>\n <div class=\"label\">${esc(basics.label)}</div>\n </div>\n <div class=\"contact-info\">\n ${lines.map((l) => `<div class=\"contact-line\">${esc(l.text)} ${l.icon}</div>`).join('\\n ')}\n </div>\n </header>\n <hr class=\"header-rule\" />`;\n}\n","import { esc } from '../utils/escape.js';\n\nexport function sectionTitle(title: string): string {\n return `<h2 class=\"section-title\">${esc(title)}</h2>`;\n}\n","import { richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderSummary(summary: string | undefined, heading: string): string {\n if (!summary) return '';\n const html = richText(summary, { block: true });\n if (!html) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"summary\">${html}</div>`;\n}\n","import type { ResumeSkill } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderSkills(skills: readonly ResumeSkill[] | undefined, heading: string): string {\n if (!has(skills)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${skills\n .map(\n (s) => `\n <div class=\"bullet-item\">\n <span class=\"skill-name\">${esc(s.name)}:</span> ${esc((s.keywords || []).join(', '))}\n </div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeWorkEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { dateRange } from '../utils/dates.js';\nimport { has, richText, stripHtml } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\n/** Check whether summary uses structured Tech-stack / Client format. */\nfunction isStructuredMeta(text: string): boolean {\n return /Tech-stack:\\s/i.test(text) || /Client:\\s/i.test(text);\n}\n\n/** Extract tech-stack, client, and any remaining narrative from a work summary. */\nfunction parseWorkMeta(summary: string | undefined): {\n techStack: string;\n client: string;\n narrative: string;\n} {\n if (!summary) return { techStack: '', client: '', narrative: '' };\n const text = stripHtml(summary).replace(/\\n/g, ' ').trim();\n if (!isStructuredMeta(text)) return { techStack: '', client: '', narrative: text };\n\n let remaining = text;\n\n const clientMatch = remaining.match(/Client:\\s*(.+)$/i);\n const client = clientMatch ? clientMatch[1].trim() : '';\n if (clientMatch) remaining = remaining.slice(0, clientMatch.index).trim();\n\n const techMatch = remaining.match(/Tech-stack:\\s*(.+)/i);\n const techStack = techMatch ? techMatch[1].trim() : '';\n if (techMatch) remaining = remaining.slice(0, techMatch.index).trim();\n\n return { techStack, client, narrative: remaining };\n}\n\nfunction renderWorkEntry(entry: ResumeWorkEntry): string {\n const { techStack, client, narrative } = parseWorkMeta(entry.summary);\n const duration = dateRange(entry.startDate, entry.endDate);\n\n return `\n <div class=\"work-entry\">\n <div class=\"work-header\">\n <div class=\"work-title\">${esc(entry.name)} - ${esc(entry.position)}</div>\n <div class=\"work-duration\">${duration ? `Duration: ${esc(duration)}` : ''}</div>\n </div>\n ${\n techStack || client\n ? `\n <div class=\"work-meta\">\n <div class=\"work-tech\">${techStack ? `Tech-stack: ${esc(techStack)}` : ''}</div>\n <div class=\"work-client\">${client ? `Client: ${esc(client)}` : ''}</div>\n </div>`\n : ''\n }\n ${narrative ? `<p class=\"work-summary\">${richText(narrative, { block: false })}</p>` : ''}\n ${\n has(entry.highlights)\n ? `\n <ul class=\"work-highlights\">\n ${entry.highlights.map((h) => `<li>${richText(h)}</li>`).join('\\n ')}\n </ul>`\n : ''\n }\n </div>`;\n}\n\nexport function renderWork(work: readonly ResumeWorkEntry[] | undefined, heading: string): string {\n if (!has(work)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${work.map(renderWorkEntry).join('')}\n </div>`;\n}\n","import type { ResumeProject } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderProjects(\n projects: readonly ResumeProject[] | undefined,\n heading: string,\n): string {\n if (!has(projects)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${projects\n .map((p) => {\n const desc = richText(p.description);\n return `\n <div class=\"bullet-item\">\n <span class=\"project-name\">${esc(p.name)}:</span> ${desc}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeEducationEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { formatDate } from '../utils/dates.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nfunction renderEducationEntry(entry: ResumeEducationEntry): string {\n const degree = [entry.studyType, entry.area].filter(Boolean).join(' ');\n\n let yearDisplay = '';\n if (entry.endDate) {\n yearDisplay = formatDate(entry.endDate);\n } else if (entry.startDate) {\n const year = parseInt(String(entry.startDate).split('-')[0], 10);\n const currentYear = new Date().getFullYear();\n yearDisplay = year >= currentYear ? `Expected ${formatDate(entry.startDate)}` : formatDate(entry.startDate);\n }\n\n const instParts = [entry.institution];\n if (entry.score) instParts.push(entry.score);\n const instLine = instParts.filter(Boolean).join(' | ');\n\n return `\n <div class=\"edu-entry\">\n <div class=\"edu-header\">\n <div class=\"edu-degree\">${esc(degree)}</div>\n <div class=\"edu-year\">${esc(yearDisplay)}</div>\n </div>\n ${entry.institution ? `<div class=\"edu-institution\">${esc(instLine)}</div>` : ''}\n ${\n has(entry.courses)\n ? `<div class=\"edu-courses\">Coursework: ${entry.courses.map((c) => richText(c)).join(', ')}</div>`\n : ''\n }\n </div>`;\n}\n\nexport function renderEducation(\n education: readonly ResumeEducationEntry[] | undefined,\n heading: string,\n): string {\n if (!has(education)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${education.map(renderEducationEntry).join('')}\n </div>`;\n}\n","import type { ResumeVolunteerEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderVolunteer(\n volunteer: readonly ResumeVolunteerEntry[] | undefined,\n heading: string,\n): string {\n if (!has(volunteer)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${volunteer\n .map((v) => {\n const years = v.endDate\n ? `${v.startDate}\\u2013${v.endDate}`\n : v.startDate || '';\n const title = v.position\n ? `${v.position} \\u2013 ${v.organization}`\n : v.organization;\n const summary = richText(v.summary);\n\n return `\n <div class=\"bullet-item volunteer-item\">\n <span class=\"vol-title\">${esc(title)}${years ? ` (${esc(years)})` : ''}:</span> ${summary}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeCertificate } from '../types/resume.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderCertificates(\n certificates: readonly ResumeCertificate[] | undefined,\n heading: string,\n): string {\n if (!has(certificates)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${certificates\n .map(\n (c) => `\n <div class=\"bullet-item\">${richText(c.name)}</div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeSchema } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderAdditional(resume: ResumeSchema, heading: string): string {\n const parts: string[] = [];\n\n if (has(resume.interests)) {\n for (const interest of resume.interests) {\n const keywords = has(interest.keywords) ? interest.keywords.join(', ') : '';\n const label = interest.name || 'Interests';\n if (keywords) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">${esc(label)}:</span> ${esc(keywords)}</div>`,\n );\n }\n }\n }\n\n if (has(resume.languages)) {\n const langStr = resume.languages\n .map((l) => `${l.language}${l.fluency ? ` (${l.fluency})` : ''}`)\n .join(', ');\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Languages:</span> ${esc(langStr)}</div>`,\n );\n }\n\n if (has(resume.references)) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Referees:</span> Available on request</div>`,\n );\n }\n\n if (parts.length === 0) return '';\n\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${parts.join('\\n ')}\n </div>`;\n}\n","import type { ResumeSchema } from './types/resume.js';\nimport { DEFAULT_HEADINGS } from './constants.js';\nimport { esc } from './utils/escape.js';\nimport { renderHeader } from './sections/header.js';\nimport { renderSummary } from './sections/summary.js';\nimport { renderSkills } from './sections/skills.js';\nimport { renderWork } from './sections/work.js';\nimport { renderProjects } from './sections/projects.js';\nimport { renderEducation } from './sections/education.js';\nimport { renderVolunteer } from './sections/volunteer.js';\nimport { renderCertificates } from './sections/certificates.js';\nimport { renderAdditional } from './sections/additional.js';\nimport css from './styles/academic.css?inline';\n\nexport function render(resume: ResumeSchema): string {\n const h = { ...DEFAULT_HEADINGS };\n\n const metaHeadings = resume?.meta?.headings;\n if (metaHeadings) {\n for (const [key, value] of Object.entries(metaHeadings)) {\n if (value && key in h) {\n h[key] = value;\n }\n }\n }\n\n const basics = resume?.basics || {};\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${esc(basics.name || 'Resume')}</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap\" rel=\"stylesheet\" />\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css\" />\n <style>${css}</style>\n</head>\n<body>\n <div class=\"resume\">\n ${renderHeader(basics)}\n ${renderSummary(basics.summary, h.summary)}\n ${renderSkills(resume.skills, h.skills)}\n ${renderWork(resume.work, h.experience)}\n ${renderProjects(resume.projects, h.projects)}\n ${renderEducation(resume.education, h.education)}\n ${renderVolunteer(resume.volunteer, h.volunteer)}\n ${renderCertificates(resume.certificates, h.certifications)}\n ${renderAdditional(resume, h.additional)}\n </div>\n</body>\n</html>`;\n}\n","export { render } from './render.js';\nexport type { ResumeSchema } from './types/resume.js';\n\n/** PDF rendering options for Puppeteer/Gotenberg. */\nexport const pdfRenderOptions = {\n mediaType: 'print' as const,\n format: 'A4' as const,\n margin: {\n top: '12mm',\n right: '14mm',\n bottom: '12mm',\n left: '14mm',\n },\n};\n"],"names":[],"mappings":";;AAAO,MAAM,SAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,gBAAkD;AAAA,EAC7D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEO,MAAM,mBAAqD;AAAA,EAChE,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AAAA,EACV,WAAW;AAAA,EACX,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,YAAY;AACd;AC7CO,SAAS,eAAe,MAAsB;AACnD,SAAO,KACJ,QAAQ,aAAa,CAAC,GAAG,MAAM,OAAO,aAAa,OAAO,CAAC,CAAC,CAAC,EAC7D,QAAQ,uBAAuB,CAAC,GAAG,MAAM,OAAO,aAAa,SAAS,GAAG,EAAE,CAAC,CAAC,EAC7E,QAAQ,UAAU,GAAG,EACrB,QAAQ,SAAS,GAAG,EACpB,QAAQ,SAAS,GAAG,EACpB,QAAQ,WAAW,GAAG,EACtB,QAAQ,UAAU,GAAG,EACrB,QAAQ,WAAW,GAAG,EACtB,QAAQ,WAAW,GAAG,EACtB,QAAQ,YAAY,GAAQ,EAC5B,QAAQ,YAAY,GAAQ,EAC5B,QAAQ,aAAa,GAAQ;AAClC;AAGO,SAAS,IAAI,MAAyC;AAC3D,MAAI,QAAQ,KAAM,QAAO;AACzB,SAAO,OAAO,IAAI,EACf,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;ACxBO,SAAS,WAAW,SAA4C;AACrE,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,QAAQ,kBAAkB,UAAW,QAAO;AAChD,QAAM,QAAQ,OAAO,OAAO,EAAE,MAAM,GAAG;AACvC,MAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC;AACtC,QAAM,OAAO,MAAM,CAAC;AACpB,QAAM,QAAQ,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK;AACpD,SAAO,GAAG,KAAK,IAAI,IAAI;AACzB;AAEO,SAAS,UAAU,OAAkC,KAAwC;AAClG,QAAM,IAAI,WAAW,KAAK;AAC1B,QAAM,IAAI,WAAW,GAAG;AACxB,MAAI,CAAC,KAAK,MAAM,UAAW,QAAO;AAClC,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,GAAG,CAAC,OAAO,CAAC;AACrB;AAEO,SAAS,WAAW,MAAyC;AAClE,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,cAAc,KAAK,YAAA,CAAa,KAAK;AAC9C;ACvBO,SAAS,YAAY,SAA4C;AACtE,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,IAAI,QAAQ,YAAA;AAClB,MAAI,MAAM,WAAY,QAAO;AAC7B,MAAI,MAAM,SAAU,QAAO;AAC3B,MAAI,MAAM,aAAa,MAAM,IAAK,QAAO;AACzC,MAAI,MAAM,gBAAiB,QAAO;AAClC,SAAO;AACT;ACPA,MAAM,iCAAiB,IAAI,CAAC,KAAK,MAAM,MAAM,MAAM,YAAY,CAAC;AAChE,MAAM,kCAAkB,IAAI;AAAA,EAC1B;AAAA,EAAM;AAAA,EAAK;AAAA,EAAU;AAAA,EAAK;AAAA,EAAM;AAAA,EAAK;AAAA,EAAK;AAAA,EAAQ;AAAA,EAAK;AAAA,EAAO;AAAA,EAAO;AACvE,CAAC;AACD,MAAM,mCAAmB,IAAI,CAAC,GAAG,YAAY,GAAG,WAAW,CAAC;AAG5D,MAAM,mBAAmB;AACzB,MAAM,eAAe;AAErB,SAAS,UAAU,MAAuB;AACxC,QAAM,UAAU,KAAK,KAAA;AACrB,MAAI,CAAC,QAAS,QAAO;AACrB,SAAO,iBAAiB,KAAK,OAAO,KAAK,aAAa,KAAK,OAAO;AACpE;AAWO,SAAS,aAAa,MAAiC,EAAE,SAAS,MAAA,IAA2B,CAAA,GAAY;AAC9G,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,UAAU,SAAS,cAAc;AAEvC,SAAO,OAAO,IAAI,EACf,QAAQ,oCAAoC,CAAC,GAAG,SAAiB;AAChE,QAAI,CAAC,UAAU,IAAI,EAAG,QAAO;AAC7B,WAAO,YAAY,KAAK,QAAQ,MAAM,QAAQ,CAAC;AAAA,EACjD,CAAC,EACA;AAAA,IAAQ;AAAA,IAAwC,CAAC,GAAG,QACnD,gBAAgB,IAAI,QAAQ,MAAM,QAAQ,CAAC;AAAA,EAAA,EAE5C,QAAQ,2CAA2C,CAAC,OAAO,OAAe,KAAa,cAAsB;AAC5G,UAAM,QAAQ,IAAI,YAAA;AAClB,QAAI,UAAU,OAAO,CAAC,SAAS,MAAM,SAAS,OAAO,EAAG,QAAO;AAC/D,QAAI,UAAU,UAAU,CAAC,SAAS,MAAM,SAAS,QAAQ,EAAG,QAAO;AACnE,QAAI,QAAQ,IAAI,KAAK,EAAG,QAAO,IAAI,KAAK,GAAG,KAAK,GAAG,SAAS;AAC5D,QAAI,UAAU,WAAW,IAAI,KAAK,EAAG,QAAO,QAAQ,MAAM;AAC1D,WAAO;AAAA,EACT,CAAC,EACA,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,WAAW,GAAG,EACtB,KAAA;AACL;AC7CO,SAAS,UAAU,MAAyC;AACjE,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO;AAAA,IACL,OAAO,IAAI,EACR,QAAQ,gBAAgB,GAAG,EAC3B,QAAQ,0BAA0B,GAAG,EACrC,QAAQ,YAAY,EAAE;AAAA,EAAA,EAExB,QAAQ,WAAW,GAAG,EACtB,KAAA;AACL;AAEA,MAAM,SAAS,CAAC,SAA0B,kBAAkB,KAAK,IAAI;AACrE,MAAM,cAAc,CAAC,SAA0B,yCAAyC,KAAK,IAAI;AAO1F,SAAS,SAAS,MAAiC,EAAE,QAAQ,MAAA,IAA+B,CAAA,GAAY;AAC7G,MAAI,QAAQ,KAAM,QAAO;AACzB,QAAM,IAAI,OAAO,IAAI;AACrB,MAAI,OAAO,CAAC,EAAG,QAAO,aAAa,GAAG,EAAE,QAAQ,CAAC,OAAO;AACxD,MAAI,YAAY,CAAC,UAAU,IAAI,eAAe,CAAC,CAAC;AAChD,SAAO,IAAI,CAAC;AACd;AAGO,SAAS,IAAO,KAAgF;AACrG,SAAO,MAAM,QAAQ,GAAG,KAAK,IAAI,SAAS;AAC5C;ACxBO,SAAS,aAAa,QAA0C;AACrE,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,WAAW,OAAO,WACpB,CAAC,OAAO,SAAS,MAAM,WAAW,OAAO,SAAS,MAAM,CAAC,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,IACpF;AAEJ,QAAM,QAAuB,CAAA;AAC7B,MAAI;AACF,UAAM,KAAK,EAAE,MAAM,4CAA4C,MAAM,UAAU;AACjF,MAAI,OAAO;AACT,UAAM,KAAK,EAAE,MAAM,4CAA4C,MAAM,OAAO,OAAO;AACrF,MAAI,OAAO;AACT,UAAM,KAAK,EAAE,MAAM,wCAAwC,MAAM,OAAO,OAAO;AACjF,MAAI,OAAO,KAAK;AACd,UAAM,UAAU,OAAO,IAAI,QAAQ,gBAAgB,EAAE;AACrD,UAAM,KAAK,EAAE,MAAM,qCAAqC,MAAM,SAAS;AAAA,EACzE;AACA,MAAI,IAAI,OAAO,QAAQ,GAAG;AACxB,eAAW,KAAK,OAAO,UAAU;AAC/B,YAAM,KAAK,EAAE,MAAM,YAAY,EAAE,OAAO,GAAG,MAAM,EAAE,YAAY,EAAE,OAAO,IAAI;AAAA,IAC9E;AAAA,EACF;AAEA,SAAO;AAAA;AAAA;AAAA,2BAGkB,IAAI,OAAO,IAAI,CAAC;AAAA,6BACd,IAAI,OAAO,KAAK,CAAC;AAAA;AAAA;AAAA,UAGpC,MAAM,IAAI,CAAC,MAAM,6BAA6B,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,QAAQ,EAAE,KAAK,YAAY,CAAC;AAAA;AAAA;AAAA;AAIzG;AC5CO,SAAS,aAAa,OAAuB;AAClD,SAAO,6BAA6B,IAAI,KAAK,CAAC;AAChD;ACDO,SAAS,cAAc,SAA6B,SAAyB;AAClF,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,OAAO,SAAS,SAAS,EAAE,OAAO,MAAM;AAC9C,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA,2BACA,IAAI;AAC/B;ACLO,SAAS,aAAa,QAA4C,SAAyB;AAChG,MAAI,CAAC,IAAI,MAAM,EAAG,QAAO;AACzB,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA;AAAA,QAEnB,OACC;AAAA,IACC,CAAC,MAAM;AAAA;AAAA,mCAEkB,IAAI,EAAE,IAAI,CAAC,YAAY,KAAK,EAAE,YAAY,CAAA,GAAI,KAAK,IAAI,CAAC,CAAC;AAAA;AAAA,EAAA,EAGnF,KAAK,EAAE,CAAC;AAAA;AAEjB;ACZA,SAAS,iBAAiB,MAAuB;AAC/C,SAAO,iBAAiB,KAAK,IAAI,KAAK,aAAa,KAAK,IAAI;AAC9D;AAGA,SAAS,cAAc,SAIrB;AACA,MAAI,CAAC,QAAS,QAAO,EAAE,WAAW,IAAI,QAAQ,IAAI,WAAW,GAAA;AAC7D,QAAM,OAAO,UAAU,OAAO,EAAE,QAAQ,OAAO,GAAG,EAAE,KAAA;AACpD,MAAI,CAAC,iBAAiB,IAAI,EAAG,QAAO,EAAE,WAAW,IAAI,QAAQ,IAAI,WAAW,KAAA;AAE5E,MAAI,YAAY;AAEhB,QAAM,cAAc,UAAU,MAAM,kBAAkB;AACtD,QAAM,SAAS,cAAc,YAAY,CAAC,EAAE,SAAS;AACrD,MAAI,yBAAyB,UAAU,MAAM,GAAG,YAAY,KAAK,EAAE,KAAA;AAEnE,QAAM,YAAY,UAAU,MAAM,qBAAqB;AACvD,QAAM,YAAY,YAAY,UAAU,CAAC,EAAE,SAAS;AACpD,MAAI,uBAAuB,UAAU,MAAM,GAAG,UAAU,KAAK,EAAE,KAAA;AAE/D,SAAO,EAAE,WAAW,QAAQ,WAAW,UAAA;AACzC;AAEA,SAAS,gBAAgB,OAAgC;AACvD,QAAM,EAAE,WAAW,QAAQ,cAAc,cAAc,MAAM,OAAO;AACpE,QAAM,WAAW,UAAU,MAAM,WAAW,MAAM,OAAO;AAEzD,SAAO;AAAA;AAAA;AAAA,kCAGyB,IAAI,MAAM,IAAI,CAAC,MAAM,IAAI,MAAM,QAAQ,CAAC;AAAA,qCACrC,WAAW,aAAa,IAAI,QAAQ,CAAC,KAAK,EAAE;AAAA;AAAA,QAGzE,aAAa,SACT;AAAA;AAAA,iCAEqB,YAAY,eAAe,IAAI,SAAS,CAAC,KAAK,EAAE;AAAA,mCAC9C,SAAS,WAAW,IAAI,MAAM,CAAC,KAAK,EAAE;AAAA,gBAE7D,EACN;AAAA,QACE,YAAY,2BAA2B,SAAS,WAAW,EAAE,OAAO,MAAA,CAAO,CAAC,SAAS,EAAE;AAAA,QAEvF,IAAI,MAAM,UAAU,IAChB;AAAA;AAAA,UAEF,MAAM,WAAW,IAAI,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC,OAAO,EAAE,KAAK,YAAY,CAAC;AAAA,eAEvE,EACN;AAAA;AAEN;AAEO,SAAS,WAAW,MAA8C,SAAyB;AAChG,MAAI,CAAC,IAAI,IAAI,EAAG,QAAO;AACvB,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA;AAAA,QAEnB,KAAK,IAAI,eAAe,EAAE,KAAK,EAAE,CAAC;AAAA;AAE1C;ACnEO,SAAS,eACd,UACA,SACQ;AACR,MAAI,CAAC,IAAI,QAAQ,EAAG,QAAO;AAC3B,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA;AAAA,QAEnB,SACC,IAAI,CAAC,MAAM;AACV,UAAM,OAAO,SAAS,EAAE,WAAW;AACnC,WAAO;AAAA;AAAA,qCAEoB,IAAI,EAAE,IAAI,CAAC,YAAY,IAAI;AAAA;AAAA,EAExD,CAAC,EACA,KAAK,EAAE,CAAC;AAAA;AAEjB;ACjBA,SAAS,qBAAqB,OAAqC;AACjE,QAAM,SAAS,CAAC,MAAM,WAAW,MAAM,IAAI,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAErE,MAAI,cAAc;AAClB,MAAI,MAAM,SAAS;AACjB,kBAAc,WAAW,MAAM,OAAO;AAAA,EACxC,WAAW,MAAM,WAAW;AAC1B,UAAM,OAAO,SAAS,OAAO,MAAM,SAAS,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE;AAC/D,UAAM,eAAc,oBAAI,KAAA,GAAO,YAAA;AAC/B,kBAAc,QAAQ,cAAc,YAAY,WAAW,MAAM,SAAS,CAAC,KAAK,WAAW,MAAM,SAAS;AAAA,EAC5G;AAEA,QAAM,YAAY,CAAC,MAAM,WAAW;AACpC,MAAI,MAAM,MAAO,WAAU,KAAK,MAAM,KAAK;AAC3C,QAAM,WAAW,UAAU,OAAO,OAAO,EAAE,KAAK,KAAK;AAErD,SAAO;AAAA;AAAA;AAAA,kCAGyB,IAAI,MAAM,CAAC;AAAA,gCACb,IAAI,WAAW,CAAC;AAAA;AAAA,QAExC,MAAM,cAAc,gCAAgC,IAAI,QAAQ,CAAC,WAAW,EAAE;AAAA,QAE9E,IAAI,MAAM,OAAO,IACb,wCAAwC,MAAM,QAAQ,IAAI,CAAC,MAAM,SAAS,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,WACxF,EACN;AAAA;AAEN;AAEO,SAAS,gBACd,WACA,SACQ;AACR,MAAI,CAAC,IAAI,SAAS,EAAG,QAAO;AAC5B,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA;AAAA,QAEnB,UAAU,IAAI,oBAAoB,EAAE,KAAK,EAAE,CAAC;AAAA;AAEpD;AC1CO,SAAS,gBACd,WACA,SACQ;AACR,MAAI,CAAC,IAAI,SAAS,EAAG,QAAO;AAC5B,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA;AAAA,QAEnB,UACC,IAAI,CAAC,MAAM;AACV,UAAM,QAAQ,EAAE,UACZ,GAAG,EAAE,SAAS,IAAS,EAAE,OAAO,KAChC,EAAE,aAAa;AACnB,UAAM,QAAQ,EAAE,WACZ,GAAG,EAAE,QAAQ,MAAW,EAAE,YAAY,KACtC,EAAE;AACN,UAAM,UAAU,SAAS,EAAE,OAAO;AAElC,WAAO;AAAA;AAAA,kCAEiB,IAAI,KAAK,CAAC,GAAG,QAAQ,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO;AAAA;AAAA,EAEzF,CAAC,EACA,KAAK,EAAE,CAAC;AAAA;AAEjB;AC1BO,SAAS,mBACd,cACA,SACQ;AACR,MAAI,CAAC,IAAI,YAAY,EAAG,QAAO;AAC/B,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA;AAAA,QAEnB,aACC;AAAA,IACC,CAAC,MAAM;AAAA,iCACgB,SAAS,EAAE,IAAI,CAAC;AAAA,EAAA,EAExC,KAAK,EAAE,CAAC;AAAA;AAEjB;ACdO,SAAS,iBAAiB,QAAsB,SAAyB;AAC9E,QAAM,QAAkB,CAAA;AAExB,MAAI,IAAI,OAAO,SAAS,GAAG;AACzB,eAAW,YAAY,OAAO,WAAW;AACvC,YAAM,WAAW,IAAI,SAAS,QAAQ,IAAI,SAAS,SAAS,KAAK,IAAI,IAAI;AACzE,YAAM,QAAQ,SAAS,QAAQ;AAC/B,UAAI,UAAU;AACZ,cAAM;AAAA,UACJ,+DAA+D,IAAI,KAAK,CAAC,YAAY,IAAI,QAAQ,CAAC;AAAA,QAAA;AAAA,MAEtG;AAAA,IACF;AAAA,EACF;AAEA,MAAI,IAAI,OAAO,SAAS,GAAG;AACzB,UAAM,UAAU,OAAO,UACpB,IAAI,CAAC,MAAM,GAAG,EAAE,QAAQ,GAAG,EAAE,UAAU,KAAK,EAAE,OAAO,MAAM,EAAE,EAAE,EAC/D,KAAK,IAAI;AACZ,UAAM;AAAA,MACJ,iFAAiF,IAAI,OAAO,CAAC;AAAA,IAAA;AAAA,EAEjG;AAEA,MAAI,IAAI,OAAO,UAAU,GAAG;AAC1B,UAAM;AAAA,MACJ;AAAA,IAAA;AAAA,EAEJ;AAEA,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SAAO;AAAA,MACH,aAAa,OAAO,CAAC;AAAA;AAAA,QAEnB,MAAM,KAAK,UAAU,CAAC;AAAA;AAE9B;;AC5BO,SAAS,OAAO,QAA8B;;AACnD,QAAM,IAAI,EAAE,GAAG,iBAAA;AAEf,QAAM,gBAAe,sCAAQ,SAAR,mBAAc;AACnC,MAAI,cAAc;AAChB,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,UAAI,SAAS,OAAO,GAAG;AACrB,UAAE,GAAG,IAAI;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAS,iCAAQ,WAAU,CAAA;AAEjC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,WAKE,IAAI,OAAO,QAAQ,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,WAK5B,GAAG;AAAA;AAAA;AAAA;AAAA,MAIR,aAAa,MAAM,CAAC;AAAA,MACpB,cAAc,OAAO,SAAS,EAAE,OAAO,CAAC;AAAA,MACxC,aAAa,OAAO,QAAQ,EAAE,MAAM,CAAC;AAAA,MACrC,WAAW,OAAO,MAAM,EAAE,UAAU,CAAC;AAAA,MACrC,eAAe,OAAO,UAAU,EAAE,QAAQ,CAAC;AAAA,MAC3C,gBAAgB,OAAO,WAAW,EAAE,SAAS,CAAC;AAAA,MAC9C,gBAAgB,OAAO,WAAW,EAAE,SAAS,CAAC;AAAA,MAC9C,mBAAmB,OAAO,cAAc,EAAE,cAAc,CAAC;AAAA,MACzD,iBAAiB,QAAQ,EAAE,UAAU,CAAC;AAAA;AAAA;AAAA;AAI5C;AClDO,MAAM,mBAAmB;AAAA,EAC9B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,IACN,KAAK;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,EAAA;AAEV;;;"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/constants.ts","../src/utils/escape.ts","../src/utils/dates.ts","../src/utils/icons.ts","../src/utils/sanitize.ts","../src/utils/text.ts","../src/sections/header.ts","../src/sections/shared.ts","../src/sections/summary.ts","../src/sections/skills.ts","../src/sections/work.ts","../src/sections/projects.ts","../src/sections/education.ts","../src/sections/volunteer.ts","../src/sections/certificates.ts","../src/sections/additional.ts","../src/render.ts","../src/index.ts"],"sourcesContent":["export const MONTHS: readonly string[] = [\r\n 'January',\r\n 'February',\r\n 'March',\r\n 'April',\r\n 'May',\r\n 'June',\r\n 'July',\r\n 'August',\r\n 'September',\r\n 'October',\r\n 'November',\r\n 'December',\r\n];\r\n\r\nexport const COUNTRY_NAMES: Readonly<Record<string, string>> = {\r\n GB: 'United Kingdom',\r\n US: 'United States',\r\n CA: 'Canada',\r\n AU: 'Australia',\r\n DE: 'Germany',\r\n FR: 'France',\r\n IN: 'India',\r\n JP: 'Japan',\r\n CN: 'China',\r\n BR: 'Brazil',\r\n NL: 'Netherlands',\r\n IT: 'Italy',\r\n ES: 'Spain',\r\n SE: 'Sweden',\r\n CH: 'Switzerland',\r\n SG: 'Singapore',\r\n NZ: 'New Zealand',\r\n IE: 'Ireland',\r\n AE: 'UAE',\r\n};\r\n\r\nexport const DEFAULT_HEADINGS: Readonly<Record<string, string>> = {\r\n summary: 'Summary',\r\n skills: 'Core Skills',\r\n experience: 'Experience',\r\n projects: 'Selected Projects',\r\n education: 'Education',\r\n volunteer: 'Leadership & Volunteering',\r\n certifications: 'Credentials',\r\n additional: 'Additional',\r\n};\r\n","/** Decode common HTML entities to literal characters. */\nexport function decodeEntities(text: string): string {\n return text\n .replace(/&#(\\d+);/g, (_, c) => String.fromCharCode(Number(c)))\n .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCharCode(parseInt(h, 16)))\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ')\n .replace(/—/g, '\\u2014')\n .replace(/–/g, '\\u2013')\n .replace(/…/g, '\\u2026');\n}\n\n/** HTML-escape for safe output (plain text fields like names, dates). */\nexport function esc(text: string | null | undefined): string {\n if (text == null) return '';\n return String(text)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","import { MONTHS, COUNTRY_NAMES } from '../constants.js';\n\nexport function formatDate(dateStr: string | null | undefined): string {\n if (!dateStr) return 'Present';\n if (dateStr.toLowerCase() === 'present') return 'Present';\n const parts = String(dateStr).split('-');\n if (parts.length === 1) return parts[0];\n const year = parts[0];\n const month = MONTHS[parseInt(parts[1], 10) - 1] || '';\n return `${month} ${year}`;\n}\n\nexport function dateRange(start: string | null | undefined, end: string | null | undefined): string {\n const s = formatDate(start);\n const e = formatDate(end);\n if (!s || s === 'Present') return ''; // start date should not be empty or present\n if (!e) return s;\n return `${s} to ${e}`;\n}\n\nexport function regionName(code: string | null | undefined): string {\n if (!code) return '';\n return COUNTRY_NAMES[code.toUpperCase()] || code;\n}\n","export function profileIcon(network: string | null | undefined): string {\n if (!network) return '<i class=\"fa-solid fa-link\"></i>';\n const n = network.toLowerCase();\n if (n === 'linkedin') return '<i class=\"fa-brands fa-linkedin-in\"></i>';\n if (n === 'github') return '<i class=\"fa-brands fa-github\"></i>';\n if (n === 'twitter' || n === 'x') return '<i class=\"fa-brands fa-x-twitter\"></i>';\n if (n === 'stackoverflow') return '<i class=\"fa-brands fa-stack-overflow\"></i>';\n return '<i class=\"fa-solid fa-link\"></i>';\n}\n","/** Tags allowed through sanitization — safe inline/block formatting. */\nconst BLOCK_TAGS = new Set(['p', 'ul', 'ol', 'li', 'blockquote']);\nconst INLINE_TAGS = new Set([\n 'br', 'b', 'strong', 'i', 'em', 'u', 's', 'mark', 'a', 'sub', 'sup', 'code',\n]);\nconst ALLOWED_TAGS = new Set([...BLOCK_TAGS, ...INLINE_TAGS]);\n\n/** URL schemes safe for <a href>. Everything else (javascript:, data:, vbscript:) is stripped. */\nconst SAFE_URL_SCHEMES = /^(?:https?|mailto|tel):/i;\nconst RELATIVE_URL = /^[#/?.]/;\n\nfunction isSafeUrl(href: string): boolean {\n const trimmed = href.trim();\n if (!trimmed) return false;\n return SAFE_URL_SCHEMES.test(trimmed) || RELATIVE_URL.test(trimmed);\n}\n\ninterface SanitizeOptions {\n readonly inline?: boolean;\n}\n\n/**\n * Sanitize HTML — keep allowed formatting tags, strip everything else.\n * Attributes are stripped from all tags except <a href> (safe schemes only) and <mark class>.\n * If inline=true, block tags (p, ul, ol, li, blockquote) are also stripped.\n */\nexport function sanitizeHtml(html: string | null | undefined, { inline = false }: SanitizeOptions = {}): string {\n if (!html) return '';\n const allowed = inline ? INLINE_TAGS : ALLOWED_TAGS;\n\n return String(html)\n .replace(/<a\\s+[^>]*href=\"([^\"]*)\"[^>]*>/gi, (_, href: string) => {\n if (!isSafeUrl(href)) return '';\n return `<a href=\"${href.replace(/\"/g, '"')}\" target=\"_blank\" rel=\"noopener\">`;\n })\n .replace(/<mark\\s+[^>]*class=\"([^\"]*)\"[^>]*>/gi, (_, cls: string) =>\n `<mark class=\"${cls.replace(/\"/g, '"')}\">`,\n )\n .replace(/<(\\/?)([a-z][a-z0-9]*)\\s*[^>]*?(\\/?)>/gi, (match, slash: string, tag: string, selfClose: string) => {\n const lower = tag.toLowerCase();\n if (lower === 'a' && !slash && match.includes('href=')) return match;\n if (lower === 'mark' && !slash && match.includes('class=')) return match;\n if (allowed.has(lower)) return `<${slash}${lower}${selfClose}>`;\n if (inline && BLOCK_TAGS.has(lower)) return slash ? ' ' : '';\n return '';\n })\n .replace(/<p>\\s*<\\/p>/gi, '')\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n","import { decodeEntities, esc } from './escape.js';\nimport { sanitizeHtml } from './sanitize.js';\n\n/** Strip HTML tags → plain text (for fields parsed as data, not displayed). */\nexport function stripHtml(text: string | null | undefined): string {\n if (!text) return '';\n return decodeEntities(\n String(text)\n .replace(/<br\\s*\\/?>/gi, ' ')\n .replace(/<\\/?(p|div|li)[^>]*>/gi, ' ')\n .replace(/<[^>]+>/g, ''),\n )\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n\nconst isHtml = (text: string): boolean => /<[a-z][\\s\\S]*>/i.test(text);\nconst hasEntities = (text: string): boolean => /&(?:amp|lt|gt|quot|#\\d+|#x[0-9a-f]+);/i.test(text);\n\n/**\n * Render user-provided rich text → safe HTML output.\n * block=true: preserve <p>, <ul>, <ol> etc.\n * block=false (default): inline mode — strip block tags, keep only inline formatting.\n */\nexport function richText(text: string | null | undefined, { block = false }: { block?: boolean } = {}): string {\n if (text == null) return '';\n const s = String(text);\n if (isHtml(s)) return sanitizeHtml(s, { inline: !block });\n if (hasEntities(s)) return esc(decodeEntities(s));\n return esc(s);\n}\n\n/** Check if an array is non-empty. */\nexport function has<T>(arr: readonly T[] | null | undefined): arr is readonly T[] & { length: number } {\n return Array.isArray(arr) && arr.length > 0;\n}\n","import type { ResumeBasics } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { regionName } from '../utils/dates.js';\nimport { profileIcon } from '../utils/icons.js';\nimport { has } from '../utils/text.js';\n\ninterface ContactLine {\n readonly icon: string;\n readonly text: string;\n}\n\nexport function renderHeader(basics: ResumeBasics | undefined): string {\n if (!basics) return '';\n\n const location = basics.location\n ? [basics.location.city, regionName(basics.location.region)].filter(Boolean).join(', ')\n : '';\n\n const lines: ContactLine[] = [];\n if (location)\n lines.push({ icon: '<i class=\"fa-solid fa-location-dot\"></i>', text: location });\n if (basics.phone)\n lines.push({ icon: '<i class=\"fa-solid fa-square-phone\"></i>', text: basics.phone });\n if (basics.email)\n lines.push({ icon: '<i class=\"fa-solid fa-envelope\"></i>', text: basics.email });\n if (basics.url) {\n const display = basics.url.replace(/^https?:\\/\\//, '');\n lines.push({ icon: '<i class=\"fa-solid fa-globe\"></i>', text: display });\n }\n if (has(basics.profiles)) {\n for (const p of basics.profiles) {\n lines.push({ icon: profileIcon(p.network), text: p.username || p.url || '' });\n }\n }\n\n return `\n <header class=\"header\">\n <div class=\"header-left\">\n <h1 class=\"name\">${esc(basics.name)}</h1>\n <div class=\"label\">${esc(basics.label)}</div>\n </div>\n <div class=\"contact-info\">\n ${lines.map((l) => `<div class=\"contact-line\">${esc(l.text)} ${l.icon}</div>`).join('\\n ')}\n </div>\n </header>\n <hr class=\"header-rule\" />`;\n}\n","import { esc } from '../utils/escape.js';\n\nexport function sectionTitle(title: string): string {\n return `<h2 class=\"section-title\">${esc(title)}</h2>`;\n}\n","import { richText } from '../utils/text.js';\r\nimport { sectionTitle } from './shared.js';\r\n\r\nexport function renderSummary(summary: string | undefined, heading: string): string {\r\n if (!summary) return '';\r\n const html = richText(summary, { block: true });\r\n if (!html) return '';\r\n return `\r\n ${sectionTitle(heading)}\r\n <div class=\"summary\">${html}</div>`;\r\n}\r\n","import type { ResumeSkill } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderSkills(skills: readonly ResumeSkill[] | undefined, heading: string): string {\n if (!has(skills)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${skills\n .map(\n (s) => `\n <div class=\"bullet-item\">\n <span class=\"skill-name\">${esc(s.name)}:</span> ${esc((s.keywords || []).join(', '))}\n </div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeWorkEntry } from '../types/resume.js';\r\nimport { esc } from '../utils/escape.js';\r\nimport { dateRange } from '../utils/dates.js';\r\nimport { has, richText, stripHtml } from '../utils/text.js';\r\nimport { sectionTitle } from './shared.js';\r\n\r\n/** Check whether summary uses structured Tech-stack / Client format. */\r\nfunction isStructuredMeta(text: string): boolean {\r\n return /Tech-stack:\\s/i.test(text) || /Client:\\s/i.test(text);\r\n}\r\n\r\n/** Extract tech-stack, client, and any remaining narrative from a work summary. */\r\nfunction parseWorkMeta(summary: string | undefined): {\r\n techStack: string;\r\n client: string;\r\n narrative: string;\r\n} {\r\n if (!summary) return { techStack: '', client: '', narrative: '' };\r\n const text = stripHtml(summary).replace(/\\n/g, ' ').trim();\r\n if (!isStructuredMeta(text)) return { techStack: '', client: '', narrative: text };\r\n\r\n let remaining = text;\r\n\r\n const clientMatch = remaining.match(/Client:\\s*(.+)$/i);\r\n const client = clientMatch ? clientMatch[1].trim() : '';\r\n if (clientMatch) remaining = remaining.slice(0, clientMatch.index).trim();\r\n\r\n const techMatch = remaining.match(/Tech-stack:\\s*(.+)/i);\r\n const techStack = techMatch ? techMatch[1].trim() : '';\r\n if (techMatch) remaining = remaining.slice(0, techMatch.index).trim();\r\n\r\n return { techStack, client, narrative: remaining };\r\n}\r\n\r\nfunction renderWorkEntry(entry: ResumeWorkEntry): string {\r\n const { techStack, client, narrative } = parseWorkMeta(entry.summary);\r\n const duration = dateRange(entry.startDate, entry.endDate);\r\n\r\n return `\r\n <div class=\"work-entry\">\r\n <div class=\"work-header\">\r\n <div class=\"work-title\">${esc(entry.name)} - ${esc(entry.position)}</div>\r\n <div class=\"work-duration\">${duration ? `Duration: ${esc(duration)}` : ''}</div>\r\n </div>\r\n ${\r\n techStack || client\r\n ? `\r\n <div class=\"work-meta\">\r\n <div class=\"work-tech\">${techStack ? `Tech-stack: ${esc(techStack)}` : ''}</div>\r\n <div class=\"work-client\">${client ? `Client: ${esc(client)}` : ''}</div>\r\n </div>`\r\n : ''\r\n }\r\n ${narrative ? `<p class=\"work-summary\">${richText(narrative, { block: false })}</p>` : ''}\r\n ${\r\n has(entry.highlights)\r\n ? `\r\n <ul class=\"work-highlights\">\r\n ${entry.highlights.map((h) => `<li>${richText(h)}</li>`).join('\\n ')}\r\n </ul>`\r\n : ''\r\n }\r\n </div>`;\r\n}\r\n\r\nexport function renderWork(work: readonly ResumeWorkEntry[] | undefined, heading: string): string {\r\n if (!has(work)) return '';\r\n return `\r\n ${sectionTitle(heading)}\r\n <div class=\"section-body\">\r\n ${work.map(renderWorkEntry).join('')}\r\n </div>`;\r\n}\r\n","import type { ResumeProject } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderProjects(\n projects: readonly ResumeProject[] | undefined,\n heading: string,\n): string {\n if (!has(projects)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${projects\n .map((p) => {\n const desc = richText(p.description);\n return `\n <div class=\"bullet-item\">\n <span class=\"project-name\">${esc(p.name)}:</span> ${desc}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeEducationEntry } from '../types/resume.js';\r\nimport { esc } from '../utils/escape.js';\r\nimport { formatDate } from '../utils/dates.js';\r\nimport { has, richText } from '../utils/text.js';\r\nimport { sectionTitle } from './shared.js';\r\n\r\nfunction renderEducationEntry(entry: ResumeEducationEntry): string {\r\n const degree = [entry.studyType, entry.area].filter(Boolean).join(' ');\r\n\r\n let yearDisplay = '';\r\n if (entry.endDate) {\r\n yearDisplay = formatDate(entry.endDate);\r\n } else if (entry.startDate) {\r\n const year = parseInt(String(entry.startDate).split('-')[0], 10);\r\n const currentYear = new Date().getFullYear();\r\n yearDisplay = year >= currentYear ? `Expected ${formatDate(entry.startDate)}` : formatDate(entry.startDate);\r\n }\r\n\r\n const instParts = [entry.institution];\r\n if (entry.score) instParts.push(entry.score);\r\n const instLine = instParts.filter(Boolean).join(' | ');\r\n\r\n return `\r\n <div class=\"edu-entry\">\r\n <div class=\"edu-header\">\r\n <div class=\"edu-degree\">${esc(degree)}</div>\r\n <div class=\"edu-year\">${esc(yearDisplay)}</div>\r\n </div>\r\n ${entry.institution ? `<div class=\"edu-institution\">${esc(instLine)}</div>` : ''}\r\n ${\r\n has(entry.courses)\r\n ? `<div class=\"edu-courses\">Coursework: ${entry.courses.map((c) => richText(c)).join(', ')}</div>`\r\n : ''\r\n }\r\n </div>`;\r\n}\r\n\r\nexport function renderEducation(\r\n education: readonly ResumeEducationEntry[] | undefined,\r\n heading: string,\r\n): string {\r\n if (!has(education)) return '';\r\n return `\r\n ${sectionTitle(heading)}\r\n <div class=\"section-body\">\r\n ${education.map(renderEducationEntry).join('')}\r\n </div>`;\r\n}\r\n","import type { ResumeVolunteerEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderVolunteer(\n volunteer: readonly ResumeVolunteerEntry[] | undefined,\n heading: string,\n): string {\n if (!has(volunteer)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${volunteer\n .map((v) => {\n const years = v.endDate\n ? `${v.startDate}\\u2013${v.endDate}`\n : v.startDate || '';\n const title = v.position\n ? `${v.position} \\u2013 ${v.organization}`\n : v.organization;\n const summary = richText(v.summary);\n\n return `\n <div class=\"bullet-item volunteer-item\">\n <span class=\"vol-title\">${esc(title)}${years ? ` (${esc(years)})` : ''}:</span> ${summary}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeCertificate } from '../types/resume.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderCertificates(\n certificates: readonly ResumeCertificate[] | undefined,\n heading: string,\n): string {\n if (!has(certificates)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${certificates\n .map(\n (c) => `\n <div class=\"bullet-item\">${richText(c.name)}</div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeSchema } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderAdditional(resume: ResumeSchema, heading: string): string {\n const parts: string[] = [];\n\n if (has(resume.interests)) {\n for (const interest of resume.interests) {\n const keywords = has(interest.keywords) ? interest.keywords.join(', ') : '';\n const label = interest.name || 'Interests';\n if (keywords) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">${esc(label)}:</span> ${esc(keywords)}</div>`,\n );\n }\n }\n }\n\n if (has(resume.languages)) {\n const langStr = resume.languages\n .map((l) => `${l.language}${l.fluency ? ` (${l.fluency})` : ''}`)\n .join(', ');\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Languages:</span> ${esc(langStr)}</div>`,\n );\n }\n\n if (has(resume.references)) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Referees:</span> Available on request</div>`,\n );\n }\n\n if (parts.length === 0) return '';\n\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${parts.join('\\n ')}\n </div>`;\n}\n","import type { ResumeSchema } from './types/resume.js';\r\nimport { DEFAULT_HEADINGS } from './constants.js';\r\nimport { esc } from './utils/escape.js';\r\nimport { renderHeader } from './sections/header.js';\r\nimport { renderSummary } from './sections/summary.js';\r\nimport { renderSkills } from './sections/skills.js';\r\nimport { renderWork } from './sections/work.js';\r\nimport { renderProjects } from './sections/projects.js';\r\nimport { renderEducation } from './sections/education.js';\r\nimport { renderVolunteer } from './sections/volunteer.js';\r\nimport { renderCertificates } from './sections/certificates.js';\r\nimport { renderAdditional } from './sections/additional.js';\r\nimport css from './styles/academic.css?inline';\r\n\r\nexport function render(resume: ResumeSchema): string {\r\n const h = { ...DEFAULT_HEADINGS };\r\n\r\n const metaHeadings = resume?.meta?.headings;\r\n if (metaHeadings) {\r\n for (const [key, value] of Object.entries(metaHeadings)) {\r\n if (value && key in h) {\r\n h[key] = value;\r\n }\r\n }\r\n }\r\n\r\n const basics = resume?.basics || {};\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <title>${esc(basics.name || 'Resume')}</title>\r\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\r\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\r\n <link href=\"https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap\" rel=\"stylesheet\" />\r\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css\" />\r\n <style>${css}</style>\r\n</head>\r\n<body>\r\n <div class=\"resume\">\r\n ${renderHeader(basics)}\r\n ${renderSummary(basics.summary, h.summary)}\r\n ${renderSkills(resume.skills, h.skills)}\r\n ${renderWork(resume.work, h.experience)}\r\n ${renderProjects(resume.projects, h.projects)}\r\n ${renderEducation(resume.education, h.education)}\r\n ${renderVolunteer(resume.volunteer, h.volunteer)}\r\n ${renderCertificates(resume.certificates, h.certifications)}\r\n ${renderAdditional(resume, h.additional)}\r\n </div>\r\n</body>\r\n</html>`;\r\n}\r\n","export { render } from './render.js';\nexport type { ResumeSchema } from './types/resume.js';\n\n/** PDF rendering options for Puppeteer/Gotenberg. */\nexport const pdfRenderOptions = {\n mediaType: 'print' as const,\n format: 'A4' as const,\n margin: {\n top: '12mm',\n right: '14mm',\n bottom: '12mm',\n left: '14mm',\n },\n};\n"],"names":[],"mappings":"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"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/constants.ts","../src/utils/escape.ts","../src/utils/dates.ts","../src/utils/icons.ts","../src/utils/sanitize.ts","../src/utils/text.ts","../src/sections/header.ts","../src/sections/shared.ts","../src/sections/summary.ts","../src/sections/skills.ts","../src/sections/work.ts","../src/sections/projects.ts","../src/sections/education.ts","../src/sections/volunteer.ts","../src/sections/certificates.ts","../src/sections/additional.ts","../src/render.ts","../src/index.ts"],"sourcesContent":["export const MONTHS: readonly string[] = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\n\nexport const COUNTRY_NAMES: Readonly<Record<string, string>> = {\n GB: 'United Kingdom',\n US: 'United States',\n CA: 'Canada',\n AU: 'Australia',\n DE: 'Germany',\n FR: 'France',\n IN: 'India',\n JP: 'Japan',\n CN: 'China',\n BR: 'Brazil',\n NL: 'Netherlands',\n IT: 'Italy',\n ES: 'Spain',\n SE: 'Sweden',\n CH: 'Switzerland',\n SG: 'Singapore',\n NZ: 'New Zealand',\n IE: 'Ireland',\n AE: 'UAE',\n};\n\nexport const DEFAULT_HEADINGS: Readonly<Record<string, string>> = {\n summary: 'Summary',\n skills: 'Core Skills',\n experience: 'Experience',\n projects: 'Selected Projects',\n education: 'Education',\n volunteer: 'Leadership & Volunteering',\n certifications: 'Credentials',\n additional: 'Additional',\n};\n","/** Decode common HTML entities to literal characters. */\nexport function decodeEntities(text: string): string {\n return text\n .replace(/&#(\\d+);/g, (_, c) => String.fromCharCode(Number(c)))\n .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCharCode(parseInt(h, 16)))\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ')\n .replace(/—/g, '\\u2014')\n .replace(/–/g, '\\u2013')\n .replace(/…/g, '\\u2026');\n}\n\n/** HTML-escape for safe output (plain text fields like names, dates). */\nexport function esc(text: string | null | undefined): string {\n if (text == null) return '';\n return String(text)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","import { MONTHS, COUNTRY_NAMES } from '../constants.js';\n\nexport function formatDate(dateStr: string | null | undefined): string {\n if (!dateStr) return 'Present';\n if (dateStr.toLowerCase() === 'present') return 'Present';\n const parts = String(dateStr).split('-');\n if (parts.length === 1) return parts[0];\n const year = parts[0];\n const month = MONTHS[parseInt(parts[1], 10) - 1] || '';\n return `${month} ${year}`;\n}\n\nexport function dateRange(start: string | null | undefined, end: string | null | undefined): string {\n const s = formatDate(start);\n const e = formatDate(end);\n if (!s || s === 'Present') return ''; // start date should not be empty or present\n if (!e) return s;\n return `${s} to ${e}`;\n}\n\nexport function regionName(code: string | null | undefined): string {\n if (!code) return '';\n return COUNTRY_NAMES[code.toUpperCase()] || code;\n}\n","export function profileIcon(network: string | null | undefined): string {\n if (!network) return '<i class=\"fa-solid fa-link\"></i>';\n const n = network.toLowerCase();\n if (n === 'linkedin') return '<i class=\"fa-brands fa-linkedin-in\"></i>';\n if (n === 'github') return '<i class=\"fa-brands fa-github\"></i>';\n if (n === 'twitter' || n === 'x') return '<i class=\"fa-brands fa-x-twitter\"></i>';\n if (n === 'stackoverflow') return '<i class=\"fa-brands fa-stack-overflow\"></i>';\n return '<i class=\"fa-solid fa-link\"></i>';\n}\n","/** Tags allowed through sanitization — safe inline/block formatting. */\nconst BLOCK_TAGS = new Set(['p', 'ul', 'ol', 'li', 'blockquote']);\nconst INLINE_TAGS = new Set([\n 'br', 'b', 'strong', 'i', 'em', 'u', 's', 'mark', 'a', 'sub', 'sup', 'code',\n]);\nconst ALLOWED_TAGS = new Set([...BLOCK_TAGS, ...INLINE_TAGS]);\n\n/** URL schemes safe for <a href>. Everything else (javascript:, data:, vbscript:) is stripped. */\nconst SAFE_URL_SCHEMES = /^(?:https?|mailto|tel):/i;\nconst RELATIVE_URL = /^[#/?.]/;\n\nfunction isSafeUrl(href: string): boolean {\n const trimmed = href.trim();\n if (!trimmed) return false;\n return SAFE_URL_SCHEMES.test(trimmed) || RELATIVE_URL.test(trimmed);\n}\n\ninterface SanitizeOptions {\n readonly inline?: boolean;\n}\n\n/**\n * Sanitize HTML — keep allowed formatting tags, strip everything else.\n * Attributes are stripped from all tags except <a href> (safe schemes only) and <mark class>.\n * If inline=true, block tags (p, ul, ol, li, blockquote) are also stripped.\n */\nexport function sanitizeHtml(html: string | null | undefined, { inline = false }: SanitizeOptions = {}): string {\n if (!html) return '';\n const allowed = inline ? INLINE_TAGS : ALLOWED_TAGS;\n\n return String(html)\n .replace(/<a\\s+[^>]*href=\"([^\"]*)\"[^>]*>/gi, (_, href: string) => {\n if (!isSafeUrl(href)) return '';\n return `<a href=\"${href.replace(/\"/g, '"')}\" target=\"_blank\" rel=\"noopener\">`;\n })\n .replace(/<mark\\s+[^>]*class=\"([^\"]*)\"[^>]*>/gi, (_, cls: string) =>\n `<mark class=\"${cls.replace(/\"/g, '"')}\">`,\n )\n .replace(/<(\\/?)([a-z][a-z0-9]*)\\s*[^>]*?(\\/?)>/gi, (match, slash: string, tag: string, selfClose: string) => {\n const lower = tag.toLowerCase();\n if (lower === 'a' && !slash && match.includes('href=')) return match;\n if (lower === 'mark' && !slash && match.includes('class=')) return match;\n if (allowed.has(lower)) return `<${slash}${lower}${selfClose}>`;\n if (inline && BLOCK_TAGS.has(lower)) return slash ? ' ' : '';\n return '';\n })\n .replace(/<p>\\s*<\\/p>/gi, '')\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n","import { decodeEntities, esc } from './escape.js';\nimport { sanitizeHtml } from './sanitize.js';\n\n/** Strip HTML tags → plain text (for fields parsed as data, not displayed). */\nexport function stripHtml(text: string | null | undefined): string {\n if (!text) return '';\n return decodeEntities(\n String(text)\n .replace(/<br\\s*\\/?>/gi, ' ')\n .replace(/<\\/?(p|div|li)[^>]*>/gi, ' ')\n .replace(/<[^>]+>/g, ''),\n )\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n\nconst isHtml = (text: string): boolean => /<[a-z][\\s\\S]*>/i.test(text);\nconst hasEntities = (text: string): boolean => /&(?:amp|lt|gt|quot|#\\d+|#x[0-9a-f]+);/i.test(text);\n\n/**\n * Render user-provided rich text → safe HTML output.\n * block=true: preserve <p>, <ul>, <ol> etc.\n * block=false (default): inline mode — strip block tags, keep only inline formatting.\n */\nexport function richText(text: string | null | undefined, { block = false }: { block?: boolean } = {}): string {\n if (text == null) return '';\n const s = String(text);\n if (isHtml(s)) return sanitizeHtml(s, { inline: !block });\n if (hasEntities(s)) return esc(decodeEntities(s));\n return esc(s);\n}\n\n/** Check if an array is non-empty. */\nexport function has<T>(arr: readonly T[] | null | undefined): arr is readonly T[] & { length: number } {\n return Array.isArray(arr) && arr.length > 0;\n}\n","import type { ResumeBasics } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { regionName } from '../utils/dates.js';\nimport { profileIcon } from '../utils/icons.js';\nimport { has } from '../utils/text.js';\n\ninterface ContactLine {\n readonly icon: string;\n readonly text: string;\n}\n\nexport function renderHeader(basics: ResumeBasics | undefined): string {\n if (!basics) return '';\n\n const location = basics.location\n ? [basics.location.city, regionName(basics.location.region)].filter(Boolean).join(', ')\n : '';\n\n const lines: ContactLine[] = [];\n if (location)\n lines.push({ icon: '<i class=\"fa-solid fa-location-dot\"></i>', text: location });\n if (basics.phone)\n lines.push({ icon: '<i class=\"fa-solid fa-square-phone\"></i>', text: basics.phone });\n if (basics.email)\n lines.push({ icon: '<i class=\"fa-solid fa-envelope\"></i>', text: basics.email });\n if (basics.url) {\n const display = basics.url.replace(/^https?:\\/\\//, '');\n lines.push({ icon: '<i class=\"fa-solid fa-globe\"></i>', text: display });\n }\n if (has(basics.profiles)) {\n for (const p of basics.profiles) {\n lines.push({ icon: profileIcon(p.network), text: p.username || p.url || '' });\n }\n }\n\n return `\n <header class=\"header\">\n <div class=\"header-left\">\n <h1 class=\"name\">${esc(basics.name)}</h1>\n <div class=\"label\">${esc(basics.label)}</div>\n </div>\n <div class=\"contact-info\">\n ${lines.map((l) => `<div class=\"contact-line\">${esc(l.text)} ${l.icon}</div>`).join('\\n ')}\n </div>\n </header>\n <hr class=\"header-rule\" />`;\n}\n","import { esc } from '../utils/escape.js';\n\nexport function sectionTitle(title: string): string {\n return `<h2 class=\"section-title\">${esc(title)}</h2>`;\n}\n","import { richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderSummary(summary: string | undefined, heading: string): string {\n if (!summary) return '';\n const html = richText(summary, { block: true });\n if (!html) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"summary\">${html}</div>`;\n}\n","import type { ResumeSkill } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderSkills(skills: readonly ResumeSkill[] | undefined, heading: string): string {\n if (!has(skills)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${skills\n .map(\n (s) => `\n <div class=\"bullet-item\">\n <span class=\"skill-name\">${esc(s.name)}:</span> ${esc((s.keywords || []).join(', '))}\n </div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeWorkEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { dateRange } from '../utils/dates.js';\nimport { has, richText, stripHtml } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\n/** Check whether summary uses structured Tech-stack / Client format. */\nfunction isStructuredMeta(text: string): boolean {\n return /Tech-stack:\\s/i.test(text) || /Client:\\s/i.test(text);\n}\n\n/** Extract tech-stack, client, and any remaining narrative from a work summary. */\nfunction parseWorkMeta(summary: string | undefined): {\n techStack: string;\n client: string;\n narrative: string;\n} {\n if (!summary) return { techStack: '', client: '', narrative: '' };\n const text = stripHtml(summary).replace(/\\n/g, ' ').trim();\n if (!isStructuredMeta(text)) return { techStack: '', client: '', narrative: text };\n\n let remaining = text;\n\n const clientMatch = remaining.match(/Client:\\s*(.+)$/i);\n const client = clientMatch ? clientMatch[1].trim() : '';\n if (clientMatch) remaining = remaining.slice(0, clientMatch.index).trim();\n\n const techMatch = remaining.match(/Tech-stack:\\s*(.+)/i);\n const techStack = techMatch ? techMatch[1].trim() : '';\n if (techMatch) remaining = remaining.slice(0, techMatch.index).trim();\n\n return { techStack, client, narrative: remaining };\n}\n\nfunction renderWorkEntry(entry: ResumeWorkEntry): string {\n const { techStack, client, narrative } = parseWorkMeta(entry.summary);\n const duration = dateRange(entry.startDate, entry.endDate);\n\n return `\n <div class=\"work-entry\">\n <div class=\"work-header\">\n <div class=\"work-title\">${esc(entry.name)} - ${esc(entry.position)}</div>\n <div class=\"work-duration\">${duration ? `Duration: ${esc(duration)}` : ''}</div>\n </div>\n ${\n techStack || client\n ? `\n <div class=\"work-meta\">\n <div class=\"work-tech\">${techStack ? `Tech-stack: ${esc(techStack)}` : ''}</div>\n <div class=\"work-client\">${client ? `Client: ${esc(client)}` : ''}</div>\n </div>`\n : ''\n }\n ${narrative ? `<p class=\"work-summary\">${richText(narrative, { block: false })}</p>` : ''}\n ${\n has(entry.highlights)\n ? `\n <ul class=\"work-highlights\">\n ${entry.highlights.map((h) => `<li>${richText(h)}</li>`).join('\\n ')}\n </ul>`\n : ''\n }\n </div>`;\n}\n\nexport function renderWork(work: readonly ResumeWorkEntry[] | undefined, heading: string): string {\n if (!has(work)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${work.map(renderWorkEntry).join('')}\n </div>`;\n}\n","import type { ResumeProject } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderProjects(\n projects: readonly ResumeProject[] | undefined,\n heading: string,\n): string {\n if (!has(projects)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${projects\n .map((p) => {\n const desc = richText(p.description);\n return `\n <div class=\"bullet-item\">\n <span class=\"project-name\">${esc(p.name)}:</span> ${desc}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeEducationEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { formatDate } from '../utils/dates.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nfunction renderEducationEntry(entry: ResumeEducationEntry): string {\n const degree = [entry.studyType, entry.area].filter(Boolean).join(' ');\n\n let yearDisplay = '';\n if (entry.endDate) {\n yearDisplay = formatDate(entry.endDate);\n } else if (entry.startDate) {\n const year = parseInt(String(entry.startDate).split('-')[0], 10);\n const currentYear = new Date().getFullYear();\n yearDisplay = year >= currentYear ? `Expected ${formatDate(entry.startDate)}` : formatDate(entry.startDate);\n }\n\n const instParts = [entry.institution];\n if (entry.score) instParts.push(entry.score);\n const instLine = instParts.filter(Boolean).join(' | ');\n\n return `\n <div class=\"edu-entry\">\n <div class=\"edu-header\">\n <div class=\"edu-degree\">${esc(degree)}</div>\n <div class=\"edu-year\">${esc(yearDisplay)}</div>\n </div>\n ${entry.institution ? `<div class=\"edu-institution\">${esc(instLine)}</div>` : ''}\n ${\n has(entry.courses)\n ? `<div class=\"edu-courses\">Coursework: ${entry.courses.map((c) => richText(c)).join(', ')}</div>`\n : ''\n }\n </div>`;\n}\n\nexport function renderEducation(\n education: readonly ResumeEducationEntry[] | undefined,\n heading: string,\n): string {\n if (!has(education)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${education.map(renderEducationEntry).join('')}\n </div>`;\n}\n","import type { ResumeVolunteerEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderVolunteer(\n volunteer: readonly ResumeVolunteerEntry[] | undefined,\n heading: string,\n): string {\n if (!has(volunteer)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${volunteer\n .map((v) => {\n const years = v.endDate\n ? `${v.startDate}\\u2013${v.endDate}`\n : v.startDate || '';\n const title = v.position\n ? `${v.position} \\u2013 ${v.organization}`\n : v.organization;\n const summary = richText(v.summary);\n\n return `\n <div class=\"bullet-item volunteer-item\">\n <span class=\"vol-title\">${esc(title)}${years ? ` (${esc(years)})` : ''}:</span> ${summary}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeCertificate } from '../types/resume.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderCertificates(\n certificates: readonly ResumeCertificate[] | undefined,\n heading: string,\n): string {\n if (!has(certificates)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${certificates\n .map(\n (c) => `\n <div class=\"bullet-item\">${richText(c.name)}</div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeSchema } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderAdditional(resume: ResumeSchema, heading: string): string {\n const parts: string[] = [];\n\n if (has(resume.interests)) {\n for (const interest of resume.interests) {\n const keywords = has(interest.keywords) ? interest.keywords.join(', ') : '';\n const label = interest.name || 'Interests';\n if (keywords) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">${esc(label)}:</span> ${esc(keywords)}</div>`,\n );\n }\n }\n }\n\n if (has(resume.languages)) {\n const langStr = resume.languages\n .map((l) => `${l.language}${l.fluency ? ` (${l.fluency})` : ''}`)\n .join(', ');\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Languages:</span> ${esc(langStr)}</div>`,\n );\n }\n\n if (has(resume.references)) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Referees:</span> Available on request</div>`,\n );\n }\n\n if (parts.length === 0) return '';\n\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${parts.join('\\n ')}\n </div>`;\n}\n","import type { ResumeSchema } from './types/resume.js';\nimport { DEFAULT_HEADINGS } from './constants.js';\nimport { esc } from './utils/escape.js';\nimport { renderHeader } from './sections/header.js';\nimport { renderSummary } from './sections/summary.js';\nimport { renderSkills } from './sections/skills.js';\nimport { renderWork } from './sections/work.js';\nimport { renderProjects } from './sections/projects.js';\nimport { renderEducation } from './sections/education.js';\nimport { renderVolunteer } from './sections/volunteer.js';\nimport { renderCertificates } from './sections/certificates.js';\nimport { renderAdditional } from './sections/additional.js';\nimport css from './styles/academic.css?inline';\n\nexport function render(resume: ResumeSchema): string {\n const h = { ...DEFAULT_HEADINGS };\n\n const metaHeadings = resume?.meta?.headings;\n if (metaHeadings) {\n for (const [key, value] of Object.entries(metaHeadings)) {\n if (value && key in h) {\n h[key] = value;\n }\n }\n }\n\n const basics = resume?.basics || {};\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${esc(basics.name || 'Resume')}</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\n <link href=\"https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap\" rel=\"stylesheet\" />\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css\" />\n <style>${css}</style>\n</head>\n<body>\n <div class=\"resume\">\n ${renderHeader(basics)}\n ${renderSummary(basics.summary, h.summary)}\n ${renderSkills(resume.skills, h.skills)}\n ${renderWork(resume.work, h.experience)}\n ${renderProjects(resume.projects, h.projects)}\n ${renderEducation(resume.education, h.education)}\n ${renderVolunteer(resume.volunteer, h.volunteer)}\n ${renderCertificates(resume.certificates, h.certifications)}\n ${renderAdditional(resume, h.additional)}\n </div>\n</body>\n</html>`;\n}\n","export { render } from './render.js';\nexport type { ResumeSchema } from './types/resume.js';\n\n/** PDF rendering options for Puppeteer/Gotenberg. */\nexport const pdfRenderOptions = {\n mediaType: 'print' as const,\n format: 'A4' as const,\n margin: {\n top: '12mm',\n right: '14mm',\n bottom: '12mm',\n left: '14mm',\n },\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/constants.ts","../src/utils/escape.ts","../src/utils/dates.ts","../src/utils/icons.ts","../src/utils/sanitize.ts","../src/utils/text.ts","../src/sections/header.ts","../src/sections/shared.ts","../src/sections/summary.ts","../src/sections/skills.ts","../src/sections/work.ts","../src/sections/projects.ts","../src/sections/education.ts","../src/sections/volunteer.ts","../src/sections/certificates.ts","../src/sections/additional.ts","../src/render.ts","../src/index.ts"],"sourcesContent":["export const MONTHS: readonly string[] = [\r\n 'January',\r\n 'February',\r\n 'March',\r\n 'April',\r\n 'May',\r\n 'June',\r\n 'July',\r\n 'August',\r\n 'September',\r\n 'October',\r\n 'November',\r\n 'December',\r\n];\r\n\r\nexport const COUNTRY_NAMES: Readonly<Record<string, string>> = {\r\n GB: 'United Kingdom',\r\n US: 'United States',\r\n CA: 'Canada',\r\n AU: 'Australia',\r\n DE: 'Germany',\r\n FR: 'France',\r\n IN: 'India',\r\n JP: 'Japan',\r\n CN: 'China',\r\n BR: 'Brazil',\r\n NL: 'Netherlands',\r\n IT: 'Italy',\r\n ES: 'Spain',\r\n SE: 'Sweden',\r\n CH: 'Switzerland',\r\n SG: 'Singapore',\r\n NZ: 'New Zealand',\r\n IE: 'Ireland',\r\n AE: 'UAE',\r\n};\r\n\r\nexport const DEFAULT_HEADINGS: Readonly<Record<string, string>> = {\r\n summary: 'Summary',\r\n skills: 'Core Skills',\r\n experience: 'Experience',\r\n projects: 'Selected Projects',\r\n education: 'Education',\r\n volunteer: 'Leadership & Volunteering',\r\n certifications: 'Credentials',\r\n additional: 'Additional',\r\n};\r\n","/** Decode common HTML entities to literal characters. */\nexport function decodeEntities(text: string): string {\n return text\n .replace(/&#(\\d+);/g, (_, c) => String.fromCharCode(Number(c)))\n .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCharCode(parseInt(h, 16)))\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n .replace(/ /g, ' ')\n .replace(/—/g, '\\u2014')\n .replace(/–/g, '\\u2013')\n .replace(/…/g, '\\u2026');\n}\n\n/** HTML-escape for safe output (plain text fields like names, dates). */\nexport function esc(text: string | null | undefined): string {\n if (text == null) return '';\n return String(text)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n","import { MONTHS, COUNTRY_NAMES } from '../constants.js';\n\nexport function formatDate(dateStr: string | null | undefined): string {\n if (!dateStr) return 'Present';\n if (dateStr.toLowerCase() === 'present') return 'Present';\n const parts = String(dateStr).split('-');\n if (parts.length === 1) return parts[0];\n const year = parts[0];\n const month = MONTHS[parseInt(parts[1], 10) - 1] || '';\n return `${month} ${year}`;\n}\n\nexport function dateRange(start: string | null | undefined, end: string | null | undefined): string {\n const s = formatDate(start);\n const e = formatDate(end);\n if (!s || s === 'Present') return ''; // start date should not be empty or present\n if (!e) return s;\n return `${s} to ${e}`;\n}\n\nexport function regionName(code: string | null | undefined): string {\n if (!code) return '';\n return COUNTRY_NAMES[code.toUpperCase()] || code;\n}\n","export function profileIcon(network: string | null | undefined): string {\n if (!network) return '<i class=\"fa-solid fa-link\"></i>';\n const n = network.toLowerCase();\n if (n === 'linkedin') return '<i class=\"fa-brands fa-linkedin-in\"></i>';\n if (n === 'github') return '<i class=\"fa-brands fa-github\"></i>';\n if (n === 'twitter' || n === 'x') return '<i class=\"fa-brands fa-x-twitter\"></i>';\n if (n === 'stackoverflow') return '<i class=\"fa-brands fa-stack-overflow\"></i>';\n return '<i class=\"fa-solid fa-link\"></i>';\n}\n","/** Tags allowed through sanitization — safe inline/block formatting. */\nconst BLOCK_TAGS = new Set(['p', 'ul', 'ol', 'li', 'blockquote']);\nconst INLINE_TAGS = new Set([\n 'br', 'b', 'strong', 'i', 'em', 'u', 's', 'mark', 'a', 'sub', 'sup', 'code',\n]);\nconst ALLOWED_TAGS = new Set([...BLOCK_TAGS, ...INLINE_TAGS]);\n\n/** URL schemes safe for <a href>. Everything else (javascript:, data:, vbscript:) is stripped. */\nconst SAFE_URL_SCHEMES = /^(?:https?|mailto|tel):/i;\nconst RELATIVE_URL = /^[#/?.]/;\n\nfunction isSafeUrl(href: string): boolean {\n const trimmed = href.trim();\n if (!trimmed) return false;\n return SAFE_URL_SCHEMES.test(trimmed) || RELATIVE_URL.test(trimmed);\n}\n\ninterface SanitizeOptions {\n readonly inline?: boolean;\n}\n\n/**\n * Sanitize HTML — keep allowed formatting tags, strip everything else.\n * Attributes are stripped from all tags except <a href> (safe schemes only) and <mark class>.\n * If inline=true, block tags (p, ul, ol, li, blockquote) are also stripped.\n */\nexport function sanitizeHtml(html: string | null | undefined, { inline = false }: SanitizeOptions = {}): string {\n if (!html) return '';\n const allowed = inline ? INLINE_TAGS : ALLOWED_TAGS;\n\n return String(html)\n .replace(/<a\\s+[^>]*href=\"([^\"]*)\"[^>]*>/gi, (_, href: string) => {\n if (!isSafeUrl(href)) return '';\n return `<a href=\"${href.replace(/\"/g, '"')}\" target=\"_blank\" rel=\"noopener\">`;\n })\n .replace(/<mark\\s+[^>]*class=\"([^\"]*)\"[^>]*>/gi, (_, cls: string) =>\n `<mark class=\"${cls.replace(/\"/g, '"')}\">`,\n )\n .replace(/<(\\/?)([a-z][a-z0-9]*)\\s*[^>]*?(\\/?)>/gi, (match, slash: string, tag: string, selfClose: string) => {\n const lower = tag.toLowerCase();\n if (lower === 'a' && !slash && match.includes('href=')) return match;\n if (lower === 'mark' && !slash && match.includes('class=')) return match;\n if (allowed.has(lower)) return `<${slash}${lower}${selfClose}>`;\n if (inline && BLOCK_TAGS.has(lower)) return slash ? ' ' : '';\n return '';\n })\n .replace(/<p>\\s*<\\/p>/gi, '')\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n","import { decodeEntities, esc } from './escape.js';\nimport { sanitizeHtml } from './sanitize.js';\n\n/** Strip HTML tags → plain text (for fields parsed as data, not displayed). */\nexport function stripHtml(text: string | null | undefined): string {\n if (!text) return '';\n return decodeEntities(\n String(text)\n .replace(/<br\\s*\\/?>/gi, ' ')\n .replace(/<\\/?(p|div|li)[^>]*>/gi, ' ')\n .replace(/<[^>]+>/g, ''),\n )\n .replace(/\\s{2,}/g, ' ')\n .trim();\n}\n\nconst isHtml = (text: string): boolean => /<[a-z][\\s\\S]*>/i.test(text);\nconst hasEntities = (text: string): boolean => /&(?:amp|lt|gt|quot|#\\d+|#x[0-9a-f]+);/i.test(text);\n\n/**\n * Render user-provided rich text → safe HTML output.\n * block=true: preserve <p>, <ul>, <ol> etc.\n * block=false (default): inline mode — strip block tags, keep only inline formatting.\n */\nexport function richText(text: string | null | undefined, { block = false }: { block?: boolean } = {}): string {\n if (text == null) return '';\n const s = String(text);\n if (isHtml(s)) return sanitizeHtml(s, { inline: !block });\n if (hasEntities(s)) return esc(decodeEntities(s));\n return esc(s);\n}\n\n/** Check if an array is non-empty. */\nexport function has<T>(arr: readonly T[] | null | undefined): arr is readonly T[] & { length: number } {\n return Array.isArray(arr) && arr.length > 0;\n}\n","import type { ResumeBasics } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { regionName } from '../utils/dates.js';\nimport { profileIcon } from '../utils/icons.js';\nimport { has } from '../utils/text.js';\n\ninterface ContactLine {\n readonly icon: string;\n readonly text: string;\n}\n\nexport function renderHeader(basics: ResumeBasics | undefined): string {\n if (!basics) return '';\n\n const location = basics.location\n ? [basics.location.city, regionName(basics.location.region)].filter(Boolean).join(', ')\n : '';\n\n const lines: ContactLine[] = [];\n if (location)\n lines.push({ icon: '<i class=\"fa-solid fa-location-dot\"></i>', text: location });\n if (basics.phone)\n lines.push({ icon: '<i class=\"fa-solid fa-square-phone\"></i>', text: basics.phone });\n if (basics.email)\n lines.push({ icon: '<i class=\"fa-solid fa-envelope\"></i>', text: basics.email });\n if (basics.url) {\n const display = basics.url.replace(/^https?:\\/\\//, '');\n lines.push({ icon: '<i class=\"fa-solid fa-globe\"></i>', text: display });\n }\n if (has(basics.profiles)) {\n for (const p of basics.profiles) {\n lines.push({ icon: profileIcon(p.network), text: p.username || p.url || '' });\n }\n }\n\n return `\n <header class=\"header\">\n <div class=\"header-left\">\n <h1 class=\"name\">${esc(basics.name)}</h1>\n <div class=\"label\">${esc(basics.label)}</div>\n </div>\n <div class=\"contact-info\">\n ${lines.map((l) => `<div class=\"contact-line\">${esc(l.text)} ${l.icon}</div>`).join('\\n ')}\n </div>\n </header>\n <hr class=\"header-rule\" />`;\n}\n","import { esc } from '../utils/escape.js';\n\nexport function sectionTitle(title: string): string {\n return `<h2 class=\"section-title\">${esc(title)}</h2>`;\n}\n","import { richText } from '../utils/text.js';\r\nimport { sectionTitle } from './shared.js';\r\n\r\nexport function renderSummary(summary: string | undefined, heading: string): string {\r\n if (!summary) return '';\r\n const html = richText(summary, { block: true });\r\n if (!html) return '';\r\n return `\r\n ${sectionTitle(heading)}\r\n <div class=\"summary\">${html}</div>`;\r\n}\r\n","import type { ResumeSkill } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderSkills(skills: readonly ResumeSkill[] | undefined, heading: string): string {\n if (!has(skills)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${skills\n .map(\n (s) => `\n <div class=\"bullet-item\">\n <span class=\"skill-name\">${esc(s.name)}:</span> ${esc((s.keywords || []).join(', '))}\n </div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeWorkEntry } from '../types/resume.js';\r\nimport { esc } from '../utils/escape.js';\r\nimport { dateRange } from '../utils/dates.js';\r\nimport { has, richText, stripHtml } from '../utils/text.js';\r\nimport { sectionTitle } from './shared.js';\r\n\r\n/** Check whether summary uses structured Tech-stack / Client format. */\r\nfunction isStructuredMeta(text: string): boolean {\r\n return /Tech-stack:\\s/i.test(text) || /Client:\\s/i.test(text);\r\n}\r\n\r\n/** Extract tech-stack, client, and any remaining narrative from a work summary. */\r\nfunction parseWorkMeta(summary: string | undefined): {\r\n techStack: string;\r\n client: string;\r\n narrative: string;\r\n} {\r\n if (!summary) return { techStack: '', client: '', narrative: '' };\r\n const text = stripHtml(summary).replace(/\\n/g, ' ').trim();\r\n if (!isStructuredMeta(text)) return { techStack: '', client: '', narrative: text };\r\n\r\n let remaining = text;\r\n\r\n const clientMatch = remaining.match(/Client:\\s*(.+)$/i);\r\n const client = clientMatch ? clientMatch[1].trim() : '';\r\n if (clientMatch) remaining = remaining.slice(0, clientMatch.index).trim();\r\n\r\n const techMatch = remaining.match(/Tech-stack:\\s*(.+)/i);\r\n const techStack = techMatch ? techMatch[1].trim() : '';\r\n if (techMatch) remaining = remaining.slice(0, techMatch.index).trim();\r\n\r\n return { techStack, client, narrative: remaining };\r\n}\r\n\r\nfunction renderWorkEntry(entry: ResumeWorkEntry): string {\r\n const { techStack, client, narrative } = parseWorkMeta(entry.summary);\r\n const duration = dateRange(entry.startDate, entry.endDate);\r\n\r\n return `\r\n <div class=\"work-entry\">\r\n <div class=\"work-header\">\r\n <div class=\"work-title\">${esc(entry.name)} - ${esc(entry.position)}</div>\r\n <div class=\"work-duration\">${duration ? `Duration: ${esc(duration)}` : ''}</div>\r\n </div>\r\n ${\r\n techStack || client\r\n ? `\r\n <div class=\"work-meta\">\r\n <div class=\"work-tech\">${techStack ? `Tech-stack: ${esc(techStack)}` : ''}</div>\r\n <div class=\"work-client\">${client ? `Client: ${esc(client)}` : ''}</div>\r\n </div>`\r\n : ''\r\n }\r\n ${narrative ? `<p class=\"work-summary\">${richText(narrative, { block: false })}</p>` : ''}\r\n ${\r\n has(entry.highlights)\r\n ? `\r\n <ul class=\"work-highlights\">\r\n ${entry.highlights.map((h) => `<li>${richText(h)}</li>`).join('\\n ')}\r\n </ul>`\r\n : ''\r\n }\r\n </div>`;\r\n}\r\n\r\nexport function renderWork(work: readonly ResumeWorkEntry[] | undefined, heading: string): string {\r\n if (!has(work)) return '';\r\n return `\r\n ${sectionTitle(heading)}\r\n <div class=\"section-body\">\r\n ${work.map(renderWorkEntry).join('')}\r\n </div>`;\r\n}\r\n","import type { ResumeProject } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderProjects(\n projects: readonly ResumeProject[] | undefined,\n heading: string,\n): string {\n if (!has(projects)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${projects\n .map((p) => {\n const desc = richText(p.description);\n return `\n <div class=\"bullet-item\">\n <span class=\"project-name\">${esc(p.name)}:</span> ${desc}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeEducationEntry } from '../types/resume.js';\r\nimport { esc } from '../utils/escape.js';\r\nimport { formatDate } from '../utils/dates.js';\r\nimport { has, richText } from '../utils/text.js';\r\nimport { sectionTitle } from './shared.js';\r\n\r\nfunction renderEducationEntry(entry: ResumeEducationEntry): string {\r\n const degree = [entry.studyType, entry.area].filter(Boolean).join(' ');\r\n\r\n let yearDisplay = '';\r\n if (entry.endDate) {\r\n yearDisplay = formatDate(entry.endDate);\r\n } else if (entry.startDate) {\r\n const year = parseInt(String(entry.startDate).split('-')[0], 10);\r\n const currentYear = new Date().getFullYear();\r\n yearDisplay = year >= currentYear ? `Expected ${formatDate(entry.startDate)}` : formatDate(entry.startDate);\r\n }\r\n\r\n const instParts = [entry.institution];\r\n if (entry.score) instParts.push(entry.score);\r\n const instLine = instParts.filter(Boolean).join(' | ');\r\n\r\n return `\r\n <div class=\"edu-entry\">\r\n <div class=\"edu-header\">\r\n <div class=\"edu-degree\">${esc(degree)}</div>\r\n <div class=\"edu-year\">${esc(yearDisplay)}</div>\r\n </div>\r\n ${entry.institution ? `<div class=\"edu-institution\">${esc(instLine)}</div>` : ''}\r\n ${\r\n has(entry.courses)\r\n ? `<div class=\"edu-courses\">Coursework: ${entry.courses.map((c) => richText(c)).join(', ')}</div>`\r\n : ''\r\n }\r\n </div>`;\r\n}\r\n\r\nexport function renderEducation(\r\n education: readonly ResumeEducationEntry[] | undefined,\r\n heading: string,\r\n): string {\r\n if (!has(education)) return '';\r\n return `\r\n ${sectionTitle(heading)}\r\n <div class=\"section-body\">\r\n ${education.map(renderEducationEntry).join('')}\r\n </div>`;\r\n}\r\n","import type { ResumeVolunteerEntry } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderVolunteer(\n volunteer: readonly ResumeVolunteerEntry[] | undefined,\n heading: string,\n): string {\n if (!has(volunteer)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${volunteer\n .map((v) => {\n const years = v.endDate\n ? `${v.startDate}\\u2013${v.endDate}`\n : v.startDate || '';\n const title = v.position\n ? `${v.position} \\u2013 ${v.organization}`\n : v.organization;\n const summary = richText(v.summary);\n\n return `\n <div class=\"bullet-item volunteer-item\">\n <span class=\"vol-title\">${esc(title)}${years ? ` (${esc(years)})` : ''}:</span> ${summary}\n </div>`;\n })\n .join('')}\n </div>`;\n}\n","import type { ResumeCertificate } from '../types/resume.js';\nimport { has, richText } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderCertificates(\n certificates: readonly ResumeCertificate[] | undefined,\n heading: string,\n): string {\n if (!has(certificates)) return '';\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${certificates\n .map(\n (c) => `\n <div class=\"bullet-item\">${richText(c.name)}</div>`,\n )\n .join('')}\n </div>`;\n}\n","import type { ResumeSchema } from '../types/resume.js';\nimport { esc } from '../utils/escape.js';\nimport { has } from '../utils/text.js';\nimport { sectionTitle } from './shared.js';\n\nexport function renderAdditional(resume: ResumeSchema, heading: string): string {\n const parts: string[] = [];\n\n if (has(resume.interests)) {\n for (const interest of resume.interests) {\n const keywords = has(interest.keywords) ? interest.keywords.join(', ') : '';\n const label = interest.name || 'Interests';\n if (keywords) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">${esc(label)}:</span> ${esc(keywords)}</div>`,\n );\n }\n }\n }\n\n if (has(resume.languages)) {\n const langStr = resume.languages\n .map((l) => `${l.language}${l.fluency ? ` (${l.fluency})` : ''}`)\n .join(', ');\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Languages:</span> ${esc(langStr)}</div>`,\n );\n }\n\n if (has(resume.references)) {\n parts.push(\n `<div class=\"additional-line\"><span class=\"additional-label\">Referees:</span> Available on request</div>`,\n );\n }\n\n if (parts.length === 0) return '';\n\n return `\n ${sectionTitle(heading)}\n <div class=\"section-body\">\n ${parts.join('\\n ')}\n </div>`;\n}\n","import type { ResumeSchema } from './types/resume.js';\r\nimport { DEFAULT_HEADINGS } from './constants.js';\r\nimport { esc } from './utils/escape.js';\r\nimport { renderHeader } from './sections/header.js';\r\nimport { renderSummary } from './sections/summary.js';\r\nimport { renderSkills } from './sections/skills.js';\r\nimport { renderWork } from './sections/work.js';\r\nimport { renderProjects } from './sections/projects.js';\r\nimport { renderEducation } from './sections/education.js';\r\nimport { renderVolunteer } from './sections/volunteer.js';\r\nimport { renderCertificates } from './sections/certificates.js';\r\nimport { renderAdditional } from './sections/additional.js';\r\nimport css from './styles/academic.css?inline';\r\n\r\nexport function render(resume: ResumeSchema): string {\r\n const h = { ...DEFAULT_HEADINGS };\r\n\r\n const metaHeadings = resume?.meta?.headings;\r\n if (metaHeadings) {\r\n for (const [key, value] of Object.entries(metaHeadings)) {\r\n if (value && key in h) {\r\n h[key] = value;\r\n }\r\n }\r\n }\r\n\r\n const basics = resume?.basics || {};\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <title>${esc(basics.name || 'Resume')}</title>\r\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\" />\r\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin />\r\n <link href=\"https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap\" rel=\"stylesheet\" />\r\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css\" />\r\n <style>${css}</style>\r\n</head>\r\n<body>\r\n <div class=\"resume\">\r\n ${renderHeader(basics)}\r\n ${renderSummary(basics.summary, h.summary)}\r\n ${renderSkills(resume.skills, h.skills)}\r\n ${renderWork(resume.work, h.experience)}\r\n ${renderProjects(resume.projects, h.projects)}\r\n ${renderEducation(resume.education, h.education)}\r\n ${renderVolunteer(resume.volunteer, h.volunteer)}\r\n ${renderCertificates(resume.certificates, h.certifications)}\r\n ${renderAdditional(resume, h.additional)}\r\n </div>\r\n</body>\r\n</html>`;\r\n}\r\n","export { render } from './render.js';\nexport type { ResumeSchema } from './types/resume.js';\n\n/** PDF rendering options for Puppeteer/Gotenberg. */\nexport const pdfRenderOptions = {\n mediaType: 'print' as const,\n format: 'A4' as const,\n margin: {\n top: '12mm',\n right: '14mm',\n bottom: '12mm',\n left: '14mm',\n },\n};\n"],"names":[],"mappings":"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"}
|