Skip to content

Commit b61e729

Browse files
committed
save cv, alfa
1 parent e81e6c8 commit b61e729

File tree

7 files changed

+193
-97
lines changed

7 files changed

+193
-97
lines changed

src/common/editor/init.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ export const createAndBindEditor = async (
3535
contextmenu: false,
3636
minimap: {
3737
enabled: false,
38-
},
39-
lineNumbers: "off"
38+
}
4039
});
4140

4241
return editor;

src/cv-maker/core/template.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { DateTime, pipe, Array } from "effect";
44
export function Resume(resume: ResumeObject) {
55
const coverLetter = resume.me.coverLetter;
66
return (
7-
<div id="resume">
7+
<div>
88

99
{ResumeHead(resume)}
1010

@@ -34,21 +34,22 @@ export function Resume(resume: ResumeObject) {
3434
<span id="label">Skills</span>
3535
</div>
3636

37-
<div className="flex flex-wrap gap-2">
38-
39-
{Object.entries(getSkills(resume)).map(([category, group]) =>
40-
<div className="skill-group">
41-
<span className="uppercase font-light">{category}</span>
42-
<div className="group-list">
43-
{group.map(t =>
37+
<div className="flex gap-1 flex-wrap">
38+
{Object.entries(getSkills(resume)).map(([category, group]) => (
39+
<div className="w-32" key={category}>
40+
<span className="uppercase font-light text-sm">{category}</span>
41+
<div className="flex flex-wrap">
42+
{group.map((t, idx) => (
4443
<span
44+
key={idx}
4545
className="bg-so p-1 text-sm ml-1 mt-1"
46-
>{t.technology.name}</span>
47-
)}
46+
>
47+
{t.technology.name}
48+
</span>
49+
))}
4850
</div>
4951
</div>
50-
)}
51-
52+
))}
5253
</div>
5354

5455
<div className="section-header pt-1">

src/cv-maker/index.html

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,41 @@
1212
<link rel="stylesheet" href="./style.css">
1313
<script defer src="./main.ts" type="module"></script>
1414
<title>CV Maker</title>
15+
16+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-FKWW5S34LH"></script>
17+
<script>
18+
window.dataLayer = window.dataLayer || [];
19+
function gtag(){dataLayer.push(arguments);}
20+
gtag('js', new Date());
21+
22+
gtag('config', 'G-FKWW5S34LH');
23+
</script>
1524
</head>
1625

1726
<body x-data="state" class="flex flex-col p-16 pt-8">
1827
<!-- navigation -->
1928
<div
20-
class="no-print pb-2 self-center flex gap-2"
29+
class="no-print pb-2 flex gap-2 justify-center"
2130
x-init="$watch('mode', value => $dispatch('mode-was-changed', value))"
2231
>
2332

24-
<label
25-
class="hover:cursor-pointer"
33+
<select
34+
class="self-start"
35+
x-model="currentResume"
36+
@change="$dispatch('init-resume')"
37+
>
38+
<template x-for="resume in availableResumes" :key="resume.id">
39+
<option
40+
:value="resume.id"
41+
x-text="resume.name"
42+
:selected="currentResume === resume.id"
43+
V></option>
44+
</template>
45+
</select>
46+
47+
<div class="mx-auto">
48+
<label
49+
class="hover:cursor-pointer text-lg"
2650
:class="{ 'text-orange-400 underline': mode === 'editor' }"
2751
>
2852
<input
@@ -34,7 +58,7 @@
3458
</label>
3559

3660
<label
37-
class="hover:cursor-pointer"
61+
class="hover:cursor-pointer text-lg"
3862
:class="{ 'text-orange-400 underline': mode === 'view' }"
3963
x-show="!editorHasError" x-transition
4064
>
@@ -45,12 +69,8 @@
4569
class="appearance-none"
4670
/> View
4771
</label>
72+
</div>
4873

49-
<select x-model="currentResume" @change="selectResume">
50-
<template x-for="resume in availableResumes" :key="resume.id">
51-
<option :value="resume.id" x-text="resume.name"></option>
52-
</template>
53-
</select>
5474

5575
</div>
5676

@@ -62,18 +82,19 @@
6282
<div class="self-start">
6383
<button
6484
class="btn bg-sky-500 hover:bg-sky-700 text-sm"
65-
x-on:click="$dispatch('resize')"
85+
x-on:click="$dispatch('save')"
6686
>Save as</button>
6787
<button
6888
class="btn bg-[#DC382D] hover:bg-[#B93224]"
89+
x-on:click="$dispatch('delete')"
6990
>Delete</button>
7091
</div>
7192

7293
<div
7394
class="self-end ml-auto"
7495
>
7596
<select
76-
class="block bg-white border border-gray-300 py-2 px-3 rounded focus:outline-none focus:ring-0 focus:border-blue-300"
97+
class=" bg-white border border-gray-300 py-2 px-3 rounded focus:outline-none focus:ring-0"
7798
@change="$dispatch('section-changed')"
7899
x-model="editorSection"
79100
>
@@ -104,6 +125,34 @@
104125
></div>
105126
</div>
106127

128+
<button
129+
@click="window.print()"
130+
x-show="mode == 'view'"
131+
class="no-print hover:cursor-pointer fixed top-10 right-20 bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-full shadow-lg"
132+
>
133+
Print
134+
</button>
135+
136+
<footer class="bg-gray-100 text-gray-700 py-4 no-print mt-20">
137+
<div class="container mx-auto text-center">
138+
<p>© 2025 Created by Aleksandr Kondaurov</p>
139+
<p>
140+
<a
141+
target="_blank"
142+
href="https://www.linkedin.com/in/alexander-kondaurov/"
143+
rel="noopener noreferrer"
144+
class="fa-brands fa-lg fa-linkedin text-blue-300"
145+
></a>
146+
<a
147+
target="_blank"
148+
href="https://github.com/orgs/effect-ak/repositories"
149+
rel="noopener noreferrer"
150+
class="fa-brands fa-lg fa-github"
151+
></a>
152+
</p>
153+
</div>
154+
</footer>
155+
107156
</body>
108157

109158
</html>

src/cv-maker/main.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { debounce, getResumeObject, parseJSON, resumeObjectToHTML } from "./core
33
import type resumeSchema from "./static/resume-schema.json"
44
import { makeJsonEditor } from "#/common/editor/make";
55
import { hasMajorError } from "#/common/editor/text-model";
6+
import * as resumeSchemaObject from "./static/resume-schema.json";
67

78
declare global {
89
interface Window {
@@ -43,25 +44,62 @@ const state: {
4344
editorSection: "me",
4445
mode: "view",
4546
editorHasError: false,
46-
availableResumes: [{ id: "example", name: "Example" }],
47+
availableResumes: [],
4748
currentResume: "example"
4849
});
4950

51+
Alpine.data("state", () => state);
52+
5053
Alpine.start();
5154

52-
async function setup() {
55+
async function loadStoredResume() {
56+
57+
state.availableResumes = [];
58+
59+
if (Object.keys(localStorage).length == 0) {
60+
const resume = await getResumeObject();
61+
localStorage.setItem("example", JSON.stringify(resume));
62+
}
63+
64+
for (const key of Object.keys(localStorage)) {
65+
const value = localStorage.getItem(key);
66+
if (!value) continue;
67+
state.availableResumes.push({
68+
id: key,
69+
name: key
70+
});
71+
};
72+
73+
}
74+
75+
function selectResume() {
76+
const resumeJson = localStorage.getItem(state.currentResume);
77+
if (!resumeJson) {
78+
console.warn("No resume to load");
79+
return;
80+
};
81+
const resume = parseJSON(resumeJson);
82+
if (!resume) {
83+
console.warn("Invalid json of resume");
84+
return;
85+
};
86+
state.resumeObject = resume;
87+
state.resumeHtml = resumeObjectToHTML(resume);
88+
}
5389

54-
Alpine.data("state", () => state);
90+
async function setup() {
5591

5692
const editor = await makeJsonEditor();
5793

58-
const resumeSchemaObject = await fetch("./resume-schema.json").then(_ => _.json());
94+
await loadStoredResume();
5995

6096
if (!editor) {
6197
console.warn("can not load editor");
6298
return;
6399
};
64100

101+
selectResume();
102+
65103
const setupJsonSchema =
66104
(ref: string) =>
67105
editor.monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
@@ -122,6 +160,17 @@ async function setup() {
122160
}
123161
});
124162

163+
window.addEventListener('init-resume', () => {
164+
selectResume();
165+
prepareEditor();
166+
});
167+
168+
window.addEventListener('save', () => {
169+
const name = window.prompt("Enter name of your resume", "simple");
170+
if (!name) return;
171+
localStorage.setItem(name, JSON.stringify(state.resumeObject));
172+
});
173+
125174
window.addEventListener('resize', () => {
126175
editor.editor.layout();
127176
});
@@ -141,11 +190,6 @@ async function setup() {
141190
}
142191
});
143192

144-
const resume = await getResumeObject();
145-
146-
state.resumeObject = resume;
147-
state.resumeHtml = resumeObjectToHTML(resume);
148-
149193
prepareEditor();
150194

151195
}

0 commit comments

Comments
 (0)