From 43c96c73b210256839e76ac8ce2074512d0ed90e Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Thu, 26 Jun 2025 16:19:52 -0600 Subject: [PATCH] Fixed term! --- README.md | 164 ++++++++++++++- src/config/data.ts | 9 +- src/pages/api/resume.json.ts | 37 ++++ src/pages/api/resume/pdf.ts | 48 ++++- src/pages/resume.astro | 49 ++++- src/types/index.ts | 6 +- src/utils/terminal/fs.ts | 381 +++++++++++++++++++++-------------- 7 files changed, 534 insertions(+), 160 deletions(-) create mode 100644 src/pages/api/resume.json.ts diff --git a/README.md b/README.md index 9aa9119..7e45a7b 100644 --- a/README.md +++ b/README.md @@ -1 +1,163 @@ -# Personal Site \ No newline at end of file +# Personal Website + +My personal website built with Astro and Preact! + +## Features + +- **Resume Management** +- **Blog Posts** +- **Projects** +- **Talks & Presentations** +- **Terminal View** + +## Resume Configuration + +The resume system supports multiple sections that can be enabled, disabled, and customized. + +### Available Resume Sections + +| Section | Required Fields | +|---------|-----------------| +| **basics** | `name`, `email`, `profiles` | +| **summary** | `content` | +| **experience** | `company`, `position`, `location`, `date`, `description` | +| **education** | `institution`, `degree`, `field`, `date` | +| **skills** | `name`, `level` (1-5) | +| **volunteer** | `organization`, `position`, `date` | +| **awards** | `title`, `organization`, `date` | +| **profiles** | `network`, `username`, `url` | + +### Section Configuration + +Each section can be configured in `src/config/data.ts`: + +```typescript +export const resumeConfig: ResumeConfig = { + tomlFile: "/files/resume.toml", + sections: { + summary: { + title: "Professional Summary", + enabled: true, + }, + experience: { + title: "Work Experience", + enabled: true, + }, + awards: { + title: "Awards & Recognition", + enabled: true, + }, + // ... other sections + }, +}; +``` + +### Resume Data Format (TOML) + +Create a `resume.toml` file in the `public/files/` directory: + +```toml +[basics] +name = "Your Name" +email = "your.email@example.com" + +[[basics.profiles]] +network = "GitHub" +username = "yourusername" +url = "https://github.com/yourusername" + +[[basics.profiles]] +network = "LinkedIn" +username = "yourname" +url = "https://linkedin.com/in/yourname" + +[summary] +content = "Your professional summary here..." + +[[experience]] +company = "Company Name" +position = "Job Title" +location = "City, State" +date = "2020 - Present" +description = [ + "Achievement or responsibility 1", + "Achievement or responsibility 2" +] +url = "https://company.com" + +[[education]] +institution = "University Name" +degree = "Bachelor of Science" +field = "Computer Science" +date = "2016 - 2020" +details = [ + "Relevant coursework or achievements" +] + +[[skills]] +name = "JavaScript" +level = 4 + +[[skills]] +name = "Python" +level = 5 + +[[volunteer]] +organization = "Organization Name" +position = "Volunteer Position" +date = "2019 - Present" + +[[awards]] +title = "Award Title" +organization = "Awarding Organization" +date = "2023" +description = "Brief description of the award" +``` + +### Section Field Details + +#### Skills Section +- `level`: Integer from 1-5 representing proficiency level +- Displays as progress bars with visual indicators + +#### Experience Section +- `description`: Array of strings for bullet points +- `url`: Optional company website link + +#### Education Section +- `details`: Optional array of additional information (coursework, achievements, etc.) + +#### Awards Section +- `description`: Optional additional details about the award + +#### Profiles Section +- `network`: Used for icon selection (GitHub, LinkedIn, etc.) +- Icons automatically selected based on network name + +## Usage + +1. **Configure Resume**: Edit `src/config/data.ts` to enable/disable sections +2. **Add Resume Data**: Create `public/files/resume.toml` with your information +3. **View Resume**: Navigate to `/resume` on your site +4. **Generate PDF**: Click "Generate PDF Resume" button for downloadable PDF + +## Development + +```bash +# Install dependencies +npm install + +# Start development server +npm run dev + +# Build for production +npm run build +``` + +## Resume PDF Generation + +The system automatically generates PDFs using Puppeteer with: +- Optimized layout for A4 paper +- Print-friendly styling +- Consistent formatting across sections +- Proper page breaks and margins diff --git a/src/config/data.ts b/src/config/data.ts index 7be6c76..893f0f7 100644 --- a/src/config/data.ts +++ b/src/config/data.ts @@ -62,7 +62,7 @@ export const homepageSections: HomepageSections = { // Resume Configuration export const resumeConfig: ResumeConfig = { - jsonFile: "/files/resume.toml", + tomlFile: "/files/resume.toml", pdfFile: { path: "/files/Atridad_Lahiji_Resume.pdf", filename: "Atridad_Lahiji_Resume.pdf", @@ -76,6 +76,7 @@ export const resumeConfig: ResumeConfig = { "skills", "volunteer", "profiles", + "awards", ], summary: { title: "Summary", @@ -101,6 +102,10 @@ export const resumeConfig: ResumeConfig = { title: "Professional Profiles", enabled: true, }, + awards: { + title: "Awards & Recognition", + enabled: true, + }, }, }; @@ -303,7 +308,7 @@ export const navigationItems: NavigationItem[] = [ path: "/resume", tooltip: "Resume", icon: BriefcaseBusiness, - enabled: !!(resumeConfig.jsonFile && resumeConfig.jsonFile.trim()), + enabled: !!(resumeConfig.tomlFile && resumeConfig.tomlFile.trim()), }, { id: "projects", diff --git a/src/pages/api/resume.json.ts b/src/pages/api/resume.json.ts new file mode 100644 index 0000000..0602326 --- /dev/null +++ b/src/pages/api/resume.json.ts @@ -0,0 +1,37 @@ +import type { APIRoute } from "astro"; +import * as TOML from "@iarna/toml"; +import { siteConfig } from "../../config/data"; + +export const GET: APIRoute = async ({ request }) => { + try { + // Check if resume TOML file is configured + if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) { + return new Response("Resume not configured", { status: 404 }); + } + + const url = new URL(request.url); + const baseUrl = `${url.protocol}//${url.host}`; + + // Fetch the TOML file from the public directory + const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`); + + if (!response.ok) { + throw new Error( + `Failed to fetch resume TOML: ${response.status} ${response.statusText}`, + ); + } + + const tomlContent = await response.text(); + const resumeData = TOML.parse(tomlContent); + + return new Response(JSON.stringify(resumeData), { + headers: { + "Content-Type": "application/json", + "Cache-Control": "public, max-age=300", // Cache for 5 minutes + }, + }); + } catch (error) { + console.error("Error parsing resume TOML:", error); + return new Response("Error parsing resume data", { status: 500 }); + } +}; diff --git a/src/pages/api/resume/pdf.ts b/src/pages/api/resume/pdf.ts index c8c1dd7..07c89d4 100644 --- a/src/pages/api/resume/pdf.ts +++ b/src/pages/api/resume/pdf.ts @@ -41,6 +41,12 @@ interface ResumeData { position: string; date: string; }[]; + awards: { + title: string; + organization: string; + date: string; + description?: string; + }[]; } const generateResumeHTML = (data: ResumeData): string => { @@ -128,6 +134,23 @@ const generateResumeHTML = (data: ResumeData): string => { }) .join("") || ""; + const awardsHTML = + data.awards + ?.map((award) => { + return ` +
+

