diff --git a/dist/api/blog/blogData.php b/dist/api/blog/blogData.php index e0eb5cd..951bda2 100644 --- a/dist/api/blog/blogData.php +++ b/dist/api/blog/blogData.php @@ -21,7 +21,8 @@ class blogData public function getBlogPosts(): array { $conn = dbConn(); - $stmt = $conn->prepare("SELECT ID, title, dateCreated, dateModified, body, categories FROM blog ORDER BY dateCreated DESC;"); + $stmt = $conn->prepare("SELECT ID, title, dateCreated, dateModified, body, categories, featured + FROM blog ORDER BY dateCreated;"); $stmt->execute(); // set the resulting array to associative @@ -99,6 +100,164 @@ class blogData return array("errorMessage" => "Error, blog post could not found"); } + /** + * Delete a blog post with the given ID + * @param int $ID - ID of the blog post to delete + * @return string - Success or error message + */ + public function deletePost(int $ID): string + { + $conn = dbConn(); + + $stmtCheckPost = $conn->prepare("SELECT * FROM blog WHERE ID = :ID"); + $stmtCheckPost->bindParam(":ID", $ID); + $stmtCheckPost->execute(); + $result = $stmtCheckPost->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "post not found"; + } + + if ($result["featured"] === 1) + { + return "cannot delete"; + } + + $stmt = $conn->prepare("DELETE FROM blog WHERE ID = :ID"); + $stmt->bindParam(":ID", $ID); + + if ($stmt->execute()) + { + $imagUtils = new imgUtils(); + $imagUtils->deleteDirectory("../blog/imgs/" . $result["title"] . "_" . $result["folderID"] . "/"); + return "success"; + } + + return "error"; + } + + /** + * Update the blog post with the given ID + * @param int $ID - ID of the blog post to update + * @param string $title - Title of the blog post + * @param bool $featured - Whether the blog post is featured or not + * @param string $body - Body of the blog post + * @param string $dateModified - Date the blog post was modified + * @param string $categories - Categories of the blog post + * @return bool|string - Success or error message + */ + public function updatePost(int $ID, string $title, bool $featured, string $body, string $dateModified, string $categories): bool|string + { + $conn = dbConn(); + + $stmtCheckPost = $conn->prepare("SELECT * FROM blog WHERE ID = :ID"); + $stmtCheckPost->bindParam(":ID", $ID); + $stmtCheckPost->execute(); + $result = $stmtCheckPost->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "post not found"; + } + + if (!$featured && $result["featured"] === 1) + { + return "unset feature"; + } + + if ($featured) + { + $stmtUnsetFeatured = $conn->prepare("UPDATE blog SET featured = 0 WHERE featured = 1;"); + $stmtUnsetFeatured->execute(); + } + + $to = "../blog/imgs/" . $title . "_" . $result["folderID"] . "/"; + if ($result["title"] !== $title) + { + $from = "../blog/imgs/" . $result["title"] . "_" . $result["folderID"] . "/"; + mkdir($to, 0777, true); + rename($result["headerImg"], $to . basename($result["headerImg"])); + $body = $this->changeHTMLSrc($body, $to, $from); + rmdir($from); + } + + $from = "../blog/imgs/tmp/"; + $newBody = $this->changeHTMLSrc($body, $to, $from); + + $stmt = $conn->prepare("UPDATE blog SET title = :title, featured = :featured, body = :body, dateModified = :dateModified, categories = :categories WHERE ID = :ID;"); + $stmt->bindParam(":ID", $ID); + $stmt->bindParam(":title", $title); + $stmt->bindParam(":featured", $featured); + $stmt->bindParam(":body", $newBody); + $stmt->bindParam(":dateModified", $dateModified); + $stmt->bindParam(":categories", $categories); + + return $stmt->execute(); + } + + /** + * Creates a new post di rectory, uploads the header image and moves the images from the + * temp folder to the new folder, then updates the post html to point to the new images, finally + * it creates the post in the database + * @param string $title - Title of the blog post + * @param string $body - Body of the blog post + * @param string $dateCreated - Date the blog post was created + * @param bool $featured - Whether the blog post is featured or not + * @param string $categories - Categories of the blog post + * @param UploadedFileInterface $headerImg - Header image of the blog post + * @return int|string - ID of the blog post or error message + */ + public function createPost(string $title, string $body, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string + { + $conn = dbConn(); + $folderID = uniqid(); + $targetFile = array("imgLocation" => "../blog/imgs/placeholder.png"); + + $targetDir = "../blog/imgs/" . $title . "_" . $folderID . "/"; + mkdir($targetDir, 0777, true); + + if ($headerImg !== null) + { + $imagUtils = new imgUtils(); + $targetFile = $imagUtils->uploadFile($targetDir, $headerImg); + } + + + if (!is_array($targetFile)) + { + return $targetFile; + } + + $newBody = $this->changeHTMLSrc($body, $targetDir, "../blog/imgs/tmp/"); + + + if ($featured) + { + $stmtMainProject = $conn->prepare("UPDATE blog SET featured = 0 WHERE featured = 1;"); + $stmtMainProject->execute(); + } + + $stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, body, categories, folderID) + VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :body, :categories, :folderID);"); + $stmt->bindParam(":title", $title); + $stmt->bindParam(":dateCreated", $dateCreated); + $stmt->bindParam(":dateModified", $dateCreated); + $isFeatured = $featured ? 1 : 0; + $stmt->bindParam(":featured", $isFeatured); + $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); + $stmt->bindParam(":body", $newBody); + $stmt->bindParam(":categories", $categories); + $stmt->bindParam(":folderID", $folderID); + + if ($stmt->execute()) + { + return intval($conn->lastInsertId()); + } + + return "Error, couldn't create post"; + } + /** * Upload the images in the post to temp folder and return image location * @param UploadedFileInterface $img - Image to upload @@ -131,37 +290,60 @@ class blogData } /** - * Creates a new post directory, uploads the header image and moves the images from the - * temp folder to the new folder, then updates the post html to point to the new images, finally - * it creates the post in the database - * @param string $title - Title of the blog post - * @param string $body - Body of the blog post - * @param string $dateCreated - Date the blog post was created - * @param string $featured - Whether the blog post is featured or not - * @param string $categories - Categories of the blog post - * @param UploadedFileInterface $headerImg - Header image of the blog post - * @return int|string - ID of the blog post or error message + * Upload the header image of the post and update the database + * @param int $ID - ID of the post + * @param UploadedFileInterface $img - Image to upload + * @return string|array - String with error message or array with the location of the uploaded file */ - public function createPost(string $title, string $body, string $dateCreated, string $featured, string $categories, UploadedFileInterface $headerImg): int|string + public function uploadHeaderImage(int $ID, UploadedFileInterface $img): string|array { $conn = dbConn(); - $targetFile = ""; - $folderID = uniqid(); - if ($headerImg !== null) + $stmt = $conn->prepare("SELECT * FROM blog WHERE ID = :ID;"); + $stmt->bindParam(":ID", $ID); + $stmt->execute(); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$result) { - $targetDir = "../blog/imgs/" . $title . "_" . $folderID . "/"; - mkdir($targetDir, 0777, true); - $imagUtils = new imgUtils(); - $targetFile = $imagUtils->uploadFile($targetDir, $headerImg); + return "Couldn't find the post"; } - $targetFile = array("imgLocation" => ".../blog/imgs/placeholder.png"); + $targetDir = "../blog/imgs/" . $result["title"] . "_" . $result["folderID"] . "/"; + $imagUtils = new imgUtils(); + $targetFile = $imagUtils->uploadFile($targetDir, $img); if (!is_array($targetFile)) { return $targetFile; } + if (file_exists($targetFile["imgLocation"])) + { + unlink($result["headerImg"]); + $stmt = $conn->prepare("UPDATE blog SET headerImg = :headerImg WHERE ID = :ID;"); + $stmt->bindParam(":ID", $ID); + $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); + $stmt->execute(); + if ($stmt->rowCount() > 0) + { + return $targetFile; + } + + return "Couldn't update the post"; + } + + return "Couldn't upload the image"; + } + + /** + * Change the HTML src of the images in the post to point to the new location + * @param string $body - Body of the post + * @param string $to - New location of the images + * @param string $from - Old location of the images + * @return string - Body of the post with the new image locations + */ + public function changeHTMLSrc(string $body, string $to, string $from): string + { $htmlDoc = new DOMDocument(); $htmlDoc->loadHTML($body, LIBXML_NOERROR); $doc = $htmlDoc->getElementsByTagName('body')->item(0); @@ -172,24 +354,25 @@ class blogData foreach ($imgs as $img) { $src = $img->getAttribute("src"); + $src = urldecode($src); $srcList[] = $src; $fileName = basename($src); - $img->setAttribute("src", $targetDir . $fileName); + $img->setAttribute("src", $to . $fileName); } - $files = scandir("../blog/imgs/tmp/"); + $files = scandir($from); foreach ($files as $file) { if ($file != "." && $file != "..") { - if (!in_array("../blog/imgs/tmp/" . $file, $srcList)) + if (!in_array($from . $file, $srcList)) { - unlink("../blog/imgs/tmp/" . $file); + unlink($from . $file); } else { - rename("../blog/imgs/tmp/" . $file, $targetDir . $file); + rename($from . $file, $to . $file); } } } @@ -199,23 +382,6 @@ class blogData { $newBody .= $htmlDoc->saveHTML($node); } - - $stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, body, categories, folderID) - VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :body, :categories, :folderID);"); - $stmt->bindParam(":title", $title); - $stmt->bindParam(":dateCreated", $dateCreated); - $stmt->bindParam(":dateModified", $dateCreated); - $stmt->bindParam(":featured", $featured); - $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); - $stmt->bindParam(":body", $newBody); - $stmt->bindParam(":categories", $categories); - $stmt->bindParam(":folderID", $folderID); - - if ($stmt->execute()) - { - return intval($conn->lastInsertId()); - } - - return "Error, couldn't create post"; + return $newBody; } } \ No newline at end of file diff --git a/dist/api/blog/blogRoutes.php b/dist/api/blog/blogRoutes.php index a12075a..a0770a9 100644 --- a/dist/api/blog/blogRoutes.php +++ b/dist/api/blog/blogRoutes.php @@ -29,12 +29,122 @@ class blogRoutes implements routesInterface */ public function createRoutes(App $app): void { - $app->post("/blog/post", function (Request $request, Response $response, array $args) + $app->get("/blog/post", function (Request $request, Response $response) + { + $posts = $this->blogData->getBlogPosts(); + + $json = json_encode($posts); + + $response->getBody()->write($json); + + if (array_key_exists("errorMessage", $posts)) + { + $response->withStatus(404); + } + + return $response; + }); + + $app->get("/blog/post/{id}", function (Request $request, Response $response, $args) + { + if ($args["id"] != null) + { + $post = $this->blogData->getBlogPost($args["id"]); + if (array_key_exists("errorMessage", $post)) + { + $response->getBody()->write(json_encode($post)); + return $response->withStatus(404); + } + + $response->getBody()->write(json_encode($post)); + return $response; + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); + + $app->patch("/blog/post/{id}", function (Request $request, Response $response, $args) + { + $data = $request->getParsedBody(); + if ($args["id"] != null) + { + if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"])) + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Only some of the data was sent"))); + return $response->withStatus(400); + } + + $message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["body"], $data["dateModified"], $data["categories"]); + + if ($message === "post not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, post not found"))); + return $response->withStatus(404); + } + + if ($message === "unset featured") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, cannot unset featured post, try updating another post to be featured first"))); + return $response->withStatus(409); + } + + if (!is_bool($message) || $message === false) + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => $message))); + return $response->withStatus(500); + } + + return $response; + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); + + $app->delete("/blog/post/{id}", function (Request $request, Response $response, $args) + { + if ($args["id"] != null) + { + $message = $this->blogData->deletePost($args["id"]); + + if ($message === "post not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, post not found"))); + return $response->withStatus(404); + } + + if ($message === "error") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, something went wrong"))); + return $response->withStatus(500); + } + + if ($message === "cannot delete") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, cannot delete featured post"))); + return $response->withStatus(409); + } + return $response; + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); + + $app->post("/blog/post", function (Request $request, Response $response) { $data = $request->getParsedBody(); $files = $request->getUploadedFiles(); $headerImg = $files["headerImg"]; - if (empty($data["title"]) || empty($data["body"]) || empty($data["dateCreated"]) || empty($data["featured"]) || empty($data["categories"])) + if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateCreated"]) || empty($data["categories"])) { // uh oh sent some empty data $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); @@ -46,7 +156,8 @@ class blogRoutes implements routesInterface $headerImg = null; } - $insertedID = $this->blogData->createPost($data["title"], $data["body"], $data["dateCreated"], $data["featured"], $data["categories"], $headerImg); + $featured = $data["featured"] === "true"; + $insertedID = $this->blogData->createPost($data["title"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg); if (!is_int($insertedID)) { // uh oh something went wrong @@ -74,9 +185,36 @@ class blogRoutes implements routesInterface return $response->withStatus(500); } - $response->getBody()->write(json_encode($message)); return $response->withStatus(201); }); + + $app->post("/blog/headerImage/{id}", function (Request $request, Response $response, $args) + { + $files = $request->getUploadedFiles(); + + if ($args["id"] != null) + { + if (empty($files)) + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent")))); + return $response->withStatus(400); + } + + $message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]); + if (!is_array($message)) + { + $response->getBody()->write(json_encode(array("error" => array("message" => $message)))); + return $response->withStatus(500); + } + + $response->getBody()->write(json_encode($message)); + return $response->withStatus(201); + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); } } \ No newline at end of file diff --git a/dist/api/project/projectData.php b/dist/api/project/projectData.php index 77a65a6..a69b22f 100644 --- a/dist/api/project/projectData.php +++ b/dist/api/project/projectData.php @@ -39,30 +39,32 @@ class projectData * Update project data in the database with the given ID * @param string $ID - ID of the project in the database to update * @param string $title - Title of the project - * @param string $isMainProject - Is the project a main project or not + * @param bool $isMainProject - Is the project a main project or not * @param string $information - Information about the project * @param string $projectLink - Link to the project * @param string $gitLink - Link to the git repository * @return bool|string - True if project was updated, false if not and there was an error, or an error string */ - public function updateProjectData(string $ID, string $title, string $isMainProject, string $information, string $projectLink, string $gitLink): bool|string + public function updateProjectData(string $ID, string $title, bool $isMainProject, string $information, string $projectLink, string $gitLink): bool|string { $conn = dbConn(); - if ($isMainProject === "false") - { - $stmtMainProject = $conn->prepare("SELECT isMainProject FROM projects WHERE ID = :ID"); - $stmtMainProject->bindParam(":ID", $ID); - $stmtMainProject->execute(); - $result = $stmtMainProject->fetch(PDO::FETCH_ASSOC); + $stmtMainProject = $conn->prepare("SELECT isMainProject FROM projects WHERE ID = :ID"); + $stmtMainProject->bindParam(":ID", $ID); + $stmtMainProject->execute(); + $result = $stmtMainProject->fetch(PDO::FETCH_ASSOC); - if ($result["isMainProject"] === "1") - { - return "unset main project"; - } + if (!$result) + { + return "project not found"; } - if ($isMainProject === "true") + if (!$isMainProject && $result["isMainProject"] === "1") + { + return "unset main project"; + } + + if ($isMainProject) { $stmtMainProject = $conn->prepare("UPDATE projects SET isMainProject = 0 WHERE isMainProject = 1;"); $stmtMainProject->execute(); @@ -70,7 +72,7 @@ class projectData $stmt = $conn->prepare("UPDATE projects SET title = :title, isMainProject = :isMainProject, information = :information, projectLink = :projectLink, gitLink = :gitLink WHERE ID = :ID"); $stmt->bindParam(":title", $title); - $isMainProj = ($isMainProject === "true") ? 1 : 0; + $isMainProj = $isMainProject ? 1 : 0; $stmt->bindParam(":isMainProject", $isMainProj); $stmt->bindParam(":information", $information); $stmt->bindParam(":projectLink", $projectLink); @@ -89,12 +91,16 @@ class projectData $conn = dbConn(); // check if the project is a main project if it is return false - $stmtMainProject = $conn->prepare("SELECT isMainProject FROM projects WHERE ID = :ID"); $stmtMainProject->bindParam(":ID", $ID); $stmtMainProject->execute(); $result = $stmtMainProject->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "project not found"; + } + if ($result["isMainProject"] === "1") { return "cannot delete"; @@ -158,6 +164,20 @@ class projectData */ public function uploadImage(int $ID, UploadedFileInterface $img): string | array { + + $conn = dbConn(); + + $stmt = $conn->prepare("SELECT ID FROM projects WHERE ID = :ID"); + $stmt->bindParam(":ID", $ID); + $stmt->execute(); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "Project with ID $ID not found"; + } + + $targetDir = "../imgs/projects/"; $imgUtils = new imgUtils(); $targetFile = $imgUtils->uploadFile($targetDir, $img); @@ -171,7 +191,6 @@ class projectData { $this->deleteImage($ID); // update the database with the new image location - $conn = dbConn(); $stmt = $conn->prepare("UPDATE projects SET imgLocation = :imgLocation WHERE ID = :ID"); $stmt->bindParam(":imgLocation", $targetFile["imgLocation"]); $stmt->bindParam(":ID", $ID); diff --git a/dist/api/project/projectRoutes.php b/dist/api/project/projectRoutes.php index 1b471cb..a7a332e 100644 --- a/dist/api/project/projectRoutes.php +++ b/dist/api/project/projectRoutes.php @@ -39,7 +39,7 @@ class projectRoutes implements routesInterface if(array_key_exists("errorMessage", $result)) { - $response = $response->withStatus(404); + $response->withStatus(404); } //use content type json to indicate json data on frontend. @@ -49,7 +49,7 @@ class projectRoutes implements routesInterface $app->patch("/projectData/{id}", function (Request $request, Response $response, array $args) { $data = $request->getParsedBody(); - if ($args["id"] != "undefined") + if ($args["id"] != null) { if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"])) { @@ -58,7 +58,15 @@ class projectRoutes implements routesInterface return $response->withStatus(400); } - $update = $this->projectData->updateProjectData($args["id"], $data["title"], $data["isMainProject"], $data["information"], $data["projectLink"], $data["gitLink"]); + $isMainProject = $data["isMainProject"] === "true"; + $update = $this->projectData->updateProjectData($args["id"], $data["title"], $isMainProject, $data["information"], $data["projectLink"], $data["gitLink"]); + + if ($update === "project not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found"))); + return $response->withStatus(404); + } if ($update === "unset main project") { @@ -73,6 +81,7 @@ class projectRoutes implements routesInterface $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); return $response->withStatus(500); } + return $response; } @@ -85,11 +94,12 @@ class projectRoutes implements routesInterface if ($args["id"] != null) { $message = $this->projectData->deleteProjectData($args["id"]); - if ($message === "error") + + if ($message === "project not found") { // uh oh something went wrong - $response->getBody()->write(json_encode(array("error" => "Something went wrong or the project with ID ".$args["id"]."does not exist"))); - return $response->withStatus(500); + $response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found"))); + return $response->withStatus(404); } if ($message === "cannot delete") @@ -99,6 +109,13 @@ class projectRoutes implements routesInterface return $response->withStatus(409); } + if ($message === "error") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); + return $response->withStatus(500); + } + return $response; } diff --git a/dist/api/timeline/timelineData.php b/dist/api/timeline/timelineData.php index b72dbdf..9c1df75 100644 --- a/dist/api/timeline/timelineData.php +++ b/dist/api/timeline/timelineData.php @@ -58,19 +58,33 @@ class timelineData * @param string $dateTo - End date * @param string $grade - Grade * @param string $course - Course - * @param string $id - ID of the education data - * @return bool - True if successful, false if not + * @param string $ID - ID of the education data + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - public function updateEduData(string $dateFrom, string $dateTo, string $grade, string $course, string $id): bool + public function updateEduData(string $dateFrom, string $dateTo, string $grade, string $course, string $ID): string { $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM edu WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $stmt = $conn->prepare("UPDATE edu SET startPeriod = :dateFrom, endPeriod = :dateTo, grade = :grade, course = :course WHERE ID = :id;"); $stmt->bindParam(":dateFrom", $dateFrom); $stmt->bindParam(":dateTo", $dateTo); $stmt->bindParam(":grade", $grade); $stmt->bindParam(":course", $course); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** @@ -80,11 +94,21 @@ class timelineData * @param string $companyName - Company name * @param string $area - Area * @param string $title - Title - * @param string $id - ID of the work data - * @return bool - True if successful, false if not + * @param string $ID - ID of the work data + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - public function updateWorkData(string $dateFrom, string $dateTo, string $companyName, string $area, string $title, string $id): bool + public function updateWorkData(string $dateFrom, string $dateTo, string $companyName, string $area, string $title, string $ID): string { + $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM work WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $conn = dbConn(); $stmt = $conn->prepare("UPDATE work SET startPeriod = :dateFrom, endPeriod = :dateTo, companyName = :companyName, area = :area, title = :title WHERE ID = :id;"); $stmt->bindParam(":dateFrom", $dateFrom); @@ -92,34 +116,67 @@ class timelineData $stmt->bindParam(":companyName", $companyName); $stmt->bindParam(":area", $area); $stmt->bindParam(":title", $title); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** * Delete education data by ID - * @param int $id - * @return bool - True if successful, false if not + * @param int $ID + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - public function deleteEduData(int $id): bool + public function deleteEduData(int $ID): string { $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM edu WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $stmt = $conn->prepare("DELETE FROM edu WHERE ID = :id;"); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** * Delete work data by ID - * @param int $id - * @return bool - True if successful, false if not + * @param int $ID + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - function deleteWorkData(int $id): bool + function deleteWorkData(int $ID): string { $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM work WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $stmt = $conn->prepare("DELETE FROM work WHERE ID = :id;"); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** diff --git a/dist/api/timeline/timelineRoutes.php b/dist/api/timeline/timelineRoutes.php index d6ab9d6..72ea6f0 100644 --- a/dist/api/timeline/timelineRoutes.php +++ b/dist/api/timeline/timelineRoutes.php @@ -53,7 +53,7 @@ class timelineRoutes implements routesInterface $app->patch("/timelineData/{timeline}/{id}", function (Request $request, Response $response, array $args) { $data = $request->getParsedBody(); - if ($args["timeline"] == "edu" && $args["id"] != "undefined") + if ($args["timeline"] == "edu" && $args["id"] != null) { if (empty($data["dateFrom"]) || empty($data["dateTo"]) || empty($data["grade"]) || empty($data["course"])) { @@ -61,8 +61,16 @@ class timelineRoutes implements routesInterface $response->getBody()->write(json_encode(array("error" => "Only some of the data was sent"))); return $response->withStatus(400); } + $message = $this->timelineData->updateEduData($data["dateFrom"], $data["dateTo"], $data["grade"], $data["course"], $args["id"]); - if (!$this->timelineData->updateEduData($data["dateFrom"], $data["dateTo"], $data["grade"], $data["course"], $args["id"])) + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Edu data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); @@ -82,7 +90,16 @@ class timelineRoutes implements routesInterface return $response->withStatus(400); } - if (!$this->timelineData->updateWorkData($data["dateFrom"], $data["dateTo"], $data["companyName"], $data["area"], $data["title"], $args["id"])) + $message = $this->timelineData->updateWorkData($data["dateFrom"], $data["dateTo"], $data["companyName"], $data["area"], $data["title"], $args["id"]); + + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Work data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); @@ -101,7 +118,16 @@ class timelineRoutes implements routesInterface { if ($args["timeline"] == "edu" && $args["id"] != null) { - if (!$this->timelineData->deleteEduData($args["id"])) + $message = $this->timelineData->deleteEduData($args["id"]); + + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Edu data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); @@ -113,7 +139,16 @@ class timelineRoutes implements routesInterface if ($args["timeline"] == "work" && $args["id"] != null) { - if (!$this->timelineData->deleteWorkData($args["id"])) + $message = $this->timelineData->deleteWorkData($args["id"]); + + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Work data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); diff --git a/dist/api/user/userData.php b/dist/api/user/userData.php index 0aa11b2..493b6db 100644 --- a/dist/api/user/userData.php +++ b/dist/api/user/userData.php @@ -46,7 +46,7 @@ class userData public function createToken(string $username): string { $now = time(); - $future = strtotime('+6 hour', $now); + $future = strtotime('+2 day', $now); $secretKey = getSecretKey(); $payload = [ "jti" => $username, diff --git a/dist/api/user/userRoutes.php b/dist/api/user/userRoutes.php index 2bde66c..465ca7a 100644 --- a/dist/api/user/userRoutes.php +++ b/dist/api/user/userRoutes.php @@ -36,15 +36,19 @@ class userRoutes implements routesInterface if (empty($data["username"]) || empty($data["password"])) { - // uh oh userData sent empty data + // uh oh user sent empty data return $response->withStatus(400); } if ($this->user->checkUser($data["username"], $data["password"])) { - // yay, userData is logged in + // yay, user is logged in $_SESSION["token"] = $this->user->createToken($data["username"]); $_SESSION["username"] = $data["username"]; + + $inactive = 60 * 60 * 48; // 2 days + $_SESSION["timeout"] = time() + $inactive; + $response->getBody()->write(json_encode(array("token" => $_SESSION["token"]))); return $response; } @@ -62,15 +66,24 @@ class userRoutes implements routesInterface { if (empty($_SESSION["token"]) && empty($_SESSION["username"])) { - // uh oh userData not logged in + // uh oh user not logged in + return $response->withStatus(401); + } + + $inactive = 60 * 60 * 48; // 2 days + $sessionLife = time() - $_SESSION["timeout"]; + if ($sessionLife > $inactive) + { + // uh oh user session expired + session_destroy(); return $response->withStatus(401); } if (empty($_SESSION["token"])) { - // userData is logged in but no token was created + // user is logged in but no token was created $_SESSION["token"] = $this->user->createToken($_SESSION["username"]); - return $response; + return $response->withStatus(201); } $response->getBody()->write(json_encode(array("token" => $_SESSION["token"]))); diff --git a/dist/api/utils/imgUtils.php b/dist/api/utils/imgUtils.php index 40f9487..1deab53 100644 --- a/dist/api/utils/imgUtils.php +++ b/dist/api/utils/imgUtils.php @@ -3,6 +3,8 @@ namespace api\utils; use Psr\Http\Message\UploadedFileInterface; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; class imgUtils { @@ -40,4 +42,29 @@ class imgUtils return array("imgLocation" => $targetFile); } + + /** + * Deletes a directory and all its contents + * @param string $path - Path to the directory to delete + */ + public function deleteDirectory(string $path): void + { + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, + RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) + { + if ($file->isDir()) + { + rmdir($file->getPathname()); + } + else + { + unlink($file->getPathname()); + } + } + + rmdir($path); + } + } \ No newline at end of file diff --git a/dist/css/main.css b/dist/css/main.css index c81af48..62cb9d7 100644 --- a/dist/css/main.css +++ b/dist/css/main.css @@ -1 +1 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%),calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%,32%, 1);--mutedGrey:hsla(0, 0%, 78%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}form input[type=submit]{padding:1.1em 2em}a.btn:hover,form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btnPrimary,form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,form input[type=submit]:hover{background:var(--primaryHover)}a.btn:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl textarea:focus{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}form .formControl.passwordControl{display:block}form input[type=submit]{align-self:flex-start}form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:4px solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:.5em;padding:0 .5em}form .formControl textarea{padding:.5em}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}form .formControl .checkContainer input:checked~.checkmark:after{display:block}form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}header{background:#6a6a6a url(../imgs/hero.jpg) no-repeat bottom;background-size:cover;height:40%;color:#fff;backdrop-filter:grayscale(100%);position:relative}nav{display:flex;flex-direction:row;justify-content:space-between;padding:.25em;position:fixed;top:0;width:100%;transition:background-color .4s ease-in;color:#fff;z-index:1}nav.scrolled{background-color:var(--navBack)}nav #nav-check{display:none}nav .nav-btn{display:none}nav h1{margin:0}nav a{text-decoration:none;color:#fff}nav ul{display:flex;flex-direction:row;gap:1em;margin:0;justify-content:flex-end;align-items:flex-end}nav ul li{list-style:none}nav ul li span{visibility:hidden}nav ul li .active span,nav ul li a:hover span{visibility:visible}header div{display:flex;flex-direction:column;justify-content:center;align-items:center;padding-top:10em}header div .btn{margin:2em 0}header div button{background:0 0;border:none;display:inline-block;text-align:center;text-decoration:none;font-size:2rem;cursor:pointer}i.fa-chevron-down{color:hsla(0,0%,67%,.58);font-size:3.75em;margin:1.5rem 0}div h1 span{visibility:visible;animation:caret 1s steps(1) infinite}@keyframes caret{50%{visibility:hidden}}section#about{margin-bottom:5rem}section#about div{padding:.1em 5em}section#curriculumVitae{background-color:var(--primaryDefault);color:#fff;padding:2em 0}section#curriculumVitae .cvGrid{display:flex;flex-direction:row;padding:0 1.5rem;flex-wrap:wrap}section#curriculumVitae .cvGrid>div{width:45%;display:flex;flex-direction:column;min-height:100%}section#curriculumVitae .cvGrid h2{text-align:center}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae #work{margin:0 auto 0 8rem}section#curriculumVitae .timeline:before{content:"";position:absolute;height:100%;border:4px var(--timelineItemBrdr) solid;right:194px;top:0}section#curriculumVitae .timeline:after{content:"";display:table;clear:both}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:0 1rem;width:50%;position:relative;background-color:var(--primaryHover)}.timelineItem:after,section#curriculumVitae .timelineItem:before{content:'';position:absolute}section#curriculumVitae .timelineItem:before{content:'';right:-20px;top:calc(50% - 5px);border-style:solid;border-color:var(--timelineItemBrdr) var(--timelineItemBrdr) transparent transparent;border-width:20px;transform:rotate(45deg)}section#curriculumVitae .timelineItem:nth-child(2n){margin-left:21em}section#curriculumVitae .timelineItem:nth-child(2n):before{right:auto;left:-20px;border-color:transparent transparent var(--timelineItemBrdr) var(--timelineItemBrdr)}section#curriculumVitae .timelineItem h3{font-weight:400}section#curriculumVitae .timelineItem span{color:#e5e5e5}section#projects{display:flex;flex-direction:row;padding:0 2.5rem;border-bottom:2px solid var(--mutedGrey)}section#projects .mainProj,section#projects .otherProj{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}section#projects .mainProj{border-right:2px solid var(--mutedGrey);padding:0 2.5em 5em 0}section#allProjects img,section#projects img{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:block;margin:1em auto}section#projects .mainProj img{width:100%;max-width:40rem}section#projects .mainProj .flexRow{display:flex;flex-direction:row;gap:4em}section#projects .mainProj .flexCol{display:flex;flex-direction:column;gap:2.5em}section#projects .otherProj>a{margin:5rem 0}section#projects .otherProj>div{display:flex;flex-direction:column;gap:2em}section#allProjects #otherProj .oProjItem,section#projects .otherProj>div .oProjItem{display:flex;justify-content:center;align-items:center;flex-direction:row;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:.75em 1em}section#projects .otherProj>div .oProjItem{margin:0 auto}section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:row-reverse}section#projects .otherProj .oProjItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .oProjItem .flexCol div:nth-child(2){display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .flexCol div:nth-child(2) .btn{padding:.25em .5em}section#allProjects{display:flex;justify-content:center;align-items:center;flex-direction:column;gap:5em}section#allProjects #mainProj{display:flex;flex-direction:column;justify-content:center;align-items:center;border:1px solid var(--grey);box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1.5em 2em;margin:3em 2.5rem 0;width:50%}section#allProjects #mainProj img{width:100%;max-width:30rem}section#allProjects #otherProj{display:flex;flex-direction:row;justify-content:space-between;align-items:stretch;flex-wrap:wrap;gap:2rem;border-top:2px solid var(--grey);padding:5em 2.5rem 0}section#allProjects #otherProj .oProjItem{flex-direction:column;width:30%;height:auto}section#allProjects #otherProj img{width:100%;max-width:20rem}section#contact{display:flex;flex-direction:row;padding:0 2.5em}div#findMe,div#sayHello{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}div#findMe .findMeContainer{display:flex;flex-direction:row;justify-content:space-around;align-items:center;gap:2em;width:100%;height:100%;margin:5em 0}div#findMe .findMeContainer .profile{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%}div#findMe .socialIcons{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:2em}div#findMe .socialIcons div{display:flex;flex-direction:column;gap:1.5em}div#findMe .socialIcons div svg{width:2.5em;fill:var(--primaryDefault);font-size:2em}div#findMe .socialIcons div a:hover svg{fill:var(--primaryHover)}div#sayHello #contactForm{display:flex;flex-direction:column;justify-content:center;align-items:center}#contactForm .flName{display:flex;flex-direction:row;gap:1em;width:100%}div.message{background:var(--primaryDefault);color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em;margin-bottom:1em}div.message.error{background:var(--errorDefault)}div.message button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.message.hidden{opacity:0;visibility:hidden;height:0}div.message button:hover{text-shadow:-1px 2px var(--mutedBlack)}footer{background-color:var(--primaryDefault);margin-top:5em;padding:2em;display:flex;color:#fff}footer .spacer{width:100%;margin-right:auto}footer p{margin:auto;width:100%;text-align:center}footer .button{margin-left:auto;width:100%;text-align:center}footer .button button{border:5px solid #fff;background:0 0;font-size:3em;padding:.5rem 1rem;width:2em;color:#fff;-webkit-border-radius:.25em;-moz-border-radius:.25em;border-radius:.25em;cursor:pointer}@media screen and (max-width:90em){section#curriculumVitae .cvGrid{flex-direction:column;justify-content:center;align-items:center}section#curriculumVitae .cvGrid>div{width:100%}section#curriculumVitae .cvGrid>div:first-child{padding-bottom:2.5em;margin-bottom:2.5em;border-bottom:5px #fff solid}section#curriculumVitae .cvGrid h2{margin-left:5em}section#curriculumVitae .cvGrid .timeline{margin:0 auto}}@media screen and (max-width:75em){section#about,section#curriculumVitae h1{padding:0 1em}nav{display:block;height:50px;width:100%;background-color:var(--navBack);position:fixed;top:0;padding:0}nav a h1{margin-left:1ch}nav .nav-btn{display:inline-block;position:absolute;right:75px;top:0}nav ul{position:fixed;display:block;width:100%;background-color:#333;transition:all .4s ease-in;overflow-y:hidden;padding-left:0;margin-top:7px}nav ul li a{display:block;width:100%;transform:translateX(-30px);transition:all .4s ease-in;opacity:0}.nav-btn label{display:inline-block;cursor:pointer;width:60px;height:50px;position:fixed;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.5s ease-in;-moz-transition:.5s ease-in;-o-transition:.5s ease-in;transition:.5s ease-in}.nav-btn label span{display:block;position:absolute;height:5px;width:100%;background-color:#fff;opacity:1;right:0;top:20px;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.25s ease-in;-moz-transition:.25s ease-in;-o-transition:.25s ease-in;transition:.25s ease-in}nav #nav-check:not(:checked)~ul{height:auto;max-height:0}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(1){top:8px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(2){top:23px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(3){top:38px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:checked~.nav-btn label,nav .nav-btn label:hover{background-color:rgba(-1,0,0,.3)}nav #nav-check:checked~ul{max-height:50vh;overflow-y:hidden}nav #nav-check:checked~ul li a{opacity:1;transform:translateX(0)}nav #nav-check:checked~ul li:nth-child(1) a{transition-delay:.15s}nav #nav-check:checked~ul li:nth-child(2) a{transition-delay:.25s}nav #nav-check:checked~ul li:nth-child(3) a{transition-delay:.35s}nav #nav-check:checked~ul li:nth-child(4) a{transition-delay:.45s}nav #nav-check:checked~ul li:nth-child(5) a{transition-delay:.55s}nav #nav-check:checked~.nav-btn label span:first-child{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}nav #nav-check:checked~.nav-btn label span:nth-child(2){width:0;opacity:0}nav #nav-check:checked~.nav-btn label span:last-child{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}section#about div{padding:.1em 2.5em}section#curriculumVitae .cvGrid{padding:0}section#projects{flex-direction:column;justify-content:center;align-items:center}section#projects .mainProj{border-right:0;padding:0;width:100%;margin:0 5em}section#projects .mainProj img{padding:0 1em}section#projects .mainProj .flexRow{flex-direction:column;margin:0 2.5em}section#projects .mainProj .flexCol{flex-direction:row;justify-content:center;align-items:center}section#projects .otherProj{width:100%}section#projects .otherProj .btn{width:10em;text-align:center}section#projects .otherProj>div .oProjItem,section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){justify-content:center;margin-left:0;margin-bottom:1em}section#projects .otherProj>a{margin-left:3em;margin-right:3em;text-align:center}section#allProjects #otherProj .oProjItem{width:45%}section#contact{flex-direction:column;justify-content:center;align-items:center}div#findMe,div#sayHello{width:100%}div#findMe .findMeContainer{flex-direction:column;justify-content:center}div#findMe .socialIcons{flex-direction:row}div#findMe .socialIcons div{flex-direction:row}}@media screen and (max-width:55em){section#curriculumVitae .cvGrid .timeline,section#curriculumVitae .cvGrid .timeline#work{margin:0 auto;width:100%}section#curriculumVitae .timeline:before{border:none}section#curriculumVitae .timelineItem,section#curriculumVitae .timelineItem:nth-child(2n){width:95%;padding:0;margin:0 auto}section#curriculumVitae .timelineItem:before{right:unset;left:unset;border:none}section#allProjects #mainProj{width:auto}section#allProjects #otherProj{flex-direction:column;align-items:center}section#allProjects #otherProj .oProjItem{width:auto}div#findMe .socialIcons{flex-direction:column}#contactForm .flName{flex-direction:column;gap:0;width:100%}}@media screen and (max-width:31em){section#about,section#curriculumVitae h1{padding:0 1em}header div h1{text-align:center;height:5.125rem}section#about div{padding:.1em 1em}section#projects .mainProj .flexCol{flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){flex-direction:column;justify-content:center;align-items:center}} \ No newline at end of file +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 78%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}form input[type=submit]{padding:1.1em 2em}a.btn:hover,form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btnPrimary,form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,form input[type=submit]:hover{background:var(--primaryHover)}a.btn:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl textarea:focus{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}form .formControl.passwordControl{display:block}form input[type=submit]{align-self:flex-start}form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:4px solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:.5em;padding:0 .5em}form .formControl textarea{padding:.5em}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}form .formControl .checkContainer input:checked~.checkmark:after{display:block}form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}header{background:#6a6a6a url(../imgs/hero.jpg) no-repeat bottom;background-size:cover;height:40%;color:#fff;backdrop-filter:grayscale(100%);position:relative}nav{display:flex;flex-direction:row;justify-content:space-between;padding:.25em;position:fixed;top:0;width:100%;transition:background-color .4s ease-in;color:#fff;z-index:1}nav.scrolled{background-color:var(--navBack)}nav #nav-check{display:none}nav .nav-btn{display:none}nav h1{margin:0}nav a{text-decoration:none;color:#fff}nav ul{display:flex;flex-direction:row;gap:1em;margin:0;justify-content:flex-end;align-items:flex-end}nav ul li{list-style:none}nav ul li span{visibility:hidden}nav ul li .active span,nav ul li a:hover span{visibility:visible}header div{display:flex;flex-direction:column;justify-content:center;align-items:center;padding-top:10em}header div .btn{margin:2em 0}header div button{background:0 0;border:none;display:inline-block;text-align:center;text-decoration:none;font-size:2rem;cursor:pointer}i.fa-chevron-down{color:hsla(0,0%,67%,.58);font-size:3.75em;margin:1.5rem 0}div h1 span{visibility:visible;animation:caret 1s steps(1) infinite}@keyframes caret{50%{visibility:hidden}}section#about{margin-bottom:5rem}section#about div{padding:.1em 5em}section#curriculumVitae{background-color:var(--primaryDefault);color:#fff;padding:2em 0}section#curriculumVitae .cvGrid{display:flex;flex-direction:row;padding:0 1.5rem;flex-wrap:wrap}section#curriculumVitae .cvGrid>div{width:45%;display:flex;flex-direction:column;min-height:100%}section#curriculumVitae .cvGrid h2{text-align:center}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae #work{margin:0 auto 0 8rem}section#curriculumVitae .timeline:before{content:"";position:absolute;height:100%;border:4px var(--timelineItemBrdr) solid;right:194px;top:0}section#curriculumVitae .timeline:after{content:"";display:table;clear:both}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:0 1rem;width:50%;position:relative;background-color:var(--primaryHover)}.timelineItem:after,section#curriculumVitae .timelineItem:before{content:'';position:absolute}section#curriculumVitae .timelineItem:before{content:'';right:-20px;top:calc(50% - 5px);border-style:solid;border-color:var(--timelineItemBrdr) var(--timelineItemBrdr) transparent transparent;border-width:20px;transform:rotate(45deg)}section#curriculumVitae .timelineItem:nth-child(2n){margin-left:21em}section#curriculumVitae .timelineItem:nth-child(2n):before{right:auto;left:-20px;border-color:transparent transparent var(--timelineItemBrdr) var(--timelineItemBrdr)}section#curriculumVitae .timelineItem h3{font-weight:400}section#curriculumVitae .timelineItem span{color:#e5e5e5}section#projects{display:flex;flex-direction:row;padding:0 2.5rem;border-bottom:2px solid var(--mutedGrey)}section#projects .mainProj,section#projects .otherProj{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}section#projects .mainProj{border-right:2px solid var(--mutedGrey);padding:0 2.5em 5em 0}section#allProjects img,section#projects img{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;display:block;margin:1em auto}section#projects .mainProj img{width:100%;max-width:40rem}section#projects .mainProj .flexRow{display:flex;flex-direction:row;gap:4em}section#projects .mainProj .flexCol{display:flex;flex-direction:column;gap:2.5em}section#projects .otherProj>a{margin:5rem 0}section#projects .otherProj>div{display:flex;flex-direction:column;gap:2em}section#allProjects #otherProj .oProjItem,section#projects .otherProj>div .oProjItem{display:flex;justify-content:center;align-items:center;flex-direction:row;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:.75em 1em}section#projects .otherProj>div .oProjItem{margin:0 auto}section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:row-reverse}section#projects .otherProj .oProjItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .oProjItem .flexCol div:nth-child(2){display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .flexCol div:nth-child(2) .btn{padding:.25em .5em}section#allProjects{display:flex;justify-content:center;align-items:center;flex-direction:column;gap:5em}section#allProjects #mainProj{display:flex;flex-direction:column;justify-content:center;align-items:center;border:1px solid var(--grey);box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1.5em 2em;margin:3em 2.5rem 0;width:50%}section#allProjects #mainProj img{width:100%;max-width:30rem}section#allProjects #otherProj{display:flex;flex-direction:row;justify-content:space-between;align-items:stretch;flex-wrap:wrap;gap:2rem;border-top:2px solid var(--grey);padding:5em 2.5rem 0}section#allProjects #otherProj .oProjItem{flex-direction:column;width:30%;height:auto}section#allProjects #otherProj img{width:100%;max-width:20rem}section#contact{display:flex;flex-direction:row;padding:0 2.5em}div#findMe,div#sayHello{width:50%;display:flex;flex-direction:column;align-items:center;gap:1em}div#findMe .findMeContainer{display:flex;flex-direction:row;justify-content:space-around;align-items:center;gap:2em;width:100%;height:100%;margin:5em 0}div#findMe .findMeContainer .profile{-webkit-border-radius:50%;-moz-border-radius:50%;border-radius:50%}div#findMe .socialIcons{display:flex;flex-direction:row;justify-content:center;align-items:center;gap:2em}div#findMe .socialIcons div{display:flex;flex-direction:column;gap:1.5em}div#findMe .socialIcons div svg{width:2.5em;fill:var(--primaryDefault);font-size:2em}div#findMe .socialIcons div a:hover svg{fill:var(--primaryHover)}div#sayHello #contactForm{display:flex;flex-direction:column;justify-content:center;align-items:center}#contactForm .flName{display:flex;flex-direction:row;gap:1em;width:100%}div.message{background:var(--primaryDefault);color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em;margin-bottom:1em}div.message.error{background:var(--errorDefault)}div.message button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.message.hidden{opacity:0;visibility:hidden;height:0}div.message button:hover{text-shadow:-1px 2px var(--mutedBlack)}footer{background-color:var(--primaryDefault);margin-top:5em;padding:2em;display:flex;color:#fff}footer .spacer{width:100%;margin-right:auto}footer p{margin:auto;width:100%;text-align:center}footer .button{margin-left:auto;width:100%;text-align:center}footer .button button{border:5px solid #fff;background:0 0;font-size:3em;padding:.5rem 1rem;width:2em;color:#fff;-webkit-border-radius:.25em;-moz-border-radius:.25em;border-radius:.25em;cursor:pointer}@media screen and (max-width:90em){section#curriculumVitae .cvGrid{flex-direction:column;justify-content:center;align-items:center}section#curriculumVitae .cvGrid>div{width:100%}section#curriculumVitae .cvGrid>div:first-child{padding-bottom:2.5em;margin-bottom:2.5em;border-bottom:5px #fff solid}section#curriculumVitae .cvGrid h2{margin-left:5em}section#curriculumVitae .cvGrid .timeline{margin:0 auto}}@media screen and (max-width:75em){section#about,section#curriculumVitae h1{padding:0 1em}nav{display:block;height:50px;width:100%;background-color:var(--navBack);position:fixed;top:0;padding:0}nav a h1{margin-left:1ch}nav .nav-btn{display:inline-block;position:absolute;right:75px;top:0}nav ul{position:fixed;display:block;width:100%;background-color:#333;transition:all .4s ease-in;overflow-y:hidden;padding-left:0;margin-top:7px}nav ul li a{display:block;width:100%;transform:translateX(-30px);transition:all .4s ease-in;opacity:0}.nav-btn label{display:inline-block;cursor:pointer;width:60px;height:50px;position:fixed;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.5s ease-in;-moz-transition:.5s ease-in;-o-transition:.5s ease-in;transition:.5s ease-in}.nav-btn label span{display:block;position:absolute;height:5px;width:100%;background-color:#fff;opacity:1;right:0;top:20px;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);-webkit-transition:.25s ease-in;-moz-transition:.25s ease-in;-o-transition:.25s ease-in;transition:.25s ease-in}nav #nav-check:not(:checked)~ul{height:auto;max-height:0}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(1){top:8px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(2){top:23px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:not(:checked)~.nav-btn label span:nth-child(3){top:38px;-webkit-transform-origin:left center;-moz-transform-origin:left center;-o-transform-origin:left center;transform-origin:left center}nav #nav-check:checked~.nav-btn label,nav .nav-btn label:hover{background-color:rgba(-1,0,0,.3)}nav #nav-check:checked~ul{max-height:50vh;overflow-y:hidden}nav #nav-check:checked~ul li a{opacity:1;transform:translateX(0)}nav #nav-check:checked~ul li:nth-child(1) a{transition-delay:.15s}nav #nav-check:checked~ul li:nth-child(2) a{transition-delay:.25s}nav #nav-check:checked~ul li:nth-child(3) a{transition-delay:.35s}nav #nav-check:checked~ul li:nth-child(4) a{transition-delay:.45s}nav #nav-check:checked~ul li:nth-child(5) a{transition-delay:.55s}nav #nav-check:checked~.nav-btn label span:first-child{-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}nav #nav-check:checked~.nav-btn label span:nth-child(2){width:0;opacity:0}nav #nav-check:checked~.nav-btn label span:last-child{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}section#about div{padding:.1em 2.5em}section#curriculumVitae .cvGrid{padding:0}section#projects{flex-direction:column;justify-content:center;align-items:center}section#projects .mainProj{border-right:0;padding:0;width:100%;margin:0 5em}section#projects .mainProj img{padding:0 1em}section#projects .mainProj .flexRow{flex-direction:column;margin:0 2.5em}section#projects .mainProj .flexCol{flex-direction:row;justify-content:center;align-items:center}section#projects .otherProj{width:100%}section#projects .otherProj .btn{width:10em;text-align:center}section#projects .otherProj>div .oProjItem,section#projects .otherProj>div .oProjItem:nth-child(2){flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){justify-content:center;margin-left:0;margin-bottom:1em}section#projects .otherProj>a{margin-left:3em;margin-right:3em;text-align:center}section#allProjects #otherProj .oProjItem{width:45%}section#contact{flex-direction:column;justify-content:center;align-items:center}div#findMe,div#sayHello{width:100%}div#findMe .findMeContainer{flex-direction:column;justify-content:center}div#findMe .socialIcons{flex-direction:row}div#findMe .socialIcons div{flex-direction:row}}@media screen and (max-width:55em){section#curriculumVitae .cvGrid .timeline,section#curriculumVitae .cvGrid .timeline#work{margin:0 auto;width:100%}section#curriculumVitae .timeline:before{border:none}section#curriculumVitae .timelineItem,section#curriculumVitae .timelineItem:nth-child(2n){width:95%;padding:0;margin:0 auto}section#curriculumVitae .timelineItem:before{right:unset;left:unset;border:none}section#allProjects #mainProj{width:auto}section#allProjects #otherProj{flex-direction:column;align-items:center}section#allProjects #otherProj .oProjItem{width:auto}div#findMe .socialIcons{flex-direction:column}#contactForm .flName{flex-direction:column;gap:0;width:100%}}@media screen and (max-width:31em){section#about,section#curriculumVitae h1{padding:0 1em}header div h1{text-align:center;height:5.125rem}section#about div{padding:.1em 1em}section#projects .mainProj .flexCol{flex-direction:column}section#projects .oProjItem .flexCol div:nth-child(2){flex-direction:column;justify-content:center;align-items:center}} \ No newline at end of file diff --git a/dist/editor/css/main.css b/dist/editor/css/main.css index bb91664..8299759 100644 --- a/dist/editor/css/main.css +++ b/dist/editor/css/main.css @@ -1 +1 @@ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%),calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%,32%, 1);--mutedGrey:hsla(0, 0%, 78%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}form input[type=submit]{padding:1.1em 2em}a.btn:hover,form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btnPrimary,form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,form input[type=submit]:hover{background:var(--primaryHover)}a.btn:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl textarea:focus{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}form .formControl.passwordControl{display:block}form input[type=submit]{align-self:flex-start}form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:4px solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:.5em;padding:0 .5em}form .formControl textarea{padding:.5em}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}form .formControl .checkContainer input:checked~.checkmark:after{display:block}form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}h1{text-transform:none}body,html{height:100%}main.login{height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;background-image:radial-gradient(var(--primaryDefault),#597226)}div.container{flex-direction:column;justify-content:center;align-items:center;background-color:#fff;padding:2em 5em;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-transform:translateX(-150vw);-moz-transform:translateX(-150vw);-ms-transform:translateX(-150vw);-o-transform:translateX(-150vw);transform:translateX(-150vw);-webkit-transition:transform .4s ease-in-out;-moz-transition:transform .4s ease-in-out;-ms-transition:transform .4s ease-in-out;-o-transition:transform .4s ease-in-out;transition:transform .4s ease-in-out;overflow:hidden}div.container.shown{-webkit-transform:translateX(0);-moz-transform:translateX(0);-ms-transform:translateX(0);-o-transform:translateX(0);transform:translateX(0)}div.container form{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}div#login #password{font-family:Verdana,serif;letter-spacing:.125em}div#login input[type=submit]{margin:0}div.error,div.success{color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em}div.error{background:var(--errorDefault)}div.success{background-color:var(--primaryHover)}div.error button,div.success button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.error.hidden,div.success.hidden{opacity:0;visibility:hidden;height:0;margin:0;padding:0}div.error button:hover,div.success button:hover{text-shadow:-1px 2px var(--mutedBlack)}div.btnContainer{width:100%;display:flex;flex-direction:row;justify-content:space-between;align-items:center}div.btnContainer a:not(.btn){color:#000}nav{font-size:var(--headingFS)}nav.sideNav{height:100%;width:250px;z-index:1;position:fixed;top:0;left:0;background-color:var(--primaryHover);overflow-x:hidden;-webkit-transition:.5s;-moz-transition:.5s;-ms-transition:.5s;-o-transition:.5s;transition:.5s;padding-top:60px}nav.sideNav ul li{list-style:none}nav.sideNav a{padding:8px 8px 8px 0;text-decoration:none;color:#fff;display:block;-webkit-transition:.3s;-moz-transition:.3s;-ms-transition:.3s;-o-transition:.3s;transition:.3s}nav.sideNav .closeBtn{position:absolute;top:0;right:25px;margin-left:50px;font-size:var(--titleFS);display:none}nav.sideNav ul li span{visibility:hidden}nav.sideNav ul li a.active span,nav.sideNav ul li a:hover span{visibility:visible}nav.sideNav ul li.dropdown ul{transition:max-height ease-out .4s;max-height:0;overflow:hidden}nav.sideNav ul li.dropdown ul.active{transition:max-height ease-in .4s;max-height:15rem}nav.sideNav ul li.dropdown ul li{margin-left:-1rem}span#navOpen{font-size:var(--titleFS);cursor:pointer}main.editor{margin-left:250px}.title{display:flex;flex-direction:column;justify-content:center;align-items:center}#navOpen{visibility:hidden;padding:.25em 0 0 .25em;align-self:flex-start}textarea{resize:none}main.editor section{margin:0 2em}input[type=submit]{margin-top:2em}.delete,.edit{border:none;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;outline:0;background-color:var(--primaryDefault);color:#fff;cursor:pointer}.timelineHeader{font-weight:400}div.editorContainer,div.projectsGrid{display:flex;flex-direction:row;justify-content:center;align-items:flex-start;gap:2em;margin-bottom:.5em}div.editorContainer>*,div.projectsGrid>*{width:45%}main.editor section{display:none}section#addPost{display:block}div.modifyBtnContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;margin-bottom:.5em;width:100%}div.companyAreaContainer,div.dateContainer{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;gap:1em;margin-bottom:.5em}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae .timelineItem,section#projects .projItem{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1rem;position:relative}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);color:#fff;background-color:var(--primaryHover)}section#curriculumVitae .timelineItem.editing{color:#000;border:5px solid var(--primaryDefault);padding:.5em}section#curriculumVitae .timelineItem.editing{background-color:#fff}form div.gradeContainer.formControl{display:flex;flex-direction:row;justify-content:flex-start;align-items:center}section#curriculumVitae form.timelineItem:not(.editing) .delete,section#curriculumVitae form.timelineItem:not(.editing) .edit{color:var(--primaryHover);background-color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.dateContainer{display:none}section#curriculumVitae form.timelineItem.editing .timelineHeader{display:none}section#curriculumVitae form.timelineItem.editing div.gradeContainer.formControl{gap:1em;margin-bottom:.5em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText,section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl input,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input,section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{outline:0;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl>*,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl>*{color:#e5e5e5}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText{color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input{padding:0 .25em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) div.formControl .courseText{padding:0}section#curriculumVitae form.timelineItem:not(.editing) input[type=submit]{display:none}.courseText{resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer input{width:30%}section#projects #projList .projItem{display:flex;justify-content:center;align-items:center;flex-direction:column;margin:0 auto;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack)}section#projects #projList{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}section#projects #projList .projItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .projItem .linkContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .linkContainer .btn{padding:.25em .5em}section#projects #isMainProject{width:auto}section#projects form.projItem.editing div.linkContainer,section#projects form.projItem.editing img.displayedImage,section#projects form.projItem:not(.editing) div.formControl.gitContainer,section#projects form.projItem:not(.editing) div.formControl.imageContainer,section#projects form.projItem:not(.editing) div.formControl.isMainProject,section#projects form.projItem:not(.editing) div.formControl.viewProjContainer,section#projects form.projItem:not(.editing) input[type=submit]{display:none}section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{font-size:1.17em;font-weight:700}section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{color:#000}section#addPost form{margin:auto 4rem}form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar{border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom:2px solid var(--mutedGrey);box-shadow:0 3px 4px var(--mutedBlack)}form .formControl .ck.ck-editor__main .ck-content{border-top-right-radius:0;border-top-left-radius:0;border-top:inherit}form .formControl .ck-editor__editable{min-height:400px}@media only screen and (max-width:75em){nav.sideNav .closeBtn{display:block}} \ No newline at end of file +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}:root{--mainHue:80;--mainSat:60%;--mainLight:50%;--primaryDefault:hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1);--primaryHover:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1);--timelineItemBrdr:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1);--errorDefault:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1);--errorHover:hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1);--grey:hsla(0, 0%, 39%, 1);--notAvailableDefault:hsla(0, 0%, 39%, 1);--notAvailableHover:hsla(0, 0%, 32%, 1);--mutedGrey:hsla(0, 0%, 78%, 1);--mutedBlack:hsla(0, 0%, 0%, 0.25);--mutedGreen:hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);--navBack:hsla(0, 0%, 30%, 1);--titleFS:2.25rem;--generalFS:1.125rem;--headingFS:1.5rem}*{box-sizing:border-box}html{scroll-behavior:smooth}body{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--generalFS);line-height:1.625rem}a:visited{color:inherit}h1,nav{font-family:Share Tech Mono,monospace;font-style:normal;font-weight:400;font-size:var(--titleFS);line-height:2.5625rem;text-transform:lowercase}h2{font-family:Noto Sans KR,sans-serif;font-style:normal;font-weight:500;font-size:var(--headingFS);line-height:2.1875rem}a.btn,form input[type=submit]{text-decoration:none;display:inline-flex;padding:1em 2em;border-radius:.625em;border:.3215em solid var(--primaryDefault);color:#fff;text-align:center;align-items:center;max-height:4em}form input[type=submit]{padding:1.1em 2em}a.btn:hover,form input[type=submit]:hover{border:.3215em solid var(--primaryHover)}a.btnPrimary,form input[type=submit]{background-color:var(--primaryDefault);cursor:pointer}a.btnOutline{background:#fff;color:var(--primaryDefault)}a.btnPrimary[disabled]{pointer-events:none;background:var(--notAvailableDefault);border:.3215em solid var(--notAvailableDefault)}a.btnPrimary[disabled]:hover{background:var(--notAvailableHover);border:.3215em solid var(--notAvailableHover)}a.btnPrimary:hover,form input[type=submit]:hover{background:var(--primaryHover)}a.btn:active,form input[type=submit]:active{padding:.8rem 1.8rem}.boxShadowOut:hover{box-shadow:0 6px 4px 0 var(--mutedBlack)}.boxShadowIn:active{box-shadow:inset 0 6px 4px 0 var(--mutedBlack)}.textShadow:hover{text-shadow:0 6px 4px var(--mutedBlack)}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl textarea:focus{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl{width:100%;display:flex;flex-direction:column;justify-content:flex-start}form .formControl.passwordControl{display:block}form input[type=submit]{align-self:flex-start}form .formControl .ck.ck-editor__main .ck-content,form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,form .formControl input:not([type=submit]),form .formControl textarea{width:100%;border:4px solid var(--primaryDefault);background:0 0;outline:0;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:.5em;padding:0 .5em}form .formControl textarea{padding:.5em}form .formControl input:not([type=submit]).invalid:invalid,form .formControl textarea.invalid:invalid{border:4px solid var(--errorDefault)}form .formControl input:not([type=submit]).invalid:invalid:focus,form .formControl textarea.invalid:invalid:focus{border:4px solid var(--errorHover);box-shadow:0 4px 2px 0 var(--mutedBlack)}form .formControl input:not([type=submit]):focus,form .formControl input:not([type=submit]):hover,form .formControl textarea:focus,form .formControl textarea:hover{border:4px solid var(--primaryHover)}form .formControl input:not([type=submit]){height:3em}form .formControl i.fa-eye,form .formControl i.fa-eye-slash{margin-left:-40px;cursor:pointer;color:var(--primaryDefault)}form .formControl input:not([type=submit]):focus+i.fa-eye,form .formControl input:not([type=submit]):focus+i.fa-eye-slash{color:var(--primaryHover)}form .formControl .checkContainer{display:block;position:relative;margin-bottom:1.25em;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}form .formControl .checkContainer input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}form .formControl .checkContainer .checkmark{position:absolute;top:1.25em;left:0;height:25px;width:25px;background-color:var(--mutedGrey)}form .formControl .checkContainer:hover input~.checkmark{background-color:var(--grey)}form .formControl .checkContainer input:checked~.checkmark{background-color:var(--primaryDefault)}form .formControl .checkContainer input:checked:hover~.checkmark{background-color:var(--primaryHover)}form .formControl .checkContainer .checkmark:after{content:"";position:absolute;display:none}form .formControl .checkContainer input:checked~.checkmark:after{display:block}form .formControl .checkContainer .checkmark:after{left:9px;top:5px;width:5px;height:10px;border:solid #fff;border-width:0 3px 3px 0;-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}form .formControl input[type=file]{padding:0;cursor:pointer}form .formControl input[type=file]::file-selector-button{background-color:var(--primaryDefault);color:#fff;border:0;border-right:5px solid var(--mutedBlack);padding:15px;margin-right:20px;-webkit-transition:all .5s;-moz-transition:all .5s;-ms-transition:all .5s;-o-transition:all .5s;transition:all .5s}form .formControl input[type=file]:hover::file-selector-button{background-color:var(--primaryHover)}section#about,section#curriculumVitae h1{padding:0 5rem}h1{text-transform:none}body,html{height:100%}main.login{height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;background-image:radial-gradient(var(--primaryDefault),#597226)}div.container{flex-direction:column;justify-content:center;align-items:center;background-color:#fff;padding:2em 5em;-webkit-border-radius:1em;-moz-border-radius:1em;border-radius:1em;box-shadow:0 6px 4px 0 var(--mutedBlack);-webkit-transform:translateX(-150vw);-moz-transform:translateX(-150vw);-ms-transform:translateX(-150vw);-o-transform:translateX(-150vw);transform:translateX(-150vw);-webkit-transition:transform .4s ease-in-out;-moz-transition:transform .4s ease-in-out;-ms-transition:transform .4s ease-in-out;-o-transition:transform .4s ease-in-out;transition:transform .4s ease-in-out;overflow:hidden}div.container.shown{-webkit-transform:translateX(0);-moz-transform:translateX(0);-ms-transform:translateX(0);-o-transform:translateX(0);transform:translateX(0)}div.container form{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}div#login #password{font-family:Verdana,serif;letter-spacing:.125em}div#login input[type=submit]{margin:0}div.error,div.success{color:#fff;padding:.5em .8em;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;display:flex;justify-content:center;align-items:center;align-self:flex-start;flex-direction:row-reverse;position:relative;height:75px;visibility:visible;overflow:hidden;-webkit-transition:all .5s ease-in-out;-moz-transition:all .5s ease-in-out;-ms-transition:all .5s ease-in-out;-o-transition:all .5s ease-in-out;transition:all .5s ease-in-out;opacity:1;margin-top:1em}div.error{background:var(--errorDefault)}div.success{background-color:var(--primaryHover)}div.error button,div.success button{border:none;background:0 0;outline:0;cursor:pointer;color:#fff;font-size:1.25rem;margin-top:-5px;position:absolute;transform:translate(0,0);transform-origin:0 0;right:10px;top:10px}div.error.hidden,div.success.hidden{opacity:0;visibility:hidden;height:0;margin:0;padding:0}div.error button:hover,div.success button:hover{text-shadow:-1px 2px var(--mutedBlack)}div.btnContainer{width:100%;display:flex;flex-direction:row;justify-content:space-between;align-items:center}div.btnContainer a:not(.btn){color:#000}nav{font-size:var(--headingFS)}nav.sideNav{height:100%;width:250px;z-index:1;position:fixed;top:0;left:0;background-color:var(--primaryHover);overflow-x:hidden;-webkit-transition:.5s;-moz-transition:.5s;-ms-transition:.5s;-o-transition:.5s;transition:.5s;padding-top:60px}nav.sideNav ul li{list-style:none}nav.sideNav a{padding:8px 8px 8px 0;text-decoration:none;color:#fff;display:block;-webkit-transition:.3s;-moz-transition:.3s;-ms-transition:.3s;-o-transition:.3s;transition:.3s}nav.sideNav .closeBtn{position:absolute;top:0;right:25px;margin-left:50px;font-size:var(--titleFS);display:none}nav.sideNav ul li span{visibility:hidden}nav.sideNav ul li a.active span,nav.sideNav ul li a:hover span{visibility:visible}nav.sideNav ul li.dropdown ul{transition:max-height ease-out .4s;max-height:0;overflow:hidden}nav.sideNav ul li.dropdown ul.active{transition:max-height ease-in .4s;max-height:15rem}nav.sideNav ul li.dropdown ul li{margin-left:-1rem}span#navOpen{font-size:var(--titleFS);cursor:pointer}main.editor{margin-left:250px}.title{display:flex;flex-direction:column;justify-content:center;align-items:center}#navOpen{visibility:hidden;padding:.25em 0 0 .25em;align-self:flex-start}textarea{resize:none}main.editor section{margin:0 2em}input[type=submit]{margin-top:2em}.delete,.edit{border:none;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;outline:0;background-color:var(--primaryDefault);color:#fff;cursor:pointer}.timelineHeader{font-weight:400}div.editorContainer,div.projectsGrid{display:flex;flex-direction:row;justify-content:center;align-items:flex-start;gap:2em;margin-bottom:.5em}div.editorContainer>*,div.projectsGrid>*{width:45%}main.editor section{display:none;flex-direction:column}section#editPost{display:flex}div.modifyBtnContainer{display:flex;flex-direction:row;justify-content:space-between;align-items:center;margin-bottom:.5em;width:100%}div.companyAreaContainer,div.dateContainer{display:flex;flex-direction:row;justify-content:flex-start;align-items:center;gap:1em;margin-bottom:.5em}section#curriculumVitae .timeline{position:relative;max-width:30em;gap:1em;display:flex;flex-direction:column;height:100%}section#curriculumVitae .timelineItem,section#projects .projItem{-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;padding:1rem;position:relative}section#curriculumVitae .timelineItem{border:2px solid var(--timelineItemBrdr);color:#fff;background-color:var(--primaryHover)}section#curriculumVitae .timelineItem.editing{color:#000;border:5px solid var(--primaryDefault);padding:.5em}section#curriculumVitae .timelineItem.editing{background-color:#fff}form div.gradeContainer.formControl{display:flex;flex-direction:row;justify-content:flex-start;align-items:center}section#curriculumVitae form.timelineItem:not(.editing) .delete,section#curriculumVitae form.timelineItem:not(.editing) .edit{color:var(--primaryHover);background-color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.dateContainer{display:none}section#curriculumVitae form.timelineItem.editing .timelineHeader{display:none}section#curriculumVitae form.timelineItem.editing div.gradeContainer.formControl{gap:1em;margin-bottom:.5em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText,section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl input,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input,section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{outline:0;border:none;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer.formControl>*,section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl>*{color:#e5e5e5}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) .formControl .jobTitleText{color:#fff}section#curriculumVitae form.timelineItem:not(.editing) div.gradeContainer.formControl input{padding:0 .25em}section#curriculumVitae form.timelineItem:not(.editing) .formControl .courseText,section#curriculumVitae form.timelineItem:not(.editing) div.formControl .courseText{padding:0}section#curriculumVitae form.timelineItem:not(.editing) input[type=submit]{display:none}.courseText{resize:none}section#curriculumVitae form.timelineItem:not(.editing) div.companyAreaContainer input{width:30%}section#projects #projList .projItem{display:flex;justify-content:center;align-items:center;flex-direction:column;margin:0 auto;width:90%;border:1px solid var(--grey);gap:1em;box-shadow:0 6px 4px 0 var(--mutedBlack)}section#projects #projList{display:flex;flex-direction:column;justify-content:center;align-items:center;gap:1em}section#projects #projList .projItem img{max-width:15rem;width:100%;padding:0 1em}section#projects .projItem .linkContainer{display:flex;flex-direction:row;justify-content:flex-start;gap:3em;margin-left:2em}section#projects .linkContainer .btn{padding:.25em .5em}section#projects #isMainProject{width:auto}section#projects form.projItem.editing div.linkContainer,section#projects form.projItem.editing img.displayedImage,section#projects form.projItem:not(.editing) div.formControl.gitContainer,section#projects form.projItem:not(.editing) div.formControl.imageContainer,section#projects form.projItem:not(.editing) div.formControl.isMainProject,section#projects form.projItem:not(.editing) div.formControl.viewProjContainer,section#projects form.projItem:not(.editing) input[type=submit]{display:none}section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{font-size:1.17em;font-weight:700}section#projects form.projItem:not(.editing) div.formControl.infoContainer textarea,section#projects form.projItem:not(.editing) div.formControl.projectTitleContainer input{color:#000}section#addPost form,section#editPost form{margin:auto 4rem}form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar{border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom:2px solid var(--mutedGrey);box-shadow:0 3px 4px var(--mutedBlack)}form .formControl .ck.ck-editor__main .ck-content{border-top-right-radius:0;border-top-left-radius:0;border-top:inherit}form .formControl .ck-editor__editable{min-height:400px}section#editPost{justify-content:center}section#editPost h2{align-self:flex-start}section#editPost table{border-collapse:collapse;border-style:hidden;align-self:center;margin-bottom:5em}section#editPost table td,th{border:1px solid var(--mutedGrey);text-align:left;padding:8px;min-width:10rem}section#editPost form{margin-bottom:2em}@media only screen and (max-width:75em){nav.sideNav .closeBtn{display:block}} \ No newline at end of file diff --git a/dist/editor/editor.html b/dist/editor/editor.html index 00eca0c..707026f 100644 --- a/dist/editor/editor.html +++ b/dist/editor/editor.html @@ -1 +1 @@ -Editor

Editor

curriculum vitae

Education

Work

projects

add post

\ No newline at end of file +Editor

Editor

curriculum vitae

Education

Work

projects

add post

edit post

TitleDate CreatedDate ModifiedAction
\ No newline at end of file diff --git a/dist/editor/js/editor.js b/dist/editor/js/editor.js index fd54661..eff59c3 100644 --- a/dist/editor/js/editor.js +++ b/dist/editor/js/editor.js @@ -1 +1 @@ -let dateOptions={month:"short",year:"numeric"},textareaLoaded=!1,editor=null;function goToPage(e){document.querySelectorAll(".editor section").forEach((t=>{t.style.display="none",t.id===e&&(t.style.display="block")}))}function addActiveClass(e){document.querySelectorAll("nav.sideNav ul li a").forEach((t=>{t.classList.remove("active"),t.id===e&&t.classList.add("active")}))}function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function showSuccessMessage(e,t){document.querySelector(`#${t}Success`).classList.remove("hidden"),document.querySelector(`#${t}Success div`).innerText=e}function editCVItem(e){if(textareaLoaded=!1,document.querySelector(`#timelineItem${e}`).classList.toggle("editing"),e.includes("e"))return document.querySelector(`#grade${e}`).toggleAttribute("disabled"),void document.querySelector(`#course${e}`).toggleAttribute("disabled");document.querySelector(`#companyName${e}`).toggleAttribute("disabled"),document.querySelector(`#area${e}`).toggleAttribute("disabled"),document.querySelector(`#jobTitle${e}`).toggleAttribute("disabled")}function addEduData(e,t,o,r,n,a=!1){let d=e+"e",i=document.createElement("form");i.id="timelineItem"+d,i.classList.add("timelineItem"),i.onsubmit=t=>updateEduItem(e,t),i.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${new Date(o).toLocaleString("en-gb",dateOptions)}

\n
\n \n \n
\n
\n \n
\n \n \n \n `,a?document.querySelector("#edu").prepend(i):document.getElementById("edu").appendChild(i)}function addWorkData(e,t,o,r,n,a,d=!1){let i=e+"w",c=document.createElement("form");c.id="timelineItem"+i,c.classList.add("timelineItem"),c.onsubmit=t=>updateWorkItem(e,t),c.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${"Present"===o?"Present":new Date(o).toLocaleString("en-gb",dateOptions)}

\n
\n \n -\n \n
\n
\n \n
\n \n \n \n\t`,d?document.querySelector("#work").prepend(c):document.getElementById("work").appendChild(c)}function updateEduItem(e,t){t.preventDefault();let o={};o.dateFrom=document.querySelector(`#dateFrom${e}e`).value,o.dateTo=document.querySelector(`#dateTo${e}e`).value,o.grade=document.querySelector(`#grade${e}e`).value,o.course=document.querySelector(`#course${e}e`).value,fetch("/api/timelineData/edu/"+e,{method:"PATCH",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}e`).innerHTML=new Date(document.querySelector(`#dateFrom${e}e`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}e`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}e`).classList.toggle("editing"),document.querySelector(`#grade${e}e`).setAttribute("disabled",""),void document.querySelector(`#course${e}e`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#eduError${e}e`).classList.remove("hidden"),document.querySelector(`#eduError${e}e div`).innerHTML=t.error})):window.location.href="./"}))}function updateWorkItem(e,t){t.preventDefault();let o={};o.dateFrom=document.querySelector(`#dateFrom${e}w`).value,o.dateTo=document.querySelector(`#dateTo${e}w`).value,o.companyName=document.querySelector(`#companyName${e}w`).value,o.area=document.querySelector(`#area${e}w`).value,o.title=document.querySelector(`#jobTitle${e}w`).value,fetch("/api/timelineData/work/"+e,{method:"PATCH",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}w`).innerHTML=new Date(document.querySelector(`#dateFrom${e}w`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}w`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}w`).classList.toggle("editing"),document.querySelector(`#companyName${e}w`).setAttribute("disabled",""),document.querySelector(`#area${e}w`).setAttribute("disabled",""),void document.querySelector(`#jobTitle${e}w`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#workError${e}w`).classList.remove("hidden"),document.querySelector(`#workError${e}w div`).innerHTML=t.error})):window.location.href="./"}))}function deleteEduItem(e){fetch("/api/timelineData/edu/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}e`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function deleteWorkItem(e){fetch("/api/timelineData/work/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}w`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function updateProjectItem(e,t){t.preventDefault();let o={};o.title=document.querySelector(`#title${e}`).value,o.isMainProject=document.querySelector(`#isMainProject${e}`).checked?"true":"false",o.information=document.querySelector(`#info${e}`).value,o.projectLink=document.querySelector(`#viewProj${e}`).value,o.gitLink=document.querySelector(`#git${e}`).value;let r=new FormData;r.append("img",document.querySelector(`#img${e}`).files[0]),fetch("/api/projectData/"+e,{method:"PATCH",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return"undefined"===r.get("img")?("true"===o.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),void document.querySelector(`#info${e}`).setAttribute("disabled","")):(console.log("updating image"),fetch("/api/projectImage/"+e,{method:"POST",body:r,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==t.status?t.json().then((t=>{document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=t.error})):window.location.href="./"})).then((t=>t.json().then((o=>{if(t.ok)return"true"===o.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),document.querySelector(`#info${e}`).setAttribute("disabled",""),void(document.querySelector(`#projectImage${e}`).src=o.imgLocation);401!==t.status?(document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=o.error):window.location.href="./"}))))}function editProjectItem(e){document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).removeAttribute("disabled"),document.querySelector(`#info${e}`).removeAttribute("disabled")}function deleteProjectItem(e){fetch("/api/projectData/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#projectItem${e}`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function addProject(e,t,o,r,n,a,d){let i=document.createElement("form");if(i.id="projectItem"+e,i.classList.add("projItem"),i.onsubmit=t=>updateProjectItem(e,t),i.innerHTML=`\n
\n \n \n
\n image preivew of the project\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n \n \n \n `,"true"===t)return document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),void document.querySelector("#projList").prepend(i);document.querySelector("#projList").appendChild(i)}document.addEventListener("DOMContentLoaded",(()=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok||(window.location.href="./")})),document.querySelector("#dateFromE").max=(new Date).toISOString().split("T")[0],document.querySelector("#dateFromW").max=(new Date).toISOString().split("T")[0],fetch("/api/timelineData/edu").then((e=>{e.json().then((t=>{if(e.ok)for(let e=0;e{e.json().then((t=>{if(e.ok)for(let e=0;e{e.json().then((t=>{e.ok?t.forEach((e=>{addProject(e.ID,1===e.isMainProject?"true":"false",""===e.imgLocation?"../imgs/placeholder.png":e.imgLocation,e.title,e.information,e.projectLink,e.gitLink)})):document.querySelector("#projList").innerHTML="No project data found"}))})),ClassicEditor.create(document.querySelector("#CKEditor"),{placeholder:"Write something amazing...",simpleUpload:{uploadUrl:"/api/blog/uploadPostImage",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}}).then((e=>{editor=e})).catch((e=>{console.error("Oops, something went wrong!"),console.error("Please, report the following error on https://github.com/ckeditor/ckeditor5/issues with the build id and the error stack trace:"),console.warn("Build id: 1eo8ioyje2om-vgar4aghypdm"),console.error(e)}))})),document.querySelector("body").addEventListener("click",(()=>{if(textareaLoaded)return;const e=document.querySelectorAll("main.editor textarea");console.log(e);for(let t=0;t{e.target.style.height="0",e.target.style.height=e.target.scrollHeight+"px"};textareaLoaded=!0})),document.querySelector("#navOpen").addEventListener("click",(e=>{document.querySelector("nav.sideNav").style.removeProperty("width"),document.querySelector("main.editor").style.removeProperty("margin-left"),e.target.style.removeProperty("visibility")})),document.querySelector("#navClose").addEventListener("click",(()=>{document.querySelector("nav.sideNav").style.width="0",document.querySelector("main.editor").style.marginLeft="0",document.querySelector("#navOpen").style.visibility="visible"})),document.querySelector("#addEdu").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromE").value),t.append("dateTo",document.querySelector("#dateToE").value),t.append("grade",document.querySelector("#grade").value),t.append("course",document.querySelector("#courseTitle").value),fetch("/api/timelineData/edu",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((o=>{if(e.ok)return addEduData(o.ID,t.get("dateFrom"),t.get("dateTo"),t.get("grade"),t.get("course"),!0),void document.querySelector("#addEdu").reset();401!==e.status?showErrorMessage(o.error,"edu"):window.location.href="./"}))))})),document.querySelector("#addWork").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromW").value),t.append("dateTo",document.querySelector("#dateToW").value),t.append("companyName",document.querySelector("#company").value),t.append("area",document.querySelector("#area").value),t.append("title",document.querySelector("#jobTitle").value),fetch("/api/timelineData/work",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((o=>{if(e.ok){let e=""===t.get("dateTo")?"Present":t.get("dateTo ");return addWorkData(o.ID,t.get("dateFrom"),e,t.get("companyName"),t.get("area"),t.get("title"),!0),void document.querySelector("#addWork").reset()}401!==e.status?showErrorMessage(o.error,"work"):window.location.href="./"}))))})),document.querySelector("#addProj").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("title",document.querySelector("#projTitle").value),t.append("isMainProject",document.querySelector("#isMainProject").checked?"true":"false"),t.append("information",document.querySelector("#projInfo").value),t.append("projectLink",document.querySelector("#projLink").value?document.querySelector("#projLink").value:"N/A"),t.append("gitLink",document.querySelector("#gitLink").value);let o=new FormData;o.append("img",document.querySelector("#projImg").files[0]);let r=0;fetch("/api/projectData",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((n=>{if(e.ok)return"undefined"===o.get("img")?(addProject(n.ID,t.get("isMainProject"),"../imgs/placeholder.png",t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset()):(r=n.ID,fetch("/api/projectImage/"+n.ID,{method:"POST",body:o,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==e.status?showErrorMessage(n.error,"proj"):window.location.href="./"})).then((e=>e.json().then((o=>{if(e.ok)return addProject(r,t.get("isMainProject"),o.imgLocation,t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset();401!==e.status?showErrorMessage(o.error,"proj"):window.location.href="./"}))))))})),document.querySelector("#addPostForm").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("title",document.querySelector("#postTitle").value),t.append("body",editor.getData()),t.append("dateCreated",(new Date).toISOString().slice(0,19).replace("T"," ")),t.append("featured",document.querySelector("#isFeatured").checked?"true":"false"),t.append("categories",document.querySelector("#postCategories").value),t.append("headerImg",document.querySelector("#headerImg").files[0]),fetch("/api/blog/post",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>{if(e.ok)return document.querySelector("#addPostForm").reset(),editor.setData(""),void showSuccessMessage("Post added successfully","addPost");401!==e.status?e.json().then((e=>showErrorMessage(e.error,"addPost"))):window.location.href="./"}))})),document.querySelector("#goToCV").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToCV"),goToPage("curriculumVitae")})),document.querySelector("#goToProjects").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToProjects"),goToPage("projects")})),document.querySelector("#blog").addEventListener("click",(()=>{document.querySelector("nav.sideNav ul li.dropdown ul").classList.toggle("active"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-down"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-right")})),document.querySelector("#goToAddPost").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToAddPost"),goToPage("addPost"),document.querySelector("#blog").classList.add("active")})),document.querySelector("#logout").addEventListener("click",(()=>{fetch("/api/user/logout").then((e=>{e.ok&&window.location.reload()}))})),document.querySelector("#eduError .close").addEventListener("click",(()=>document.querySelector("#eduError").classList.toggle("hidden"))),document.querySelector("#workError .close").addEventListener("click",(()=>document.querySelector("#workError").classList.toggle("hidden"))),document.querySelector("#projError .close").addEventListener("click",(()=>document.querySelector("#projError").classList.toggle("hidden"))),document.querySelector("#addPostError .close").addEventListener("click",(()=>document.querySelector("#addPostError").classList.toggle("hidden"))),document.querySelector("#addPostSuccess .close").addEventListener("click",(()=>document.querySelector("#addPostSuccess").classList.toggle("hidden"))); \ No newline at end of file +let dateOptions={month:"short",year:"numeric"},textareaLoaded=!1,editors={},posts=null;function goToPage(e){document.querySelectorAll(".editor section").forEach((t=>{t.style.display="none",t.id===e&&(t.style.display="flex")}))}function addActiveClass(e){document.querySelectorAll("nav.sideNav ul li a").forEach((t=>{t.classList.remove("active"),t.id===e&&t.classList.add("active")}))}function editProjectItem(e){document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}proj`).toggleAttribute("disabled"),document.querySelector(`#info${e}proj`).toggleAttribute("disabled")}function createEditors(...e){e.forEach((e=>{ClassicEditor.create(document.querySelector(`#${e}`),{placeholder:"Write something amazing...",simpleUpload:{uploadUrl:"/api/blog/uploadPostImage",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}}).then((t=>{editors[e]=t})).catch((e=>{console.error("Oops, something went wrong!"),console.error("Please, report the following error on https://github.com/ckeditor/ckeditor5/issues with the build id and the error stack trace:"),console.warn("Build id: 1eo8ioyje2om-vgar4aghypdm"),console.error(e)}))}))}function editPostItem(e){posts.forEach((t=>{t.ID===e&&(document.querySelector("#editPostTitle").value=t.title,document.querySelector("#editIsFeatured").checked=1===t.featured,document.querySelector("#editPostCategories").value=t.categories,editors.CKEditorEditPost.setData(t.body),document.querySelector("#editPostForm input[type='submit']").id=e)}))}function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function showSuccessMessage(e,t){document.querySelector(`#${t}Success`).classList.remove("hidden"),document.querySelector(`#${t}Success div`).innerText=e}function editCVItem(e){if(textareaLoaded=!1,document.querySelector(`#timelineItem${e}`).classList.toggle("editing"),e.includes("e"))return document.querySelector(`#grade${e}`).toggleAttribute("disabled"),void document.querySelector(`#course${e}`).toggleAttribute("disabled");document.querySelector(`#companyName${e}`).toggleAttribute("disabled"),document.querySelector(`#area${e}`).toggleAttribute("disabled"),document.querySelector(`#jobTitle${e}`).toggleAttribute("disabled")}function addEduData(e,t,o,r,n,d=!1){let a=e+"e",i=document.createElement("form");i.id="timelineItem"+a,i.classList.add("timelineItem"),i.onsubmit=t=>updateEduItem(e,t),i.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${new Date(o).toLocaleString("en-gb",dateOptions)}

\n
\n \n \n
\n
\n \n
\n \n \n \n `,d?document.querySelector("#edu").prepend(i):document.getElementById("edu").appendChild(i)}function addWorkData(e,t,o,r,n,d,a=!1){let i=e+"w",c=document.createElement("form");c.id="timelineItem"+i,c.classList.add("timelineItem"),c.onsubmit=t=>updateWorkItem(e,t),c.innerHTML=`\n
\n \n \n
\n
\n \n -\n \n
\n

${new Date(t).toLocaleString("en-gb",dateOptions)} - ${"Present"===o?"Present":new Date(o).toLocaleString("en-gb",dateOptions)}

\n
\n \n -\n \n
\n
\n \n
\n \n \n \n\t`,a?document.querySelector("#work").prepend(c):document.getElementById("work").appendChild(c)}function updateEduItem(e,t){t.preventDefault();let o={};o.dateFrom=document.querySelector(`#dateFrom${e}e`).value,o.dateTo=document.querySelector(`#dateTo${e}e`).value,o.grade=document.querySelector(`#grade${e}e`).value,o.course=document.querySelector(`#course${e}e`).value,fetch("/api/timelineData/edu/"+e,{method:"PATCH",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}e`).innerHTML=new Date(document.querySelector(`#dateFrom${e}e`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}e`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}e`).classList.toggle("editing"),document.querySelector(`#grade${e}e`).setAttribute("disabled",""),void document.querySelector(`#course${e}e`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#eduError${e}e`).classList.remove("hidden"),document.querySelector(`#eduError${e}e div`).innerHTML=t.error})):window.location.href="./"}))}function updateWorkItem(e,t){t.preventDefault();let o={};o.dateFrom=document.querySelector(`#dateFrom${e}w`).value,o.dateTo=document.querySelector(`#dateTo${e}w`).value,o.companyName=document.querySelector(`#companyName${e}w`).value,o.area=document.querySelector(`#area${e}w`).value,o.title=document.querySelector(`#jobTitle${e}w`).value,fetch("/api/timelineData/work/"+e,{method:"PATCH",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return document.querySelector(`#timelineHeader${e}w`).innerHTML=new Date(document.querySelector(`#dateFrom${e}w`).value).toLocaleString("en-gb",dateOptions)+" - "+new Date(document.querySelector(`#dateTo${e}w`).value).toLocaleString("en-gb",dateOptions),document.querySelector(`#timelineItem${e}w`).classList.toggle("editing"),document.querySelector(`#companyName${e}w`).setAttribute("disabled",""),document.querySelector(`#area${e}w`).setAttribute("disabled",""),void document.querySelector(`#jobTitle${e}w`).setAttribute("disabled","");401!==t.status?t.json().then((t=>{document.querySelector(`#workError${e}w`).classList.remove("hidden"),document.querySelector(`#workError${e}w div`).innerHTML=t.error})):window.location.href="./"}))}function deleteEduItem(e){fetch("/api/timelineData/edu/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}e`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function deleteWorkItem(e){fetch("/api/timelineData/work/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#timelineItem${e}w`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function updateProjectItem(e,t){t.preventDefault();let o={};o.title=document.querySelector(`#title${e}`).value,o.isMainProject=document.querySelector(`#isMainProject${e}`).checked?"true":"false",o.information=document.querySelector(`#info${e}`).value,o.projectLink=document.querySelector(`#viewProj${e}`).value,o.gitLink=document.querySelector(`#git${e}`).value;let r=new FormData;r.append("img",document.querySelector(`#img${e}`).files[0]),fetch("/api/projectData/"+e,{method:"PATCH",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{if(t.ok)return"undefined"===r.get("img")?("true"===o.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),void document.querySelector(`#info${e}`).setAttribute("disabled","")):(console.log("updating image"),fetch("/api/projectImage/"+e,{method:"POST",body:r,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==t.status?t.json().then((t=>{document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=t.error})):window.location.href="./"})).then((t=>t.json().then((o=>{if(t.ok)return"true"===o.isMainProject&&(document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),document.querySelector(`#isMainProject${e}`).checked=!0,document.querySelector("#projList").prepend(document.querySelector(`#projectItem${e}`))),document.querySelector(`#projectItem${e}`).classList.toggle("editing"),document.querySelector(`#title${e}`).setAttribute("disabled",""),document.querySelector(`#info${e}`).setAttribute("disabled",""),void(document.querySelector(`#projectImage${e}`).src=o.imgLocation);401!==t.status?(document.querySelector(`#projError${e}`).classList.remove("hidden"),document.querySelector(`#projError${e} div`).innerHTML=o.error):window.location.href="./"}))))}function deleteProjectItem(e){fetch("/api/projectData/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#projectItem${e}`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function addProject(e,t,o,r,n,d,a){let i=document.createElement("form"),c=e+"proj";if(i.id="projectItem"+e,i.classList.add("projItem"),i.onsubmit=e=>updateProjectItem(c,e),i.innerHTML=`\n
\n \n \n
\n image preivew of the project\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n
\n \n
\n \n \n \n `,"true"===t)return document.querySelectorAll(".isMainProject input").forEach((e=>e.checked=!1)),void document.querySelector("#projList").prepend(i);document.querySelector("#projList").appendChild(i)}function deletePostItem(e){fetch("/api/blog/post/"+e,{method:"DELETE",headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((t=>{t.ok?document.querySelector(`#postInfo${e}`).remove():401!==t.status?t.json().then((e=>alert(e.error))):window.location.href="./"}))}function addPostInfo(e,t,o,r){let n=document.createElement("tr"),d=e+"post";n.id="postInfo"+e,n.innerHTML=`\n \n ${t}\n \n \n ${new Date(o).toLocaleDateString()}\n \n \n ${new Date(r).toLocaleDateString()}\n \n \n \n \n \n `,document.querySelector("#editPost table tbody").appendChild(n)}document.addEventListener("DOMContentLoaded",(()=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok||(window.location.href="./")})),document.querySelector("#dateFromE").max=(new Date).toISOString().split("T")[0],document.querySelector("#dateFromW").max=(new Date).toISOString().split("T")[0],fetch("/api/timelineData/edu").then((e=>e.json().then((t=>{if(e.ok)for(let e=0;ee.json().then((t=>{if(e.ok)for(let e=0;ee.json().then((t=>{e.ok?t.forEach((e=>{addProject(e.ID,1===e.isMainProject?"true":"false",""===e.imgLocation?"../imgs/placeholder.png":e.imgLocation,e.title,e.information,e.projectLink,e.gitLink)})):document.querySelector("#projList").innerHTML="No project data found"})))),fetch("/api/blog/post").then((e=>e.json().then((t=>{e.ok&&(posts=t,t.forEach((e=>{addPostInfo(e.ID,e.title,e.dateCreated,e.dateModified)})))})))),createEditors("CKEditorAddPost","CKEditorEditPost")})),document.querySelector("body").addEventListener("click",(()=>{if(textareaLoaded)return;const e=document.querySelectorAll("main.editor textarea");console.log(e);for(let t=0;t{e.target.style.height="0",e.target.style.height=e.target.scrollHeight+"px"};textareaLoaded=!0})),document.querySelector("#navOpen").addEventListener("click",(e=>{document.querySelector("nav.sideNav").style.removeProperty("width"),document.querySelector("main.editor").style.removeProperty("margin-left"),e.target.style.removeProperty("visibility")})),document.querySelector("#navClose").addEventListener("click",(()=>{document.querySelector("nav.sideNav").style.width="0",document.querySelector("main.editor").style.marginLeft="0",document.querySelector("#navOpen").style.visibility="visible"})),document.querySelector("#addEdu").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromE").value),t.append("dateTo",document.querySelector("#dateToE").value),t.append("grade",document.querySelector("#grade").value),t.append("course",document.querySelector("#courseTitle").value),fetch("/api/timelineData/edu",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((o=>{if(e.ok)return addEduData(o.ID,t.get("dateFrom"),t.get("dateTo"),t.get("grade"),t.get("course"),!0),void document.querySelector("#addEdu").reset();401!==e.status?showErrorMessage(o.error,"edu"):window.location.href="./"}))))})),document.querySelector("#addWork").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("dateFrom",document.querySelector("#dateFromW").value),t.append("dateTo",document.querySelector("#dateToW").value),t.append("companyName",document.querySelector("#company").value),t.append("area",document.querySelector("#area").value),t.append("title",document.querySelector("#jobTitle").value),fetch("/api/timelineData/work",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((o=>{if(e.ok){let e=""===t.get("dateTo")?"Present":t.get("dateTo ");return addWorkData(o.ID,t.get("dateFrom"),e,t.get("companyName"),t.get("area"),t.get("title"),!0),void document.querySelector("#addWork").reset()}401!==e.status?showErrorMessage(o.error,"work"):window.location.href="./"}))))})),document.querySelector("#addProj").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;t.append("title",document.querySelector("#projTitle").value),t.append("isMainProject",document.querySelector("#isMainProject").checked?"true":"false"),t.append("information",document.querySelector("#projInfo").value),t.append("projectLink",document.querySelector("#projLink").value?document.querySelector("#projLink").value:"N/A"),t.append("gitLink",document.querySelector("#gitLink").value);let o=new FormData;o.append("img",document.querySelector("#projImg").files[0]);let r=0;fetch("/api/projectData",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((n=>{if(e.ok)return"undefined"===o.get("img")?(addProject(n.ID,t.get("isMainProject"),"../imgs/placeholder.png",t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset()):(r=n.ID,fetch("/api/projectImage/"+n.ID,{method:"POST",body:o,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}));401!==e.status?showErrorMessage(n.error,"proj"):window.location.href="./"})).then((e=>e.json().then((o=>{if(e.ok)return addProject(r,t.get("isMainProject"),o.imgLocation,t.get("title"),t.get("information"),t.get("projectLink"),t.get("gitLink")),void document.querySelector("#addProj").reset();401!==e.status?showErrorMessage(o.error,"proj"):window.location.href="./"}))))))})),document.querySelector("#addPostForm").addEventListener("submit",(e=>{if(e.preventDefault(),""===editors.CKEditorAddPost.getData())return void showErrorMessage("Post body cannot be empty","addPost");let t=new FormData;t.append("title",document.querySelector("#postTitle").value),t.append("featured",document.querySelector("#isFeatured").checked?"1":"0"),t.append("body",editors.CKEditorAddPost.getData()),t.append("dateCreated",(new Date).toISOString().slice(0,19).replace("T"," ")),t.append("categories",document.querySelector("#postCategories").value),t.append("headerImg",document.querySelector("#headerImg").files[0]),fetch("/api/blog/post",{method:"POST",body:t,headers:{Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>e.json().then((o=>{if(e.ok)return document.querySelector("#addPostForm").reset(),editors.CKEditorAddPost.setData(""),addPostInfo(o.ID,t.get("title"),t.get("dateCreated"),t.get("dateModified")),void showSuccessMessage("Post added successfully","addPost");401!==e.status?e.json().then((e=>showErrorMessage(e.error,"addPost"))):window.location.href="./"}))))})),document.querySelector("#editPostForm").addEventListener("submit",(e=>{e.preventDefault();let t=document.querySelector("#editPostForm input[type='submit']").id;if(""===t)return void showErrorMessage("Currently not editing any post","editPost");if(""===editors.CKEditorEditPost.getData())return void showErrorMessage("Post body cannot be empty","editPost");let o={};o.title=document.querySelector("#editPostTitle").value,o.featured=document.querySelector("#editIsFeatured").checked?"1":"0",o.body=editors.CKEditorEditPost.getData(),o.dateModified=(new Date).toISOString().slice(0,19).replace("T"," "),o.categories=document.querySelector("#editPostCategories").value;let r=new FormData;r.append("headerImg",document.querySelector("#editHeaderImg").files[0]),fetch("/api/blog/post/"+t,{method:"PATCH",body:JSON.stringify(o),headers:{"Content-Type":"application/json",Authorization:"Bearer "+localStorage.getItem("token")}}).then((e=>{if(e.ok)return"undefined"===r.get("headerImg")?(document.querySelector("#editPostForm").reset(),document.querySelector("#editPostForm input[type='submit']").id="",editors.CKEditorEditPost.setData(""),void showSuccessMessage("Post edited successfully","editPost")):fetch("/api/blog/headerImage/"+t,{method:"POST",body:r,headers:{Authorization:"Bearer "+localStorage.getItem("token")}});401!==e.status?e.json().then((e=>showErrorMessage(e.error,"editPost"))):window.location.href="./"})).then((e=>e.json().then((t=>{if(e.ok)return document.querySelector("#editPostForm").reset(),document.querySelector("#editPostForm input[type='submit']").id="",editors.CKEditorEditPost.setData(""),void showSuccessMessage("Post edited successfully","editPost");401!==e.status?showErrorMessage(t.error.message,"editPost"):window.location.href="./"}))))})),document.querySelector("#goToCV").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToCV"),goToPage("curriculumVitae")})),document.querySelector("#goToProjects").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToProjects"),goToPage("projects")})),document.querySelector("#blog").addEventListener("click",(()=>{document.querySelector("nav.sideNav ul li.dropdown ul").classList.toggle("active"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-down"),document.querySelector("#blog i.fa").classList.toggle("fa-caret-right")})),document.querySelector("#goToAddPost").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToAddPost"),goToPage("addPost"),document.querySelector("#blog").classList.add("active")})),document.querySelector("#goToEditPost").addEventListener("click",(()=>{textareaLoaded=!1,addActiveClass("goToEditPost"),goToPage("editPost"),document.querySelector("#blog").classList.add("active")})),document.querySelector("#logout").addEventListener("click",(()=>{fetch("/api/user/logout").then((e=>{e.ok&&window.location.reload()}))})),document.querySelector("#eduError .close").addEventListener("click",(()=>document.querySelector("#eduError").classList.toggle("hidden"))),document.querySelector("#workError .close").addEventListener("click",(()=>document.querySelector("#workError").classList.toggle("hidden"))),document.querySelector("#projError .close").addEventListener("click",(()=>document.querySelector("#projError").classList.toggle("hidden"))),document.querySelector("#addPostError .close").addEventListener("click",(()=>document.querySelector("#addPostError").classList.toggle("hidden"))),document.querySelector("#addPostSuccess .close").addEventListener("click",(()=>document.querySelector("#addPostSuccess").classList.toggle("hidden"))),document.querySelector("#editPostError .close").addEventListener("click",(()=>document.querySelector("#editPostError").classList.toggle("hidden"))),document.querySelector("#editPostSuccess .close").addEventListener("click",(()=>document.querySelector("#editPostSuccess").classList.toggle("hidden"))); \ No newline at end of file diff --git a/src/api/blog/blogData.php b/src/api/blog/blogData.php index e0eb5cd..951bda2 100644 --- a/src/api/blog/blogData.php +++ b/src/api/blog/blogData.php @@ -21,7 +21,8 @@ class blogData public function getBlogPosts(): array { $conn = dbConn(); - $stmt = $conn->prepare("SELECT ID, title, dateCreated, dateModified, body, categories FROM blog ORDER BY dateCreated DESC;"); + $stmt = $conn->prepare("SELECT ID, title, dateCreated, dateModified, body, categories, featured + FROM blog ORDER BY dateCreated;"); $stmt->execute(); // set the resulting array to associative @@ -99,6 +100,164 @@ class blogData return array("errorMessage" => "Error, blog post could not found"); } + /** + * Delete a blog post with the given ID + * @param int $ID - ID of the blog post to delete + * @return string - Success or error message + */ + public function deletePost(int $ID): string + { + $conn = dbConn(); + + $stmtCheckPost = $conn->prepare("SELECT * FROM blog WHERE ID = :ID"); + $stmtCheckPost->bindParam(":ID", $ID); + $stmtCheckPost->execute(); + $result = $stmtCheckPost->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "post not found"; + } + + if ($result["featured"] === 1) + { + return "cannot delete"; + } + + $stmt = $conn->prepare("DELETE FROM blog WHERE ID = :ID"); + $stmt->bindParam(":ID", $ID); + + if ($stmt->execute()) + { + $imagUtils = new imgUtils(); + $imagUtils->deleteDirectory("../blog/imgs/" . $result["title"] . "_" . $result["folderID"] . "/"); + return "success"; + } + + return "error"; + } + + /** + * Update the blog post with the given ID + * @param int $ID - ID of the blog post to update + * @param string $title - Title of the blog post + * @param bool $featured - Whether the blog post is featured or not + * @param string $body - Body of the blog post + * @param string $dateModified - Date the blog post was modified + * @param string $categories - Categories of the blog post + * @return bool|string - Success or error message + */ + public function updatePost(int $ID, string $title, bool $featured, string $body, string $dateModified, string $categories): bool|string + { + $conn = dbConn(); + + $stmtCheckPost = $conn->prepare("SELECT * FROM blog WHERE ID = :ID"); + $stmtCheckPost->bindParam(":ID", $ID); + $stmtCheckPost->execute(); + $result = $stmtCheckPost->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "post not found"; + } + + if (!$featured && $result["featured"] === 1) + { + return "unset feature"; + } + + if ($featured) + { + $stmtUnsetFeatured = $conn->prepare("UPDATE blog SET featured = 0 WHERE featured = 1;"); + $stmtUnsetFeatured->execute(); + } + + $to = "../blog/imgs/" . $title . "_" . $result["folderID"] . "/"; + if ($result["title"] !== $title) + { + $from = "../blog/imgs/" . $result["title"] . "_" . $result["folderID"] . "/"; + mkdir($to, 0777, true); + rename($result["headerImg"], $to . basename($result["headerImg"])); + $body = $this->changeHTMLSrc($body, $to, $from); + rmdir($from); + } + + $from = "../blog/imgs/tmp/"; + $newBody = $this->changeHTMLSrc($body, $to, $from); + + $stmt = $conn->prepare("UPDATE blog SET title = :title, featured = :featured, body = :body, dateModified = :dateModified, categories = :categories WHERE ID = :ID;"); + $stmt->bindParam(":ID", $ID); + $stmt->bindParam(":title", $title); + $stmt->bindParam(":featured", $featured); + $stmt->bindParam(":body", $newBody); + $stmt->bindParam(":dateModified", $dateModified); + $stmt->bindParam(":categories", $categories); + + return $stmt->execute(); + } + + /** + * Creates a new post di rectory, uploads the header image and moves the images from the + * temp folder to the new folder, then updates the post html to point to the new images, finally + * it creates the post in the database + * @param string $title - Title of the blog post + * @param string $body - Body of the blog post + * @param string $dateCreated - Date the blog post was created + * @param bool $featured - Whether the blog post is featured or not + * @param string $categories - Categories of the blog post + * @param UploadedFileInterface $headerImg - Header image of the blog post + * @return int|string - ID of the blog post or error message + */ + public function createPost(string $title, string $body, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string + { + $conn = dbConn(); + $folderID = uniqid(); + $targetFile = array("imgLocation" => "../blog/imgs/placeholder.png"); + + $targetDir = "../blog/imgs/" . $title . "_" . $folderID . "/"; + mkdir($targetDir, 0777, true); + + if ($headerImg !== null) + { + $imagUtils = new imgUtils(); + $targetFile = $imagUtils->uploadFile($targetDir, $headerImg); + } + + + if (!is_array($targetFile)) + { + return $targetFile; + } + + $newBody = $this->changeHTMLSrc($body, $targetDir, "../blog/imgs/tmp/"); + + + if ($featured) + { + $stmtMainProject = $conn->prepare("UPDATE blog SET featured = 0 WHERE featured = 1;"); + $stmtMainProject->execute(); + } + + $stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, body, categories, folderID) + VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :body, :categories, :folderID);"); + $stmt->bindParam(":title", $title); + $stmt->bindParam(":dateCreated", $dateCreated); + $stmt->bindParam(":dateModified", $dateCreated); + $isFeatured = $featured ? 1 : 0; + $stmt->bindParam(":featured", $isFeatured); + $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); + $stmt->bindParam(":body", $newBody); + $stmt->bindParam(":categories", $categories); + $stmt->bindParam(":folderID", $folderID); + + if ($stmt->execute()) + { + return intval($conn->lastInsertId()); + } + + return "Error, couldn't create post"; + } + /** * Upload the images in the post to temp folder and return image location * @param UploadedFileInterface $img - Image to upload @@ -131,37 +290,60 @@ class blogData } /** - * Creates a new post directory, uploads the header image and moves the images from the - * temp folder to the new folder, then updates the post html to point to the new images, finally - * it creates the post in the database - * @param string $title - Title of the blog post - * @param string $body - Body of the blog post - * @param string $dateCreated - Date the blog post was created - * @param string $featured - Whether the blog post is featured or not - * @param string $categories - Categories of the blog post - * @param UploadedFileInterface $headerImg - Header image of the blog post - * @return int|string - ID of the blog post or error message + * Upload the header image of the post and update the database + * @param int $ID - ID of the post + * @param UploadedFileInterface $img - Image to upload + * @return string|array - String with error message or array with the location of the uploaded file */ - public function createPost(string $title, string $body, string $dateCreated, string $featured, string $categories, UploadedFileInterface $headerImg): int|string + public function uploadHeaderImage(int $ID, UploadedFileInterface $img): string|array { $conn = dbConn(); - $targetFile = ""; - $folderID = uniqid(); - if ($headerImg !== null) + $stmt = $conn->prepare("SELECT * FROM blog WHERE ID = :ID;"); + $stmt->bindParam(":ID", $ID); + $stmt->execute(); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$result) { - $targetDir = "../blog/imgs/" . $title . "_" . $folderID . "/"; - mkdir($targetDir, 0777, true); - $imagUtils = new imgUtils(); - $targetFile = $imagUtils->uploadFile($targetDir, $headerImg); + return "Couldn't find the post"; } - $targetFile = array("imgLocation" => ".../blog/imgs/placeholder.png"); + $targetDir = "../blog/imgs/" . $result["title"] . "_" . $result["folderID"] . "/"; + $imagUtils = new imgUtils(); + $targetFile = $imagUtils->uploadFile($targetDir, $img); if (!is_array($targetFile)) { return $targetFile; } + if (file_exists($targetFile["imgLocation"])) + { + unlink($result["headerImg"]); + $stmt = $conn->prepare("UPDATE blog SET headerImg = :headerImg WHERE ID = :ID;"); + $stmt->bindParam(":ID", $ID); + $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); + $stmt->execute(); + if ($stmt->rowCount() > 0) + { + return $targetFile; + } + + return "Couldn't update the post"; + } + + return "Couldn't upload the image"; + } + + /** + * Change the HTML src of the images in the post to point to the new location + * @param string $body - Body of the post + * @param string $to - New location of the images + * @param string $from - Old location of the images + * @return string - Body of the post with the new image locations + */ + public function changeHTMLSrc(string $body, string $to, string $from): string + { $htmlDoc = new DOMDocument(); $htmlDoc->loadHTML($body, LIBXML_NOERROR); $doc = $htmlDoc->getElementsByTagName('body')->item(0); @@ -172,24 +354,25 @@ class blogData foreach ($imgs as $img) { $src = $img->getAttribute("src"); + $src = urldecode($src); $srcList[] = $src; $fileName = basename($src); - $img->setAttribute("src", $targetDir . $fileName); + $img->setAttribute("src", $to . $fileName); } - $files = scandir("../blog/imgs/tmp/"); + $files = scandir($from); foreach ($files as $file) { if ($file != "." && $file != "..") { - if (!in_array("../blog/imgs/tmp/" . $file, $srcList)) + if (!in_array($from . $file, $srcList)) { - unlink("../blog/imgs/tmp/" . $file); + unlink($from . $file); } else { - rename("../blog/imgs/tmp/" . $file, $targetDir . $file); + rename($from . $file, $to . $file); } } } @@ -199,23 +382,6 @@ class blogData { $newBody .= $htmlDoc->saveHTML($node); } - - $stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, body, categories, folderID) - VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :body, :categories, :folderID);"); - $stmt->bindParam(":title", $title); - $stmt->bindParam(":dateCreated", $dateCreated); - $stmt->bindParam(":dateModified", $dateCreated); - $stmt->bindParam(":featured", $featured); - $stmt->bindParam(":headerImg", $targetFile["imgLocation"]); - $stmt->bindParam(":body", $newBody); - $stmt->bindParam(":categories", $categories); - $stmt->bindParam(":folderID", $folderID); - - if ($stmt->execute()) - { - return intval($conn->lastInsertId()); - } - - return "Error, couldn't create post"; + return $newBody; } } \ No newline at end of file diff --git a/src/api/blog/blogRoutes.php b/src/api/blog/blogRoutes.php index a12075a..a0770a9 100644 --- a/src/api/blog/blogRoutes.php +++ b/src/api/blog/blogRoutes.php @@ -29,12 +29,122 @@ class blogRoutes implements routesInterface */ public function createRoutes(App $app): void { - $app->post("/blog/post", function (Request $request, Response $response, array $args) + $app->get("/blog/post", function (Request $request, Response $response) + { + $posts = $this->blogData->getBlogPosts(); + + $json = json_encode($posts); + + $response->getBody()->write($json); + + if (array_key_exists("errorMessage", $posts)) + { + $response->withStatus(404); + } + + return $response; + }); + + $app->get("/blog/post/{id}", function (Request $request, Response $response, $args) + { + if ($args["id"] != null) + { + $post = $this->blogData->getBlogPost($args["id"]); + if (array_key_exists("errorMessage", $post)) + { + $response->getBody()->write(json_encode($post)); + return $response->withStatus(404); + } + + $response->getBody()->write(json_encode($post)); + return $response; + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); + + $app->patch("/blog/post/{id}", function (Request $request, Response $response, $args) + { + $data = $request->getParsedBody(); + if ($args["id"] != null) + { + if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"])) + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Only some of the data was sent"))); + return $response->withStatus(400); + } + + $message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["body"], $data["dateModified"], $data["categories"]); + + if ($message === "post not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, post not found"))); + return $response->withStatus(404); + } + + if ($message === "unset featured") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, cannot unset featured post, try updating another post to be featured first"))); + return $response->withStatus(409); + } + + if (!is_bool($message) || $message === false) + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => $message))); + return $response->withStatus(500); + } + + return $response; + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); + + $app->delete("/blog/post/{id}", function (Request $request, Response $response, $args) + { + if ($args["id"] != null) + { + $message = $this->blogData->deletePost($args["id"]); + + if ($message === "post not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, post not found"))); + return $response->withStatus(404); + } + + if ($message === "error") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, something went wrong"))); + return $response->withStatus(500); + } + + if ($message === "cannot delete") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Error, cannot delete featured post"))); + return $response->withStatus(409); + } + return $response; + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); + + $app->post("/blog/post", function (Request $request, Response $response) { $data = $request->getParsedBody(); $files = $request->getUploadedFiles(); $headerImg = $files["headerImg"]; - if (empty($data["title"]) || empty($data["body"]) || empty($data["dateCreated"]) || empty($data["featured"]) || empty($data["categories"])) + if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateCreated"]) || empty($data["categories"])) { // uh oh sent some empty data $response->getBody()->write(json_encode(array("error" => "Error, empty data sent"))); @@ -46,7 +156,8 @@ class blogRoutes implements routesInterface $headerImg = null; } - $insertedID = $this->blogData->createPost($data["title"], $data["body"], $data["dateCreated"], $data["featured"], $data["categories"], $headerImg); + $featured = $data["featured"] === "true"; + $insertedID = $this->blogData->createPost($data["title"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg); if (!is_int($insertedID)) { // uh oh something went wrong @@ -74,9 +185,36 @@ class blogRoutes implements routesInterface return $response->withStatus(500); } - $response->getBody()->write(json_encode($message)); return $response->withStatus(201); }); + + $app->post("/blog/headerImage/{id}", function (Request $request, Response $response, $args) + { + $files = $request->getUploadedFiles(); + + if ($args["id"] != null) + { + if (empty($files)) + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent")))); + return $response->withStatus(400); + } + + $message = $this->blogData->uploadHeaderImage($args["id"], $files["headerImg"]); + if (!is_array($message)) + { + $response->getBody()->write(json_encode(array("error" => array("message" => $message)))); + return $response->withStatus(500); + } + + $response->getBody()->write(json_encode($message)); + return $response->withStatus(201); + } + + $response->getBody()->write(json_encode(array("error" => "Please provide an ID"))); + return $response->withStatus(400); + }); } } \ No newline at end of file diff --git a/src/api/project/projectData.php b/src/api/project/projectData.php index 77a65a6..a69b22f 100644 --- a/src/api/project/projectData.php +++ b/src/api/project/projectData.php @@ -39,30 +39,32 @@ class projectData * Update project data in the database with the given ID * @param string $ID - ID of the project in the database to update * @param string $title - Title of the project - * @param string $isMainProject - Is the project a main project or not + * @param bool $isMainProject - Is the project a main project or not * @param string $information - Information about the project * @param string $projectLink - Link to the project * @param string $gitLink - Link to the git repository * @return bool|string - True if project was updated, false if not and there was an error, or an error string */ - public function updateProjectData(string $ID, string $title, string $isMainProject, string $information, string $projectLink, string $gitLink): bool|string + public function updateProjectData(string $ID, string $title, bool $isMainProject, string $information, string $projectLink, string $gitLink): bool|string { $conn = dbConn(); - if ($isMainProject === "false") - { - $stmtMainProject = $conn->prepare("SELECT isMainProject FROM projects WHERE ID = :ID"); - $stmtMainProject->bindParam(":ID", $ID); - $stmtMainProject->execute(); - $result = $stmtMainProject->fetch(PDO::FETCH_ASSOC); + $stmtMainProject = $conn->prepare("SELECT isMainProject FROM projects WHERE ID = :ID"); + $stmtMainProject->bindParam(":ID", $ID); + $stmtMainProject->execute(); + $result = $stmtMainProject->fetch(PDO::FETCH_ASSOC); - if ($result["isMainProject"] === "1") - { - return "unset main project"; - } + if (!$result) + { + return "project not found"; } - if ($isMainProject === "true") + if (!$isMainProject && $result["isMainProject"] === "1") + { + return "unset main project"; + } + + if ($isMainProject) { $stmtMainProject = $conn->prepare("UPDATE projects SET isMainProject = 0 WHERE isMainProject = 1;"); $stmtMainProject->execute(); @@ -70,7 +72,7 @@ class projectData $stmt = $conn->prepare("UPDATE projects SET title = :title, isMainProject = :isMainProject, information = :information, projectLink = :projectLink, gitLink = :gitLink WHERE ID = :ID"); $stmt->bindParam(":title", $title); - $isMainProj = ($isMainProject === "true") ? 1 : 0; + $isMainProj = $isMainProject ? 1 : 0; $stmt->bindParam(":isMainProject", $isMainProj); $stmt->bindParam(":information", $information); $stmt->bindParam(":projectLink", $projectLink); @@ -89,12 +91,16 @@ class projectData $conn = dbConn(); // check if the project is a main project if it is return false - $stmtMainProject = $conn->prepare("SELECT isMainProject FROM projects WHERE ID = :ID"); $stmtMainProject->bindParam(":ID", $ID); $stmtMainProject->execute(); $result = $stmtMainProject->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "project not found"; + } + if ($result["isMainProject"] === "1") { return "cannot delete"; @@ -158,6 +164,20 @@ class projectData */ public function uploadImage(int $ID, UploadedFileInterface $img): string | array { + + $conn = dbConn(); + + $stmt = $conn->prepare("SELECT ID FROM projects WHERE ID = :ID"); + $stmt->bindParam(":ID", $ID); + $stmt->execute(); + $result = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$result) + { + return "Project with ID $ID not found"; + } + + $targetDir = "../imgs/projects/"; $imgUtils = new imgUtils(); $targetFile = $imgUtils->uploadFile($targetDir, $img); @@ -171,7 +191,6 @@ class projectData { $this->deleteImage($ID); // update the database with the new image location - $conn = dbConn(); $stmt = $conn->prepare("UPDATE projects SET imgLocation = :imgLocation WHERE ID = :ID"); $stmt->bindParam(":imgLocation", $targetFile["imgLocation"]); $stmt->bindParam(":ID", $ID); diff --git a/src/api/project/projectRoutes.php b/src/api/project/projectRoutes.php index 1b471cb..a7a332e 100644 --- a/src/api/project/projectRoutes.php +++ b/src/api/project/projectRoutes.php @@ -39,7 +39,7 @@ class projectRoutes implements routesInterface if(array_key_exists("errorMessage", $result)) { - $response = $response->withStatus(404); + $response->withStatus(404); } //use content type json to indicate json data on frontend. @@ -49,7 +49,7 @@ class projectRoutes implements routesInterface $app->patch("/projectData/{id}", function (Request $request, Response $response, array $args) { $data = $request->getParsedBody(); - if ($args["id"] != "undefined") + if ($args["id"] != null) { if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"])) { @@ -58,7 +58,15 @@ class projectRoutes implements routesInterface return $response->withStatus(400); } - $update = $this->projectData->updateProjectData($args["id"], $data["title"], $data["isMainProject"], $data["information"], $data["projectLink"], $data["gitLink"]); + $isMainProject = $data["isMainProject"] === "true"; + $update = $this->projectData->updateProjectData($args["id"], $data["title"], $isMainProject, $data["information"], $data["projectLink"], $data["gitLink"]); + + if ($update === "project not found") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found"))); + return $response->withStatus(404); + } if ($update === "unset main project") { @@ -73,6 +81,7 @@ class projectRoutes implements routesInterface $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); return $response->withStatus(500); } + return $response; } @@ -85,11 +94,12 @@ class projectRoutes implements routesInterface if ($args["id"] != null) { $message = $this->projectData->deleteProjectData($args["id"]); - if ($message === "error") + + if ($message === "project not found") { // uh oh something went wrong - $response->getBody()->write(json_encode(array("error" => "Something went wrong or the project with ID ".$args["id"]."does not exist"))); - return $response->withStatus(500); + $response->getBody()->write(json_encode(array("error" => "Project with ID " . $args["id"] . " not found"))); + return $response->withStatus(404); } if ($message === "cannot delete") @@ -99,6 +109,13 @@ class projectRoutes implements routesInterface return $response->withStatus(409); } + if ($message === "error") + { + // uh oh something went wrong + $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); + return $response->withStatus(500); + } + return $response; } diff --git a/src/api/timeline/timelineData.php b/src/api/timeline/timelineData.php index b72dbdf..9c1df75 100644 --- a/src/api/timeline/timelineData.php +++ b/src/api/timeline/timelineData.php @@ -58,19 +58,33 @@ class timelineData * @param string $dateTo - End date * @param string $grade - Grade * @param string $course - Course - * @param string $id - ID of the education data - * @return bool - True if successful, false if not + * @param string $ID - ID of the education data + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - public function updateEduData(string $dateFrom, string $dateTo, string $grade, string $course, string $id): bool + public function updateEduData(string $dateFrom, string $dateTo, string $grade, string $course, string $ID): string { $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM edu WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $stmt = $conn->prepare("UPDATE edu SET startPeriod = :dateFrom, endPeriod = :dateTo, grade = :grade, course = :course WHERE ID = :id;"); $stmt->bindParam(":dateFrom", $dateFrom); $stmt->bindParam(":dateTo", $dateTo); $stmt->bindParam(":grade", $grade); $stmt->bindParam(":course", $course); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** @@ -80,11 +94,21 @@ class timelineData * @param string $companyName - Company name * @param string $area - Area * @param string $title - Title - * @param string $id - ID of the work data - * @return bool - True if successful, false if not + * @param string $ID - ID of the work data + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - public function updateWorkData(string $dateFrom, string $dateTo, string $companyName, string $area, string $title, string $id): bool + public function updateWorkData(string $dateFrom, string $dateTo, string $companyName, string $area, string $title, string $ID): string { + $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM work WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $conn = dbConn(); $stmt = $conn->prepare("UPDATE work SET startPeriod = :dateFrom, endPeriod = :dateTo, companyName = :companyName, area = :area, title = :title WHERE ID = :id;"); $stmt->bindParam(":dateFrom", $dateFrom); @@ -92,34 +116,67 @@ class timelineData $stmt->bindParam(":companyName", $companyName); $stmt->bindParam(":area", $area); $stmt->bindParam(":title", $title); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** * Delete education data by ID - * @param int $id - * @return bool - True if successful, false if not + * @param int $ID + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - public function deleteEduData(int $id): bool + public function deleteEduData(int $ID): string { $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM edu WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $stmt = $conn->prepare("DELETE FROM edu WHERE ID = :id;"); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** * Delete work data by ID - * @param int $id - * @return bool - True if successful, false if not + * @param int $ID + * @return string - "not found" if the ID is not found, "ok" if successful, "error" if not */ - function deleteWorkData(int $id): bool + function deleteWorkData(int $ID): string { $conn = dbConn(); + $chkStmt = $conn->prepare("SELECT ID FROM work WHERE ID = :id;"); + $chkStmt->bindParam(":id", $ID); + $chkStmt->execute(); + $result = $chkStmt->fetch(PDO::FETCH_ASSOC); + if (!$result) + { + return "not found"; + } + $stmt = $conn->prepare("DELETE FROM work WHERE ID = :id;"); - $stmt->bindParam(":id", $id); - return $stmt->execute(); + $stmt->bindParam(":id", $ID); + if ($stmt->execute()) + { + return "ok"; + } + + return "error"; } /** diff --git a/src/api/timeline/timelineRoutes.php b/src/api/timeline/timelineRoutes.php index d6ab9d6..72ea6f0 100644 --- a/src/api/timeline/timelineRoutes.php +++ b/src/api/timeline/timelineRoutes.php @@ -53,7 +53,7 @@ class timelineRoutes implements routesInterface $app->patch("/timelineData/{timeline}/{id}", function (Request $request, Response $response, array $args) { $data = $request->getParsedBody(); - if ($args["timeline"] == "edu" && $args["id"] != "undefined") + if ($args["timeline"] == "edu" && $args["id"] != null) { if (empty($data["dateFrom"]) || empty($data["dateTo"]) || empty($data["grade"]) || empty($data["course"])) { @@ -61,8 +61,16 @@ class timelineRoutes implements routesInterface $response->getBody()->write(json_encode(array("error" => "Only some of the data was sent"))); return $response->withStatus(400); } + $message = $this->timelineData->updateEduData($data["dateFrom"], $data["dateTo"], $data["grade"], $data["course"], $args["id"]); - if (!$this->timelineData->updateEduData($data["dateFrom"], $data["dateTo"], $data["grade"], $data["course"], $args["id"])) + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Edu data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); @@ -82,7 +90,16 @@ class timelineRoutes implements routesInterface return $response->withStatus(400); } - if (!$this->timelineData->updateWorkData($data["dateFrom"], $data["dateTo"], $data["companyName"], $data["area"], $data["title"], $args["id"])) + $message = $this->timelineData->updateWorkData($data["dateFrom"], $data["dateTo"], $data["companyName"], $data["area"], $data["title"], $args["id"]); + + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Work data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); @@ -101,7 +118,16 @@ class timelineRoutes implements routesInterface { if ($args["timeline"] == "edu" && $args["id"] != null) { - if (!$this->timelineData->deleteEduData($args["id"])) + $message = $this->timelineData->deleteEduData($args["id"]); + + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Edu data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); @@ -113,7 +139,16 @@ class timelineRoutes implements routesInterface if ($args["timeline"] == "work" && $args["id"] != null) { - if (!$this->timelineData->deleteWorkData($args["id"])) + $message = $this->timelineData->deleteWorkData($args["id"]); + + if ($message == "not found") + { + // uh oh sent some empty data + $response->getBody()->write(json_encode(array("error" => "Work data with ID " . $args["id"] . " was not found"))); + return $response->withStatus(404); + } + + if ($message == "error") { // uh oh something went wrong $response->getBody()->write(json_encode(array("error" => "Something went wrong"))); diff --git a/src/api/user/userData.php b/src/api/user/userData.php index 0aa11b2..493b6db 100644 --- a/src/api/user/userData.php +++ b/src/api/user/userData.php @@ -46,7 +46,7 @@ class userData public function createToken(string $username): string { $now = time(); - $future = strtotime('+6 hour', $now); + $future = strtotime('+2 day', $now); $secretKey = getSecretKey(); $payload = [ "jti" => $username, diff --git a/src/api/user/userRoutes.php b/src/api/user/userRoutes.php index 2bde66c..465ca7a 100644 --- a/src/api/user/userRoutes.php +++ b/src/api/user/userRoutes.php @@ -36,15 +36,19 @@ class userRoutes implements routesInterface if (empty($data["username"]) || empty($data["password"])) { - // uh oh userData sent empty data + // uh oh user sent empty data return $response->withStatus(400); } if ($this->user->checkUser($data["username"], $data["password"])) { - // yay, userData is logged in + // yay, user is logged in $_SESSION["token"] = $this->user->createToken($data["username"]); $_SESSION["username"] = $data["username"]; + + $inactive = 60 * 60 * 48; // 2 days + $_SESSION["timeout"] = time() + $inactive; + $response->getBody()->write(json_encode(array("token" => $_SESSION["token"]))); return $response; } @@ -62,15 +66,24 @@ class userRoutes implements routesInterface { if (empty($_SESSION["token"]) && empty($_SESSION["username"])) { - // uh oh userData not logged in + // uh oh user not logged in + return $response->withStatus(401); + } + + $inactive = 60 * 60 * 48; // 2 days + $sessionLife = time() - $_SESSION["timeout"]; + if ($sessionLife > $inactive) + { + // uh oh user session expired + session_destroy(); return $response->withStatus(401); } if (empty($_SESSION["token"])) { - // userData is logged in but no token was created + // user is logged in but no token was created $_SESSION["token"] = $this->user->createToken($_SESSION["username"]); - return $response; + return $response->withStatus(201); } $response->getBody()->write(json_encode(array("token" => $_SESSION["token"]))); diff --git a/src/api/utils/imgUtils.php b/src/api/utils/imgUtils.php index 40f9487..1deab53 100644 --- a/src/api/utils/imgUtils.php +++ b/src/api/utils/imgUtils.php @@ -3,6 +3,8 @@ namespace api\utils; use Psr\Http\Message\UploadedFileInterface; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; class imgUtils { @@ -40,4 +42,29 @@ class imgUtils return array("imgLocation" => $targetFile); } + + /** + * Deletes a directory and all its contents + * @param string $path - Path to the directory to delete + */ + public function deleteDirectory(string $path): void + { + $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, + RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) + { + if ($file->isDir()) + { + rmdir($file->getPathname()); + } + else + { + unlink($file->getPathname()); + } + } + + rmdir($path); + } + } \ No newline at end of file diff --git a/src/blog/index.js b/src/blog/js/index.js similarity index 95% rename from src/blog/index.js rename to src/blog/js/index.js index 2beb7a8..fd6b514 100644 --- a/src/blog/index.js +++ b/src/blog/js/index.js @@ -4,7 +4,7 @@ document.addEventListener('DOMContentLoaded', () => goToURL(window.location.pathname); }); -window.addEventListener('popstate', e => +window.addEventListener('popstate', _ => { goToURL(window.history.state); }); diff --git a/src/css/templateStyles.css b/src/css/templateStyles.css index 85d8459..b26db45 100644 --- a/src/css/templateStyles.css +++ b/src/css/templateStyles.css @@ -10,13 +10,14 @@ --primaryDefault: hsla(var(--mainHue), var(--mainSat), var(--mainLight), 1); --primaryHover: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 10%), 1); --timelineItemBrdr: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) - 20%), 1); - --errorDefault: hsla(0, calc(var(--mainSat) + 10%),calc(var(--mainLight) + 10%), 1); + --errorDefault: hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) + 10%), 1); --errorHover: hsla(0, calc(var(--mainSat) + 10%), calc(var(--mainLight) - 10%), 1); --grey: hsla(0, 0%, 39%, 1); --notAvailableDefault: hsla(0, 0%, 39%, 1); - --notAvailableHover: hsla(0, 0%,32%, 1); + --notAvailableHover: hsla(0, 0%, 32%, 1); --mutedGrey: hsla(0, 0%, 78%, 1); --mutedBlack: hsla(0, 0%, 0%, 0.25); + --mutedGreen: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5); --navBack: hsla(0, 0%, 30%, 1); /* Font Sizes */ diff --git a/src/editor/css/editor.css b/src/editor/css/editor.css index c7a92ad..ad15e90 100644 --- a/src/editor/css/editor.css +++ b/src/editor/css/editor.css @@ -42,10 +42,11 @@ div.editorContainer > *, div.projectsGrid > * { main.editor section { display: none; + flex-direction: column; } -section#addPost { - display: block; +section#editPost { + display: flex; } div.modifyBtnContainer { @@ -232,7 +233,7 @@ section#projects form.projItem:not(.editing) div.formControl.infoContainer texta color: #000000; } -section#addPost form { +section#addPost form, section#editPost form { margin: auto 4rem; } @@ -251,4 +252,30 @@ form .formControl .ck.ck-editor__main .ck-content { form .formControl .ck-editor__editable { min-height: 400px; +} + +section#editPost { + justify-content: center; +} + +section#editPost h2 { + align-self: flex-start; +} + +section#editPost table { + border-collapse: collapse; + border-style: hidden; + align-self: center; + margin-bottom: 5em; +} + +section#editPost table td, th { + border: 1px solid var(--mutedGrey); + text-align: left; + padding: 8px; + min-width: 10rem; +} + +section#editPost form { + margin-bottom: 2em; } \ No newline at end of file diff --git a/src/editor/editor.html b/src/editor/editor.html index 8a3fa53..6888395 100644 --- a/src/editor/editor.html +++ b/src/editor/editor.html @@ -15,11 +15,11 @@
  • <CV>
  • <Projects>
  • <Blog>
  • + class="fa fa-caret-right"> +