Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/blog-platform/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/blog-platform",
"version": "3.10.0",
"version": "3.10.2",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 1 addition & 1 deletion apps/docs-platform/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/docs-platform",
"version": "3.10.0",
"version": "3.10.2",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 1 addition & 1 deletion apps/server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/server",
"version": "3.10.0",
"version": "3.10.2",
"description": "VeriWorkly Resume Backend API",
"main": "dist/index.js",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion apps/site/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/site",
"version": "3.10.0",
"version": "3.10.2",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/components/dashboard/StudioShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface StudioShellProps {
mainClassName?: string;
}

const STUDIO_VERSION = "v3.10.0";
const STUDIO_VERSION = "v3.10.2";

const StudioShell = ({ children, mainClassName }: StudioShellProps) => {
const router = useRouter();
Expand Down
43 changes: 43 additions & 0 deletions apps/studio/features/documents/rendering/resume-rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@ export interface RenderContactItem {
href?: string;
}

export interface ResumeRenderModel {
style: ResumeRenderStyle;
contactItems: RenderContactItem[];
renderedLinks: ResumeLinkItem[];
showBasics: boolean;
showLinks: boolean;
showSummary: boolean;
showExperience: boolean;
showEducation: boolean;
showProjects: boolean;
showSkills: boolean;
visibleExperience: ResumeData["experience"];
visibleEducation: ResumeData["education"];
visibleProjects: ResumeData["projects"];
visibleSkills: ResumeData["skills"];
visibleCustomSections: ResumeData["customSections"];
}

export function cleanResumeText(value: string | null | undefined): string {
return stripEmoji(safeText(value ?? "")).replace(/\s+/g, " ");
}
Expand Down Expand Up @@ -268,3 +286,28 @@ export function hasResumeSectionContent(resume: ResumeData, sectionId: ResumeSec
.some((section) => hasCustomSectionContent(section));
}
}

