Skip to content

Commit f41d571

Browse files
committed
tailwind usage + persistence feature WIP
1 parent 8972459 commit f41d571

File tree

12 files changed

+513
-217
lines changed

12 files changed

+513
-217
lines changed

makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ build:
22
PROJECT=tg-bot-playground vite build
33
PROJECT=cv-maker vite build
44

5-
dev:
5+
dev-tg:
66
PROJECT=tg-bot-playground vite
77

8-
dev-cv-maker:
8+
dev-cv:
99
PROJECT=cv-maker vite
1010

1111
gen-openapi-ui:

package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55
"@edge-runtime/vm": "^5.0.0",
66
"@effect-ak/tg-bot-client": "^0.5.1",
77
"@monaco-editor/loader": "^1.4.0",
8+
"@preact/preset-vite": "^2.10.1",
9+
"@tailwindcss/vite": "^4.0.6",
810
"@types/alpinejs": "^3.13.11",
911
"@types/node": "^22.10.5",
1012
"alpinejs": "^3.14.8",
1113
"effect": "^3.12.6",
1214
"monaco-editor": "^0.52.2",
13-
"vite": "^6.0.11",
14-
"vite-tsconfig-paths": "^5.1.4",
15-
"vitest": "^3.0.3",
1615
"preact": "^10.25.4",
1716
"preact-render-to-string": "^6.5.13",
1817
"prettier": "^3.4.2",
1918
"puppeteer": "^24.0.0",
20-
"@preact/preset-vite": "^2.10.1"
19+
"tailwindcss": "^4.0.6",
20+
"vite": "^6.0.11",
21+
"vite-tsconfig-paths": "^5.1.4",
22+
"vitest": "^3.0.3"
2123
}
2224
}

pnpm-lock.yaml

Lines changed: 314 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/editor/init.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ export const createAndBindEditor = async (
3434
model: textModel,
3535
contextmenu: false,
3636
minimap: {
37-
enabled: false
37+
enabled: false,
3838
},
39+
lineNumbers: "off"
3940
});
4041

4142
return editor;