${award.title}

+
+ ${award.organization} + + ${award.date} +
+ ${award.description ? `
${award.description}
` : ""} +
+ `; + }) + .join("") || ""; + return ` @@ -217,6 +240,23 @@ const generateResumeHTML = (data: ResumeData): string => { ` : "" } + + ${ + data.awards && + data.awards.length > 0 && + resumeConfig.sections.awards?.enabled + ? ` +
+

+ ${resumeConfig.sections.awards.title || "Awards & Recognition"} +

+
+ ${awardsHTML} +
+
+ ` + : "" + }
@@ -263,14 +303,14 @@ const generateResumeHTML = (data: ResumeData): string => { export const GET: APIRoute = async ({ request }) => { try { - if (!siteConfig.resume.jsonFile || !siteConfig.resume.jsonFile.trim()) { + if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) { return new Response("Resume not configured", { status: 404 }); } const url = new URL(request.url); const baseUrl = `${url.protocol}//${url.host}`; - const response = await fetch(`${baseUrl}${siteConfig.resume.jsonFile}`); + const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`); if (!response.ok) { throw new Error( @@ -279,7 +319,9 @@ export const GET: APIRoute = async ({ request }) => { } const tomlContent = await response.text(); - const resumeData: ResumeData = TOML.parse(tomlContent) as unknown as ResumeData; + const resumeData: ResumeData = TOML.parse( + tomlContent, + ) as unknown as ResumeData; const htmlContent = generateResumeHTML(resumeData); diff --git a/src/pages/resume.astro b/src/pages/resume.astro index 81f1783..48cafab 100644 --- a/src/pages/resume.astro +++ b/src/pages/resume.astro @@ -45,13 +45,19 @@ interface ResumeData { position: string; date: string; }[]; + awards: { + title: string; + organization: string; + date: string; + description?: string; + }[]; } let resumeData: ResumeData | undefined = undefined; let fetchError: string | null = null; -// Check if resume JSON file is configured before attempting to fetch -if (!siteConfig.resume.jsonFile || !siteConfig.resume.jsonFile.trim()) { +// Check if resume TOML file is configured before attempting to fetch +if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) { return Astro.redirect("/"); } @@ -60,7 +66,7 @@ try { const baseUrl = Astro.url.origin; // Fetch the TOML file from the public directory - const response = await fetch(`${baseUrl}${siteConfig.resume.jsonFile}`); + const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`); if (!response.ok) { throw new Error( @@ -331,5 +337,42 @@ if (!data) {
) } + + { + data.awards && + data.awards.length > 0 && + resumeConfig.sections.awards?.enabled && ( +
+
+

+ {resumeConfig.sections.awards.title || + "Awards & Recognition"} +

+
+ {data.awards.map((award) => ( +
+

+ {award.title} +

+
+ + {award.organization} + + + {award.date} + +
+ {award.description && ( +
+ {award.description} +
+ )} +
+ ))} +
+
+
+ ) + } diff --git a/src/types/index.ts b/src/types/index.ts index 77fc679..aa3b2b7 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -51,7 +51,7 @@ export interface NavigationItem { } export interface ResumeConfig { - jsonFile: string; + tomlFile: string; pdfFile: { path: string; filename: string; @@ -83,6 +83,10 @@ export interface ResumeConfig { title?: string; enabled?: boolean; }; + awards?: { + title?: string; + enabled?: boolean; + }; }; } diff --git a/src/utils/terminal/fs.ts b/src/utils/terminal/fs.ts index aa6d411..1315a0e 100644 --- a/src/utils/terminal/fs.ts +++ b/src/utils/terminal/fs.ts @@ -1,18 +1,27 @@ -import type { FileSystemNode, ResumeData } from './types'; -import { talks, projects, socialLinks, techLinks } from '../../config/data'; +import type { FileSystemNode, ResumeData } from "./types"; +import { talks, projects, socialLinks, techLinks } from "../../config/data"; -export async function buildFileSystem(): Promise<{ [key: string]: FileSystemNode }> { +export async function buildFileSystem(): Promise<{ + [key: string]: FileSystemNode; +}> { try { - const response = await fetch('/files/resume.json'); - const resumeData: ResumeData = await response.json(); + const response = await fetch("/api/resume.json"); + + if (!response.ok) { + throw new Error( + `Failed to fetch resume data: ${response.status} ${response.statusText}`, + ); + } + + const resumeData: any = await response.json(); // Fetch blog posts - const postsResponse = await fetch('/api/posts.json'); + const postsResponse = await fetch("/api/posts.json"); let postsData = []; try { postsData = await postsResponse.json(); } catch (error) { - console.log('Could not fetch posts data:', error); + console.log("Could not fetch posts data:", error); } // Build resume files from rxresume json @@ -25,117 +34,140 @@ export async function buildFileSystem(): Promise<{ [key: string]: FileSystemNode const contactContent = buildContactContent(resumeData); const fs: { [key: string]: FileSystemNode } = { - '/': { - type: 'directory', - name: '/', + "/": { + type: "directory", + name: "/", children: { - 'about.txt': { - type: 'file', - name: 'about.txt', - content: `${resumeData.basics.name}\nResearcher, Full-Stack Developer, and IT Professional.\n\nExplore the directories:\n- /resume - Professional experience and skills\n- /posts - Blog posts and articles\n- /talks - Conference presentations\n- /projects - Personal and professional projects\n- /social - Social media and contact links\n- /tech - Technologies and tools I use\n\nType "ls" to see all available files and directories.` + "about.txt": { + type: "file", + name: "about.txt", + content: `${resumeData.basics.name}\nResearcher, Full-Stack Developer, and IT Professional.\n\nExplore the directories:\n- /resume - Professional experience and skills\n- /posts - Blog posts and articles\n- /talks - Conference presentations\n- /projects - Personal and professional projects\n- /social - Social media and contact links\n- /tech - Technologies and tools I use\n\nType "ls" to see all available files and directories.`, }, - 'resume': { - type: 'directory', - name: 'resume', - children: resumeFiles + resume: { + type: "directory", + name: "resume", + children: resumeFiles, }, - 'posts': { - type: 'directory', - name: 'posts', - children: postsFiles + posts: { + type: "directory", + name: "posts", + children: postsFiles, }, - 'talks': { - type: 'directory', - name: 'talks', - children: talksFiles + talks: { + type: "directory", + name: "talks", + children: talksFiles, }, - 'projects': { - type: 'directory', - name: 'projects', - children: projectsFiles + projects: { + type: "directory", + name: "projects", + children: projectsFiles, }, - 'social': { - type: 'directory', - name: 'social', - children: socialFiles + social: { + type: "directory", + name: "social", + children: socialFiles, }, - 'tech': { - type: 'directory', - name: 'tech', - children: techFiles + tech: { + type: "directory", + name: "tech", + children: techFiles, }, - 'contact.txt': { - type: 'file', - name: 'contact.txt', - content: contactContent - } - } - } + "contact.txt": { + type: "file", + name: "contact.txt", + content: contactContent, + }, + }, + }, }; return fs; } catch (error) { - console.error('Error loading resume data:', error); + console.error("Error loading resume data:", error); return buildFallbackFileSystem(); } } -function buildResumeFiles(resumeData: ResumeData): { [key: string]: FileSystemNode } { +function buildResumeFiles(resumeData: any): { [key: string]: FileSystemNode } { const resumeFiles: { [key: string]: FileSystemNode } = {}; - if (resumeData.sections.summary) { - resumeFiles['summary.txt'] = { - type: 'file', - name: 'summary.txt', - content: resumeData.sections.summary.content.replace(/<[^>]*>/g, '') - }; - } + try { + if (resumeData.summary) { + resumeFiles["summary.txt"] = { + type: "file", + name: "summary.txt", + content: resumeData.summary.content, + }; + } - if (resumeData.sections.skills?.items) { - const skillsContent = resumeData.sections.skills.items - .map(skill => `${skill.name} (Level: ${skill.level}/5)`) - .join('\n'); - resumeFiles['skills.txt'] = { - type: 'file', - name: 'skills.txt', - content: skillsContent - }; - } + if (resumeData.skills && Array.isArray(resumeData.skills)) { + const skillsContent = resumeData.skills + .map((skill: any) => `${skill.name} (Level: ${skill.level}/5)`) + .join("\n"); + resumeFiles["skills.txt"] = { + type: "file", + name: "skills.txt", + content: skillsContent, + }; + } - if (resumeData.sections.experience?.items) { - const experienceContent = resumeData.sections.experience.items - .map(exp => { - const summary = exp.summary.replace(/<[^>]*>/g, '').replace(/ /g, ' '); - return `${exp.position} at ${exp.company}\n${exp.date} | ${exp.location}\n${summary}\n${exp.url?.href ? `URL: ${exp.url.href}` : ''}\n`; - }) - .join('\n---\n\n'); - resumeFiles['experience.txt'] = { - type: 'file', - name: 'experience.txt', - content: experienceContent - }; - } + if (resumeData.experience && Array.isArray(resumeData.experience)) { + const experienceContent = resumeData.experience + .map((exp: any) => { + const description = Array.isArray(exp.description) + ? exp.description.join("\n• ") + : ""; + return `${exp.position} at ${exp.company}\n${exp.date} | ${exp.location}\n• ${description}\n${exp.url ? `URL: ${exp.url}` : ""}\n`; + }) + .join("\n---\n\n"); + resumeFiles["experience.txt"] = { + type: "file", + name: "experience.txt", + content: experienceContent, + }; + } - if (resumeData.sections.education?.items) { - const educationContent = resumeData.sections.education.items - .map(edu => `${edu.institution}\n${edu.studyType} - ${edu.area}\n${edu.date}\n${edu.summary ? edu.summary.replace(/<[^>]*>/g, '') : ''}`) - .join('\n\n---\n\n'); - resumeFiles['education.txt'] = { - type: 'file', - name: 'education.txt', - content: educationContent - }; - } + if (resumeData.education && Array.isArray(resumeData.education)) { + const educationContent = resumeData.education + .map( + (edu: any) => + `${edu.institution}\n${edu.degree} - ${edu.field}\n${edu.date}\n${edu.details && Array.isArray(edu.details) ? edu.details.join("\n• ") : ""}`, + ) + .join("\n\n---\n\n"); + resumeFiles["education.txt"] = { + type: "file", + name: "education.txt", + content: educationContent, + }; + } - if (resumeData.sections.volunteer?.items) { - const volunteerContent = resumeData.sections.volunteer.items - .map(vol => `${vol.organization}\n${vol.position}\n${vol.date}`) - .join('\n\n---\n\n'); - resumeFiles['volunteer.txt'] = { - type: 'file', - name: 'volunteer.txt', - content: volunteerContent - }; + if (resumeData.volunteer && Array.isArray(resumeData.volunteer)) { + const volunteerContent = resumeData.volunteer + .map((vol: any) => `${vol.organization}\n${vol.position}\n${vol.date}`) + .join("\n\n---\n\n"); + resumeFiles["volunteer.txt"] = { + type: "file", + name: "volunteer.txt", + content: volunteerContent, + }; + } + + if (resumeData.awards && Array.isArray(resumeData.awards)) { + const awardsContent = resumeData.awards + .map( + (award: any) => + `${award.title}\n${award.organization}\n${award.date}\n${award.description || ""}`, + ) + .join("\n\n---\n\n"); + resumeFiles["awards.txt"] = { + type: "file", + name: "awards.txt", + content: awardsContent, + }; + } + } catch (error) { + console.error("Error building resume files:", error); } return resumeFiles; @@ -150,15 +182,15 @@ function buildPostsFiles(postsData: any[]): { [key: string]: FileSystemNode } { title: "${post.title}" description: "${post.description}" pubDate: "${post.pubDate}" -tags: [${post.tags.map((tag: string) => `"${tag}"`).join(', ')}] +tags: [${post.tags.map((tag: string) => `"${tag}"`).join(", ")}] --- ${post.content}`; postsFiles[fileName] = { - type: 'file', + type: "file", name: fileName, - content + content, }; }); @@ -168,18 +200,17 @@ ${post.content}`; function buildTalksFiles(): { [key: string]: FileSystemNode } { const talksFiles: { [key: string]: FileSystemNode } = {}; - talks.forEach(talk => { + talks.forEach((talk) => { const fileName = `${talk.id}.txt`; let content = `${talk.name} ${talk.description} -${talk.venue || ''} -${talk.date || ''} +${talk.date || ""} ${talk.link}`; talksFiles[fileName] = { - type: 'file', + type: "file", name: fileName, - content + content, }; }); @@ -189,18 +220,18 @@ ${talk.link}`; function buildProjectsFiles(): { [key: string]: FileSystemNode } { const projectsFiles: { [key: string]: FileSystemNode } = {}; - projects.forEach(project => { + projects.forEach((project) => { const fileName = `${project.id}.txt`; let content = `${project.name} ${project.description} -${project.status || ''} -${project.technologies ? project.technologies.join(', ') : ''} +${project.status || ""} +${project.technologies ? project.technologies.join(", ") : ""} ${project.link}`; projectsFiles[fileName] = { - type: 'file', + type: "file", name: fileName, - content + content, }; }); @@ -210,15 +241,15 @@ ${project.link}`; function buildSocialFiles(): { [key: string]: FileSystemNode } { const socialFiles: { [key: string]: FileSystemNode } = {}; - socialLinks.forEach(link => { + socialLinks.forEach((link) => { const fileName = `${link.id}.txt`; let content = `${link.name} ${link.url}`; socialFiles[fileName] = { - type: 'file', + type: "file", name: fileName, - content + content, }; }); @@ -228,76 +259,126 @@ ${link.url}`; function buildTechFiles(): { [key: string]: FileSystemNode } { const techFiles: { [key: string]: FileSystemNode } = {}; - techLinks.forEach(link => { + techLinks.forEach((link) => { const fileName = `${link.id}.txt`; let content = `${link.name} ${link.url}`; techFiles[fileName] = { - type: 'file', + type: "file", name: fileName, - content + content, }; }); return techFiles; } -function buildContactContent(resumeData: ResumeData): string { - return [ - `Email: ${resumeData.basics.email}`, - '', - 'Social Profiles:', - ...resumeData.sections.profiles.items.map(profile => - `${profile.network}: ${profile.url.href}` - ) - ].join('\n'); +function buildContactContent(resumeData: any): string { + try { + const basics = resumeData.basics || {}; + const email = basics.email || "Not provided"; + const profiles = basics.profiles || []; + + return [ + `Email: ${email}`, + "", + "Social Profiles:", + ...profiles.map((profile: any) => `${profile.network}: ${profile.url}`), + ].join("\n"); + } catch (error) { + console.error("Error building contact content:", error); + return "Contact information unavailable"; + } } function buildFallbackFileSystem(): { [key: string]: FileSystemNode } { + const talksFiles = buildTalksFiles(); + const projectsFiles = buildProjectsFiles(); + const socialFiles = buildSocialFiles(); + const techFiles = buildTechFiles(); + return { - '/': { - type: 'directory', - name: '/', + "/": { + type: "directory", + name: "/", children: { - 'about.txt': { - type: 'file', - name: 'about.txt', - content: 'Atridad Lahiji\nResearcher, Full-Stack Developer, and IT Professional.\n\nError loading detailed information. Please check the website directly.' - } - } - } + "about.txt": { + type: "file", + name: "about.txt", + content: + "Atridad Lahiji\nResearcher, Full-Stack Developer, and IT Professional.\n\nError loading resume data. Basic navigation still available.\n\nExplore the directories:\n- /talks - Conference presentations\n- /projects - Personal and professional projects\n- /social - Social media and contact links\n- /tech - Technologies and tools I use\n\nType 'ls' to see all available files and directories.", + }, + talks: { + type: "directory", + name: "talks", + children: talksFiles, + }, + projects: { + type: "directory", + name: "projects", + children: projectsFiles, + }, + social: { + type: "directory", + name: "social", + children: socialFiles, + }, + tech: { + type: "directory", + name: "tech", + children: techFiles, + }, + "help.txt": { + type: "file", + name: "help.txt", + content: + "Available commands:\n- ls - list files\n- cd - change directory\n- cat - view file contents\n- pwd - show current directory\n- clear - clear terminal\n- help - show this help\n- train - run the train animation", + }, + }, + }, }; } -export function getCurrentDirectory(fileSystem: { [key: string]: FileSystemNode }, currentPath: string): FileSystemNode { - const pathParts = currentPath.split('/').filter((part: string) => part !== ''); - let current = fileSystem['/']; - +export function getCurrentDirectory( + fileSystem: { [key: string]: FileSystemNode }, + currentPath: string, +): FileSystemNode { + const pathParts = currentPath + .split("/") + .filter((part: string) => part !== ""); + let current = fileSystem["/"]; + for (const part of pathParts) { - if (current?.children && current.children[part] && current.children[part].type === 'directory') { + if ( + current?.children && + current.children[part] && + current.children[part].type === "directory" + ) { current = current.children[part]; } } - + return current; } export function resolvePath(currentPath: string, path: string): string { - if (path.startsWith('/')) { + if (path.startsWith("/")) { return path; } - - const currentParts = currentPath.split('/').filter((part: string) => part !== ''); - const pathParts = path.split('/'); - + + const currentParts = currentPath + .split("/") + .filter((part: string) => part !== ""); + const pathParts = path.split("/"); + for (const part of pathParts) { - if (part === '..') { + if (part === "..") { currentParts.pop(); - } else if (part !== '.' && part !== '') { + } else if (part !== "." && part !== "") { currentParts.push(part); } } - - return '/' + currentParts.join('/'); -} \ No newline at end of file + + return "/" + currentParts.join("/"); +}