Compare commits

..

10 Commits

Author SHA1 Message Date
ecbd9a0480 New Crowdin translations by GitHub Action 2025-11-08 00:10:45 +00:00
25021d1b20 Set up CI with Azure Pipelines
[skip ci]
2025-11-07 12:11:28 +01:00
53fdfdf8db Merge pull request #2437 from Sahilbhatane/main
fix(artboard): bypass Google Fonts for local system fonts and post PAGE_LOADED immediately; add utils docstrings and unit tests for isLocalFont issue #2435
2025-11-06 17:19:33 +01:00
8a45f2de4d fixed issue [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 19:08:38 +05:30
4ccc7bae40 Fixed issue [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 18:59:20 +05:30
2585c47de8 Fixed bug [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 16:33:56 +05:30
b49798950a FIxed bug [Bug] <Error MIME al cargar fuente “Arial” desde Google Fonts en exportaciones públicas>
Fixes #2435
2025-11-05 16:12:57 +05:30
03f15f91b3 chore(package): bump version to 4.5.5 2025-11-04 18:36:52 +01:00
8e13d9c3ac Merge pull request #2434 from AmruthPillai/2275-bug-certifications-texts-dont-fit-inside-the-page
Bugfix: links/urls don't fit inside the page
2025-11-04 18:34:42 +01:00
80932eb80c Fix bug: links/urls don't fit inside the page 2025-11-04 18:31:47 +01:00
25 changed files with 160 additions and 97 deletions

View File

@ -5,7 +5,6 @@
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"extends": ["plugin:prettier/recommended"],
"plugins": ["simple-import-sort", "unused-imports"],
"rules": {
// eslint
@ -42,14 +41,6 @@
}
]
}
],
// prettier
"prettier/prettier": [
"warn",
{
"endOfLine": "auto"
}
]
}
},

View File