src/common/editor/make.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const makeJsonEditor = async () => {
5454
if (!editor) return;
5555

5656
return {
57+
editor,
5758
monaco,
5859
textModel: model
5960
} as const;

src/common/utils.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ export const fetchText = (path: string) =>
44
fetch(path).then(_ => _.text());
55

66
export const getMonacoLoader = () => {
7-
if (!("monaco_loader" in window) || typeof window.monaco_loader != "object" || window.monaco_loader == null) {
8-
console.warn("monaco loader is not available");
9-
return;
10-
}
7+
if (!("monaco_loader" in window) || typeof window.monaco_loader != "object" || window.monaco_loader == null) {
8+
console.warn("monaco loader is not available");
9+
return;
10+
}
1111

12-
return window.monaco_loader as MonacoLoader;
12+
return window.monaco_loader as MonacoLoader;
1313

1414
}

src/cv-maker/core/template.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function Resume(resume: ResumeObject) {
1313
<div className="section-header">
1414
<span id="label">Why I'm the Right Choice for "{coverLetter.position}"</span>
1515
</div>
16-
<div id="summary">
16+
<div className="p-3 bg-so">
1717
{coverLetter.content.map(line => <p dangerouslySetInnerHTML={{ __html: line }}></p>)}
1818
</div>
1919

@@ -24,7 +24,7 @@ export function Resume(resume: ResumeObject) {
2424
<span id="label">Summary</span>
2525
</div>
2626

27-
<div id="summary">
27+
<div className="bg-so p-3">
2828
{resume.me.expertSummary.map(s =>
2929
<p dangerouslySetInnerHTML={{ __html: s }}></p>
3030
)}
@@ -34,23 +34,25 @@ export function Resume(resume: ResumeObject) {
3434
<span id="label">Skills</span>
3535
</div>
3636

37-
<div id="skills">
37+
<div className="flex flex-wrap gap-2">
3838

3939
{Object.entries(getSkills(resume)).map(([category, group]) =>
4040
<div className="skill-group">
41-
<span>{category}</span>
41+
<span className="uppercase font-light">{category}</span>
4242
<div className="group-list">
4343
{group.map(t =>
44-
<span className="stack-item">{t.technology.name}</span>
44+
<span
45+
className="bg-so p-1 text-sm ml-1 mt-1"
46+
>{t.technology.name}</span>
4547
)}
4648
</div>
4749
</div>
4850
)}
4951

5052
</div>
5153

52-
<div className="section-header">
53-
<span id="label">Employment</span>
54+
<div className="section-header pt-1">
55+
<span id="label">Employment history</span>
5456
</div>
5557

5658
<div id="employment">
@@ -64,9 +66,10 @@ export function Resume(resume: ResumeObject) {
6466

6567
function Headline(resume: ResumeObject) {
6668
return (
67-
<span>
68-
Software Engineer. <span id='expertise'>Expertise: {resume.me?.expertise.join("/")}</span>
69-
</span>
69+
<div className="flex gap-2 items-baseline">
70+
<span className="text-2xl font-thin">Software Engineer</span>
71+
<span className="text-base">Expertise: {resume.me?.expertise.join("/")}</span>
72+
</div>
7073
)
7174
}
7275

@@ -95,18 +98,18 @@ function ProjectStack(project: ProjectDetails) {
9598

9699
function ResumeHead(resume: ResumeObject) {
97100
return (
98-
<div id="head">
99-
<div id="name">{resume.me.name}</div>
100-
<div id="role">{Headline(resume)}</div>
101+
<div id="head" className="pb-6">
102+
<div className="text-5xl font-thin">{resume.me.name}</div>
103+
<div className="text-lg font-light">{Headline(resume)}</div>
101104
<div id="location">{resume.me?.location}</div>
102-
<div id="contact">
103-
<div id="email">
105+
<div className="flex gap-1 text-sky-600 text-sm">
106+
<div>
104107
<span className="fa-regular fa-envelope"></span>
105108
<a
106109
href={`mailto:${resume.me.email}`}
107110
> {resume.me.email}</a>
108111
</div>
109-
<div id="phone">
112+
<div>
110113
<span className="fa-solid fa-mobile-screen-button"></span>
111114
<a
112115
href={`tel:${resume.me.phone}`}
@@ -132,7 +135,7 @@ function ResumeHead(resume: ResumeObject) {
132135

133136
function CompanyProject(project: ProjectDetails) {
134137
return (
135-
<div className="project">
138+
<div className="project no-break">
136139
<div style={{ display: "flex" }}>
137140
<div style={{ marginBottom: "3px" }}>
138141
<b>Project: </b>
@@ -146,11 +149,9 @@ function CompanyProject(project: ProjectDetails) {
146149
<span
147150
style={{ display: "block" }}
148151
><b>Stack: </b>{ProjectStack(project)}</span>
149-
<ul>
152+
<ul className="list-disc pt-2 pl-10">
150153
{project.achivements.map(achivement =>
151-
<div>
152-
<li>{achivement.human ?? achivement.technical}</li>
153-
</div>
154+
<li>{achivement.human ?? achivement.technical}</li>
154155
)}
155156
</ul>
156157
</div>
@@ -162,8 +163,10 @@ function EmploymentHistory(resume: ResumeObject) {
162163
<div id="employment">
163164
{resume.employmentHistory.map(company => {
164165

166+
const projects = [...company.projects].sort((a,b) => a.order - b.order).map(CompanyProject)
167+
165168
return (
166-
<div className="company">
169+
<div className="company border-b-1 border-gray-300 mb-2 pb-1">
167170
<div style={{ "display": "flex" }}>
168171
<span>{CompanyHeader(company)}</span>
169172
<span
@@ -173,8 +176,7 @@ function EmploymentHistory(resume: ResumeObject) {
173176
<span
174177
style={{ display: "block", marginBottom: "5px" }}
175178
>{CompanySubHeader(company)}</span>
176-
{company.projects.map(CompanyProject)}
177-
<hr />
179+
{projects}
178180
</div>
179181
)
180182
})}

src/cv-maker/core/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,15 @@ export const parseJSON = (input: string | undefined) => {
2929
return JSON.parse(input);
3030
} catch (error) {}
3131
}
32+
33+
export function debounce<T extends (...args: unknown[]) => void>(
34+
func: T, wait: number
35+
) {
36+
let timeout: ReturnType<typeof setTimeout>;
37+
return (...args: Parameters<T>) => {
38+
clearTimeout(timeout);
39+
timeout = setTimeout(() => {
40+
func(...args);
41+
}, wait);
42+
};
43+
}

src/cv-maker/index.html

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,97 @@
22

33
<head>
44
<meta charset="utf-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
66
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
77
integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
88
crossorigin="anonymous" referrerpolicy="no-referrer" />
9-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css"
10-
integrity="sha512-NhSC1YmyruXifcj/KFRWoC561YpHpc5Jtzgvbuzx5VozKpWvQ+4nXhPdFgmx8xqexRcpAglTj9sIBWINXa8x5w=="
11-
crossorigin="anonymous" referrerpolicy="no-referrer" />
129
<script defer src="https://cdn.jsdelivr.net/npm/@monaco-editor/[email protected]/lib/umd/monaco-loader.min.js"></script>
10+
1311
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>✍️</text></svg>">
1412
<link rel="stylesheet" href="./style.css">
1513
<script defer src="./main.ts" type="module"></script>
1614
<title>CV Maker</title>
1715
</head>
1816

19-
<body x-data style="display: flex; flex-direction: column;">
17+
<body x-data="state" class="flex flex-col p-16 pt-8">
18+
<!-- navigation -->
19+
<div
20+
class="no-print pb-2 self-center"
21+
x-init="$watch('mode', value => $dispatch('mode-was-changed', value))"
22+
>
2023

21-
<div style="padding-bottom: 20px;" class="no-print">
22-
<label class="menu-item" :class="{ 'active': $store.state.mode === 'editor' }">
23-
<input type="radio" value="editor" x-model="$store.state.mode" /> Editor
24+
<label class="menu-item" :class="{ 'active': mode === 'editor' }">
25+
<input type="radio" value="editor" x-model="mode" /> Editor
2426
</label>
2527

2628
<label
2729
class="menu-item"
28-
:class="{ 'active': $store.state.mode === 'view' }"
29-
x-show="!$store.state.editorHasError" x-transition
30+
:class="{ 'active': mode === 'view' }"
31+
x-show="!editorHasError" x-transition
3032
>
3133
<input
3234
type="radio"
3335
value="view"
34-
x-model="$store.state.mode"
36+
x-model="mode"
3537
/> View
3638
</label>
3739

40+
<select x-model="currentResume" @change="selectResume">
41+
<template x-for="resume in availableResumes" :key="resume.id">
42+
<option :value="resume.id" x-text="resume.name"></option>
43+
</template>
44+
</select>
45+
3846
</div>
3947

40-
<!-- code editor -->
41-
<div x-show="$store.state.mode == 'editor'">
42-
<div x-data="sections">
43-
<template x-for="mode in sections">
44-
<label
45-
class="menu-item"
46-
:class="{ 'active': $store.state.editorSection === mode.id }"
47-
>
48-
<input
49-
type="radio"
50-
:value="mode.id"
51-
x-model="$store.state.editorSection"
52-
@change="$dispatch('section-changed')"
53-
/>
54-
<span x-text="mode.label"></span>
55-
</label>
56-
</template>
48+
<!-- code editor and code section -->
49+
<div
50+
class="flex gap-2 pb-2"
51+
x-show="mode == 'editor'"
52+
>
53+
<div class="self-start">
54+
<button
55+
class="btn bg-sky-500 hover:bg-sky-700"
56+
x-on:click="$dispatch('resize')"
57+
>Save as</button>
58+
<button
59+
class="btn bg-[#DC382D] hover:bg-[#B93224]"
60+
>Delete</button>
61+
</div>
62+
63+
<div
64+
class="self-end ml-auto"
65+
>
66+
<select
67+
class="block bg-white border border-gray-300 py-2 px-3 rounded focus:outline-none focus:ring-0 focus:border-blue-300"
68+
@change="$dispatch('section-changed')"
69+
x-model="editorSection"
70+
>
71+
<template x-for="section in $store.sections" :key="section.id">
72+
<option
73+
:value="section.id"
74+
:selected="editorSection === section.id"
75+
x-text="section.label"
76+
></option>
77+
</template>
78+
</select>
5779
</div>
5880

59-
<div
60-
id="code-editor"
61-
></div>
6281
</div>
6382

64-
<div x-show="$store.state.mode == 'view'">
65-
<div x-html="$store.state.resumeHtml"></div>
83+
<div
84+
class="w-full h-full"
85+
x-show="mode == 'editor'"
86+
id="code-editor"
87+
></div>
88+
89+
<div
90+
class="self-center"
91+
x-show="mode == 'view'"
92+
>
93+
<div
94+
x-html="resumeHtml"
95+
></div>
6696
</div>
6797

6898
</body>

0 commit comments

Comments
 (0)