export function getResumeRenderModel(resume: ResumeData): ResumeRenderModel {
const style = getResumeRenderStyle(resume);

return {
style,
contactItems: getContactItems(resume.basics),
renderedLinks: resume.links.items.filter((link) => normalizeLinkHref(link.url)),
showBasics: hasResumeSectionContent(resume, "basics"),
showLinks: hasResumeSectionContent(resume, "links"),
showSummary: hasResumeSectionContent(resume, "summary"),
showExperience: hasResumeSectionContent(resume, "experience"),
showEducation: hasResumeSectionContent(resume, "education"),
showProjects: hasResumeSectionContent(resume, "projects"),
showSkills: hasResumeSectionContent(resume, "skills"),
visibleExperience: resume.experience.filter(hasExperienceContent),
visibleEducation: resume.education.filter(hasEducationContent),
visibleProjects: resume.projects.filter(hasProjectContent),
visibleSkills: resume.skills.filter(hasSkillGroupContent),
visibleCustomSections: resume.customSections.filter(
(section) =>
hasResumeSectionContent(resume, section.kind) && hasCustomSectionContent(section),
),
};
}
9 changes: 4 additions & 5 deletions apps/studio/features/resume/editor/ResumePagedPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,10 @@ export function ResumePagedPreview({ children }: { children: ReactNode }) {

useLayoutEffect(() => {
const frame = window.requestAnimationFrame(() => {
const container = measureRef.current?.querySelector(
"#resume-container",
) as HTMLElement | null;
const measureRoot = measureRef.current;
const container = measureRoot?.querySelector("#resume-container") as HTMLElement | null;

if (!container) {
if (!measureRoot || !container) {
setPages([]);
return;
}
Expand All @@ -91,7 +90,7 @@ export function ResumePagedPreview({ children }: { children: ReactNode }) {
width: `${RESUME_PAGE_WIDTH_PX}px`,
});

measureRef.current.appendChild(probe);
measureRoot.appendChild(probe);

const fitsPage = (elements: HTMLElement[]) => {
probe.innerHTML = elements.map((element) => element.outerHTML).join("");
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@veriworkly/studio",
"version": "3.10.0",
"version": "3.10.2",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
151 changes: 75 additions & 76 deletions apps/studio/templates/cover-letter/professional/pdf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import type { CoverLetterContent } from "@/features/cover-letter/types";
import { FONT_REGISTRY, normalizeFontFamilyId } from "@/features/documents/constants/fonts";

import {
buildCoverLetterFlowContent,
buildProfessionalFlowItems,
getCoverLetterFlowSenderName,
pt,
PX_TO_PT,
splitParagraphs,
splitMarkdownLines,
getCoverLetterLinks,
splitRichTextBlocks,
getCoverLetterLinkDisplayMode,
isCoverLetterSectionVisible,
type ProfessionalFlowItem,
} from "../shared";

import {
Expand Down Expand Up @@ -152,7 +153,6 @@ export function ProfessionalCoverLetterPdf({ content }: { content: CoverLetterCo
const showProfile = isCoverLetterSectionVisible(content, "profile");
const showLinks = isCoverLetterSectionVisible(content, "links");
const showTarget = isCoverLetterSectionVisible(content, "target");
const showLetter = isCoverLetterSectionVisible(content, "letter");
const font = FONT_REGISTRY[normalizeFontFamilyId(appearance.fontFamily)];
const bodyLineHeight = appearance.lineHeight;

Expand All @@ -169,7 +169,7 @@ export function ProfessionalCoverLetterPdf({ content }: { content: CoverLetterCo
marginBottom: appearance.paragraphSpacing * PX_TO_PT,
};

const senderName = content.senderName || content.signature || "Your Name";
const senderName = getCoverLetterFlowSenderName(content);

const contact = showProfile
? [
Expand All @@ -192,14 +192,76 @@ export function ProfessionalCoverLetterPdf({ content }: { content: CoverLetterCo
].filter(Boolean)
: [];

const bodyBlocks = showLetter
? [
...splitParagraphs(content.opening).map((text) => ({ type: "paragraph" as const, text })),
...splitRichTextBlocks(content.body),
]
: [];
const flowItems = buildProfessionalFlowItems(buildCoverLetterFlowContent(content), senderName);

function renderFlowItem(item: ProfessionalFlowItem) {
if (item.type === "greeting") {
return (
<Text
key={item.id}
style={[styles.paragraph, paragraphStyle, { fontWeight: 600, color: "#09090b" }]}
>
{item.text}
</Text>
);
}

if (item.type === "paragraph") {
return (
<Text key={item.id} style={[styles.paragraph, paragraphStyle]}>
{item.text}
</Text>
);
}

if (item.type === "body-list" || item.type === "proof-list") {
return (
<View key={item.id} style={[styles.bullets, listStyle]}>
{item.items.map((listItem, index) => (
<View
key={`${listItem}-${index}`}
style={[styles.bulletRow, index === item.items.length - 1 ? { marginBottom: 0 } : {}]}
>
<View style={styles.bulletDot}>
<Svg width={pt(8)} height={pt(8)} viewBox="0 0 8 8">
<Circle
cx="4"
cy="4"
r="3.5"
fill={item.type === "proof-list" ? "#09090b" : appearance.accentColor}
/>
</Svg>
</View>

const highlights = showLetter ? splitMarkdownLines(content.highlights) : [];
<Text style={styles.bulletText}>{listItem}</Text>
</View>
))}
</View>
);
}

if (item.type === "closing") {
return (
<Text key={item.id} style={styles.closing}>
{item.text}
</Text>
);
}

if (item.type === "signature") {
return (
<Text key={item.id} style={styles.signature}>
{item.text}
</Text>
);
}

return (
<Text key={item.id} style={styles.postscript}>
P.S. {item.text}
</Text>
);
}

return (
<Document>
Expand Down Expand Up @@ -270,70 +332,7 @@ export function ProfessionalCoverLetterPdf({ content }: { content: CoverLetterCo
) : null}

<View style={[styles.body, { fontSize: bodyFontSize, lineHeight: bodyLineHeight }]}>
{showLetter && content.greeting ? (
<Text style={[styles.paragraph, paragraphStyle, { fontWeight: 600, color: "#09090b" }]}>
{content.greeting}
</Text>
) : null}

{bodyBlocks.map((block, index) =>
block.type === "list" ? (
<View key={`list-${index}`} style={[styles.bullets, listStyle]}>
{block.items.map((item, i) => (
<View
key={i}
style={[
styles.bulletRow,
i === block.items.length - 1 ? { marginBottom: 0 } : {},
]}
>
<View style={styles.bulletDot}>
<Svg width={pt(8)} height={pt(8)} viewBox="0 0 8 8">
<Circle cx="4" cy="4" r="3.5" fill={appearance.accentColor} />
</Svg>
</View>

<Text style={styles.bulletText}>{item}</Text>
</View>
))}
</View>
) : (
<Text key={`paragraph-${index}`} style={[styles.paragraph, paragraphStyle]}>
{block.text}
</Text>
),
)}

{highlights.length > 0 ? (
<View style={[styles.bullets, listStyle]}>
{highlights.map((highlight, i) => (
<View
key={i}
style={[styles.bulletRow, i === highlights.length - 1 ? { marginBottom: 0 } : {}]}
>
<View style={styles.bulletDot}>
<Svg width={pt(8)} height={pt(8)} viewBox="0 0 8 8">
<Circle cx="4" cy="4" r="3.5" fill="#09090b" />
</Svg>
</View>

<Text style={styles.bulletText}>{highlight}</Text>
</View>
))}
</View>
) : null}

{showLetter && content.closing ? (
<Text style={styles.closing}>{content.closing}</Text>
) : null}

{showLetter ? (
<Text style={styles.signature}>{content.signature || senderName}</Text>
) : null}

{showLetter && content.postscript ? (
<Text style={styles.postscript}>P.S. {content.postscript}</Text>
) : null}
{flowItems.map((item) => renderFlowItem(item))}
</View>
</Page>
</Document>
Expand Down
Loading
Loading