Merge pull request 'Search' (#48) from search into master
All checks were successful
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
All checks were successful
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
Reviewed-on: #48
This commit is contained in:
commit
f3f68717ee
138
dist/api/blog/blogData.php
vendored
138
dist/api/blog/blogData.php
vendored
@ -23,7 +23,7 @@ class blogData
|
|||||||
public function getBlogPosts(): array
|
public function getBlogPosts(): array
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY dateCreated DESC;");
|
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY featured DESC, dateCreated DESC;");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
// set the resulting array to associative
|
// set the resulting array to associative
|
||||||
@ -102,28 +102,9 @@ class blogData
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the blog posts with the given category
|
* Get all unique categories
|
||||||
* @param string $category - Category of the blog post
|
* @return string[] - Array of all categories or error message
|
||||||
* @return array - Array of the blog posts with the given category or error message
|
|
||||||
*/
|
*/
|
||||||
public function getBlogPostsWithCategory(string $category): array
|
|
||||||
{
|
|
||||||
$conn = dbConn();
|
|
||||||
$stmt = $conn->prepare("SELECT * FROM blog WHERE categories LIKE :category;");
|
|
||||||
$stmt->bindParam(":category", $category);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
// set the resulting array to associative
|
|
||||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($result)
|
|
||||||
{
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array("errorMessage" => "Error, blog post could not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCategories(): array
|
public function getCategories(): array
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
@ -185,11 +166,12 @@ class blogData
|
|||||||
* @param bool $featured - Whether the blog post is featured or not
|
* @param bool $featured - Whether the blog post is featured or not
|
||||||
* @param string $abstract - Abstract of the blog post i.e. a short description
|
* @param string $abstract - Abstract of the blog post i.e. a short description
|
||||||
* @param string $body - Body of the blog post
|
* @param string $body - Body of the blog post
|
||||||
|
* @param string $bodyText - Body of the blog post as plain text
|
||||||
* @param string $dateModified - Date the blog post was modified
|
* @param string $dateModified - Date the blog post was modified
|
||||||
* @param string $categories - Categories of the blog post
|
* @param string $categories - Categories of the blog post
|
||||||
* @return bool|string - Success or error message
|
* @return bool|string - Success or error message
|
||||||
*/
|
*/
|
||||||
public function updatePost(int $ID, string $title, bool $featured, string $abstract, string $body, string $dateModified, string $categories): bool|string
|
public function updatePost(int $ID, string $title, bool $featured, string $abstract, string $body, string $bodyText, string $dateModified, string $categories): bool|string
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
|
|
||||||
@ -227,12 +209,13 @@ class blogData
|
|||||||
$from = "../blog/imgs/tmp/";
|
$from = "../blog/imgs/tmp/";
|
||||||
$newBody = $this->changeHTMLSrc($body, $to, $from);
|
$newBody = $this->changeHTMLSrc($body, $to, $from);
|
||||||
|
|
||||||
$stmt = $conn->prepare("UPDATE blog SET title = :title, featured = :featured, abstract = :abstract, body = :body, dateModified = :dateModified, categories = :categories WHERE ID = :ID;");
|
$stmt = $conn->prepare("UPDATE blog SET title = :title, featured = :featured, abstract = :abstract, body = :body, bodyText = :bodyText, dateModified = :dateModified, categories = :categories WHERE ID = :ID;");
|
||||||
$stmt->bindParam(":ID", $ID);
|
$stmt->bindParam(":ID", $ID);
|
||||||
$stmt->bindParam(":title", $title);
|
$stmt->bindParam(":title", $title);
|
||||||
$stmt->bindParam(":featured", $featured);
|
$stmt->bindParam(":featured", $featured);
|
||||||
$stmt->bindParam(":abstract", $abstract);
|
$stmt->bindParam(":abstract", $abstract);
|
||||||
$stmt->bindParam(":body", $newBody);
|
$stmt->bindParam(":body", $newBody);
|
||||||
|
$stmt->bindParam(":bodyText", $bodyText);
|
||||||
$stmt->bindParam(":dateModified", $dateModified);
|
$stmt->bindParam(":dateModified", $dateModified);
|
||||||
$stmt->bindParam(":categories", $categories);
|
$stmt->bindParam(":categories", $categories);
|
||||||
|
|
||||||
@ -246,13 +229,14 @@ class blogData
|
|||||||
* @param string $title - Title of the blog post
|
* @param string $title - Title of the blog post
|
||||||
* @param string $abstract - Abstract of the blog post i.e. a short description
|
* @param string $abstract - Abstract of the blog post i.e. a short description
|
||||||
* @param string $body - Body of the blog post
|
* @param string $body - Body of the blog post
|
||||||
|
* @param string $bodyText - Body of the blog post as plain text
|
||||||
* @param string $dateCreated - Date the blog post was created
|
* @param string $dateCreated - Date the blog post was created
|
||||||
* @param bool $featured - Whether the blog post is featured or not
|
* @param bool $featured - Whether the blog post is featured or not
|
||||||
* @param string $categories - Categories of the blog post
|
* @param string $categories - Categories of the blog post
|
||||||
* @param UploadedFileInterface $headerImg - Header image of the blog post
|
* @param UploadedFileInterface $headerImg - Header image of the blog post
|
||||||
* @return int|string - ID of the blog post or error message
|
* @return int|string - ID of the blog post or error message
|
||||||
*/
|
*/
|
||||||
public function createPost(string $title, string $abstract, string $body, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string
|
public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
$folderID = uniqid();
|
$folderID = uniqid();
|
||||||
@ -282,8 +266,8 @@ class blogData
|
|||||||
$stmtMainProject->execute();
|
$stmtMainProject->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, categories, folderID)
|
$stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, bodyText, categories, folderID)
|
||||||
VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :categories, :folderID);");
|
VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :bodyText, :categories, :folderID);");
|
||||||
$stmt->bindParam(":title", $title);
|
$stmt->bindParam(":title", $title);
|
||||||
$stmt->bindParam(":dateCreated", $dateCreated);
|
$stmt->bindParam(":dateCreated", $dateCreated);
|
||||||
$stmt->bindParam(":dateModified", $dateCreated);
|
$stmt->bindParam(":dateModified", $dateCreated);
|
||||||
@ -292,6 +276,7 @@ class blogData
|
|||||||
$stmt->bindParam(":headerImg", $targetFile["imgLocation"]);
|
$stmt->bindParam(":headerImg", $targetFile["imgLocation"]);
|
||||||
$stmt->bindParam(":abstract", $abstract);
|
$stmt->bindParam(":abstract", $abstract);
|
||||||
$stmt->bindParam(":body", $newBody);
|
$stmt->bindParam(":body", $newBody);
|
||||||
|
$stmt->bindParam(":bodyText", $bodyText);
|
||||||
$stmt->bindParam(":categories", $categories);
|
$stmt->bindParam(":categories", $categories);
|
||||||
$stmt->bindParam(":folderID", $folderID);
|
$stmt->bindParam(":folderID", $folderID);
|
||||||
|
|
||||||
@ -443,4 +428,103 @@ class blogData
|
|||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a blog post with the given search term
|
||||||
|
* @param string $searchTerm - Search term
|
||||||
|
* @return array - Array of all posts with the given search term or error message
|
||||||
|
*/
|
||||||
|
public function searchBlog(string $searchTerm): array
|
||||||
|
{
|
||||||
|
$conn = dbConn();
|
||||||
|
$stmt = $conn->prepare("SELECT * FROM blog WHERE MATCH(title, bodyText) AGAINST(:searchTerm IN NATURAL LANGUAGE MODE);");
|
||||||
|
$stmt->bindParam(":searchTerm", $searchTerm);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($result)
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < count($result); $i++)
|
||||||
|
{
|
||||||
|
$result[$i]["abstract"] = $this->getShortPost($searchTerm, stripcslashes($result[$i]["bodyText"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array("errorMessage" => "Error, could not find posts");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the short post with the search term
|
||||||
|
* @param string $searchTerm - Search term
|
||||||
|
* @param $text - Body of the post as plain text
|
||||||
|
* @return string - Short post with the search term
|
||||||
|
*/
|
||||||
|
private function getShortPost(string $searchTerm, $text): string
|
||||||
|
{
|
||||||
|
$pattern = '/([,:;!?.-]+)/u';
|
||||||
|
$parts = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
|
$cleanedParts = [];
|
||||||
|
|
||||||
|
foreach ($parts as $part)
|
||||||
|
{
|
||||||
|
$part = trim($part); // Remove leading/trailing spaces and newline characters
|
||||||
|
if (!empty($part))
|
||||||
|
{
|
||||||
|
$cleanedParts[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$combinedParts = [];
|
||||||
|
$currentPart = '';
|
||||||
|
|
||||||
|
foreach ($cleanedParts as $part)
|
||||||
|
{
|
||||||
|
if (preg_match('/[,:;!?.-]/u', $part))
|
||||||
|
{
|
||||||
|
$currentPart .= $part;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!empty($currentPart))
|
||||||
|
{
|
||||||
|
$combinedParts[] = trim($currentPart);
|
||||||
|
}
|
||||||
|
$currentPart = rtrim($part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($currentPart))
|
||||||
|
{
|
||||||
|
$combinedParts[] = trim($currentPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = "";
|
||||||
|
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($combinedParts); $i++)
|
||||||
|
{
|
||||||
|
$part = $combinedParts[$i];
|
||||||
|
|
||||||
|
if (stripos($part, $searchTerm) !== false)
|
||||||
|
{
|
||||||
|
$before = ($i > 0) ? $combinedParts[$i - 1] : "";
|
||||||
|
$after = ($i < count($combinedParts) - 1) ? $combinedParts[$i + 1] : "";
|
||||||
|
|
||||||
|
if ($before === "" && $i > 0)
|
||||||
|
{
|
||||||
|
$before = $combinedParts[$i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $before . " " . $part . " " . $after;
|
||||||
|
|
||||||
|
// If the search term is found, we don't need to continue checking subsequent parts
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
28
dist/api/blog/blogRoutes.php
vendored
28
dist/api/blog/blogRoutes.php
vendored
@ -123,12 +123,34 @@ class blogRoutes implements routesInterface
|
|||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
|
||||||
|
{
|
||||||
|
if ($args["searchTerm"] != null)
|
||||||
|
{
|
||||||
|
$posts = $this->blogData->searchBlog($args["searchTerm"]);
|
||||||
|
|
||||||
|
$json = json_encode($posts);
|
||||||
|
|
||||||
|
$response->getBody()->write($json);
|
||||||
|
|
||||||
|
if (array_key_exists("errorMessage", $posts))
|
||||||
|
{
|
||||||
|
$response->withStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
|
||||||
|
return $response->withStatus(400);
|
||||||
|
});
|
||||||
|
|
||||||
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if ($args["id"] != null)
|
if ($args["id"] != null)
|
||||||
{
|
{
|
||||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
||||||
{
|
{
|
||||||
// uh oh sent some empty data
|
// uh oh sent some empty data
|
||||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||||
@ -142,7 +164,7 @@ class blogRoutes implements routesInterface
|
|||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["dateModified"], $data["categories"]);
|
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["bodyText"], $data["dateModified"], $data["categories"]);
|
||||||
|
|
||||||
if ($message === "post not found")
|
if ($message === "post not found")
|
||||||
{
|
{
|
||||||
@ -232,7 +254,7 @@ class blogRoutes implements routesInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$featured = $data["featured"] === "true";
|
$featured = $data["featured"] === "true";
|
||||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
||||||
if (!is_int($insertedID))
|
if (!is_int($insertedID))
|
||||||
{
|
{
|
||||||
// uh oh something went wrong
|
// uh oh something went wrong
|
||||||
|
2
dist/blog/css/main.css
vendored
2
dist/blog/css/main.css
vendored
File diff suppressed because one or more lines are too long
2
dist/blog/index.html
vendored
2
dist/blog/index.html
vendored
@ -1 +1 @@
|
|||||||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Rohit Pai - Blog</title><meta name="title" content="Rohit Pai - Blog"><meta name="description" content="This is all the blog posts that Rohit Pai has posted. You'll find posts on various topics, mostly on tech but some on various other random topics."><meta name="keywords" content="Blog, all posts, rohit, pai, rohit pai, tech, web development, self-hosting, hosting"><meta name="robots" content="index, follow"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="language" content="English"><meta name="author" content="Rohit Pai"><link rel="stylesheet" href="/blog/css/main.css"><script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script></head><body><nav><input type="checkbox" id="nav-check"><h1><a href="/" class="link">rohit pai</a></h1><div class="nav-btn"><label for="nav-check"><span></span> <span></span> <span></span></label></div><ul><li><a href="/#about" class="textShadow link">about</a></li><li><a href="/#curriculumVitae" class="textShadow link">cv</a></li><li><a href="/#projects" class="textShadow link">projects</a></li><li><a href="/#contact" class="textShadow link">contact</a></li><li><a href="/blog" class="textShadow link active">blog</a></li></ul></nav><header><div><h1>full stack developer</h1><a href="/#sayHello" class="btn btnPrimary boxShadowIn boxShadowOut">Contact Me</a> <a href="" id="arrow"><i class="fa-solid fa-chevron-down"></i></a></div></header><main id="main"></main><footer class="flexRow"><div class="spacer"></div><p>© <span id="year"></span> Rohit Pai all rights reserved</p><div class="button"><button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button></div></footer><script src="/js/typewriter.js"></script><script src="/blog/js/index.js"></script><script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script></body></html>
|
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Rohit Pai - Blog</title><meta name="title" content="Rohit Pai - Blog"><meta name="description" content="This is all the blog posts that Rohit Pai has posted. You'll find posts on various topics, mostly on tech but some on various other random topics."><meta name="keywords" content="Blog, all posts, rohit, pai, rohit pai, tech, web development, self-hosting, hosting"><meta name="robots" content="index, follow"><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><meta name="language" content="English"><meta name="author" content="Rohit Pai"><link rel="stylesheet" href="/blog/css/main.css"><script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script></head><body><nav><input type="checkbox" id="nav-check"><h1><a href="/" class="link">rohit pai</a></h1><div class="nav-btn"><label for="nav-check"><span></span> <span></span> <span></span></label></div><ul><li><a href="/#about" class="textShadow link">about</a></li><li><a href="/#curriculumVitae" class="textShadow link">cv</a></li><li><a href="/#projects" class="textShadow link">projects</a></li><li><a href="/#contact" class="textShadow link">contact</a></li><li><a href="/blog" class="textShadow link active">blog</a></li></ul></nav><header><div><h1>full stack developer</h1><a href="/#sayHello" class="btn btnPrimary boxShadowIn boxShadowOut">Contact Me</a> <a href="" id="arrow"><i class="fa-solid fa-chevron-down"></i></a></div></header><div class="menuBar"><div class="menu"><ul><li><a href="/blog" class="link active">All posts</a></li><li><a href="/blog/category" class="link">categories</a></li><li><label for="searchField" aria-hidden="true" hidden>search</label> <input type="search" name="search" id="searchField" placeholder="Search..."> <button type="submit" id="searchBtn" class="btn btnPrimary"><i class="fa fa-search"></i></button></li></ul></div></div><main id="main"></main><footer class="flexRow"><div class="spacer"></div><p>© <span id="year"></span> Rohit Pai all rights reserved</p><div class="button"><button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button></div></footer><script src="/js/typewriter.js"></script><script src="/blog/js/index.js"></script><script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script></body></html>
|
2
dist/blog/js/index.js
vendored
2
dist/blog/js/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/css/main.css
vendored
2
dist/css/main.css
vendored
File diff suppressed because one or more lines are too long
2
dist/editor/css/main.css
vendored
2
dist/editor/css/main.css
vendored
File diff suppressed because one or more lines are too long
2
dist/editor/editor.html
vendored
2
dist/editor/editor.html
vendored
File diff suppressed because one or more lines are too long
2
dist/editor/js/editor.js
vendored
2
dist/editor/js/editor.js
vendored
File diff suppressed because one or more lines are too long
2
dist/index.html
vendored
2
dist/index.html
vendored
File diff suppressed because one or more lines are too long
1006
package-lock.json
generated
1006
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -12,6 +12,7 @@
|
|||||||
"author": "Rohit Pai",
|
"author": "Rohit Pai",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ckeditor/ckeditor5-clipboard": "^40.0.0",
|
||||||
"browser-sync": "^2.27.5",
|
"browser-sync": "^2.27.5",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-clean-css": "^4.3.0",
|
"gulp-clean-css": "^4.3.0",
|
||||||
@ -25,5 +26,11 @@
|
|||||||
"require": "^0.4.4",
|
"require": "^0.4.4",
|
||||||
"source-map-generator": "^0.8.0",
|
"source-map-generator": "^0.8.0",
|
||||||
"vinyl-ftp": "^0.6.1"
|
"vinyl-ftp": "^0.6.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
|
"vinyl-named-with-path": "^1.0.0",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-stream": "^7.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class blogData
|
|||||||
public function getBlogPosts(): array
|
public function getBlogPosts(): array
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY dateCreated DESC;");
|
$stmt = $conn->prepare("SELECT * FROM blog ORDER BY featured DESC, dateCreated DESC;");
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
// set the resulting array to associative
|
// set the resulting array to associative
|
||||||
@ -102,28 +102,9 @@ class blogData
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the blog posts with the given category
|
* Get all unique categories
|
||||||
* @param string $category - Category of the blog post
|
* @return string[] - Array of all categories or error message
|
||||||
* @return array - Array of the blog posts with the given category or error message
|
|
||||||
*/
|
*/
|
||||||
public function getBlogPostsWithCategory(string $category): array
|
|
||||||
{
|
|
||||||
$conn = dbConn();
|
|
||||||
$stmt = $conn->prepare("SELECT * FROM blog WHERE categories LIKE :category;");
|
|
||||||
$stmt->bindParam(":category", $category);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
// set the resulting array to associative
|
|
||||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
|
|
||||||
if ($result)
|
|
||||||
{
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return array("errorMessage" => "Error, blog post could not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCategories(): array
|
public function getCategories(): array
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
@ -185,11 +166,12 @@ class blogData
|
|||||||
* @param bool $featured - Whether the blog post is featured or not
|
* @param bool $featured - Whether the blog post is featured or not
|
||||||
* @param string $abstract - Abstract of the blog post i.e. a short description
|
* @param string $abstract - Abstract of the blog post i.e. a short description
|
||||||
* @param string $body - Body of the blog post
|
* @param string $body - Body of the blog post
|
||||||
|
* @param string $bodyText - Body of the blog post as plain text
|
||||||
* @param string $dateModified - Date the blog post was modified
|
* @param string $dateModified - Date the blog post was modified
|
||||||
* @param string $categories - Categories of the blog post
|
* @param string $categories - Categories of the blog post
|
||||||
* @return bool|string - Success or error message
|
* @return bool|string - Success or error message
|
||||||
*/
|
*/
|
||||||
public function updatePost(int $ID, string $title, bool $featured, string $abstract, string $body, string $dateModified, string $categories): bool|string
|
public function updatePost(int $ID, string $title, bool $featured, string $abstract, string $body, string $bodyText, string $dateModified, string $categories): bool|string
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
|
|
||||||
@ -227,12 +209,13 @@ class blogData
|
|||||||
$from = "../blog/imgs/tmp/";
|
$from = "../blog/imgs/tmp/";
|
||||||
$newBody = $this->changeHTMLSrc($body, $to, $from);
|
$newBody = $this->changeHTMLSrc($body, $to, $from);
|
||||||
|
|
||||||
$stmt = $conn->prepare("UPDATE blog SET title = :title, featured = :featured, abstract = :abstract, body = :body, dateModified = :dateModified, categories = :categories WHERE ID = :ID;");
|
$stmt = $conn->prepare("UPDATE blog SET title = :title, featured = :featured, abstract = :abstract, body = :body, bodyText = :bodyText, dateModified = :dateModified, categories = :categories WHERE ID = :ID;");
|
||||||
$stmt->bindParam(":ID", $ID);
|
$stmt->bindParam(":ID", $ID);
|
||||||
$stmt->bindParam(":title", $title);
|
$stmt->bindParam(":title", $title);
|
||||||
$stmt->bindParam(":featured", $featured);
|
$stmt->bindParam(":featured", $featured);
|
||||||
$stmt->bindParam(":abstract", $abstract);
|
$stmt->bindParam(":abstract", $abstract);
|
||||||
$stmt->bindParam(":body", $newBody);
|
$stmt->bindParam(":body", $newBody);
|
||||||
|
$stmt->bindParam(":bodyText", $bodyText);
|
||||||
$stmt->bindParam(":dateModified", $dateModified);
|
$stmt->bindParam(":dateModified", $dateModified);
|
||||||
$stmt->bindParam(":categories", $categories);
|
$stmt->bindParam(":categories", $categories);
|
||||||
|
|
||||||
@ -246,13 +229,14 @@ class blogData
|
|||||||
* @param string $title - Title of the blog post
|
* @param string $title - Title of the blog post
|
||||||
* @param string $abstract - Abstract of the blog post i.e. a short description
|
* @param string $abstract - Abstract of the blog post i.e. a short description
|
||||||
* @param string $body - Body of the blog post
|
* @param string $body - Body of the blog post
|
||||||
|
* @param string $bodyText - Body of the blog post as plain text
|
||||||
* @param string $dateCreated - Date the blog post was created
|
* @param string $dateCreated - Date the blog post was created
|
||||||
* @param bool $featured - Whether the blog post is featured or not
|
* @param bool $featured - Whether the blog post is featured or not
|
||||||
* @param string $categories - Categories of the blog post
|
* @param string $categories - Categories of the blog post
|
||||||
* @param UploadedFileInterface $headerImg - Header image of the blog post
|
* @param UploadedFileInterface $headerImg - Header image of the blog post
|
||||||
* @return int|string - ID of the blog post or error message
|
* @return int|string - ID of the blog post or error message
|
||||||
*/
|
*/
|
||||||
public function createPost(string $title, string $abstract, string $body, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string
|
public function createPost(string $title, string $abstract, string $body, string $bodyText, string $dateCreated, bool $featured, string $categories, UploadedFileInterface $headerImg): int|string
|
||||||
{
|
{
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
$folderID = uniqid();
|
$folderID = uniqid();
|
||||||
@ -282,8 +266,8 @@ class blogData
|
|||||||
$stmtMainProject->execute();
|
$stmtMainProject->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, categories, folderID)
|
$stmt = $conn->prepare("INSERT INTO blog (title, dateCreated, dateModified, featured, headerImg, abstract, body, bodyText, categories, folderID)
|
||||||
VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :categories, :folderID);");
|
VALUES (:title, :dateCreated, :dateModified, :featured, :headerImg, :abstract, :body, :bodyText, :categories, :folderID);");
|
||||||
$stmt->bindParam(":title", $title);
|
$stmt->bindParam(":title", $title);
|
||||||
$stmt->bindParam(":dateCreated", $dateCreated);
|
$stmt->bindParam(":dateCreated", $dateCreated);
|
||||||
$stmt->bindParam(":dateModified", $dateCreated);
|
$stmt->bindParam(":dateModified", $dateCreated);
|
||||||
@ -292,6 +276,7 @@ class blogData
|
|||||||
$stmt->bindParam(":headerImg", $targetFile["imgLocation"]);
|
$stmt->bindParam(":headerImg", $targetFile["imgLocation"]);
|
||||||
$stmt->bindParam(":abstract", $abstract);
|
$stmt->bindParam(":abstract", $abstract);
|
||||||
$stmt->bindParam(":body", $newBody);
|
$stmt->bindParam(":body", $newBody);
|
||||||
|
$stmt->bindParam(":bodyText", $bodyText);
|
||||||
$stmt->bindParam(":categories", $categories);
|
$stmt->bindParam(":categories", $categories);
|
||||||
$stmt->bindParam(":folderID", $folderID);
|
$stmt->bindParam(":folderID", $folderID);
|
||||||
|
|
||||||
@ -443,4 +428,103 @@ class blogData
|
|||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a blog post with the given search term
|
||||||
|
* @param string $searchTerm - Search term
|
||||||
|
* @return array - Array of all posts with the given search term or error message
|
||||||
|
*/
|
||||||
|
public function searchBlog(string $searchTerm): array
|
||||||
|
{
|
||||||
|
$conn = dbConn();
|
||||||
|
$stmt = $conn->prepare("SELECT * FROM blog WHERE MATCH(title, bodyText) AGAINST(:searchTerm IN NATURAL LANGUAGE MODE);");
|
||||||
|
$stmt->bindParam(":searchTerm", $searchTerm);
|
||||||
|
$stmt->execute();
|
||||||
|
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($result)
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < count($result); $i++)
|
||||||
|
{
|
||||||
|
$result[$i]["abstract"] = $this->getShortPost($searchTerm, stripcslashes($result[$i]["bodyText"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array("errorMessage" => "Error, could not find posts");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the short post with the search term
|
||||||
|
* @param string $searchTerm - Search term
|
||||||
|
* @param $text - Body of the post as plain text
|
||||||
|
* @return string - Short post with the search term
|
||||||
|
*/
|
||||||
|
private function getShortPost(string $searchTerm, $text): string
|
||||||
|
{
|
||||||
|
$pattern = '/([,:;!?.-]+)/u';
|
||||||
|
$parts = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
|
$cleanedParts = [];
|
||||||
|
|
||||||
|
foreach ($parts as $part)
|
||||||
|
{
|
||||||
|
$part = trim($part); // Remove leading/trailing spaces and newline characters
|
||||||
|
if (!empty($part))
|
||||||
|
{
|
||||||
|
$cleanedParts[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$combinedParts = [];
|
||||||
|
$currentPart = '';
|
||||||
|
|
||||||
|
foreach ($cleanedParts as $part)
|
||||||
|
{
|
||||||
|
if (preg_match('/[,:;!?.-]/u', $part))
|
||||||
|
{
|
||||||
|
$currentPart .= $part;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!empty($currentPart))
|
||||||
|
{
|
||||||
|
$combinedParts[] = trim($currentPart);
|
||||||
|
}
|
||||||
|
$currentPart = rtrim($part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($currentPart))
|
||||||
|
{
|
||||||
|
$combinedParts[] = trim($currentPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = "";
|
||||||
|
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($combinedParts); $i++)
|
||||||
|
{
|
||||||
|
$part = $combinedParts[$i];
|
||||||
|
|
||||||
|
if (stripos($part, $searchTerm) !== false)
|
||||||
|
{
|
||||||
|
$before = ($i > 0) ? $combinedParts[$i - 1] : "";
|
||||||
|
$after = ($i < count($combinedParts) - 1) ? $combinedParts[$i + 1] : "";
|
||||||
|
|
||||||
|
if ($before === "" && $i > 0)
|
||||||
|
{
|
||||||
|
$before = $combinedParts[$i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $before . " " . $part . " " . $after;
|
||||||
|
|
||||||
|
// If the search term is found, we don't need to continue checking subsequent parts
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
@ -123,12 +123,34 @@ class blogRoutes implements routesInterface
|
|||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
|
||||||
|
{
|
||||||
|
if ($args["searchTerm"] != null)
|
||||||
|
{
|
||||||
|
$posts = $this->blogData->searchBlog($args["searchTerm"]);
|
||||||
|
|
||||||
|
$json = json_encode($posts);
|
||||||
|
|
||||||
|
$response->getBody()->write($json);
|
||||||
|
|
||||||
|
if (array_key_exists("errorMessage", $posts))
|
||||||
|
{
|
||||||
|
$response->withStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
|
||||||
|
return $response->withStatus(400);
|
||||||
|
});
|
||||||
|
|
||||||
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if ($args["id"] != null)
|
if ($args["id"] != null)
|
||||||
{
|
{
|
||||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
||||||
{
|
{
|
||||||
// uh oh sent some empty data
|
// uh oh sent some empty data
|
||||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||||
@ -142,7 +164,7 @@ class blogRoutes implements routesInterface
|
|||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["dateModified"], $data["categories"]);
|
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $data["body"], $data["bodyText"], $data["dateModified"], $data["categories"]);
|
||||||
|
|
||||||
if ($message === "post not found")
|
if ($message === "post not found")
|
||||||
{
|
{
|
||||||
@ -232,7 +254,7 @@ class blogRoutes implements routesInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$featured = $data["featured"] === "true";
|
$featured = $data["featured"] === "true";
|
||||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
||||||
if (!is_int($insertedID))
|
if (!is_int($insertedID))
|
||||||
{
|
{
|
||||||
// uh oh something went wrong
|
// uh oh something went wrong
|
||||||
|
@ -26,8 +26,47 @@ h3 {
|
|||||||
line-height: 2.1875rem;
|
line-height: 2.1875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.menu {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 5px solid var(--mutedGrey);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu input:not([type="submit"]) {
|
||||||
|
width: auto;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu > ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li button.btn {
|
||||||
|
padding: initial;
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li input:not([type="submit"]):focus + button.btn,
|
||||||
|
div.menu ul li:hover button.btn,
|
||||||
|
div.menu ul li:focus button.btn {
|
||||||
|
background: var(--primaryHover);
|
||||||
|
border: 0.3215em solid var(--primaryHover);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li:hover input:not([type="submit"]),
|
||||||
|
div.menu ul li:focus input:not([type="submit"]) {
|
||||||
|
border: 0.3215em solid var(--primaryHover);
|
||||||
|
}
|
||||||
|
|
||||||
section.largePost {
|
section.largePost {
|
||||||
/*margin: 0 5em;*/
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
|
@ -49,6 +49,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div class="menuBar">
|
||||||
|
<div class="menu">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/blog" class="link active">All posts</a></li>
|
||||||
|
<li><a href="/blog/category" class="link">categories</a></li>
|
||||||
|
<li>
|
||||||
|
<label for="searchField" aria-hidden="true" hidden>search</label>
|
||||||
|
<input type="search" name="search" id="searchField"
|
||||||
|
placeholder="Search...">
|
||||||
|
<button type="submit" id="searchBtn" class="btn btnPrimary"><i
|
||||||
|
class="fa fa-search"></i></button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main id="main">
|
<main id="main">
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
@ -44,7 +44,6 @@ function goToURL(url)
|
|||||||
if (urlArray[2] === 'post')
|
if (urlArray[2] === 'post')
|
||||||
{
|
{
|
||||||
// Create a new URL with the dynamic part
|
// Create a new URL with the dynamic part
|
||||||
// window.history.pushState(null, null, url);
|
|
||||||
loadIndividualPost(urlArray[urlArray.length - 1]).catch(err => console.log(err));
|
loadIndividualPost(urlArray[urlArray.length - 1]).catch(err => console.log(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -52,14 +51,20 @@ function goToURL(url)
|
|||||||
if (urlArray[2] === 'category')
|
if (urlArray[2] === 'category')
|
||||||
{
|
{
|
||||||
// Create a new URL with the dynamic part
|
// Create a new URL with the dynamic part
|
||||||
// window.history.pushState(null, null, url);
|
|
||||||
if (urlArray[3])
|
if (urlArray[3])
|
||||||
{
|
{
|
||||||
loadPostsByCategory(urlArray[urlArray.length - 1]);
|
loadPostsByCategory(urlArray[urlArray.length - 1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAllCategories();
|
loadAllCategories().catch(err => console.log(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (urlArray[2] === 'search' && urlArray[3])
|
||||||
|
{
|
||||||
|
// Create a new URL with the dynamic part
|
||||||
|
loadSearchResults(urlArray[urlArray.length - 1]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +72,26 @@ function goToURL(url)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.querySelector('#searchBtn').addEventListener('click', _ =>
|
||||||
|
{
|
||||||
|
let searchTerm = document.querySelector('#searchField').value;
|
||||||
|
if (searchTerm.length > 0)
|
||||||
|
{
|
||||||
|
window.history.pushState(null, null, `/blog/search/${searchTerm}`);
|
||||||
|
document.querySelector('#searchField').value = '';
|
||||||
|
document.querySelector('#main').innerHTML = '';
|
||||||
|
goToURL(`/blog/search/${searchTerm}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('#searchField').addEventListener('keyup', e =>
|
||||||
|
{
|
||||||
|
if (e.key === 'Enter')
|
||||||
|
{
|
||||||
|
document.querySelector('#searchBtn').click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a large post element
|
* Creates a large post element
|
||||||
* @param post the post object
|
* @param post the post object
|
||||||
@ -130,10 +155,10 @@ function loadHomeContent()
|
|||||||
featuredPost.appendChild(h1);
|
featuredPost.appendChild(h1);
|
||||||
let outerContent = createLargePost(json[i]);
|
let outerContent = createLargePost(json[i]);
|
||||||
featuredPost.appendChild(outerContent);
|
featuredPost.appendChild(outerContent);
|
||||||
document.querySelector('#main').prepend(featuredPost);
|
document.querySelector('#main').appendChild(featuredPost);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === 0)
|
if (i === 1)
|
||||||
{
|
{
|
||||||
let latestPost = document.createElement('section');
|
let latestPost = document.createElement('section');
|
||||||
latestPost.classList.add('largePost');
|
latestPost.classList.add('largePost');
|
||||||
@ -143,8 +168,9 @@ function loadHomeContent()
|
|||||||
latestPost.appendChild(h1);
|
latestPost.appendChild(h1);
|
||||||
let outerContent = createLargePost(json[i]);
|
let outerContent = createLargePost(json[i]);
|
||||||
latestPost.appendChild(outerContent);
|
latestPost.appendChild(outerContent);
|
||||||
document.querySelector('#main').prepend(latestPost);
|
document.querySelector('#main').appendChild(latestPost);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -438,6 +464,34 @@ function loadPostsByCategory(category)
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadSearchResults(searchTerm)
|
||||||
|
{
|
||||||
|
document.title = 'Rohit Pai - Search Results for ' + decodeURI(searchTerm);
|
||||||
|
fetch(`/api/blog/search/${searchTerm}`).then(res => res.json().then(json =>
|
||||||
|
{
|
||||||
|
let main = document.querySelector('#main');
|
||||||
|
let posts = document.createElement('section');
|
||||||
|
posts.classList.add('catPosts');
|
||||||
|
posts.id = 'searchResults';
|
||||||
|
let h1 = document.createElement('h1');
|
||||||
|
h1.innerHTML = 'Search Results';
|
||||||
|
main.appendChild(h1);
|
||||||
|
for (let i = 0; i < json.length; i++)
|
||||||
|
{
|
||||||
|
let largePost = document.createElement('section');
|
||||||
|
largePost.classList.add('largePost');
|
||||||
|
if (i < json.length - 1)
|
||||||
|
{
|
||||||
|
largePost.classList.add('categoryPost');
|
||||||
|
}
|
||||||
|
let outerContent = createLargePost(json[i]);
|
||||||
|
largePost.appendChild(outerContent);
|
||||||
|
posts.appendChild(largePost);
|
||||||
|
}
|
||||||
|
main.appendChild(posts);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the 404 page
|
* Shows the 404 page
|
||||||
*/
|
*/
|
||||||
|
@ -64,7 +64,6 @@ nav ul li span {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
nav ul li .active::before,
|
nav ul li .active::before,
|
||||||
nav ul li .active::after {
|
nav ul li .active::after {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
@ -108,8 +108,9 @@ a.btnPrimary[disabled]:hover, button.btnPrimary[disabled]:hover {
|
|||||||
border: 0.3215em solid var(--notAvailableHover);
|
border: 0.3215em solid var(--notAvailableHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btnPrimary:hover, button.btnPrimary:hover form input[type="submit"]:hover {
|
a.btnPrimary:hover, button.btnPrimary:hover, form input[type="submit"]:hover {
|
||||||
background: var(--primaryHover);
|
background: var(--primaryHover);
|
||||||
|
border: 0.3215em solid var(--primaryHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn:active, button.btn:active, form input[type="submit"]:active {
|
a.btn:active, button.btn:active, form input[type="submit"]:active {
|
||||||
@ -129,18 +130,14 @@ a.btn:active, button.btn:active, form input[type="submit"]:active {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
||||||
border: 4px solid var(--errorDefault);
|
border: 0.3125em solid var(--errorDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
|
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
|
||||||
border: 4px solid var(--errorHover);
|
border: 0.3125em solid var(--errorHover);
|
||||||
box-shadow: 0 4px 2px 0 var(--mutedBlack);
|
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"]) {
|
form .formControl input:not([type="submit"]) {
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
@ -150,7 +147,6 @@ form .formControl {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
/*align-items: flex-start;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl.passwordControl {
|
form .formControl.passwordControl {
|
||||||
@ -163,13 +159,13 @@ form input[type="submit"] {
|
|||||||
|
|
||||||
form .formControl input:not([type="submit"]), form .formControl textarea,
|
form .formControl input:not([type="submit"]), form .formControl textarea,
|
||||||
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
|
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
|
||||||
form .formControl .ck.ck-editor__main .ck-content {
|
form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 4px solid var(--primaryDefault);
|
border: 0.3125em solid var(--primaryDefault);
|
||||||
background: none;
|
background: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
-webkit-border-radius: 1em;
|
-webkit-border-radius: 0.5em;
|
||||||
-moz-border-radius: 1em;
|
-moz-border-radius: 0.5em;
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
@ -179,17 +175,18 @@ form .formControl textarea {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
||||||
border: 4px solid var(--errorDefault);
|
border: 0.3125em solid var(--errorDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
|
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
|
||||||
border: 4px solid var(--errorHover);
|
border: 0.3125em solid var(--errorHover);
|
||||||
box-shadow: 0 4px 2px 0 var(--mutedBlack);
|
box-shadow: 0 4px 2px 0 var(--mutedBlack);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
|
form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
|
||||||
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover {
|
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover,
|
||||||
border: 4px solid var(--primaryHover);
|
div.menu input:not([type="submit"]):focus, div.menu input:not([type="submit"]):hover {
|
||||||
|
border: 0.3125em solid var(--primaryHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]) {
|
form .formControl input:not([type="submit"]) {
|
||||||
|
@ -45,7 +45,7 @@ main.editor section {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
section#editPost {
|
section#curriculumVitae {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,10 +200,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
<label for="postCategories">Categories</label>
|
<label for="postCategories">Categories</label>
|
||||||
<input type="text" name="postCategories" id="postCategories"
|
<input type="text" name="postCategories" id="postCategories" title="CSV format" required>
|
||||||
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
|
|
||||||
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
|
|
||||||
oninput="this.setCustomValidity('')" required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
@ -264,10 +261,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
<label for="editPostCategories">Categories</label>
|
<label for="editPostCategories">Categories</label>
|
||||||
<input type="text" name="editPostCategories" id="editPostCategories"
|
<input type="text" name="editPostCategories" id="editPostCategories" title="CSV format" required>
|
||||||
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
|
|
||||||
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
|
|
||||||
oninput="this.setCustomValidity('')" required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
|
@ -2,6 +2,7 @@ let dateOptions = {month: 'short', year: 'numeric'};
|
|||||||
let textareaLoaded = false;
|
let textareaLoaded = false;
|
||||||
let editors = {};
|
let editors = {};
|
||||||
let posts = null;
|
let posts = null;
|
||||||
|
const smallPaddingElements = ['figcaption', 'li'];
|
||||||
document.addEventListener('DOMContentLoaded', () =>
|
document.addEventListener('DOMContentLoaded', () =>
|
||||||
{
|
{
|
||||||
// check if the userData is logged in, if not redirect to log in
|
// check if the userData is logged in, if not redirect to log in
|
||||||
@ -262,6 +263,7 @@ document.querySelector("#addPostForm").addEventListener("submit", e =>
|
|||||||
data.append("featured", document.querySelector("#isFeatured").checked ? "1" : "0");
|
data.append("featured", document.querySelector("#isFeatured").checked ? "1" : "0");
|
||||||
data.append("abstract", document.querySelector("#postAbstract").value);
|
data.append("abstract", document.querySelector("#postAbstract").value);
|
||||||
data.append("body", editors["CKEditorAddPost"].getData());
|
data.append("body", editors["CKEditorAddPost"].getData());
|
||||||
|
data.append('bodyText', viewToPlainText(editors['CKEditorAddPost'].editing.view.document.getRoot()));
|
||||||
data.append("dateCreated", new Date().toISOString().slice(0, 19).replace('T', ' '));
|
data.append("dateCreated", new Date().toISOString().slice(0, 19).replace('T', ' '));
|
||||||
data.append('categories', document.querySelector('#postCategories').value.toLowerCase());
|
data.append('categories', document.querySelector('#postCategories').value.toLowerCase());
|
||||||
data.append("headerImg", document.querySelector("#headerImg").files[0]);
|
data.append("headerImg", document.querySelector("#headerImg").files[0]);
|
||||||
@ -315,6 +317,7 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
|
|||||||
data["featured"] = document.querySelector("#editIsFeatured").checked ? "1" : "0";
|
data["featured"] = document.querySelector("#editIsFeatured").checked ? "1" : "0";
|
||||||
data["abstract"] = document.querySelector("#editPostAbstract").value;
|
data["abstract"] = document.querySelector("#editPostAbstract").value;
|
||||||
data["body"] = editors["CKEditorEditPost"].getData();
|
data["body"] = editors["CKEditorEditPost"].getData();
|
||||||
|
data['bodyText'] = viewToPlainText(editors['CKEditorEditPost'].editing.view.document.getRoot());
|
||||||
data["dateModified"] = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
data["dateModified"] = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||||
data['categories'] = document.querySelector('#editPostCategories').value.toLowerCase();
|
data['categories'] = document.querySelector('#editPostCategories').value.toLowerCase();
|
||||||
|
|
||||||
@ -364,6 +367,7 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
|
|||||||
{
|
{
|
||||||
document.querySelector("#editPostForm").reset();
|
document.querySelector("#editPostForm").reset();
|
||||||
document.querySelector("#editPostForm input[type='submit']").id = "";
|
document.querySelector("#editPostForm input[type='submit']").id = "";
|
||||||
|
console.log();
|
||||||
editors["CKEditorEditPost"].setData("");
|
editors["CKEditorEditPost"].setData("");
|
||||||
showSuccessMessage("Post edited successfully", "editPost");
|
showSuccessMessage("Post edited successfully", "editPost");
|
||||||
return;
|
return;
|
||||||
@ -468,6 +472,62 @@ function goToPage(id)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts th CKEditor data to plain text
|
||||||
|
* @param viewItem - The CKEditor data
|
||||||
|
* @returns {string} - The plain text
|
||||||
|
*/
|
||||||
|
function viewToPlainText(viewItem)
|
||||||
|
{
|
||||||
|
let text = '';
|
||||||
|
|
||||||
|
if (viewItem.is('$text') || viewItem.is('$textProxy'))
|
||||||
|
{
|
||||||
|
// If item is `Text` or `TextProxy` simple take its text data.
|
||||||
|
text = viewItem.data;
|
||||||
|
}
|
||||||
|
// else if (viewItem.is('element', 'img') && viewItem.hasAttribute('alt'))
|
||||||
|
// {
|
||||||
|
// // Special case for images - use alt attribute if it is provided.
|
||||||
|
// text = viewItem.getAttribute('alt');
|
||||||
|
// }
|
||||||
|
else if (viewItem.is('element', 'br'))
|
||||||
|
{
|
||||||
|
// A soft break should be converted into a single line break (#8045).
|
||||||
|
text = '\n';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Other elements are document fragments, attribute elements or container elements.
|
||||||
|
// They don't have their own text value, so convert their children.
|
||||||
|
let prev = null;
|
||||||
|
|
||||||
|
for (const child of viewItem.getChildren())
|
||||||
|
{
|
||||||
|
const childText = viewToPlainText(child);
|
||||||
|
|
||||||
|
// Separate container element children with one or more new-line characters.
|
||||||
|
if (prev && (prev.is('containerElement') || child.is('containerElement')))
|
||||||
|
{
|
||||||
|
if (smallPaddingElements.includes(prev.name) || smallPaddingElements.includes(child.name))
|
||||||
|
{
|
||||||
|
text += '\n';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text += '\n\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text += childText;
|
||||||
|
prev = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the active class from all nav items and adds it to the one with the given id
|
* Removes the active class from all nav items and adds it to the one with the given id
|
||||||
* @param {string} id - The id to add the active class to
|
* @param {string} id - The id to add the active class to
|
||||||
@ -768,7 +828,7 @@ function createEditors(...ids)
|
|||||||
console.warn('Build id: 1eo8ioyje2om-vgar4aghypdm');
|
console.warn('Build id: 1eo8ioyje2om-vgar4aghypdm');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1250,7 +1310,7 @@ function addProject(ID, isMainProject, imgLocation, title, information, projectL
|
|||||||
<input type="checkbox" id="isMainProject${id}" name="isMainProject${id}" ${(isMainProject === "true" ? "checked=''" : "")}>
|
<input type="checkbox" id="isMainProject${id}" name="isMainProject${id}" ${(isMainProject === "true" ? "checked=''" : "")}>
|
||||||
<span class="checkmark"></span>
|
<span class="checkmark"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="formControl infoContainer">
|
<div class="formControl infoContainer">
|
||||||
<textarea name="info${id}" id="info${id}" disabled>${information}</textarea>
|
<textarea name="info${id}" id="info${id}" disabled>${information}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<section id="about">
|
<section id="about">
|
||||||
<h1>about</h1>
|
<h1>about</h1>
|
||||||
<div>
|
<div>
|
||||||
<p>Hi, I'm Rohit, a computer science student at The University of Nottingham with experience in multiple
|
<p>Hi, I'm Rohit, a Full Stack Developer at Cadonix with experience in multiple
|
||||||
programming languages such as Java, C#, Python, HTML, CSS, JS, PHP. Bringing forth a motivated
|
programming languages such as Java, C#, Python, HTML, CSS, JS, PHP. Bringing forth a motivated
|
||||||
attitude and a variety of powerful skills. Very good at bringing a team together to get a project
|
attitude and a variety of powerful skills. Very good at bringing a team together to get a project
|
||||||
finished. Below are some of my projects that I have worked on. </p>
|
finished. Below are some of my projects that I have worked on. </p>
|
||||||
|
Loading…
Reference in New Issue
Block a user