Skip to content

Commit

Permalink
skills sorting added
Browse files Browse the repository at this point in the history
  • Loading branch information
kondaurovDev committed Feb 20, 2025
1 parent 00e691c commit 86a8933
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 46 deletions.
27 changes: 11 additions & 16 deletions src/cv-maker/core/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,22 @@ const httpsRegex = /https:\/\//;
// hide social profiles
// hide phone

export class ViewSettings
extends S.Class<ProjectTechnology>("ProjectTechnology")({
collapseOld: S.Boolean
}) { }
// export class ViewSettings
// extends S.Class<ProjectTechnology>("ProjectTechnology")({
// collapseOld: S.Boolean
// }) { }

export const STACK_CATEGORIES = [
"programming language", "cloud computing", "testing", "framework", "frontend", "database",
"devops tool", "collaboration tool", "other"
] as const;

export class ProjectTechnology
extends S.Class<ProjectTechnology>("ProjectTechnology")({
id: S.NonEmptyString,
name: S.NonEmptyString,
category:
S.Literal(
"programming language",
"framework",
"cloud computing",
"database",
"devops tool",
"testing",
"collaboration tool",
"frontend",
"other"
),
category: S.Literal(...STACK_CATEGORIES),
display: S.Literal("force", "hide").pipe(S.optional),
version: S.NonEmptyString.pipe(S.optional),
url: S.NonEmptyString.pipe(S.optional)
}) { }
Expand Down
45 changes: 28 additions & 17 deletions src/cv-maker/core/template.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EmploymentRecord, ProjectDetails, ProjectTechnology, ResumeObject } from "#/cv-maker/core/schema";
import { DateTime, pipe, Array } from "effect";
import { EmploymentRecord, ProjectDetails, ProjectTechnology, ResumeObject, STACK_CATEGORIES } from "#/cv-maker/core/schema";
import { DateTime, pipe, Array, Order, HashMap } from "effect";

export function Resume(resume: ResumeObject) {
const coverLetter = resume.me.coverLetter;
Expand All @@ -16,9 +16,9 @@ export function Resume(resume: ResumeObject) {
<div className="p-2 bg-so">
{coverLetter.content.map(line => <p dangerouslySetInnerHTML={{ __html: line }}></p>)}
</div>

</div>
): null}
) : null}