@ -4,13 +4,6 @@
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"extends": ["plugin:tailwindcss/recommended"],
"settings": {
"tailwindcss": {
"callees": ["cn", "clsx", "cva"],
"config": "tailwind.config.js"
}
},
"rules": {
// eslint
"@typescript-eslint/no-require-imports": "off",
@ -28,10 +21,7 @@
],
// react-hooks
"react-hooks/exhaustive-deps": "off",
// tailwindcss
"tailwindcss/no-custom-classname": "off"
"react-hooks/exhaustive-deps": "off"
}
},
{

View File

@ -1,3 +1,4 @@
import { isLocalFont } from "@reactive-resume/utils";
import { useEffect, useMemo } from "react";
import { Helmet } from "react-helmet-async";
import { Outlet } from "react-router";
@ -18,6 +19,18 @@ export const ArtboardPage = () => {
}, [metadata.typography.font]);
useEffect(() => {
const family = metadata.typography.font.family;
if (isLocalFont(family)) {
let frame = 0;
frame = requestAnimationFrame(() => {
const width = window.document.body.offsetWidth;
const height = window.document.body.offsetHeight;
const message = { type: "PAGE_LOADED", payload: { width, height } };
window.postMessage(message, "*");
});
return () => { cancelAnimationFrame(frame); };
}
webfontloader.load({
google: { families: [fontString] },
active: () => {

View File

@ -131,13 +131,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -553,7 +553,7 @@ export const Azurill = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="p-custom space-y-3">
<div className="space-y-3 p-custom">
{isFirstPage && <Header />}
<div className="grid grid-cols-3 gap-x-4">

View File

@ -122,13 +122,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -568,7 +568,7 @@ export const Bronzor = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="p-custom space-y-4">
<div className="space-y-4 p-custom">
{isFirstPage && <Header />}
<div className="space-y-4">

View File

@ -125,14 +125,14 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-white" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -571,7 +571,7 @@ export const Chikorita = ({ columns, isFirstPage = false }: TemplateProps) => {
<div className="grid min-h-[inherit] grid-cols-3">
<div
className={cn(
"main p-custom group space-y-4",
"main group space-y-4 p-custom",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>
@ -584,7 +584,7 @@ export const Chikorita = ({ columns, isFirstPage = false }: TemplateProps) => {
<div
className={cn(
"sidebar p-custom group h-full space-y-4 bg-primary text-background",
"sidebar group h-full space-y-4 bg-primary p-custom text-background",
sidebar.length === 0 && "hidden",
)}
>

View File

@ -28,7 +28,7 @@ const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="p-custom relative grid grid-cols-3 space-x-4 pb-0">
<div className="relative grid grid-cols-3 space-x-4 p-custom pb-0">
<Picture className="mx-auto" />
<div className="relative z-10 col-span-2 text-background">
@ -142,13 +142,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -604,7 +604,7 @@ export const Ditto = ({ columns, isFirstPage = false }: TemplateProps) => {
)}
<div className="grid grid-cols-3">
<div className="sidebar p-custom group space-y-4">
<div className="sidebar group space-y-4 p-custom">
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
@ -612,7 +612,7 @@ export const Ditto = ({ columns, isFirstPage = false }: TemplateProps) => {
<div
className={cn(
"main p-custom group space-y-4",
"main group space-y-4 p-custom",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>

View File

@ -28,7 +28,7 @@ const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
return (
<div className="p-custom space-y-4 bg-primary text-background">
<div className="space-y-4 bg-primary p-custom text-background">
<Picture className="border-background" />
<div>
@ -86,7 +86,7 @@ const Summary = () => {
if (!section.visible || isEmptyString(section.content)) return null;
return (
<div className="p-custom space-y-4" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
<div className="space-y-4 p-custom" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
<section id={section.id}>
<div
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
@ -123,7 +123,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight &&
(icon ?? (
<i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-background" />
@ -132,7 +132,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -531,7 +531,7 @@ const mapSectionToComponent = (section: SectionKey) => {
case "education": {
return <Education />;
}
case "summary": {
return <Summary />;
}
@ -587,7 +587,7 @@ export const Gengar = ({ columns, isFirstPage = false }: TemplateProps) => {
{isFirstPage && <Header />}
<div
className="p-custom flex-1 space-y-4"
className="flex-1 space-y-4 p-custom"
style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}
>
{sidebar.map((section) => (
@ -597,7 +597,7 @@ export const Gengar = ({ columns, isFirstPage = false }: TemplateProps) => {
</div>
<div className={cn("main group", sidebar.length > 0 ? "col-span-2" : "col-span-3")}>
<div className="p-custom space-y-4">
<div className="space-y-4 p-custom">
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}

View File

@ -135,14 +135,14 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -587,7 +587,7 @@ export const Glalie = ({ columns, isFirstPage = false }: TemplateProps) => {
return (
<div className="grid min-h-[inherit] grid-cols-3">
<div
className={cn("sidebar p-custom group space-y-4", sidebar.length === 0 && "hidden")}
className={cn("sidebar group space-y-4 p-custom", sidebar.length === 0 && "hidden")}
style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}
>
{isFirstPage && <Header />}
@ -599,7 +599,7 @@ export const Glalie = ({ columns, isFirstPage = false }: TemplateProps) => {
<div
className={cn(
"main p-custom group space-y-4",
"main group space-y-4 p-custom",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>

View File

@ -141,13 +141,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -524,7 +524,7 @@ export const Kakuna = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="p-custom space-y-4">
<div className="space-y-4 p-custom">
{isFirstPage && <Header />}
<div className="space-y-4">

View File

@ -32,7 +32,7 @@ const Header = () => {
return (
<div>
<div
className="p-custom flex items-center space-x-8"
className="flex items-center space-x-8 p-custom"
style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}
>
<div className="space-y-3">
@ -51,7 +51,7 @@ const Header = () => {
<Picture />
</div>
<div className="p-custom space-y-3" style={{ backgroundColor: hexToRgb(primaryColor, 0.4) }}>
<div className="space-y-3 p-custom" style={{ backgroundColor: hexToRgb(primaryColor, 0.4) }}>
<div className="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-sm">
{basics.location && (
<div className="flex items-center gap-x-1.5">
@ -136,13 +136,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -519,7 +519,7 @@ export const Leafish = ({ columns, isFirstPage = false }: TemplateProps) => {
<div>
{isFirstPage && <Header />}
<div className="p-custom grid grid-cols-2 items-start space-x-6">
<div className="grid grid-cols-2 items-start space-x-6 p-custom">
<div className={cn("grid gap-y-4", sidebar.length === 0 && "col-span-2")}>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>

View File

@ -126,13 +126,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -575,7 +575,7 @@ export const Nosepass = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="p-custom space-y-6">
<div className="space-y-6 p-custom">
<div className="flex items-center justify-between">
<img alt="Europass Logo" className="h-[42px]" src="/assets/europass.png" />

View File

@ -142,13 +142,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -564,7 +564,7 @@ export const Onyx = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="p-custom space-y-4">
<div className="space-y-4 p-custom">
{isFirstPage && <Header />}
{main.map((section) => (

View File

@ -126,15 +126,11 @@ const Rating = ({ level }: RatingProps) => (
<i
key={index}
className={cn(
"ph ph-diamond text-primary",
"ph ph-bold ph-diamond text-primary",
level > index && "ph-fill",
level <= index && "ph-bold",
)}
/>
// <div
// key={index}
// className={cn("h-2 w-4 border border-primary", level > index && "bg-primary")}
// />
))}
</div>
);
@ -151,7 +147,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 break-all">
{!iconOnRight &&
(icon ?? (
<i className="ph ph-bold ph-link text-primary group-[.summary]:text-background" />
@ -160,7 +156,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -601,7 +597,7 @@ export const Pikachu = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="p-custom grid grid-cols-3 space-x-6">
<div className="grid grid-cols-3 space-x-6 p-custom">
<div className="sidebar group space-y-4">
{isFirstPage && <Picture className="w-full !max-w-none" />}

View File

@ -123,13 +123,13 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5 border-r pr-2 last:border-r-0 last:pr-0">
<div className="flex items-center gap-x-1.5 break-all border-r pr-2 last:border-r-0 last:pr-0">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}
target="_blank"
rel="noreferrer noopener nofollow"
className={cn("inline-block", className)}
className={cn("line-clamp-1 max-w-fit", className)}
>
{label ?? (url.label || url.href)}
</a>
@ -567,7 +567,7 @@ export const Rhyhorn = ({ columns, isFirstPage = false }: TemplateProps) => {
const [main, sidebar] = columns;
return (
<div className="p-custom space-y-4">
<div className="space-y-4 p-custom">
{isFirstPage && <Header />}
{main.map((section) => (

View File

@ -4,20 +4,10 @@
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"extends": [
"plugin:tailwindcss/recommended",
"plugin:@tanstack/eslint-plugin-query/recommended"
],
"extends": ["plugin:@tanstack/eslint-plugin-query/recommended"],
"parserOptions": {
"projectService": "./apps/client/tsconfig.json"
},
"settings": {
"tailwindcss": {
"callees": ["cn", "clsx", "cva"],
"config": "tailwind.config.js",
"whitelist": ["ph", "ph\\-.*", "si", "si\\-.*"]
}
},
"plugins": ["lingui"],
"rules": {
// eslint

View File

@ -8,7 +8,7 @@ msgstr ""
"Language: te\n"
"Project-Id-Version: reactive-resume\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2025-11-04 14:22\n"
"PO-Revision-Date: 2025-11-06 00:11\n"
"Last-Translator: \n"
"Language-Team: Telugu\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@ -314,13 +314,13 @@ msgstr "కోడ్ ఖచ్చితంగా 6 అంకెలు ఉండ
#: apps/client/src/pages/builder/sidebars/left/index.tsx:272
msgid "Collapse All"
msgstr ""
msgstr "అన్నిటినీ మూసివేయి"
#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:30
#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-base.tsx:115
#: apps/client/src/pages/builder/sidebars/left/sections/summary.tsx:33
msgid "Collapse section"
msgstr ""
msgstr "విభాగాన్ని మూసివేయి"
#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-options.tsx:136
msgid "Columns"
@ -583,13 +583,13 @@ msgstr "మీరు ఆర్థికంగా సహాయం చేయలే
#: apps/client/src/pages/builder/sidebars/left/index.tsx:271
msgid "Expand All"
msgstr ""
msgstr "అన్నింటినీ తెరవు"
#: apps/client/src/pages/builder/sidebars/left/sections/basics.tsx:30
#: apps/client/src/pages/builder/sidebars/left/sections/shared/section-base.tsx:115
#: apps/client/src/pages/builder/sidebars/left/sections/summary.tsx:33
msgid "Expand section"
msgstr ""
msgstr "విభాగాన్ని తెరవు"
#: apps/client/src/pages/home/sections/templates/index.tsx:12
msgid "Explore the templates available in Reactive Resume and view the resumes crafted with them. They could also serve as examples to help guide the creation of your next resume."

View File

@ -23,7 +23,6 @@ export const AuthLayout = () => {
const hideDivider = !providers.includes("email") || providers.length === 1;
return (
// eslint-disable-next-line tailwindcss/enforces-shorthand -- size-screen not implemented yet
<div className="flex h-screen w-screen">
<div className="relative flex w-full flex-col justify-center gap-y-8 px-12 sm:mx-auto sm:basis-[420px] sm:px-0 lg:basis-[480px] lg:px-12">
<div className="flex items-center justify-between">

View File

@ -53,7 +53,11 @@ export const CustomField = ({ field, onChange, onRemove }: CustomFieldProps) =>
<Tooltip content={t`Icon`}>
<PopoverTrigger asChild>
<Button size="icon" variant="ghost" className="shrink-0">
{field.icon ? <i className={cn(`ph ph-${field.icon}`)} /> : <EnvelopeIcon />}
{field.icon ? (
<i className={cn(`ph ph-bold ph-${field.icon}`)} />
) : (
<EnvelopeIcon />
)}
</Button>
</PopoverTrigger>
</Tooltip>

View File

@ -21,6 +21,13 @@ export default defineConfig({
host: true,
port: 5173,
fs: { allow: [searchForWorkspaceRoot(process.cwd())] },
proxy: {
"/artboard": {
target: "http://localhost:6173",
changeOrigin: true,
},
},
},
optimizeDeps: {

40
azure-pipelines.yml Normal file
View File

@ -0,0 +1,40 @@
# Docker
# Build and push an image to Azure Container Registry
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker
trigger:
- main
resources:
- repo: self
variables:
# Container registry service connection established during pipeline creation
dockerRegistryServiceConnection: 'c6da80c0-7d65-4b93-9203-3b91c4f6b5dc'
imageRepository: 'reactiveresume'
containerRegistry: 'reactiveresume.azurecr.io'
dockerfilePath: '$(Build.SourcesDirectory)/Dockerfile'
tag: '$(Build.BuildId)'
# Agent VM image name
vmImageName: 'ubuntu-latest'
stages:
- stage: Build
displayName: Build and push stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: $(vmImageName)
steps:
- task: Docker@2
displayName: Build and push an image to container registry
inputs:
command: buildAndPush
repository: $(imageRepository)
dockerfile: $(dockerfilePath)
containerRegistry: $(dockerRegistryServiceConnection)
arguments: '--platform linux/amd64,linux/arm64'
tags: |
$(tag)

View File

@ -4,13 +4,6 @@
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"extends": ["plugin:tailwindcss/recommended"],
"settings": {
"tailwindcss": {
"callees": ["cn", "clsx", "cva"],
"config": "tailwind.config.js"
}
},
"rules": {
// eslint
"@typescript-eslint/no-require-imports": "off",

View File

@ -6,6 +6,21 @@ export type Font = {
files: Record<string, string>;
};
/**
* Known system fonts we consider available locally without fetching from Google Fonts.
* Extend this list when adding more system-safe families to the app.
*/
export const localFonts = ["Arial", "Cambria", "Garamond", "Times New Roman"];
/**
* Checks whether a font family is a local/system font.
*
* Input: font family name (case-insensitive)
* Output: true if present in localFonts, otherwise false
*/
export const isLocalFont = (family: string): boolean =>
localFonts.some((f) => f.toLowerCase() === family.toLowerCase());
export const fonts: Font[] = [
{
family: "Roboto",

View File

@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { isLocalFont, localFonts } from "../fonts";
describe("isLocalFont", () => {
it("returns true for known local fonts (case-insensitive)", () => {
expect(isLocalFont("Arial")).toBe(true);
expect(isLocalFont("arial")).toBe(true);
expect(isLocalFont("Times New Roman")).toBe(true);
expect(isLocalFont("times new roman")).toBe(true);
});
it("returns false for non-local fonts", () => {
expect(isLocalFont("Roboto")).toBe(false);
expect(isLocalFont("Open Sans")).toBe(false);
});
});
describe("localFonts", () => {
it("includes the expected base set", () => {
for (const f of ["Arial", "Cambria", "Garamond", "Times New Roman"]) {
expect(localFonts).toContain(f);
}
});
});

View File

@ -1,7 +1,7 @@
{
"name": "@reactive-resume/source",
"description": "A free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.",
"version": "4.5.4",
"version": "4.5.5",
"license": "MIT",
"private": true,
"packageManager": "pnpm@10.20.0",