mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-06-22 04:11:55 +10:00
fix: improper rendering of text blocks in PDFs
This commit is contained in:
+16
-16
@@ -17,10 +17,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.4.1",
|
||||
"@better-auth/api-key": "^1.6.9",
|
||||
"@better-auth/api-key": "^1.6.10",
|
||||
"@better-auth/infra": "^0.2.6",
|
||||
"@better-auth/oauth-provider": "^1.6.9",
|
||||
"@better-auth/passkey": "^1.6.9",
|
||||
"@better-auth/oauth-provider": "^1.6.10",
|
||||
"@better-auth/passkey": "^1.6.10",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@@ -46,30 +46,30 @@
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@reactive-resume/ui": "workspace:*",
|
||||
"@reactive-resume/utils": "workspace:*",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@tanstack/react-form": "^1.29.1",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@tanstack/react-form": "^1.31.0",
|
||||
"@tanstack/react-hotkeys": "^0.10.0",
|
||||
"@tanstack/react-query": "^5.100.9",
|
||||
"@tanstack/react-router": "^1.169.2",
|
||||
"@tanstack/react-router-ssr-query": "^1.166.12",
|
||||
"@tanstack/react-start": "^1.167.65",
|
||||
"@tiptap/extension-color": "^3.22.5",
|
||||
"@tiptap/extension-highlight": "^3.22.5",
|
||||
"@tiptap/extension-table": "^3.22.5",
|
||||
"@tiptap/extension-text-align": "^3.22.5",
|
||||
"@tiptap/extension-text-style": "^3.22.5",
|
||||
"@tiptap/pm": "^3.22.5",
|
||||
"@tiptap/react": "^3.22.5",
|
||||
"@tiptap/starter-kit": "^3.22.5",
|
||||
"@tiptap/extension-color": "^3.23.1",
|
||||
"@tiptap/extension-highlight": "^3.23.1",
|
||||
"@tiptap/extension-table": "^3.23.1",
|
||||
"@tiptap/extension-text-align": "^3.23.1",
|
||||
"@tiptap/extension-text-style": "^3.23.1",
|
||||
"@tiptap/pm": "^3.23.1",
|
||||
"@tiptap/react": "^3.23.1",
|
||||
"@tiptap/starter-kit": "^3.23.1",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@uiw/color-convert": "^2.10.1",
|
||||
"@uiw/react-color-colorful": "^2.10.1",
|
||||
"better-auth": "1.6.9",
|
||||
"better-auth": "1.6.10",
|
||||
"cmdk": "^1.1.1",
|
||||
"drizzle-orm": "1.0.0-beta.22",
|
||||
"es-toolkit": "^1.46.1",
|
||||
"fuse.js": "^7.3.0",
|
||||
"immer": "^11.1.7",
|
||||
"immer": "^11.1.8",
|
||||
"js-cookie": "^3.0.5",
|
||||
"motion": "^12.38.0",
|
||||
"pdfjs-dist": "5.7.284",
|
||||
@@ -97,7 +97,7 @@
|
||||
"@types/pg": "^8.20.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"@vitejs/plugin-react": "^6.0.1",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"nitro": "3.0.260429-beta",
|
||||
|
||||
+6
-6
@@ -13,7 +13,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/amruthpillai/reactive-resume.git"
|
||||
},
|
||||
"packageManager": "pnpm@11.0.8+sha512.4c4097e1dd2d42372c4e7fa5a791ff28fc75a484c7ac192e64b1df0fdef17594ba982f9b4fed9adfb3c757846f565b799b2763fb3733d1de1bcb82cf46684912",
|
||||
"packageManager": "pnpm@11.0.9",
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
@@ -36,9 +36,9 @@
|
||||
"test:agent": "turbo run test:agent"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.14",
|
||||
"@commitlint/cli": "^20.5.3",
|
||||
"@commitlint/config-conventional": "^20.5.3",
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"@commitlint/cli": "^21.0.0",
|
||||
"@commitlint/config-conventional": "^21.0.0",
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
@@ -47,10 +47,10 @@
|
||||
"@types/node": "^25.6.2",
|
||||
"@vitest/coverage-v8": "^4.1.5",
|
||||
"jsdom": "^29.1.1",
|
||||
"knip": "^6.12.1",
|
||||
"knip": "^6.12.2",
|
||||
"lefthook": "^2.1.6",
|
||||
"npm-check-updates": "^22.1.1",
|
||||
"turbo": "^2.9.10",
|
||||
"turbo": "^2.9.12",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^4.1.5"
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
"@reactive-resume/utils": "workspace:*",
|
||||
"deepmerge-ts": "^7.1.5",
|
||||
"fast-json-patch": "^3.1.1",
|
||||
"immer": "^11.1.7",
|
||||
"immer": "^11.1.8",
|
||||
"jsonrepair": "^3.14.0",
|
||||
"zod": "^4.4.3",
|
||||
"zustand": "^5.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.76",
|
||||
"@ai-sdk/google": "^3.0.70",
|
||||
"@ai-sdk/google": "^3.0.71",
|
||||
"@ai-sdk/openai": "^3.0.63",
|
||||
"@ai-sdk/openai-compatible": "^2.0.47",
|
||||
"@aws-sdk/client-s3": "^3.1045.0",
|
||||
@@ -33,9 +33,9 @@
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@reactive-resume/utils": "workspace:*",
|
||||
"@tanstack/react-start": "^1.167.65",
|
||||
"ai": "^6.0.176",
|
||||
"ai": "^6.0.177",
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-auth": "1.6.9",
|
||||
"better-auth": "1.6.10",
|
||||
"drizzle-orm": "1.0.0-beta.22",
|
||||
"drizzle-zod": "1.0.0-beta.14-a36c63d",
|
||||
"es-toolkit": "^1.46.1",
|
||||
@@ -49,7 +49,7 @@
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
"test:agent": "vitest run --reporter=agent --reporter=json --outputFile.json=reports/vitest-results.json --passWithNoTests"
|
||||
},
|
||||
"dependencies": {
|
||||
"@better-auth/api-key": "^1.6.9",
|
||||
"@better-auth/drizzle-adapter": "^1.6.9",
|
||||
"@better-auth/api-key": "^1.6.10",
|
||||
"@better-auth/drizzle-adapter": "^1.6.10",
|
||||
"@better-auth/infra": "^0.2.6",
|
||||
"@better-auth/oauth-provider": "^1.6.9",
|
||||
"@better-auth/passkey": "^1.6.9",
|
||||
"@better-auth/oauth-provider": "^1.6.10",
|
||||
"@better-auth/passkey": "^1.6.10",
|
||||
"@reactive-resume/db": "workspace:*",
|
||||
"@reactive-resume/email": "workspace:*",
|
||||
"@reactive-resume/env": "workspace:*",
|
||||
"@reactive-resume/utils": "workspace:*",
|
||||
"@tanstack/react-start": "^1.167.65",
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-auth": "1.6.9",
|
||||
"better-auth": "1.6.10",
|
||||
"drizzle-orm": "1.0.0-beta.22",
|
||||
"jose": "^6.2.3",
|
||||
"react": "^19.2.6",
|
||||
@@ -37,7 +37,7 @@
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@types/pg": "^8.20.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"drizzle-kit": "1.0.0-beta.22",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@types/nodemailer": "^8.0.0",
|
||||
"@types/react": "^19.2.14",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -22,7 +22,7 @@
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@types/node": "^25.6.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@reactive-resume/fonts": "workspace:*",
|
||||
"@reactive-resume/schema": "workspace:*",
|
||||
"@reactive-resume/utils": "workspace:*",
|
||||
"node-html-parser": "^7.1.0",
|
||||
"phosphor-icons-react-pdf": "^0.1.3",
|
||||
"react": "^19.2.6",
|
||||
"react-pdf-html": "^2.1.5",
|
||||
@@ -30,7 +31,7 @@
|
||||
"@react-pdf/types": "^2.11.1",
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@types/react": "^19.2.14",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { Node } from "node-html-parser";
|
||||
import { NodeType, parse } from "node-html-parser";
|
||||
|
||||
const inlineTags = new Set([
|
||||
"a",
|
||||
"abbr",
|
||||
"b",
|
||||
"br",
|
||||
"button",
|
||||
"cite",
|
||||
"code",
|
||||
"dfn",
|
||||
"em",
|
||||
"i",
|
||||
"label",
|
||||
"q",
|
||||
"s",
|
||||
"span",
|
||||
"strong",
|
||||
"sub",
|
||||
"sup",
|
||||
"u",
|
||||
]);
|
||||
|
||||
const getTagName = (node: Node) => node.rawTagName.toLowerCase();
|
||||
|
||||
const hasBlockDescendant = (node: Node): boolean =>
|
||||
node.childNodes.some((child) => child.nodeType === NodeType.ELEMENT_NODE && !isInlineNode(child));
|
||||
|
||||
const isInlineNode = (node: Node): boolean => {
|
||||
if (node.nodeType === NodeType.TEXT_NODE || node.nodeType === NodeType.COMMENT_NODE) return true;
|
||||
if (node.nodeType !== NodeType.ELEMENT_NODE) return false;
|
||||
|
||||
return inlineTags.has(getTagName(node)) && !hasBlockDescendant(node);
|
||||
};
|
||||
|
||||
export const normalizeRichTextHtml = (html: string): string => {
|
||||
const root = parse(html.trim(), { comment: false });
|
||||
const normalized: string[] = [];
|
||||
let inlineNodes: string[] = [];
|
||||
|
||||
const flushInlineNodes = () => {
|
||||
const inlineHtml = inlineNodes.join("").trim();
|
||||
|
||||
if (inlineHtml) normalized.push(`<p>${inlineHtml}</p>`);
|
||||
|
||||
inlineNodes = [];
|
||||
};
|
||||
|
||||
for (const node of root.childNodes) {
|
||||
const nodeHtml = node.toString();
|
||||
|
||||
if (isInlineNode(node)) {
|
||||
inlineNodes.push(nodeHtml);
|
||||
continue;
|
||||
}
|
||||
|
||||
flushInlineNodes();
|
||||
normalized.push(nodeHtml);
|
||||
}
|
||||
|
||||
flushInlineNodes();
|
||||
|
||||
return normalized.join("");
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeRichTextHtml } from "./rich-text-html";
|
||||
|
||||
describe("normalizeRichTextHtml", () => {
|
||||
it("wraps top-level inline rich text in a paragraph", () => {
|
||||
const html =
|
||||
"Passionate game developer with 5+ years of professional experience</strong> creating engaging gameplay. <a href='https://www.google.com'>Specialized</a> in Unity.";
|
||||
|
||||
expect(normalizeRichTextHtml(html)).toBe(
|
||||
"<p>Passionate game developer with 5+ years of professional experience creating engaging gameplay. <a href='https://www.google.com'>Specialized</a> in Unity.</p>",
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves existing block rich text", () => {
|
||||
expect(normalizeRichTextHtml("<p>Existing paragraph.</p><ul><li><p>Existing item.</p></li></ul>")).toBe(
|
||||
"<p>Existing paragraph.</p><ul><li><p>Existing item.</p></li></ul>",
|
||||
);
|
||||
});
|
||||
|
||||
it("wraps inline runs around top-level blocks", () => {
|
||||
expect(normalizeRichTextHtml("Intro <strong>text</strong><ul><li><p>Item</p></li></ul>Outro")).toBe(
|
||||
"<p>Intro <strong>text</strong></p><ul><li><p>Item</p></li></ul><p>Outro</p>",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ import { Text as PdfText, View } from "@react-pdf/renderer";
|
||||
import { Html } from "react-pdf-html";
|
||||
import { useTemplateStyle } from "./context";
|
||||
import { safeTextStyle } from "./primitives";
|
||||
import { normalizeRichTextHtml } from "./rich-text-html";
|
||||
import { composeStyles, mergeLinkStyles, mergeStyles } from "./styles";
|
||||
|
||||
const richListItemContentStackStyle = {
|
||||
@@ -17,7 +18,9 @@ export const RichText = ({ children }: { children: string }) => {
|
||||
const richListItemMarkerStyle = useTemplateStyle("richListItemMarker");
|
||||
const richListItemContentStyle = useTemplateStyle("richListItemContent");
|
||||
|
||||
if (!children.trim()) return null;
|
||||
const html = normalizeRichTextHtml(children);
|
||||
|
||||
if (!html) return null;
|
||||
|
||||
return (
|
||||
<Html
|
||||
@@ -54,7 +57,7 @@ export const RichText = ({ children }: { children: string }) => {
|
||||
a: mergeLinkStyles(linkStyle, safeTextStyle),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
{html}
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,7 +460,7 @@ export const sampleResumeData: ResumeData = {
|
||||
},
|
||||
customSections: [
|
||||
{
|
||||
title: "Experience",
|
||||
title: "",
|
||||
columns: 1,
|
||||
hidden: false,
|
||||
id: "019becaf-0b87-769d-98a6-46ccf558c0e8",
|
||||
@@ -551,7 +551,7 @@ export const sampleResumeData: ResumeData = {
|
||||
marginX: 16,
|
||||
marginY: 16,
|
||||
format: "a4",
|
||||
locale: "de-DE",
|
||||
locale: "en-US",
|
||||
hideIcons: false,
|
||||
},
|
||||
design: {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@reactive-resume/env": "workspace:*",
|
||||
"@types/pg": "^8.20.0",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"drizzle-orm": "1.0.0-beta.22",
|
||||
"pg": "^8.20.0",
|
||||
"tsx": "^4.21.0"
|
||||
|
||||
@@ -38,8 +38,8 @@
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"tailwindcss": "^4.3.0",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"devDependencies": {
|
||||
"@reactive-resume/config": "workspace:*",
|
||||
"@types/node": "^25.6.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260508.1",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260510.1",
|
||||
"typescript": "^6.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+831
-795
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user