Compare commits

...

73 Commits

Author SHA1 Message Date
65ba13e503 update dependencies 2025-10-01 10:46:46 +02:00
44ec9e1d43 remove version in lint-test-build.yml 2025-10-01 10:40:35 +02:00
7c53949741 fix formatting 2025-10-01 10:39:49 +02:00
6d37769e38 Update dependencies and refactor icon imports across the codebase 2025-10-01 10:38:50 +02:00
c525f8d2cc fix security issue, with notes being visible in public api response 2025-10-01 10:07:04 +02:00
04dfcae898 update links to digitalocean 2025-10-01 09:44:26 +02:00
9b5a99a8ca update hyperlinks 2025-10-01 09:39:26 +02:00
c44f1c5282 update hyperlinks 2025-10-01 09:39:00 +02:00
24dfa99034 Update README with Warp Sponsorship
Added Warp sponsorship section and re-format app description.
2025-10-01 09:38:10 +02:00
b995a6b6c0 Merge pull request #2346 from l0b0/main
chore: Remove executable flag from images
2025-08-08 08:58:38 +00:00
3e76a52306 Merge branch 'main' into main 2025-07-13 16:40:03 +02:00
4e91a2e2ef fix build error for ci workflow 2025-07-03 15:40:09 +02:00
4314912d5a security updates, dependencies house-keeping, add new translations etc. 2025-07-03 15:35:00 +02:00
93da5157ff chore: Remove executable flag from images 2025-06-30 14:52:56 +02:00
cd21860535 Update typography.tsx
fixes #2061
2025-02-09 12:10:14 +01:00
7054623678 chore(dependencies): update TypeScript ESLint and Vitest to latest versions
- Bump @typescript-eslint packages to 8.23.0
- Update Vitest and related packages to 2.1.9
- Minor version upgrades for ts-api-utils and other related dependencies
2025-02-03 19:51:39 +01:00
ec4e43d4fc sync translations from crowdin 2025-02-03 10:14:15 +01:00
1d4529128f fix lint issues 2025-02-03 09:41:21 +01:00
60ed3e2a8d Update lint-test-build.yml 2025-02-02 16:41:31 +01:00
5b67e7c0b4 Update package.json 2025-02-02 16:40:12 +01:00
1399d3c44b Merge pull request #2202 from Pieczasz/feat/2169-project-tags-order
feat(projects): allow reordering of project item tags
2025-02-02 16:39:47 +01:00
eb543cf32d feat(projects): allow reordering of project item tags
Related to #2169
2025-02-02 13:33:39 +01:00
9c6d9833d6 Merge pull request #2201 from AmruthPillai/l10n
New Translations from Crowdin
2025-02-01 04:16:05 +01:00
f50cbd71b7 New Crowdin translations by GitHub Action 2025-02-01 00:10:44 +00:00
3d5b3db321 Merge pull request #2198 from omimouni/main
feat(router): add global error boundary for route errors
2025-01-31 16:08:21 +01:00
e438602773 fix(errorpage): added localProvider, replaced a with Link and added custom messages
- Add LocaleProvider wrapper for i18n support
- Replace a tag with React Router Link components
- Add custom error messages for different HTTP status codes
- Implement proper translation support using Trans and t components
2025-01-31 14:38:17 +01:00
f42b29e4ac feat(router): add global error boundary for route errors
- Use React Router’s useRouteError to get error details.
- Create an ErrorPage component in pages/public/error.tsx.
- Show useful error messages based on status codes.
Add a button to help users go back or retry.
2025-01-31 14:01:18 +01:00
92995d9c2b fix formatting and lint errors 2025-01-30 17:57:14 +01:00
b21f1648c4 Merge pull request #2188 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-30 17:46:57 +01:00
94c04b44df Merge pull request #2194 from Creative-Geek/patch-1
Allow Usage of OpenAI Compatible APIs
2025-01-30 17:46:31 +01:00
54ed0678bf Merge pull request #2195 from ADecametre/main
Fix broken images
2025-01-30 17:45:17 +01:00
817ec96963 Fix TypeError 2025-01-30 09:14:44 -05:00
8ad5458e2a Fix broken images
- Allowed img tags to be used in summaries
2025-01-30 08:56:43 -05:00
a87c5edd60 attempt to fix ci errors 2025-01-30 10:41:38 +01:00
e4327736bd fix formatting issues 2025-01-30 09:35:10 +01:00
1fddbe5f92 Update package.json 2025-01-30 08:19:25 +01:00
2c482e7df8 Merge pull request #2186 from ADecametre/main
Fix broken hyperlinks, images and custom CSS (#2182)
2025-01-30 08:18:47 +01:00
c8edcd3dad Allow usage of openai compatible apis
Changed front-end fields verification to allow the user to enter any openai compatible endpoint and api key.
to do: make a verify connection button and edit paragraphs to reflect the change
2025-01-30 08:49:22 +02:00
a82c25c7cb New Crowdin translations by GitHub Action 2025-01-30 00:09:54 +00:00
73b423030f Removed unused import 2025-01-27 23:10:29 -05:00
809551d0f8 Fix #2182 2025-01-27 19:23:28 -05:00
e795ec64d6 update translations 2025-01-27 09:59:37 +01:00
f8373e4798 Merge pull request #2179 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-26 21:25:38 +01:00
a31c434fbc New Crowdin translations by GitHub Action 2025-01-26 00:10:57 +00:00
1fa8aae80a bump version to v4.4.2 2025-01-26 01:00:49 +01:00
27b60a4df9 fixes #2176, text align was not being reflected in summary sections 2025-01-26 01:00:38 +01:00
d21983aab4 update translations 2025-01-25 00:49:33 +01:00
9406d78653 - include more local fonts, such as Times New Roman and Arial (fixes #2170)
- remove embedding fonts to PDF as it wasn't doing anything
2025-01-25 00:47:39 +01:00
c7ae0e94d7 sanitize all user inputs, fix #2172 2025-01-24 23:53:45 +01:00
308a8e3ae3 update translations 2025-01-24 21:20:12 +01:00
4c90cc1838 fix security vulnerability with update password API route 2025-01-24 21:13:24 +01:00
460a40711e fix issue with missing DialogTitle/DialogDescription, fix issue with hot reloads 2025-01-19 22:01:37 +01:00
18cf814779 fixes #2161, remove v1 validation requirement for openai api url 2025-01-17 22:38:45 +01:00
a9656afbbf sync translations from crowdin 2025-01-17 22:33:13 +01:00
385fe008ce Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-01-17 22:32:06 +01:00
7e25e853d7 update dependencies 2025-01-17 22:31:57 +01:00
de5adbe3d2 Merge pull request #2160 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-16 22:36:05 +01:00
c239ae3f49 update dependencies 2025-01-16 12:09:14 +01:00
1bfdff5b30 New Crowdin translations by GitHub Action 2025-01-16 00:10:16 +00:00
63db927924 - fixes #2153, attempt to fix 401 unauthorized error when implementing OIDC 2025-01-15 16:32:43 +01:00
9a34e4af27 Merge pull request #2155 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-15 15:59:02 +01:00
b5589338ec New Crowdin translations by GitHub Action 2025-01-15 00:10:04 +00:00
a19059aa76 Merge pull request #2152 from AmruthPillai/2151-bug-openid-authentication-only-works-when-node_env-is-development
fixes #2151: apply secure cookie session only if using SSL (https)
2025-01-14 10:24:11 +01:00
15f962310b fix lint issue 2025-01-14 09:52:26 +01:00
a32def2086 fixes #2151, apply secure cookie session only if using SSL (https) 2025-01-14 09:45:57 +01:00
21af624096 Merge pull request #2149 from AmruthPillai/feat/implement-openid-connect-strategy
Implement OpenID Connect Authentication Strategy
2025-01-13 16:22:43 +01:00
227870ac78 fix issues suggested by coderabbit 2025-01-13 16:22:29 +01:00
33cb3dbd6a ensure secure cookies are used in express-session 2025-01-13 16:04:27 +01:00
eb7813ac6f Implement OpenID Connect Authentication Strategy (works with Keycloak, Authentik etc.) 2025-01-13 15:56:29 +01:00
0f8f2fe560 cleanup internal package resolution 2025-01-13 11:53:42 +01:00
51f38f0884 update translations 2025-01-13 01:50:39 +01:00
6b93fd179d Merge pull request #2147 from AmruthPillai/l10n
New Translations from Crowdin
2025-01-13 01:15:29 +01:00
9385f36832 New Crowdin translations by GitHub Action 2025-01-13 00:10:39 +00:00
286 changed files with 24188 additions and 17659 deletions

View File

@ -68,3 +68,14 @@ STORAGE_SKIP_BUCKET_CHECK=false
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
# GOOGLE_CALLBACK_URL=http://localhost:5173/api/auth/google/callback
# OpenID (Optional)
# VITE_OPENID_NAME=
# OPENID_AUTHORIZATION_URL=
# OPENID_CALLBACK_URL=http://localhost:5173/api/auth/openid/callback
# OPENID_CLIENT_ID=
# OPENID_CLIENT_SECRET=
# OPENID_ISSUER=
# OPENID_SCOPE=openid profile email
# OPENID_TOKEN_URL=
# OPENID_USER_INFO_URL=

View File

@ -78,6 +78,7 @@
"@typescript-eslint/no-misused-promises": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/restrict-template-expressions": "off",
"@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/consistent-type-definitions": ["error", "type"],

View File

@ -39,7 +39,7 @@ jobs:
run: pnpm run lint
- name: Format
run: pnpm run format:check
run: pnpm run format
- name: Test
run: pnpm run test

1
.gitignore vendored
View File

@ -40,6 +40,7 @@ Thumbs.db
# Generated Files
.nx
.swc
.turbo
fly.toml
stats.html

View File

@ -5,7 +5,9 @@
"install": "always",
"packageManager": "pnpm",
"reject": [
"nx",
"eslint",
"@nx/*",
"@swc/*",
"@swc-node/*",
"@reactive-resume/*",

View File

@ -1,9 +1,9 @@
{
"css.validate": false,
"vitest.disableWorkspaceWarning": true,
"typescript.tsdk": "node_modules/typescript/lib",
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
]
],
"i18n-ally.localesPaths": ["apps/client/src/locales"]
}

View File

@ -7,7 +7,7 @@ ARG NX_CLOUD_ACCESS_TOKEN
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable pnpm && corepack prepare pnpm --activate
RUN corepack enable
WORKDIR /app

View File

@ -1,4 +1,15 @@
![Reactive Resume](https://i.imgur.com/FFc4nyZ.jpg)
<div align="center" markdown="1">
<a href="https://go.warp.dev/Reactive-Resume">
<img alt="Warp Sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-03.png?raw=true" />
</a>
### [Warp, built for coding with multiple AI agents.](https://go.warp.dev/Reactive-Resume)
[Available for MacOS, Linux, & Windows](https://go.warp.dev/Reactive-Resume)<br>
---
<img alt="Reactive Resume" width="800" src="https://i.imgur.com/FFc4nyZ.jpg" />
![App Version](https://img.shields.io/github/package-json/version/AmruthPillai/Reactive-Resume?label=version)
[![Docker Pulls](https://img.shields.io/docker/pulls/amruthpillai/reactive-resume)](https://hub.docker.com/repository/docker/amruthpillai/reactive-resume)
@ -12,6 +23,8 @@ A free and open-source resume builder that simplifies the process of creating, u
### [Go to App](https://rxresu.me/) | [Docs](https://docs.rxresu.me/)
</div>
## Description
Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume. With zero user tracking or advertising, your privacy is a top priority. The platform is extremely user-friendly and can be self-hosted in less than 30 seconds if you wish to own your data completely.
@ -93,7 +106,7 @@ _By the community, for the community._
A passion project by [Amruth Pillai](https://www.amruthpillai.com/)
<p>
<a href="https://www.digitalocean.com/?utm_medium=opensource&utm_source=Reactive-Resume">
<a href="https://m.do.co/c/ceae1fff245e">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="200px">
</a>
</p>

View File

@ -1,17 +1,24 @@
import { forwardRef } from "react";
type BrandIconProps = {
slug: string;
};
export const BrandIcon = ({ slug }: BrandIconProps) => {
export const BrandIcon = forwardRef<HTMLImageElement, BrandIconProps>(({ slug }, ref) => {
if (slug === "linkedin") {
return (
<img
alt="linkedin"
ref={ref}
alt="LinkedIn"
className="size-4"
src={`${window.location.origin}/support-logos/linkedin.svg`}
/>
);
}
return <img alt={slug} className="size-4" src={`https://cdn.simpleicons.org/${slug}`} />;
};
return (
<img ref={ref} alt={slug} className="size-4" src={`https://cdn.simpleicons.org/${slug}`} />
);
});
BrandIcon.displayName = "BrandIcon";

View File

@ -0,0 +1,5 @@
import { HelmetData } from "react-helmet-async";
export const helmetData = new HelmetData({});
export const helmetContext = helmetData.context;

View File

@ -1,10 +1,12 @@
import { useEffect, useMemo } from "react";
import { Helmet } from "react-helmet-async";
import { Outlet } from "react-router";
import webfontloader from "webfontloader";
import { useArtboardStore } from "../store/artboard";
export const ArtboardPage = () => {
const name = useArtboardStore((state) => state.resume.basics.name);
const metadata = useArtboardStore((state) => state.resume.metadata);
const fontString = useMemo(() => {
@ -57,7 +59,14 @@ export const ArtboardPage = () => {
return (
<>
{metadata.css.visible && <style lang="css">{`[data-page] { ${metadata.css.value} }`}</style>}
<Helmet>
<title>{name} | Reactive Resume</title>
{metadata.css.visible && (
<style id="custom-css" lang="css">
{metadata.css.value}
</style>
)}
</Helmet>
<Outlet />
</>

View File

@ -1,8 +1,10 @@
import { SectionKey } from "@reactive-resume/schema";
import { pageSizeMap, Template } from "@reactive-resume/utils";
import type { SectionKey } from "@reactive-resume/schema";
import type { Template } from "@reactive-resume/utils";
import { pageSizeMap } from "@reactive-resume/utils";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useMemo, useRef, useState } from "react";
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import type { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { MM_TO_PX, Page } from "../components/page";
import { useArtboardStore } from "../store/artboard";

View File

@ -1,5 +1,5 @@
import { SectionKey } from "@reactive-resume/schema";
import { Template } from "@reactive-resume/utils";
import type { SectionKey } from "@reactive-resume/schema";
import type { Template } from "@reactive-resume/utils";
import { useMemo } from "react";
import { Page } from "../components/page";

View File

@ -1,6 +1,8 @@
import { useEffect } from "react";
import { HelmetProvider } from "react-helmet-async";
import { Outlet } from "react-router";
import { helmetContext } from "../constants/helmet";
import { useArtboardStore } from "../store/artboard";
export const Providers = () => {
@ -10,35 +12,28 @@ export const Providers = () => {
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return;
if (event.data.type === "SET_RESUME") setResume(event.data.payload);
if (event.data.type === "SET_THEME") {
event.data.payload === "dark"
? document.documentElement.classList.add("dark")
: document.documentElement.classList.remove("dark");
}
};
const resumeData = window.localStorage.getItem("resume");
if (resumeData) {
setResume(JSON.parse(resumeData));
return;
}
window.addEventListener("message", handleMessage);
window.addEventListener("message", handleMessage, false);
return () => {
window.removeEventListener("message", handleMessage);
window.removeEventListener("message", handleMessage, false);
};
}, [setResume]);
}, []);
// Only for testing, in production this will be fetched from window.postMessage
// useEffect(() => {
// setResume(sampleResume);
// }, [setResume]);
useEffect(() => {
const resumeData = window.localStorage.getItem("resume");
if (resumeData) setResume(JSON.parse(resumeData));
}, [window.localStorage.getItem("resume")]);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!resume) return null;
return <Outlet />;
return (
<HelmetProvider context={helmetContext}>
<Outlet />
</HelmetProvider>
);
};

View File

@ -6,7 +6,7 @@ import { PreviewLayout } from "../pages/preview";
import { Providers } from "../providers";
export const routes = createRoutesFromChildren(
<Route element={<Providers />} hydrateFallbackElement={<div>Loading...</div>}>
<Route element={<Providers />}>
<Route path="artboard" element={<ArtboardPage />}>
<Route path="builder" element={<BuilderLayout />} />
<Route path="preview" element={<PreviewLayout />} />

View File

@ -1,4 +1,4 @@
import { ResumeData } from "@reactive-resume/schema";
import type { ResumeData } from "@reactive-resume/schema";
import { create } from "zustand";
export type ArtboardStore = {

View File

@ -8,6 +8,10 @@
@apply border-current;
}
body {
overflow: hidden;
}
#root {
@apply antialiased;
}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, linearTransform } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, linearTransform, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import React, { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -100,9 +98,9 @@ const Summary = () => {
<div className="absolute left-[-4.5px] top-[8px] hidden size-[8px] rounded-full bg-primary group-[.main]:block" />
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</main>
</section>
@ -226,7 +224,10 @@ const Section = <T,>({
<div>{children?.(item as T)}</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -91,9 +89,9 @@ const Summary = () => {
</div>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg col-span-4"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg col-span-4"
/>
</section>
);
@ -207,7 +205,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -91,9 +89,9 @@ const Summary = () => {
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
@ -211,7 +209,7 @@ const Section = <T,>({
{summary !== undefined && !isEmptyString(summary) && (
<div
dangerouslySetInnerHTML={{ __html: summary }}
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg group-[.sidebar]:prose-invert"
/>
)}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -111,9 +109,9 @@ const Summary = () => {
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
@ -232,7 +230,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -91,9 +89,9 @@ const Summary = () => {
<div className="p-custom space-y-4" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
<section id={section.id}>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
</div>
@ -212,7 +210,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,23 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl, linearTransform } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import {
cn,
hexToRgb,
isEmptyString,
isUrl,
linearTransform,
sanitize,
} from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -91,9 +96,9 @@ const Summary = () => {
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
@ -215,7 +220,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,4 +1,4 @@
import { Template } from "@reactive-resume/utils";
import type { Template } from "@reactive-resume/utils";
import { Azurill } from "./azurill";
import { Bronzor } from "./bronzor";

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Project,
@ -14,16 +12,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import React, { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -110,9 +108,9 @@ const Summary = () => {
</h4>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
@ -223,7 +221,10 @@ const Section = <T,>({
<div>{children?.(item as T)}</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Project,
@ -14,16 +12,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import React, { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -44,9 +42,9 @@ const Header = () => {
</div>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</div>
@ -218,7 +216,10 @@ const Section = <T,>({
<div>{children?.(item as T)}</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -107,9 +105,9 @@ const Summary = () => {
</div>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</div>
</section>
@ -219,7 +217,10 @@ const Section = <T,>({
{url !== undefined && section.separateLinks && <Link url={url} />}
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{keywords !== undefined && keywords.length > 0 && (
@ -254,7 +255,10 @@ const Section = <T,>({
{url !== undefined && section.separateLinks && <Link url={url} />}
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{keywords !== undefined && keywords.length > 0 && (

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Project,
@ -14,16 +12,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import React, { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -111,9 +109,9 @@ const Summary = () => {
<h4 className="font-bold text-primary">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
@ -225,7 +223,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -112,9 +110,9 @@ const Summary = () => {
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
@ -240,7 +238,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,10 +1,8 @@
import {
import type {
Award,
Certification,
CustomSection,
CustomSectionGroup,
Education,
Experience,
Interest,
Language,
Profile,
@ -15,16 +13,16 @@ import {
SectionWithItem,
Skill,
URL,
Volunteer,
} from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
import get from "lodash.get";
import { Fragment } from "react";
import { BrandIcon } from "../components/brand-icon";
import { Picture } from "../components/picture";
import { useArtboardStore } from "../store/artboard";
import { TemplateProps } from "../types/template";
import type { TemplateProps } from "../types/template";
const Header = () => {
const basics = useArtboardStore((state) => state.resume.basics);
@ -92,9 +90,9 @@ const Summary = () => {
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
style={{ columns: section.columns }}
className="wysiwyg"
/>
</section>
);
@ -206,7 +204,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
className="wysiwyg"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}

View File

@ -1,4 +1,4 @@
import { SectionKey } from "@reactive-resume/schema";
import type { SectionKey } from "@reactive-resume/schema";
export type TemplateProps = {
columns: SectionKey[][];

0
apps/client/public/templates/jpg/azurill.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

0
apps/client/public/templates/jpg/bronzor.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

0
apps/client/public/templates/jpg/chikorita.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 145 KiB

0
apps/client/public/templates/jpg/ditto.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

0
apps/client/public/templates/jpg/kakuna.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 119 KiB

0
apps/client/public/templates/jpg/nosepass.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

0
apps/client/public/templates/jpg/onyx.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

0
apps/client/public/templates/jpg/pikachu.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 136 KiB

0
apps/client/public/templates/jpg/rhyhorn.jpg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

@ -1,11 +1,11 @@
import { t } from "@lingui/macro";
import {
CaretDown,
ChatTeardropText,
CircleNotch,
Exam,
MagicWand,
PenNib,
CaretDownIcon,
ChatTeardropTextIcon,
CircleNotchIcon,
ExamIcon,
MagicWandIcon,
PenNibIcon,
} from "@phosphor-icons/react";
import {
Badge,
@ -75,27 +75,31 @@ export const AiActions = ({ value, onChange, className }: Props) => {
variant="primary"
className="-rotate-90 bg-background px-2 text-[10px] leading-[10px]"
>
<MagicWand size={10} className="mr-1" />
<MagicWandIcon size={10} className="mr-1" />
{t`AI`}
</Badge>
</div>
<Button size="sm" variant="outline" disabled={!!loading} onClick={() => onClick("improve")}>
{loading === "improve" ? <CircleNotch className="animate-spin" /> : <PenNib />}
{loading === "improve" ? <CircleNotchIcon className="animate-spin" /> : <PenNibIcon />}
<span className="ml-2 text-xs">{t`Improve Writing`}</span>
</Button>
<Button size="sm" variant="outline" disabled={!!loading} onClick={() => onClick("fix")}>
{loading === "fix" ? <CircleNotch className="animate-spin" /> : <Exam />}
{loading === "fix" ? <CircleNotchIcon className="animate-spin" /> : <ExamIcon />}
<span className="ml-2 text-xs">{t`Fix Spelling & Grammar`}</span>
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button size="sm" variant="outline" disabled={!!loading}>
{loading === "tone" ? <CircleNotch className="animate-spin" /> : <ChatTeardropText />}
{loading === "tone" ? (
<CircleNotchIcon className="animate-spin" />
) : (
<ChatTeardropTextIcon />
)}
<span className="mx-2 text-xs">{t`Change Tone`}</span>
<CaretDown />
<CaretDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>

View File

@ -1,13 +1,23 @@
import { cn } from "@reactive-resume/utils";
import { forwardRef, useEffect } from "react";
import { useDebounceValue } from "usehooks-ts";
type BrandIconProps = {
slug: string;
};
export const BrandIcon = ({ slug }: BrandIconProps) => {
if (slug === "linkedin") {
export const BrandIcon = forwardRef<HTMLImageElement, BrandIconProps>(({ slug }, ref) => {
const [debouncedSlug, setValue] = useDebounceValue(slug, 600);
useEffect(() => {
setValue(slug);
}, [slug]);
if (!slug) return null;
if (debouncedSlug === "linkedin") {
return (
<img
ref={ref}
alt="LinkedIn"
className="size-5"
src={`${window.location.origin}/support-logos/linkedin.svg`}
@ -15,5 +25,14 @@ export const BrandIcon = ({ slug }: BrandIconProps) => {
);
}
return <i className={cn("si si--color text-[1.25rem]", `si-${slug}`)} />;
};
return (
<img
ref={ref}
alt={debouncedSlug}
className="size-5"
src={`https://cdn.simpleicons.org/${debouncedSlug}`}
/>
);
});
BrandIcon.displayName = "BrandIcon";

View File

@ -1,5 +1,5 @@
import { t } from "@lingui/macro";
import { CaretDown, Check } from "@phosphor-icons/react";
import { CaretDownIcon, CheckIcon } from "@phosphor-icons/react";
import {
Button,
Command,
@ -61,7 +61,7 @@ export const LocaleCombobox = ({ value, onValueChange }: Props) => {
onValueChange(result.original.locale);
}}
>
<Check
<CheckIcon
className={cn(
"mr-2 size-4 opacity-0",
value === original.locale && "opacity-100",
@ -104,7 +104,7 @@ export const LocaleComboboxPopover = ({ value, onValueChange }: Props) => {
<span className="line-clamp-1 text-left font-normal">
{selected?.name} <span className="ml-1 text-xs opacity-50">({selected?.locale})</span>
</span>
<CaretDown
<CaretDownIcon
className={cn(
"ml-2 size-4 shrink-0 rotate-0 opacity-50 transition-transform",
open && "rotate-180",

View File

@ -1,5 +1,5 @@
import { useLingui } from "@lingui/react";
import { Translate } from "@phosphor-icons/react";
import { TranslateIcon } from "@phosphor-icons/react";
import { Button, Popover, PopoverContent, PopoverTrigger } from "@reactive-resume/ui";
import { useState } from "react";
@ -14,7 +14,7 @@ export const LocaleSwitch = () => {
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button size="icon" variant="ghost">
<Translate size={20} />
<TranslateIcon size={20} />
</Button>
</PopoverTrigger>
<PopoverContent align="end" className="p-0">

View File

@ -1,14 +1,16 @@
import { CloudSun, Moon, Sun } from "@phosphor-icons/react";
import { CloudSunIcon, MoonIcon, SunIcon } from "@phosphor-icons/react";
import { useTheme } from "@reactive-resume/hooks";
import { Button } from "@reactive-resume/ui";
import { motion, Variants } from "framer-motion";
import type { Variants } from "framer-motion";
import { motion } from "framer-motion";
import { useMemo } from "react";
type Props = {
size?: number;
className?: string;
};
export const ThemeSwitch = ({ size = 20 }: Props) => {
export const ThemeSwitch = ({ size = 20, className }: Props) => {
const { theme, toggleTheme } = useTheme();
const variants: Variants = useMemo(() => {
@ -20,12 +22,12 @@ export const ThemeSwitch = ({ size = 20 }: Props) => {
}, [size]);
return (
<Button size="icon" variant="ghost" onClick={toggleTheme}>
<Button size="icon" variant="ghost" className={className} onClick={toggleTheme}>
<div className="cursor-pointer overflow-hidden" style={{ width: size, height: size }}>
<motion.div animate={theme} variants={variants} className="flex">
<Sun size={size} className="shrink-0" />
<CloudSun size={size} className="shrink-0" />
<Moon size={size} className="shrink-0" />
<SunIcon size={size} className="shrink-0" />
<CloudSunIcon size={size} className="shrink-0" />
<MoonIcon size={size} className="shrink-0" />
</motion.div>
</div>
</Button>

View File

@ -1,4 +1,4 @@
import { ReactParallaxTiltProps } from "react-parallax-tilt";
import type { ReactParallaxTiltProps } from "react-parallax-tilt";
export const defaultTiltProps: ReactParallaxTiltProps = {
scale: 1.05,

View File

@ -1,4 +1,4 @@
import { QueryKey } from "@tanstack/react-query";
import type { QueryKey } from "@tanstack/react-query";
export const USER_KEY: QueryKey = ["user"];
export const AUTH_PROVIDERS_KEY: QueryKey = ["auth", "providers"];

View File

@ -1,5 +1,5 @@
import { createId } from "@paralleldrive/cuid2";
import { ToastActionElement, ToastProps } from "@reactive-resume/ui";
import type { ToastActionElement, ToastProps } from "@reactive-resume/ui";
import { useEffect, useState } from "react";
const TOAST_LIMIT = 1;

View File

@ -1,5 +1,6 @@
import { t } from "@lingui/macro";
import { deepSearchAndParseDates, ErrorMessage } from "@reactive-resume/utils";
import type { ErrorMessage } from "@reactive-resume/utils";
import { deepSearchAndParseDates } from "@reactive-resume/utils";
import _axios from "axios";
import createAuthRefreshInterceptor from "axios-auth-refresh";
import { redirect } from "react-router";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More