<div className="section-header">
<span id="label">Summary</span>
Expand All @@ -35,12 +35,12 @@ export function Resume(resume: ResumeObject) {
</div>

<div className="flex flex-wrap gap-1">
{Object.entries(getSkills(resume)).map(([category, group]) => (
{getSkills(resume).map(([category, group]) => (
<div key={category} className="flex items-center gap-1">
<span className="uppercase font-medium">{category}:</span>
{group.map((t, idx) => (
<span
key={idx}
<span
key={idx}
className="bg-so p-1 text-xs"
>
{t.technology.name}
Expand Down Expand Up @@ -95,7 +95,7 @@ function CompanyHeader(company: EmploymentRecord) {
function ProjectStack(project: ProjectDetails) {
return (
<span>
{project.stack.map(t =>
{project.stack.map(t =>
<span className="bg-so py-1 px-1 mr-1 text-sm">{t}</span>)
}
</span>
Expand Down Expand Up @@ -186,15 +186,15 @@ function EmploymentHistory(resume: ResumeObject) {
<span className="ml-auto">{getPeriod(er)}</span>
</div>
<span className="block">{CompanySubHeader(er)}</span>
{isOld ?
{isOld ?
<div>
<span className="font-medium">Roles: </span>
<span>{allRoles}</span>
</div> :
</div> :
<div className="flex flex-col">
{ er.sortedProjects.map((project, id) => CompanyProject(project, id == er.projects.length - 1)) }
{er.sortedProjects.map((project, id) => CompanyProject(project, id == er.projects.length - 1))}
</div>
}
}

</div>
)
Expand Down Expand Up @@ -232,14 +232,14 @@ function getPeriod(company: EmploymentRecord) {

}

function getSkills(resume: ResumeObject) {
function getSkills(resume: ResumeObject): [ string, { code: string, technology: ProjectTechnology }[] ][] {

const categories: { code: string, technology: ProjectTechnology }[] | undefined =
resume.employmentHistory?.flatMap(e =>
e.projects.flatMap(p =>
[...p.stack, ...p.tools].flatMap(t => {
const technology = resume.technologies?.find(_ => _.id == t);
if (!technology) return [];
if (!technology || technology.display == "hide") return [];
return [{
code: t,
category: technology.category,
Expand All @@ -251,14 +251,25 @@ function getSkills(resume: ResumeObject) {

if (!categories) {
console.warn("Skill categories not found")
return {};
return [];
};

const grouped =
pipe(
categories,
[
...categories,
...resume.technologies
.filter(_ => _.display === "force")
.map(_ => ({
code: _.id,
category: _.category,
technology: _
}))
],
Array.dedupeWith((a, b) => a.code == b.code),
Array.groupBy(_ => _.technology.category)
Array.groupBy(_ => _.technology.category),
_ => Object.entries(_),
Array.sortWith(t => STACK_CATEGORIES.findIndex(_ => _ == t[0]), Order.number)
);

return grouped;
Expand Down
34 changes: 29 additions & 5 deletions src/cv-maker/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,24 @@ export const resumeObjectToHTML =
}


export const getResumeObject = async () => {
const resume = await fetch("./john-doe.json").then(_ => _.json());
export const getExampleResume = async () => {
const resume = await fetch("./john-doe.jsonc").then(_ => _.text()).then(_ => parseJSON(_, true));

delete resume["$schema"]
delete resume["$schema"];

const input = S.decodeUnknownSync(ResumeObject)(resume);
return input;
}

export const parseJSON = (input: string | undefined) => {
export const parseJSON = (input: string | undefined, sanity = false) => {
if (!input) return;
try {
return JSON.parse(input);
if (sanity) {
return JSON.parse(removeTrailingCommas(removeJsonComments(input)));
} else {
return JSON.parse(input);
}

} catch (error) {}
}

Expand All @@ -52,3 +57,22 @@ export function setUrlParam(name: string, value: string) {
url.searchParams.set(name, value);
window.history.replaceState(null, '', url);
}

const commentsRegex = /("(?:\\.|[^"\\])*")|(?:\/\*(?:[\s\S]*?)\*\/)|(\/\/.*)/g

export function removeJsonComments(input: string): string {
return input.replace(
commentsRegex,
(_match, quotedString) => {
if (quotedString !== undefined) {
return quotedString;
}
return "";
}
);
}

const trailingCommasRegex = /,\s*([\]}])/g;
function removeTrailingCommas(input: string): string {
return input.replace(trailingCommasRegex, '$1');
}
19 changes: 12 additions & 7 deletions src/cv-maker/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Alpine from "alpinejs";
import { debounce, getResumeObject, getUrlParam, parseJSON, resumeObjectToHTML, setUrlParam } from "./core/utils";
import { debounce, getExampleResume, getUrlParam, parseJSON, resumeObjectToHTML, setUrlParam } from "./core/utils";
import type resumeSchema from "./static/resume-schema.json"
import { makeJsonEditor } from "#/common/editor/make";
import { hasMajorError } from "#/common/editor/text-model";
Expand Down Expand Up @@ -68,8 +68,8 @@ async function loadStoredResume() {
state.availableResumes = [];

if (Object.keys(localStorage).length == 0) {
const resume = await getResumeObject();
localStorage.setItem("example", JSON.stringify(resume));
const resume = await getExampleResume();
localStorage.setItem("example", JSON.stringify(resume, undefined, 2));
}

for (const key of Object.keys(localStorage)) {
Expand All @@ -89,7 +89,7 @@ function selectResume() {
console.warn("No resume to load");
return;
};
const resume = parseJSON(resumeJson);
const resume = parseJSON(resumeJson, true);
if (!resume) {
console.warn("Invalid json of resume");
return;
Expand All @@ -116,6 +116,8 @@ async function setup() {
(ref: string) =>
editor.monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
allowComments: true,
trailingCommas: "ignore",
schemas: [
{
uri: "resume.json",
Expand All @@ -134,14 +136,17 @@ async function setup() {
editor.textModel.model.setValue(JSON.stringify(state.resumeObject, undefined, 2));
setupJsonSchema("#/$defs/ResumeObject");
} else {
editor.textModel.model.setValue(JSON.stringify(state.resumeObject[state.editorSection], undefined, 2))
const section = state.resumeObject[state.editorSection] ?? {};
editor.textModel.model.setValue(JSON.stringify(section, undefined, 2))
setupJsonSchema(`#/$defs/ResumeObject/properties/${state.editorSection}`);
}
}

const saveResume = () => {
const parsed = parseJSON(editor.textModel.model.getValue());
if (!parsed) return;
const parsed = parseJSON(editor.textModel.model.getValue(), true);
if (!parsed) {
console.log("invalid json"); return;
};
if (state.editorSection == "all") {
state.resumeObject = parsed;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@
"id": "docker",
"name": "Docker",
"category": "devops tool",
"url": "https://en.wikipedia.org/wiki/Docker_(software)"
"url": "https://en.wikipedia.org/wiki/Docker_(software)",
"display": "force"
},
{
"id": "react",
Expand Down
7 changes: 7 additions & 0 deletions src/cv-maker/static/resume-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,13 @@
"other"
]
},
"display": {
"type": "string",
"enum": [
"force",
"hide"
]
},
"version": {
"$ref": "#/$defs/NonEmptyString"
},
Expand Down

0 comments on commit 86a8933

Please sign in to comment.