Compare commits
38 Commits
3db2520339
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 00851d37b8 | |||
| a55ba6ce2f | |||
| c2e01dd1e8 | |||
| 860c52e829 | |||
| b28e7b2da5 | |||
| 7d6eeb2310 | |||
| 558ac03fbb | |||
| 646cfa6561 | |||
| 591db4dfa3 | |||
| 7f96aa9277 | |||
| 7b8e81e1f7 | |||
| 430e1c65ca | |||
| 364e2d2675 | |||
| 804d8a9390 | |||
| a5f17a70ed | |||
| 62f871f4ca | |||
| 0cb57d0813 | |||
| 52614e5835 | |||
| e6522fb05e | |||
| 5b063afad3 | |||
| f27a5113b1 | |||
| 6cfea3fc98 | |||
| d8a7901574 | |||
| f3f68717ee | |||
| f54ed2f8fb | |||
| b4ab7900db | |||
| 929060ce70 | |||
| 03f14ba174 | |||
| 801e336c29 | |||
| d3a8ff927c | |||
| 5878dbaf9a | |||
| edabd92c17 | |||
| 730b822a2b | |||
| 9e94f6cade | |||
| a868136a99 | |||
| fbf9449116 | |||
| 9349f73016 | |||
| b06614a8c7 |
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"require": {
|
||||
"slim/slim-skeleton": "^4.3",
|
||||
"ext-pdo": "*",
|
||||
"slim/psr7": "^1.4",
|
||||
@@ -15,6 +15,16 @@
|
||||
"rbdwllr/psr-jwt": "^2.0",
|
||||
"tuupola/slim-jwt-auth": "^3.6",
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*"
|
||||
"ext-libxml": "*",
|
||||
"donatello-za/rake-php-plus": "^1.0",
|
||||
"phpmailer/phpmailer": "^6.9",
|
||||
"onelogin/php-saml": "^4.1",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https:\/\/www.phpclasses.org\/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace api\blog;
|
||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||
require_once "blogData.php";
|
||||
|
||||
|
||||
use api\utils\routesInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
@@ -80,35 +81,15 @@ class blogRoutes implements routesInterface
|
||||
|
||||
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["type"] != null)
|
||||
if ($args["type"] == null)
|
||||
{
|
||||
if ($args["type"] == "latest")
|
||||
{
|
||||
$post = $this->blogData->getLatestBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($args["type"] == "featured")
|
||||
{
|
||||
$post = $this->blogData->getFeaturedBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
}
|
||||
|
||||
$post = $this->blogData->getBlogPost($args["type"]);
|
||||
if ($args["type"] == "latest")
|
||||
{
|
||||
$post = $this->blogData->getLatestBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
@@ -119,100 +100,208 @@ class blogRoutes implements routesInterface
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||
if ($args["type"] == "featured")
|
||||
{
|
||||
$post = $this->blogData->getFeaturedBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
}
|
||||
|
||||
$post = $this->blogData->getBlogPost($args["type"]);
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
|
||||
});
|
||||
|
||||
$app->get("/blog/feed/{type}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["type"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$feed = $this->blogData->getFeed($args["type"]);
|
||||
if (is_array($feed))
|
||||
{
|
||||
$response->getBody()->write(json_encode($feed));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
if ($args["type"] == "atom")
|
||||
{
|
||||
$response->getBody()->write($feed);
|
||||
return $response->withHeader("Content-Type", "application/atom+xml");
|
||||
}
|
||||
|
||||
if ($args["type"] == "rss")
|
||||
{
|
||||
$response->getBody()->write($feed);
|
||||
return $response->withHeader("Content-Type", "application/rss+xml");
|
||||
}
|
||||
|
||||
if ($args["type"] == "json")
|
||||
{
|
||||
$response->getBody()->write(json_encode($feed));
|
||||
return $response->withHeader("Content-Type", "application/feed+json");
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Invalid feed type")));
|
||||
return $response->withStatus(400);
|
||||
});
|
||||
|
||||
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
|
||||
{
|
||||
if ($args["searchTerm"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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;
|
||||
|
||||
});
|
||||
|
||||
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
$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"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $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);
|
||||
}
|
||||
|
||||
$response->withStatus(201);
|
||||
return $response;
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
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
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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")
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
$response->withStatus(201);
|
||||
return $response;
|
||||
|
||||
});
|
||||
|
||||
$app->delete("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["id"] != null)
|
||||
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);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
$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;
|
||||
});
|
||||
|
||||
$app->delete("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["email"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->deleteNewsletterEmail($args["email"]);
|
||||
|
||||
if ($message === "email not found")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("message" => "Woah, you're already trying to leave without signing up?")));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
if ($message === "error")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("message" => "Error, something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Sorry to see you go! You'll no longer receive any emails from me. If you change your mind, you can always sign up again.")));
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->post("/blog/post", function (Request $request, Response $response)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
$files = $request->getUploadedFiles();
|
||||
$headerImg = $files["headerImg"];
|
||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||
@@ -226,13 +315,18 @@ class blogRoutes implements routesInterface
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (array_key_exists("headerImg", $files))
|
||||
{
|
||||
$headerImg = $files["headerImg"];
|
||||
}
|
||||
|
||||
if (empty($files["headerImg"]))
|
||||
{
|
||||
$headerImg = null;
|
||||
}
|
||||
|
||||
$featured = $data["featured"] === "true";
|
||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
||||
// $featured = $data["featured"] === "true";
|
||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], intval($data["featured"]), $data["categories"], $headerImg);
|
||||
if (!is_int($insertedID))
|
||||
{
|
||||
// uh oh something went wrong
|
||||
@@ -268,28 +362,75 @@ class blogRoutes implements routesInterface
|
||||
{
|
||||
$files = $request->getUploadedFiles();
|
||||
|
||||
if ($args["id"] != null)
|
||||
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);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
if (empty($files))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "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" => $message)));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($message));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
$app->post("/blog/newsletter", function (Request $request, Response $response)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
if (empty($data["subject"]) || empty($data["message"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->sendNewsletter(strtolower($data["subject"]), $data["message"]);
|
||||
if (is_array($message))
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Message sent")));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
$app->post("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["email"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->addNewsletterEmail($args["email"]);
|
||||
if ($message === "Email already exists")
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("message" => "exists")));
|
||||
return $response->withStatus(409);
|
||||
}
|
||||
|
||||
if (is_array($message) || !$message || $message === "error")
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("message" => "Something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Thanks for signing up!")));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace api\project;
|
||||
use api\utils\imgUtils;
|
||||
use PDO;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use function api\utils\dbConn;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
require_once __DIR__ . "/../utils/imgUtils.php";
|
||||
@@ -17,7 +18,7 @@ class projectData
|
||||
{
|
||||
/**
|
||||
* Get all project data
|
||||
* @return array - Array of all project data or error message
|
||||
* @return array<array> - Array of all project data or error message
|
||||
*/
|
||||
public function getProjectData(): array
|
||||
{
|
||||
|
||||
@@ -50,78 +50,78 @@ class projectRoutes implements routesInterface
|
||||
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
if ($args["id"] != null)
|
||||
if ($args["id"] == null)
|
||||
{
|
||||
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!$update)
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
return $response;
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!$update)
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->delete("/projectData/{id}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
if ($args["id"] != null)
|
||||
if ($args["id"] == null)
|
||||
{
|
||||
$message = $this->projectData->deleteProjectData($args["id"]);
|
||||
|
||||
if ($message === "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 ($message === "cannot delete")
|
||||
{
|
||||
//uh oh cannot delete the main project
|
||||
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
|
||||
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;
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
$message = $this->projectData->deleteProjectData($args["id"]);
|
||||
|
||||
if ($message === "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 ($message === "cannot delete")
|
||||
{
|
||||
//uh oh cannot delete the main project
|
||||
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
|
||||
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;
|
||||
});
|
||||
|
||||
$app->post("/projectData", function (Request $request, Response $response)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace api\timeline;
|
||||
|
||||
use PDO;
|
||||
use function api\utils\dbConn;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
@@ -14,7 +15,7 @@ class timelineData
|
||||
{
|
||||
/**
|
||||
* Get all education data
|
||||
* @return array - Array of all education data or error message
|
||||
* @return array<array> - Array of all education data or error message
|
||||
*/
|
||||
public function getEduData(): array
|
||||
{
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
<?php
|
||||
namespace api\user;
|
||||
use Firebase\JWT\JWT;
|
||||
use PDO;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
/**
|
||||
* User Class
|
||||
* Define all functions which either check, update or delete user data
|
||||
*/
|
||||
class user
|
||||
{
|
||||
/**
|
||||
* Check if user exists and can be logged in
|
||||
* @param $username string - Username
|
||||
* @param $password string - Password
|
||||
* @return bool - True if logged in, false if not
|
||||
*/
|
||||
function checkUser(string $username, string $password): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("SELECT * FROM users WHERE username = :username");
|
||||
$stmt->bindParam(":username", $username);
|
||||
$stmt->execute();
|
||||
|
||||
// set the resulting array to associative
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result)
|
||||
{
|
||||
if (password_verify($password, $result[0]["password"]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a JWT token
|
||||
* @param $username string - Username
|
||||
* @return string - JWT token
|
||||
*/
|
||||
function createToken(string $username): string
|
||||
{
|
||||
$now = time();
|
||||
$future = strtotime('+6 hour',$now);
|
||||
$secretKey = getSecretKey();
|
||||
$payload = [
|
||||
"jti"=>$username,
|
||||
"iat"=>$now,
|
||||
"exp"=>$future
|
||||
];
|
||||
|
||||
return JWT::encode($payload,$secretKey,"HS256");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if email is already in use
|
||||
* @param string $email - Email to check
|
||||
* @return bool - True if email exists, false if not
|
||||
*/
|
||||
function checkEmail(string $email): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("SELECT * FROM users WHERE email = :email");
|
||||
$stmt->bindParam(":email", $email);
|
||||
$stmt->execute();
|
||||
|
||||
// set the resulting array to associative
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a verification email to the user
|
||||
* @param $email - email address of the user
|
||||
* @return string - verification code
|
||||
*/
|
||||
function sendResetEmail($email): string
|
||||
{
|
||||
//generate a random token and email the address
|
||||
$token = uniqid("rpe-");
|
||||
$headers1 = "From: noreply@rohitpai.co.uk\r\n";
|
||||
$headers1 .= "MIME-Version: 1.0\r\n";
|
||||
$headers1 .= "Content-Type: text/html; charset=UTF-8\r\n";
|
||||
|
||||
$message = "
|
||||
<!doctype html>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<meta charset='UTF-8'>
|
||||
<meta name='viewport' content='width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'>
|
||||
<meta http-equiv='X-UA-Compatible' content='ie=edge'>
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Reset Password Verification Code</h1>
|
||||
<br>
|
||||
<p>Please enter the following code to reset your password: $token</p>
|
||||
</body>
|
||||
</html>
|
||||
";
|
||||
|
||||
mail($email, "Reset Password Verification Code", $message, $headers1);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change password for an email with new password
|
||||
* @param $email string Email
|
||||
* @param $password string Password
|
||||
* @return bool - true if the password was changed, false if not
|
||||
*/
|
||||
function changePassword(string $email, string $password): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("UPDATE users SET password = :password WHERE email = :email");
|
||||
$newPwd = password_hash($password, PASSWORD_BCRYPT);
|
||||
$stmt->bindParam(":password", $newPwd);
|
||||
$stmt->bindParam(":email", $email);
|
||||
|
||||
if ($stmt->execute())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -4,6 +4,9 @@ namespace api\user;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use PDO;
|
||||
use function api\utils\dbConn;
|
||||
use function api\utils\getSAMLSettings;
|
||||
use function api\utils\getSecretKey;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
@@ -136,5 +139,38 @@ class userData
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SAML settings
|
||||
* @return array - SAML settings
|
||||
*/
|
||||
public function getSamlConf(): array
|
||||
{
|
||||
return getSAMLSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the SAML user exists
|
||||
* @param string $username - Username
|
||||
* @param string $email - Email
|
||||
* @return bool - True if the user exists, false if not
|
||||
*/
|
||||
public function checkSAMLUser(string $username, string $email): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("SELECT * FROM users WHERE username = :username AND email = :email");
|
||||
$stmt->bindParam(":username", $username);
|
||||
$stmt->bindParam(":email", $email);
|
||||
$stmt->execute();
|
||||
|
||||
// set the resulting array to associative
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ require_once __DIR__ . "/../utils/routesInterface.php";
|
||||
require_once "userData.php";
|
||||
|
||||
use api\utils\routesInterface;
|
||||
use OneLogin\Saml2\Auth;
|
||||
use OneLogin\Saml2\Error;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\App;
|
||||
@@ -12,14 +14,17 @@ use Slim\App;
|
||||
class userRoutes implements routesInterface
|
||||
{
|
||||
private userData $user;
|
||||
private Auth $samlAuth;
|
||||
|
||||
/**
|
||||
* constructor used to instantiate a base user routes, to be used in the index.php file.
|
||||
* constructor used to instantiate base user routes, to be used in the index.php file.
|
||||
* @param App $app - the slim app used to create the routes
|
||||
* @throws Error
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->user = new userData();
|
||||
$this->samlAuth = new Auth($this->user->getSamlConf());
|
||||
$this->createRoutes($app);
|
||||
}
|
||||
|
||||
@@ -30,31 +35,9 @@ class userRoutes implements routesInterface
|
||||
*/
|
||||
public function createRoutes(App $app): void
|
||||
{
|
||||
$app->post("/user/login", function (Request $request, Response $response)
|
||||
$app->get("/user/login", function (Request $request, Response $response)
|
||||
{
|
||||
// get request data
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (empty($data["username"]) || empty($data["password"]))
|
||||
{
|
||||
// uh oh user sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($this->user->checkUser($data["username"], $data["password"]))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
|
||||
return $response->withStatus(401);
|
||||
$this->samlAuth->login();
|
||||
});
|
||||
|
||||
$app->get("/user/logout", function (Request $request, Response $response)
|
||||
@@ -92,6 +75,20 @@ class userRoutes implements routesInterface
|
||||
|
||||
});
|
||||
|
||||
$app->get("/user/metadata", function (Request $request, Response $response)
|
||||
{
|
||||
$settings = $this->samlAuth->getSettings();
|
||||
$metadata = $settings->getSPMetadata();
|
||||
$errors = $settings->validateMetadata($metadata);
|
||||
if (empty($errors))
|
||||
{
|
||||
$response->getBody()->write($metadata);
|
||||
return $response->withHeader("Content-Type", "text/xml");
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => $errors)));
|
||||
return $response->withStatus(500);
|
||||
});
|
||||
|
||||
$app->get("/user/checkResetEmail/{email}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
if (empty($args["email"]))
|
||||
@@ -139,6 +136,58 @@ class userRoutes implements routesInterface
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->post("/user/login", function (Request $request, Response $response)
|
||||
{
|
||||
// get request data
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (empty($data["username"]) || empty($data["password"]))
|
||||
{
|
||||
// uh oh user sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($this->user->checkUser($data["username"], $data["password"]))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->post("/user/acs", function (Request $request, Response $response)
|
||||
{
|
||||
$this->samlAuth->processResponse();
|
||||
|
||||
$attributes = $this->samlAuth->getAttributes();
|
||||
$username = $attributes["username"][0];
|
||||
$email = $attributes["email"][0];
|
||||
|
||||
if ($this->user->checkSAMLUser($username, $email))
|
||||
{
|
||||
// yay, user is logged in
|
||||
$_SESSION["token"] = $this->user->createToken($username);
|
||||
$_SESSION["username"] = $username;
|
||||
$_SESSION["email"] = $email;
|
||||
|
||||
$inactive = 60 * 60 * 48; // 2 days
|
||||
$_SESSION["timeout"] = time() + $inactive;
|
||||
|
||||
return $response->withHeader("Location", "https://rohitpai.co.uk/editor/")->withStatus(302);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->post("/user/changePassword", function (Request $request, Response $response)
|
||||
{
|
||||
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
|
||||
@@ -165,4 +214,4 @@ class userRoutes implements routesInterface
|
||||
return $response->withStatus(500);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace api\utils\feedGenerator;
|
||||
|
||||
/**
|
||||
* Universal Feed Writer
|
||||
*
|
||||
* FeedItem class - Used as a feed element in FeedWriter class
|
||||
*
|
||||
* @package UniversalFeedWriter
|
||||
* @author Anis uddin Ahmad <anisniit@gmail.com>
|
||||
* @link http://www.ajaxray.com/projects/rss
|
||||
*/
|
||||
class FeedItem
|
||||
{
|
||||
private array $elements = []; // Collection of feed elements
|
||||
private string $version;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $version (RSS1/RSS2/ATOM) RSS2 is the default.
|
||||
*/
|
||||
public function __construct(string $version = RSS2)
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to elements array
|
||||
*
|
||||
* @param string $elementName The tag name of an element
|
||||
* @param string $content The content of the tag
|
||||
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
|
||||
*/
|
||||
public function addElement(string $elementName, string $content, ?array $attributes = null): void
|
||||
{
|
||||
$this->elements[$elementName]['name'] = $elementName;
|
||||
$this->elements[$elementName]['content'] = $content;
|
||||
$this->elements[$elementName]['attributes'] = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple feed elements from an array.
|
||||
* Elements that have attributes cannot be added by this method
|
||||
*
|
||||
* @param array $elementArray Array of elements in 'tagName' => 'tagContent' format.
|
||||
*/
|
||||
public function addElementArray(array $elementArray): void
|
||||
{
|
||||
foreach ($elementArray as $elementName => $content)
|
||||
{
|
||||
$this->addElement($elementName, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the collection of elements in this feed item
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getElements(): array
|
||||
{
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
// Wrapper functions ------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the 'description' element of the feed item
|
||||
*
|
||||
* @param string $description The content of the 'description' element
|
||||
*/
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$tag = ($this->version === ATOM) ? 'summary' : 'description';
|
||||
$this->addElement($tag, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'title' element of the feed item
|
||||
*
|
||||
* @param string $title The content of the 'title' element
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->addElement('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'date' element of the feed item
|
||||
*
|
||||
* @param string|int $date The content of the 'date' element
|
||||
*/
|
||||
public function setDate(string|int $date): void
|
||||
{
|
||||
if (!is_numeric($date))
|
||||
{
|
||||
$date = strtotime($date);
|
||||
}
|
||||
|
||||
if ($this->version === ATOM)
|
||||
{
|
||||
$tag = 'updated';
|
||||
$value = date(DATE_ATOM, $date);
|
||||
}
|
||||
elseif ($this->version === RSS2)
|
||||
{
|
||||
$tag = 'pubDate';
|
||||
$value = date(DATE_RSS, $date);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tag = 'dc:date';
|
||||
$value = date("Y-m-d", $date);
|
||||
}
|
||||
|
||||
$this->addElement($tag, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'link' element of the feed item
|
||||
*
|
||||
* @param string $link The content of the 'link' element
|
||||
*/
|
||||
public function setLink(string $link): void
|
||||
{
|
||||
if ($this->version === RSS2 || $this->version === RSS1)
|
||||
{
|
||||
$this->addElement('link', $link);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->addElement('link', '', ['href' => $link]);
|
||||
$this->addElement('id', FeedWriter::uuid($link, 'urn:uuid:'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'encloser' element of the feed item
|
||||
* For RSS 2.0 only
|
||||
*
|
||||
* @param string $url The url attribute of the encloser tag
|
||||
* @param string $length The length attribute of the encloser tag
|
||||
* @param string $type The type attribute of the encloser tag
|
||||
*/
|
||||
public function setEncloser(string $url, string $length, string $type): void
|
||||
{
|
||||
$attributes = ['url' => $url, 'length' => $length, 'type' => $type];
|
||||
$this->addElement('enclosure', '', $attributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
namespace api\utils\feedGenerator;
|
||||
|
||||
require_once "FeedItem.php";
|
||||
|
||||
/**
|
||||
* Universal Feed Writer class
|
||||
*
|
||||
* Generate RSS 1.0, RSS 2.0, and Atom Feed
|
||||
*
|
||||
* @package UniversalFeedWriter
|
||||
* @link http://www.ajaxray.com/projects/rss
|
||||
*/
|
||||
class FeedWriter
|
||||
{
|
||||
private array $channels = []; // Collection of channel elements
|
||||
private array $items = []; // Collection of items as objects of FeedItem class
|
||||
private array $data = []; // Store some other version-wise data
|
||||
private array $CDATAEncoding = []; // The tag names that need to be encoded as CDATA
|
||||
|
||||
private string $version;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $version The version (RSS1, RSS2, ATOM).
|
||||
*/
|
||||
public function __construct(string $version = RSS2)
|
||||
{
|
||||
$this->version = $version;
|
||||
|
||||
// Setting default values for essential channel elements
|
||||
$this->channels['title'] = $version . ' Feed';
|
||||
$this->channels['link'] = 'http://www.ajaxray.com/blog';
|
||||
$this->channels["feedUrl"] = "http://example.com/feed";
|
||||
|
||||
// Tag names to encode in CDATA
|
||||
$this->CDATAEncoding = ['description', 'content:encoded', 'summary'];
|
||||
}
|
||||
|
||||
// Public functions
|
||||
|
||||
/**
|
||||
* Set a channel element
|
||||
*
|
||||
* @param string $elementName Name of the channel tag
|
||||
* @param string $content Content of the channel tag
|
||||
*/
|
||||
public function setChannelElement(string $elementName, string|array $content): void
|
||||
{
|
||||
$this->channels[$elementName] = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the actual RSS/Atom file
|
||||
*/
|
||||
public function generateFeed(): void
|
||||
{
|
||||
$this->printHead();
|
||||
$this->printChannels();
|
||||
$this->printItems();
|
||||
$this->printTail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FeedItem.
|
||||
*
|
||||
* @return FeedItem An instance of FeedItem class
|
||||
*/
|
||||
public function createNewItem(): FeedItem
|
||||
{
|
||||
$item = new FeedItem($this->version);
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a FeedItem to the main class
|
||||
*
|
||||
* @param FeedItem $feedItem An instance of FeedItem class
|
||||
*/
|
||||
public function addItem(FeedItem $feedItem): void
|
||||
{
|
||||
$this->items[] = $feedItem;
|
||||
}
|
||||
|
||||
// Wrapper functions
|
||||
|
||||
/**
|
||||
* Set the 'title' channel element
|
||||
*
|
||||
* @param string $title Value of 'title' channel tag
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->setChannelElement('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'description' channel element
|
||||
*
|
||||
* @param string $description Value of 'description' channel tag
|
||||
*/
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$this->setChannelElement('description', $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'link' channel element
|
||||
*
|
||||
* @param string $link Value of 'link' channel tag
|
||||
*/
|
||||
public function setLink(string $link): void
|
||||
{
|
||||
$this->setChannelElement('link', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'image' channel element
|
||||
*
|
||||
* @param string $title Title of the image
|
||||
* @param string $link Link URL of the image
|
||||
* @param string $url Path URL of the image
|
||||
*/
|
||||
public function setImage(string $title, string $link, string $url): void
|
||||
{
|
||||
$this->setChannelElement('image', ['title' => $title, 'link' => $link, 'url' => $url]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'about' channel element. Only for RSS 1.0
|
||||
*
|
||||
* @param string $url Value of 'about' channel tag
|
||||
*/
|
||||
public function setChannelAbout(string $url): void
|
||||
{
|
||||
$this->data['ChannelAbout'] = $url;
|
||||
}
|
||||
|
||||
// Other functions
|
||||
|
||||
/**
|
||||
* Generates a UUID
|
||||
*
|
||||
* @param string $key An optional prefix
|
||||
* @param string $prefix A prefix
|
||||
* @return string The formatted UUID
|
||||
*/
|
||||
public static function uuid(?string $key = null, string $prefix = ''): string
|
||||
{
|
||||
$key = $key ?? uniqid((string)rand());
|
||||
$chars = md5($key);
|
||||
$uuid = substr($chars, 0, 8) . '-';
|
||||
$uuid .= substr($chars, 8, 4) . '-';
|
||||
$uuid .= substr($chars, 12, 4) . '-';
|
||||
$uuid .= substr($chars, 16, 4) . '-';
|
||||
$uuid .= substr($chars, 20, 12);
|
||||
|
||||
return $prefix . $uuid;
|
||||
}
|
||||
|
||||
// Private functions
|
||||
|
||||
/**
|
||||
* Prints the XML and RSS namespace
|
||||
*/
|
||||
private function printHead(): void
|
||||
{
|
||||
$out = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
||||
|
||||
if ($this->version == RSS2)
|
||||
{
|
||||
$out .= '<rss version="2.0"
|
||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
|
||||
>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == RSS1)
|
||||
{
|
||||
$out .= '<rdf:RDF
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://purl.org/rss/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
$out .= '<feed xmlns="http://www.w3.org/2005/Atom">' . PHP_EOL;
|
||||
}
|
||||
echo $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the open tags at the end of the file
|
||||
*/
|
||||
private function printTail(): void
|
||||
{
|
||||
if ($this->version == RSS2)
|
||||
{
|
||||
echo '</channel>' . PHP_EOL . '</rss>';
|
||||
}
|
||||
elseif ($this->version == RSS1)
|
||||
{
|
||||
echo '</rdf:RDF>';
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
echo '</feed>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single node in XML format
|
||||
*
|
||||
* @param string $tagName Name of the tag
|
||||
* @param mixed $tagContent Tag value as a string or an array of nested tags in 'tagName' => 'tagValue' format
|
||||
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
|
||||
* @return string Formatted XML tag
|
||||
*/
|
||||
private function makeNode(string $tagName, $tagContent, ?array $attributes = null): string
|
||||
{
|
||||
$nodeText = '';
|
||||
$attrText = '';
|
||||
|
||||
if (is_array($attributes))
|
||||
{
|
||||
foreach ($attributes as $key => $value)
|
||||
{
|
||||
$attrText .= " $key=\"$value\"";
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($tagContent) && $this->version == RSS1)
|
||||
{
|
||||
$attrText = ' rdf:parseType="Resource"';
|
||||
}
|
||||
|
||||
$attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == ATOM) ? ' type="html" ' : '';
|
||||
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "<{$tagName}{$attrText}><![CDATA[" : "<{$tagName}{$attrText}>";
|
||||
|
||||
if (is_array($tagContent))
|
||||
{
|
||||
foreach ($tagContent as $key => $value)
|
||||
{
|
||||
$nodeText .= $this->makeNode($key, $value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $tagContent : htmlentities($tagContent);
|
||||
}
|
||||
|
||||
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "]]></$tagName>" : "</$tagName>";
|
||||
|
||||
return $nodeText . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints channel elements
|
||||
*/
|
||||
private function printChannels(): void
|
||||
{
|
||||
// Start channel tag
|
||||
switch ($this->version)
|
||||
{
|
||||
case RSS2:
|
||||
echo '<channel>' . PHP_EOL;
|
||||
break;
|
||||
case RSS1:
|
||||
echo (isset($this->data['ChannelAbout'])) ? "<channel rdf:about=\"{$this->data['ChannelAbout']}\">" : "<channel rdf:about=\"{$this->channels['link']}\">";
|
||||
break;
|
||||
}
|
||||
|
||||
// Print items of channel
|
||||
foreach ($this->channels as $key => $value)
|
||||
{
|
||||
if ($this->version == ATOM && $key == 'link')
|
||||
{
|
||||
// ATOM prints the link element as an href attribute
|
||||
echo $this->makeNode($key, '', ['href' => $value]);
|
||||
// Add the id for ATOM
|
||||
echo $this->makeNode('id', $this->uuid($value, 'urn:uuid:'));
|
||||
}
|
||||
else if ($this->version == ATOM && $key == 'feedUrl')
|
||||
{
|
||||
echo $this->makeNode('link', '', ['rel' => 'self', 'href' => $value]);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo $this->makeNode($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// RSS 1.0 has a special tag <rdf:Seq> with channel
|
||||
if ($this->version == RSS1)
|
||||
{
|
||||
echo "<items>" . PHP_EOL . "<rdf:Seq>" . PHP_EOL;
|
||||
foreach ($this->items as $item)
|
||||
{
|
||||
$thisItems = $item->getElements();
|
||||
echo "<rdf:li resource=\"{$thisItems['link']['content']}\"/>" . PHP_EOL;
|
||||
}
|
||||
echo "</rdf:Seq>" . PHP_EOL . "</items>" . PHP_EOL . "</channel>" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints formatted feed items
|
||||
*/
|
||||
private function printItems(): void
|
||||
{
|
||||
foreach ($this->items as $item)
|
||||
{
|
||||
$thisItems = $item->getElements();
|
||||
|
||||
// The argument is printed as rdf:about attribute of item in RSS 1.0
|
||||
echo $this->startItem($thisItems['link']['content']);
|
||||
|
||||
foreach ($thisItems as $feedItem)
|
||||
{
|
||||
echo $this->makeNode($feedItem['name'], $feedItem['content'], $feedItem['attributes']);
|
||||
}
|
||||
echo $this->endItem();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the starting tag of items
|
||||
*
|
||||
* @param string|false $about The value of about tag, which is used only for RSS 1.0
|
||||
*/
|
||||
private function startItem($about = false): void
|
||||
{
|
||||
if ($this->version == RSS2)
|
||||
{
|
||||
echo '<item>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == RSS1)
|
||||
{
|
||||
if ($about)
|
||||
{
|
||||
echo "<item rdf:about=\"$about\">" . PHP_EOL;
|
||||
}
|
||||
else
|
||||
{
|
||||
die("link element is not set.\n It's required for RSS 1.0 to be used as the about attribute of the item");
|
||||
}
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
echo "<entry>" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the feed item tag
|
||||
*/
|
||||
private function endItem(): void
|
||||
{
|
||||
if ($this->version == RSS2 || $this->version == RSS1)
|
||||
{
|
||||
echo '</item>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
echo "</entry>" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Feed URL
|
||||
* @param string $string - The URL of the feed
|
||||
* @return void
|
||||
*/
|
||||
public function setFeedURL(string $string): void
|
||||
{
|
||||
$this->setChannelElement("feedUrl", $string);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Define constants for RSS 1.0, RSS 2.0, and Atom
|
||||
const RSS1 = 'RSS 1.0';
|
||||
const RSS2 = 'RSS 2.0';
|
||||
const ATOM = 'ATOM';
|
||||
@@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException;
|
||||
use Slim\Exception\HttpMethodNotAllowedException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Slim\Psr7\Response;
|
||||
use Throwable;
|
||||
use Tuupola\Middleware\JwtAuthentication;
|
||||
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
|
||||
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
|
||||
@@ -65,7 +66,12 @@ class middleware
|
||||
$app->add(function ($request, $handler)
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
return $response->withHeader("Content-Type", "application/json");
|
||||
$contentType = $response->getHeaderLine("Content-Type");
|
||||
if (empty($contentType) || $contentType === "text/html")
|
||||
{
|
||||
return $response->withHeader("Content-Type", "application/json");
|
||||
}
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,8 +85,8 @@ class middleware
|
||||
$app->add(new JwtAuthentication([
|
||||
"rules" => [
|
||||
new RequestPathRule([
|
||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
|
||||
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"]
|
||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"],
|
||||
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
|
||||
]),
|
||||
new RequestMethodRule([
|
||||
"ignore" => ["OPTIONS", "GET"]
|
||||
@@ -128,8 +134,27 @@ class middleware
|
||||
return $response;
|
||||
}
|
||||
});
|
||||
$app->addErrorMiddleware(true, true, true);
|
||||
|
||||
|
||||
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
|
||||
|
||||
|
||||
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
|
||||
|
||||
$errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception,
|
||||
bool $displayErrorDetails,
|
||||
bool $logErrors,
|
||||
bool $logErrorDetails
|
||||
) use ($app, $errorHandler)
|
||||
{
|
||||
$statusCode = $exception->getCode() ?: 500;
|
||||
|
||||
// Create a JSON response with the error message
|
||||
$response = $app->getResponseFactory()->createResponse($statusCode);
|
||||
$response->getBody()->write(json_encode(['error' => $exception->getMessage()]));
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers+show-language+inline-color+previewers+unescaped-markup+toolbar+copy-to-clipboard+download-button+match-braces */
|
||||
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
|
||||
div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}
|
||||
span.inline-color-wrapper{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=);background-position:center;background-size:110%;display:inline-block;height:1.333ch;width:1.333ch;margin:0 .333ch;box-sizing:border-box;border:1px solid #fff;outline:1px solid rgba(0,0,0,.5);overflow:hidden}span.inline-color{display:block;height:120%;width:120%}
|
||||
.prism-previewer,.prism-previewer:after,.prism-previewer:before{position:absolute;pointer-events:none}.prism-previewer,.prism-previewer:after{left:50%}.prism-previewer{margin-top:-48px;width:32px;height:32px;margin-left:-16px;z-index:10;opacity:0;-webkit-transition:opacity .25s;-o-transition:opacity .25s;transition:opacity .25s}.prism-previewer.flipped{margin-top:0;margin-bottom:-48px}.prism-previewer:after,.prism-previewer:before{content:'';position:absolute;pointer-events:none}.prism-previewer:before{top:-5px;right:-5px;left:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer:after{top:100%;width:0;height:0;margin:5px 0 0 -7px;border:7px solid transparent;border-color:rgba(255,0,0,0);border-top-color:#fff}.prism-previewer.flipped:after{top:auto;bottom:100%;margin-top:0;margin-bottom:5px;border-top-color:rgba(255,0,0,0);border-bottom-color:#fff}.prism-previewer.active{opacity:1}.prism-previewer-angle:before{border-radius:50%;background:#fff}.prism-previewer-angle:after{margin-top:4px}.prism-previewer-angle svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-angle[data-negative] svg{-webkit-transform:scaleX(-1) rotate(-90deg);-moz-transform:scaleX(-1) rotate(-90deg);-ms-transform:scaleX(-1) rotate(-90deg);-o-transform:scaleX(-1) rotate(-90deg);transform:scaleX(-1) rotate(-90deg)}.prism-previewer-angle circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500}.prism-previewer-gradient{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px;width:64px;margin-left:-32px}.prism-previewer-gradient:before{content:none}.prism-previewer-gradient div{position:absolute;top:-5px;left:-5px;right:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer-color{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px}.prism-previewer-color:before{background-color:inherit;background-clip:padding-box}.prism-previewer-easing{margin-top:-76px;margin-left:-30px;width:60px;height:60px;background:#333}.prism-previewer-easing.flipped{margin-bottom:-116px}.prism-previewer-easing svg{width:60px;height:60px}.prism-previewer-easing circle{fill:#2d3438;stroke:#fff}.prism-previewer-easing path{fill:none;stroke:#fff;stroke-linecap:round;stroke-width:4}.prism-previewer-easing line{stroke:#fff;stroke-opacity:.5;stroke-width:2}@-webkit-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-o-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-moz-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}.prism-previewer-time:before{border-radius:50%;background:#fff}.prism-previewer-time:after{margin-top:4px}.prism-previewer-time svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-time circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500;stroke-dashoffset:0;-webkit-animation:prism-previewer-time linear infinite 3s;-moz-animation:prism-previewer-time linear infinite 3s;-o-animation:prism-previewer-time linear infinite 3s;animation:prism-previewer-time linear infinite 3s}
|
||||
[class*=lang-] script[type='text/plain'],[class*=language-] script[type='text/plain'],script[type='text/plain'][class*=lang-],script[type='text/plain'][class*=language-]{display:block;font:100% Consolas,Monaco,monospace;white-space:pre;overflow:auto}
|
||||
.token.punctuation.brace-hover,.token.punctuation.brace-selected{outline:solid 1px}.rainbow-braces .token.punctuation.brace-level-1,.rainbow-braces .token.punctuation.brace-level-5,.rainbow-braces .token.punctuation.brace-level-9{color:#e50;opacity:1}.rainbow-braces .token.punctuation.brace-level-10,.rainbow-braces .token.punctuation.brace-level-2,.rainbow-braces .token.punctuation.brace-level-6{color:#0b3;opacity:1}.rainbow-braces .token.punctuation.brace-level-11,.rainbow-braces .token.punctuation.brace-level-3,.rainbow-braces .token.punctuation.brace-level-7{color:#26f;opacity:1}.rainbow-braces .token.punctuation.brace-level-12,.rainbow-braces .token.punctuation.brace-level-4,.rainbow-braces .token.punctuation.brace-level-8{color:#e0e;opacity:1}
|
||||
|
||||
/*gruvbox light*/
|
||||
code[class*=language-],pre[class*=language-]{color:#3c3836;font-family:Consolas,Monaco,"Andale Mono",monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{color:#282828;background:#a89984}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{color:#282828;background:#a89984}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f9f5d7}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em}.token.attr-name,.token.attr-value,.token.attr-value .punctuation,.token.cdata,.token.comment,.token.operator,.token.prolog,.token.punctuation{color:#7c6f64}.token.atrule,.token.boolean,.token.constant,.token.delimiter,.token.important,.token.keyword,.token.property,.token.selector,.token.variable{color:#9d0006}.token.builtin,.token.doctype,.token.function,.token.tag,.token.tag .punctuation{color:#b57614}.token.entity,.token.number,.token.symbol{color:#8f3f71}.token.char,.token.string,.token.url{color:#797403}.token.url{text-decoration:underline}.token.regex{background:#797403}.token.bold{font-weight:700}.token.italic{font-style:italic}.token.inserted{background:#7c6f64}.token.deleted{background:#9d0006}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
id="svg2"
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-filename="C:\Users\Ilia\SVG\Atom_icon.png"
|
||||
viewBox="0 0 285.45 280.91"
|
||||
inkscape:export-xdpi="90"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre3 r13670"
|
||||
sodipodi:docname="_svgclean2.svg"
|
||||
>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
fit-margin-left="10"
|
||||
inkscape:zoom="1.979899"
|
||||
borderopacity="1.0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:cx="30.185218"
|
||||
inkscape:cy="330.70792"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:snap-bbox="true"
|
||||
showgrid="false"
|
||||
fit-margin-right="10"
|
||||
inkscape:snap-nodes="false"
|
||||
units="mm"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:window-width="718"
|
||||
fit-margin-bottom="10"
|
||||
inkscape:snap-page="false"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:window-height="645"
|
||||
fit-margin-top="10"
|
||||
/>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(-.31824 -771.33)"
|
||||
>
|
||||
<g
|
||||
id="g8434"
|
||||
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
|
||||
>
|
||||
<ellipse
|
||||
id="path3089"
|
||||
style="fill:#FFFFFF"
|
||||
rx="2.0633"
|
||||
ry="2.0618"
|
||||
cy="1037.7"
|
||||
cx="14.936"
|
||||
/>
|
||||
<path
|
||||
id="circle3412"
|
||||
style="fill:#FFFFFF"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,1020.4)"
|
||||
d="m15.045 2.7051v3.3496a11.266 11.258 0 0 1 11.154 11.213h3.3516a14.617 14.606 0 0 0 -14.506 -14.563z"
|
||||
/>
|
||||
<path
|
||||
id="circle3428"
|
||||
style="fill:#FFFFFF"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,1020.4)"
|
||||
d="m15.045 9.4453v2.9492a4.9226 4.919 0 0 1 4.8105 4.873h2.9492a7.8711 7.8654 0 0 0 -7.76 -7.8217z"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
<g
|
||||
id="g8430"
|
||||
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
|
||||
>
|
||||
<path
|
||||
id="path3302"
|
||||
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,1020.4)"
|
||||
d="m14.865 12.807c-3.944 0.0057-7.5137 0.47474-10.127 1.2383-1.3146 0.3841-2.3876 0.844-3.1562 1.375-0.76861 0.5309-1.2617 1.1648-1.2617 1.8906 0 0.7259 0.49311 1.3617 1.2617 1.8926 0.76861 0.5309 1.8417 0.97918 3.1562 1.3633 2.6292 0.7682 6.2246 1.2402 10.197 1.2402 3.9727 0 7.57-0.47204 10.199-1.2402 1.3146-0.3841 2.3857-0.83238 3.1543-1.3633 0.71878-0.49648 1.1813-1.0881 1.2402-1.7559h-0.64453c-0.05963 0.38822-0.35991 0.79958-0.9668 1.2188-0.67801 0.4684-1.6967 0.90955-2.9688 1.2812-2.5442 0.7433-6.0921 1.209-10.014 1.209-3.9216 0-7.4675-0.46568-10.012-1.209-1.2721-0.3717-2.3005-0.81285-2.9785-1.2812-0.67801-0.4683-0.98438-0.92797-0.98438-1.3555 0-0.4274 0.30637-0.89488 0.98438-1.3633 0.67801-0.4683 1.7064-0.90965 2.9785-1.2812 2.5286-0.73883 6.0485-1.2033 9.9414-1.209v-0.65039z"
|
||||
/>
|
||||
<circle
|
||||
id="path8416"
|
||||
cy="1034.4"
|
||||
cx="6.1108"
|
||||
r=".75893"
|
||||
style="fill:#FFFFFF"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
<g
|
||||
id="g8439"
|
||||
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
|
||||
>
|
||||
<g
|
||||
id="g8426"
|
||||
>
|
||||
<path
|
||||
id="path3304"
|
||||
d="m14.865 12.236c-0.72049 0.36795-1.4472 0.75012-2.1816 1.1738-3.4404 1.9848-6.3203 4.1911-8.2129 6.1699-0.94628 0.9894-1.6489 1.914-2.0488 2.7578-0.39997 0.8438-0.50968 1.639-0.14648 2.2676s1.1078 0.93201 2.0391 1.0078c0.93131 0.076 2.0893-0.06106 3.4199-0.38476 2.6613-0.6484 6.0127-2.0366 9.4531-4.0215 2.0899-1.2056 3.9354-2.4914 5.5156-3.7598h-1.0293c-1.411 1.0794-3.0286 2.1662-4.8125 3.1953-3.3962 1.9593-6.7001 3.3277-9.2754 3.9551-1.2876 0.3137-2.3914 0.43995-3.2129 0.37305-0.82153-0.067-1.3291-0.31535-1.543-0.68555-0.21387-0.3702-0.16922-0.92367 0.18359-1.668 0.35282-0.7444 1.014-1.6364 1.9297-2.5938 1.8314-1.9149 4.6702-4.0894 8.0664-6.0488 0.62532-0.36075 1.2403-0.68024 1.8555-1v-0.73828z"
|
||||
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
|
||||
transform="translate(0,1020.4)"
|
||||
inkscape:connector-curvature="0"
|
||||
/>
|
||||
<circle
|
||||
id="circle8418"
|
||||
cy="1045.1"
|
||||
cx="2.8068"
|
||||
r=".75893"
|
||||
style="fill:#FFFFFF"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
<g
|
||||
id="g8422"
|
||||
>
|
||||
<path
|
||||
id="path3300"
|
||||
d="m11.682 3.1523c-0.17928-0.012267-0.35779 0.00188-0.5332 0.048828-0.70164 0.1879-1.1876 0.82968-1.502 1.709-0.31431 0.8792-0.47091 2.0283-0.50195 3.3965-0.062086 2.7365 0.4132 6.3316 1.4414 10.166s2.4148 7.1846 3.8379 9.5234c0.71153 1.1695 1.4246 2.087 2.1367 2.6914 0.71217 0.6045 1.4527 0.91642 2.1543 0.72852 0.70164-0.1879 1.1876-0.82783 1.502-1.707 0.31431-0.8793 0.48067-2.034 0.51172-3.4023 0.05491-2.42-0.33654-5.5249-1.1328-8.8594h-0.66797c0.81117 3.3483 1.2082 6.4642 1.1543 8.8418-0.03005 1.3241-0.19344 2.4196-0.4707 3.1953-0.27726 0.7756-0.64933 1.2019-1.0625 1.3125s-0.93818-0.07442-1.5664-0.60742c-0.62822-0.5332-1.3193-1.4036-2.0078-2.5352-1.377-2.2632-2.7448-5.5645-3.7598-9.3496-1.015-3.785-1.482-7.3305-1.4219-9.9785 0.030055-1.324 0.18954-2.4314 0.4668-3.207s0.64152-1.1882 1.0547-1.2988c0.41317-0.1106 0.946 0.062403 1.5742 0.5957 0.6182 0.52469 1.2989 1.3866 1.9766 2.4922v-1.1836c-0.521-0.7474-1.043-1.3562-1.561-1.7957-0.534-0.4534-1.086-0.7406-1.623-0.7774z"
|
||||
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
|
||||
transform="translate(0,1020.4)"
|
||||
inkscape:connector-curvature="0"
|
||||
/>
|
||||
<circle
|
||||
id="circle8420"
|
||||
cx="20.264"
|
||||
style="fill:#FFFFFF"
|
||||
r=".75893"
|
||||
cy="1043.4"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
</g
|
||||
>
|
||||
</g
|
||||
>
|
||||
<metadata
|
||||
id="metadata19"
|
||||
>
|
||||
<rdf:RDF
|
||||
>
|
||||
<cc:Work
|
||||
>
|
||||
<dc:format
|
||||
>image/svg+xml</dc:format
|
||||
>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
|
||||
/>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/publicdomain/"
|
||||
/>
|
||||
<dc:publisher
|
||||
>
|
||||
<cc:Agent
|
||||
rdf:about="http://openclipart.org/"
|
||||
>
|
||||
<dc:title
|
||||
>Openclipart</dc:title
|
||||
>
|
||||
</cc:Agent
|
||||
>
|
||||
</dc:publisher
|
||||
>
|
||||
</cc:Work
|
||||
>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/publicdomain/"
|
||||
>
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction"
|
||||
/>
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution"
|
||||
/>
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
|
||||
/>
|
||||
</cc:License
|
||||
>
|
||||
</rdf:RDF
|
||||
>
|
||||
</metadata
|
||||
>
|
||||
</svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||
<path fill="#FFFFFF"
|
||||
d="M854 175q-27 0 -46 19t-19 45.5t18.5 45t45 18.5t45.5 -18.5t19 -45t-18.5 -45.5t-44.5 -19zM205 192l-34 34q-83 83 -88 154t68 144l82 82q45 46 48.5 78t-33.5 69v0q-16 19 -15.5 44.5t18.5 43.5t43.5 18.5t44.5 -15.5l1 1q25 -25 47 -32t45.5 4.5t53.5 41.5l95 96
|
||||
q75 74 147.5 70t155.5 -87l33 -34l-71 -72l-18 18q-47 47 -84 47.5t-82 -44.5l-112 -112q-86 -86 -169 -17l-11 -11q35 -42 31.5 -83t-45.5 -82l-100 -101q-31 -31 -40.5 -56.5t1 -51.5t42.5 -59l17 -17zM703 326q-28 0 -46.5 19t-18.5 45.5t18.5 45.5t45 19t45.5 -19
|
||||
t19 -45.5t-18.5 -45t-44.5 -19.5zM551 477q-27 0 -46 19t-19 45.5t19 45.5t45.5 19t45.5 -19t19 -45.5t-19 -45t-45 -19.5z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1023 B |
@@ -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/prism.css"><link rel="stylesheet" href="/blog/css/main.css"><script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script><script type="text/javascript" src="https://platform-api.sharethis.com/js/sharethis.js#property=6550cdc47a115e0012964576&product=sop" async="async"></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><div class="modal-container" id="cookiePopup"><div class="modal"><div class="modal-content"><h2><i class="fas fa-cookie-bite"></i> Hey I use cookies btw</h2><p>Just to let you know, I use cookies to give you the best experience on my blog. By clicking agree I'll assume that you are happy with it. <a href="/blog/policy/cookie" class="link">Read more</a></p><div class="flexRow"><button class="btn btnPrimary" id="cookieAccept">agree</button></div></div></div></div><footer class="flexRow"><div class="nav"><ul><li><a href="/blog/policy/privacy" class="link">privacy policy</a></li><li><a href="/blog/policy/cookie" class="link">cookie policy</a></li></ul></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/prism.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>
|
||||
@@ -1 +1 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Editor</title><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="stylesheet" href="css/main.css"><script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script></head><body><main class="login"><div id="login" class="container shown"><h1>Login To Editor</h1><form action="" method="POST"><div class="formControl"><label for="username">Username</label> <input type="text" id="username" name="username" required></div><div class="formControl passwordControl"><label for="password">Password</label> <input type="password" id="password" name="password" required> <i class="fa-solid fa-eye"></i></div><div class="error hidden" id="loginError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <a href="#" id="resetPwd">Reset Password</a></div></form></div><div id="resetPassword" class="container" style="display: none; transform: translateX(150vw)"><h1>Reset Password</h1><form action="#" method="POST"><div class="formControl"><label for="email">Email</label> <input type="email" id="email" name="email"></div><div class="error hidden" id="resetError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="loginBtn">Login</a></div></form></div><div id="checkResetCode" class="container" style="display: none; transform: translateX(150vw)"><h1>Check Reset Code</h1><form action="#" method="POST"><div class="formControl"><label for="code">Code</label> <input type="text" id="code" name="code"></div><div class="error hidden" id="resetCodeError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="resendEmail">Resend Email</a></div></form></div><div id="changePassword" class="container" style="display: none; transform: translateX(150vw)"><h1>Reset Password</h1><form action="" method="POST"><div class="formControl"><label for="pass">Password</label> <input type="password" name="pass" id="pass"> <i class="fa-solid fa-eye"></i></div><div class="formControl"><label for="rePass">Password</label> <input type="password" name="rePass" id="rePass"> <i class="fa-solid fa-eye"></i></div><div class="error hidden" id="changeError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut">Login</a></div></form></div></main><script src="js/index.js"></script></body></html>
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Editor</title><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="stylesheet" href="css/main.css"><script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script></head><body><main class="login"><div id="login" class="container shown"><h1>Login To Editor</h1><form action="" method="POST"><div class="formControl"><label for="username">Username</label> <input type="text" id="username" name="username" required></div><div class="formControl passwordControl"><label for="password">Password</label> <input type="password" id="password" name="password" required> <i class="fa-solid fa-eye"></i></div><div class="error hidden" id="loginError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <a href="/api/user/login" class="btn btnPrimary boxShadowIn boxShadowOut">Login with Jump Cloud</a> <button type="button" id="resetPwd" class="btn btnPrimary boxShadowIn boxShadowOut">Reset Password</button></div></form></div><div id="resetPassword" class="container" style="display: none; transform: translateX(150vw)"><h1>Reset Password</h1><form action="#" method="POST"><div class="formControl"><label for="email">Email</label> <input type="email" id="email" name="email"></div><div class="error hidden" id="resetError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="loginBtn">Login</button></div></form></div><div id="checkResetCode" class="container" style="display: none; transform: translateX(150vw)"><h1>Check Reset Code</h1><form action="#" method="POST"><div class="formControl"><label for="code">Code</label> <input type="text" id="code" name="code"></div><div class="error hidden" id="resetCodeError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="resendEmail">Resend Email</button></div></form></div><div id="changePassword" class="container" style="display: none; transform: translateX(150vw)"><h1>Reset Password</h1><form action="" method="POST"><div class="formControl"><label for="pass">Password</label> <input type="password" name="pass" id="pass"> <i class="fa-solid fa-eye"></i></div><div class="formControl"><label for="rePass">Password</label> <input type="password" name="rePass" id="rePass"> <i class="fa-solid fa-eye"></i></div><div class="error hidden" id="changeError"><button class="close" type="button">×</button><div></div></div><div class="btnContainer"><input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut"> <button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut">Login</button></div></form></div></main><script src="js/index.js"></script></body></html>
|
||||
@@ -1 +1 @@
|
||||
function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function switchView(e,t){document.querySelector(e).classList.toggle("shown"),setTimeout((()=>document.querySelector(e).style.transform="translateX(150vw)"),500),setTimeout((()=>document.querySelector(e).style.display="none"),500),setTimeout((()=>document.querySelector(t).style.removeProperty("display")),200),setTimeout((()=>document.querySelector(t).classList.toggle("shown")),300),setTimeout((()=>document.querySelector(t).style.removeProperty("transform")),400)}document.addEventListener("DOMContentLoaded",(e=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok&&(window.location.href="./editor.html")}))})),document.querySelector("#login form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;if(e.target.username.value.length>0&&e.target.password.value.length>0)return t.append("username",e.target.username.value),t.append("password",e.target.password.value),void fetch("/api/user/login",{method:"POST",body:t}).then((e=>e.json().then((t=>{if(e.ok)return localStorage.setItem("token",t.token),void(window.location.href="./editor.html");400!==e.status?showErrorMessage("Invalid username or password.","login"):showErrorMessage("Please type in a username and password.","login")}))));showErrorMessage("Please type in a username and password.","login")})),document.querySelector("#loginError .close").addEventListener("click",(()=>document.querySelector("#loginError").classList.toggle("hidden"))),document.querySelector("#resetError .close").addEventListener("click",(()=>document.querySelector("#resetError").classList.toggle("hidden"))),document.querySelector("#changeError .close").addEventListener("click",(()=>document.querySelector("#changeError").classList.toggle("hidden"))),document.querySelectorAll("form i.fa-eye").forEach((e=>{e.addEventListener("click",(e=>{if("password"===e.target.previousElementSibling.type)return e.target.previousElementSibling.type="text",e.target.classList.remove("fa-eye"),void e.target.classList.add("fa-eye-slash");e.target.previousElementSibling.type="password",e.target.classList.remove("fa-eye-slash"),e.target.classList.add("fa-eye")}))})),document.querySelector("#resetPwd").addEventListener("click",(()=>{switchView("#login","#resetPassword")})),document.querySelector("#loginBtn").addEventListener("click",(()=>{switchView("#resetPassword","#login")})),document.querySelector("#resetPassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.email?(window.email=e.target.email.value,t.append("email",e.target.email.value),fetch(`/api/user/checkResetEmail/${e.target.email.value}`).then((e=>{e.ok&&switchView("#resetPassword","#checkResetCode"),showErrorMessage("Invalid email.","reset")}))):showErrorMessage("Please type in your email.","reset")})),document.querySelector("#checkResetCode form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.code.value?(t.append("code",e.target.code.value),fetch(`/api/user/checkResetCode/${e.target.code.value}`).then((e=>{e.ok&&switchView("#checkResetCode","#changePassword"),showErrorMessage("Invalid code.","resetCode")}))):showErrorMessage("Please type in your reset code.","check")})),document.querySelector("#changePassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.pass.value||""!==e.target.rePass.value?e.target.pass.value===e.target.rePass.value?(t.append("password",e.target.pass.value),fetch("/api/user/changePassword",{method:"POST",body:t}).then((e=>{e.ok&&switchView("#changePassword","#login"),showErrorMessage("Something went wrong.","change")}))):showErrorMessage("Passwords do not match.","change"):showErrorMessage("Please type in a new password.","change")}));
|
||||
function showErrorMessage(e,t){document.querySelector(`#${t}Error`).classList.remove("hidden"),document.querySelector(`#${t}Error div`).innerText=e}function switchView(e,t){document.querySelector(e).classList.toggle("shown"),setTimeout((()=>document.querySelector(e).style.transform="translateX(150vw)"),500),setTimeout((()=>document.querySelector(e).style.display="none"),500),setTimeout((()=>document.querySelector(t).style.removeProperty("display")),200),setTimeout((()=>document.querySelector(t).classList.toggle("shown")),300),setTimeout((()=>document.querySelector(t).style.removeProperty("transform")),400)}document.addEventListener("DOMContentLoaded",(e=>{fetch("/api/user/isLoggedIn").then((e=>{e.ok&&(window.location.href="./editor.html")}))})),document.querySelector("#login form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;if(e.target.username.value.length>0&&e.target.password.value.length>0)return t.append("username",e.target.username.value),t.append("password",e.target.password.value),void fetch("/api/user/login",{method:"POST",body:t}).then((e=>e.json().then((t=>{if(e.ok)return localStorage.setItem("token",t.token),void(window.location.href="./editor.html");400!==e.status?401!==e.status?showErrorMessage(t.error,"login"):showErrorMessage("Invalid username or password.","login"):showErrorMessage("Please type in a username and password.","login")}))));showErrorMessage("Please type in a username and password.","login")})),document.querySelector("#loginError .close").addEventListener("click",(()=>document.querySelector("#loginError").classList.toggle("hidden"))),document.querySelector("#resetError .close").addEventListener("click",(()=>document.querySelector("#resetError").classList.toggle("hidden"))),document.querySelector("#changeError .close").addEventListener("click",(()=>document.querySelector("#changeError").classList.toggle("hidden"))),document.querySelectorAll("form i.fa-eye").forEach((e=>{e.addEventListener("click",(e=>{if("password"===e.target.previousElementSibling.type)return e.target.previousElementSibling.type="text",e.target.classList.remove("fa-eye"),void e.target.classList.add("fa-eye-slash");e.target.previousElementSibling.type="password",e.target.classList.remove("fa-eye-slash"),e.target.classList.add("fa-eye")}))})),document.querySelector("#resetPwd").addEventListener("click",(()=>{switchView("#login","#resetPassword")})),document.querySelector("#loginBtn").addEventListener("click",(()=>{switchView("#resetPassword","#login")})),document.querySelector("#resetPassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.email?(window.email=e.target.email.value,t.append("email",e.target.email.value),fetch(`/api/user/checkResetEmail/${e.target.email.value}`).then((e=>{e.ok&&switchView("#resetPassword","#checkResetCode"),showErrorMessage("Invalid email.","reset")}))):showErrorMessage("Please type in your email.","reset")})),document.querySelector("#checkResetCode form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.code.value?(t.append("code",e.target.code.value),fetch(`/api/user/checkResetCode/${e.target.code.value}`).then((e=>{e.ok&&switchView("#checkResetCode","#changePassword"),showErrorMessage("Invalid code.","resetCode")}))):showErrorMessage("Please type in your reset code.","check")})),document.querySelector("#changePassword form").addEventListener("submit",(e=>{e.preventDefault();let t=new FormData;""!==e.target.pass.value||""!==e.target.rePass.value?e.target.pass.value===e.target.rePass.value?(t.append("password",e.target.pass.value),fetch("/api/user/changePassword",{method:"POST",body:t}).then((e=>{e.ok&&switchView("#changePassword","#login"),showErrorMessage("Something went wrong.","change")}))):showErrorMessage("Passwords do not match.","change"):showErrorMessage("Please type in a new password.","change")}));
|
||||
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 87 KiB |
@@ -12,6 +12,7 @@
|
||||
"author": "Rohit Pai",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ckeditor/ckeditor5-clipboard": "^40.0.0",
|
||||
"browser-sync": "^2.27.5",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
@@ -25,5 +26,11 @@
|
||||
"require": "^0.4.4",
|
||||
"source-map-generator": "^0.8.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace api\blog;
|
||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||
require_once "blogData.php";
|
||||
|
||||
|
||||
use api\utils\routesInterface;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
@@ -80,35 +81,15 @@ class blogRoutes implements routesInterface
|
||||
|
||||
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["type"] != null)
|
||||
if ($args["type"] == null)
|
||||
{
|
||||
if ($args["type"] == "latest")
|
||||
{
|
||||
$post = $this->blogData->getLatestBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($args["type"] == "featured")
|
||||
{
|
||||
$post = $this->blogData->getFeaturedBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
}
|
||||
|
||||
$post = $this->blogData->getBlogPost($args["type"]);
|
||||
if ($args["type"] == "latest")
|
||||
{
|
||||
$post = $this->blogData->getLatestBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
@@ -119,100 +100,208 @@ class blogRoutes implements routesInterface
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||
if ($args["type"] == "featured")
|
||||
{
|
||||
$post = $this->blogData->getFeaturedBlogPost();
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
}
|
||||
|
||||
$post = $this->blogData->getBlogPost($args["type"]);
|
||||
if (array_key_exists("errorMessage", $post))
|
||||
{
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($post));
|
||||
return $response;
|
||||
|
||||
});
|
||||
|
||||
$app->get("/blog/feed/{type}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["type"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$feed = $this->blogData->getFeed($args["type"]);
|
||||
if (is_array($feed))
|
||||
{
|
||||
$response->getBody()->write(json_encode($feed));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
if ($args["type"] == "atom")
|
||||
{
|
||||
$response->getBody()->write($feed);
|
||||
return $response->withHeader("Content-Type", "application/atom+xml");
|
||||
}
|
||||
|
||||
if ($args["type"] == "rss")
|
||||
{
|
||||
$response->getBody()->write($feed);
|
||||
return $response->withHeader("Content-Type", "application/rss+xml");
|
||||
}
|
||||
|
||||
if ($args["type"] == "json")
|
||||
{
|
||||
$response->getBody()->write(json_encode($feed));
|
||||
return $response->withHeader("Content-Type", "application/feed+json");
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Invalid feed type")));
|
||||
return $response->withStatus(400);
|
||||
});
|
||||
|
||||
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
|
||||
{
|
||||
if ($args["searchTerm"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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;
|
||||
|
||||
});
|
||||
|
||||
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
$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"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->updatePost($args["id"], $data["title"], intval($data["featured"]), $data["abstract"], $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);
|
||||
}
|
||||
|
||||
$response->withStatus(201);
|
||||
return $response;
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
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
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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")
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
$response->withStatus(201);
|
||||
return $response;
|
||||
|
||||
});
|
||||
|
||||
$app->delete("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["id"] != null)
|
||||
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);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
$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;
|
||||
});
|
||||
|
||||
$app->delete("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["email"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->deleteNewsletterEmail($args["email"]);
|
||||
|
||||
if ($message === "email not found")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("message" => "Woah, you're already trying to leave without signing up?")));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
if ($message === "error")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("message" => "Error, something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Sorry to see you go! You'll no longer receive any emails from me. If you change your mind, you can always sign up again.")));
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->post("/blog/post", function (Request $request, Response $response)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
$files = $request->getUploadedFiles();
|
||||
$headerImg = $files["headerImg"];
|
||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||
@@ -226,13 +315,18 @@ class blogRoutes implements routesInterface
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (array_key_exists("headerImg", $files))
|
||||
{
|
||||
$headerImg = $files["headerImg"];
|
||||
}
|
||||
|
||||
if (empty($files["headerImg"]))
|
||||
{
|
||||
$headerImg = null;
|
||||
}
|
||||
|
||||
$featured = $data["featured"] === "true";
|
||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
||||
// $featured = $data["featured"] === "true";
|
||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], intval($data["featured"]), $data["categories"], $headerImg);
|
||||
if (!is_int($insertedID))
|
||||
{
|
||||
// uh oh something went wrong
|
||||
@@ -268,28 +362,75 @@ class blogRoutes implements routesInterface
|
||||
{
|
||||
$files = $request->getUploadedFiles();
|
||||
|
||||
if ($args["id"] != null)
|
||||
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);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
if (empty($files))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "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" => $message)));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode($message));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
$app->post("/blog/newsletter", function (Request $request, Response $response)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
if (empty($data["subject"]) || empty($data["message"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->sendNewsletter(strtolower($data["subject"]), $data["message"]);
|
||||
if (is_array($message))
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Error, something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Message sent")));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
$app->post("/blog/newsletter/{email}", function (Request $request, Response $response, $args)
|
||||
{
|
||||
if ($args["email"] == null)
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an email")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$message = $this->blogData->addNewsletterEmail($args["email"]);
|
||||
if ($message === "Email already exists")
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("message" => "exists")));
|
||||
return $response->withStatus(409);
|
||||
}
|
||||
|
||||
if (is_array($message) || !$message || $message === "error")
|
||||
{
|
||||
$response->getBody()->write(json_encode(array("message" => "Something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("message" => "Thanks for signing up!")));
|
||||
return $response->withStatus(201);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace api\project;
|
||||
use api\utils\imgUtils;
|
||||
use PDO;
|
||||
use Psr\Http\Message\UploadedFileInterface;
|
||||
use function api\utils\dbConn;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
require_once __DIR__ . "/../utils/imgUtils.php";
|
||||
@@ -17,7 +18,7 @@ class projectData
|
||||
{
|
||||
/**
|
||||
* Get all project data
|
||||
* @return array - Array of all project data or error message
|
||||
* @return array<array> - Array of all project data or error message
|
||||
*/
|
||||
public function getProjectData(): array
|
||||
{
|
||||
|
||||
@@ -50,78 +50,78 @@ class projectRoutes implements routesInterface
|
||||
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
$data = $request->getParsedBody();
|
||||
if ($args["id"] != null)
|
||||
if ($args["id"] == null)
|
||||
{
|
||||
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!$update)
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
return $response;
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
||||
{
|
||||
// uh oh sent some empty data
|
||||
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$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")
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Can't unset project as main project, try updating another project as the main project")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if (!$update)
|
||||
{
|
||||
// uh oh something went wrong
|
||||
$response->getBody()->write(json_encode(array("error" => "Something went wrong")));
|
||||
return $response->withStatus(500);
|
||||
}
|
||||
|
||||
return $response;
|
||||
});
|
||||
|
||||
$app->delete("/projectData/{id}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
if ($args["id"] != null)
|
||||
if ($args["id"] == null)
|
||||
{
|
||||
$message = $this->projectData->deleteProjectData($args["id"]);
|
||||
|
||||
if ($message === "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 ($message === "cannot delete")
|
||||
{
|
||||
//uh oh cannot delete the main project
|
||||
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
|
||||
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;
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||
return $response->withStatus(400);
|
||||
$message = $this->projectData->deleteProjectData($args["id"]);
|
||||
|
||||
if ($message === "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 ($message === "cannot delete")
|
||||
{
|
||||
//uh oh cannot delete the main project
|
||||
$response->getBody()->write(json_encode(array("error" => "Cannot delete the main project")));
|
||||
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;
|
||||
});
|
||||
|
||||
$app->post("/projectData", function (Request $request, Response $response)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace api\timeline;
|
||||
|
||||
use PDO;
|
||||
use function api\utils\dbConn;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
@@ -14,7 +15,7 @@ class timelineData
|
||||
{
|
||||
/**
|
||||
* Get all education data
|
||||
* @return array - Array of all education data or error message
|
||||
* @return array<array> - Array of all education data or error message
|
||||
*/
|
||||
public function getEduData(): array
|
||||
{
|
||||
|
||||
@@ -4,6 +4,9 @@ namespace api\user;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use PDO;
|
||||
use function api\utils\dbConn;
|
||||
use function api\utils\getSAMLSettings;
|
||||
use function api\utils\getSecretKey;
|
||||
|
||||
require_once __DIR__ . "/../utils/config.php";
|
||||
|
||||
@@ -136,5 +139,38 @@ class userData
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the SAML settings
|
||||
* @return array - SAML settings
|
||||
*/
|
||||
public function getSamlConf(): array
|
||||
{
|
||||
return getSAMLSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the SAML user exists
|
||||
* @param string $username - Username
|
||||
* @param string $email - Email
|
||||
* @return bool - True if the user exists, false if not
|
||||
*/
|
||||
public function checkSAMLUser(string $username, string $email): bool
|
||||
{
|
||||
$conn = dbConn();
|
||||
$stmt = $conn->prepare("SELECT * FROM users WHERE username = :username AND email = :email");
|
||||
$stmt->bindParam(":username", $username);
|
||||
$stmt->bindParam(":email", $email);
|
||||
$stmt->execute();
|
||||
|
||||
// set the resulting array to associative
|
||||
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -5,6 +5,8 @@ require_once __DIR__ . "/../utils/routesInterface.php";
|
||||
require_once "userData.php";
|
||||
|
||||
use api\utils\routesInterface;
|
||||
use OneLogin\Saml2\Auth;
|
||||
use OneLogin\Saml2\Error;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\App;
|
||||
@@ -12,14 +14,17 @@ use Slim\App;
|
||||
class userRoutes implements routesInterface
|
||||
{
|
||||
private userData $user;
|
||||
private Auth $samlAuth;
|
||||
|
||||
/**
|
||||
* constructor used to instantiate a base user routes, to be used in the index.php file.
|
||||
* constructor used to instantiate base user routes, to be used in the index.php file.
|
||||
* @param App $app - the slim app used to create the routes
|
||||
* @throws Error
|
||||
*/
|
||||
public function __construct(App $app)
|
||||
{
|
||||
$this->user = new userData();
|
||||
$this->samlAuth = new Auth($this->user->getSamlConf());
|
||||
$this->createRoutes($app);
|
||||
}
|
||||
|
||||
@@ -30,31 +35,9 @@ class userRoutes implements routesInterface
|
||||
*/
|
||||
public function createRoutes(App $app): void
|
||||
{
|
||||
$app->post("/user/login", function (Request $request, Response $response)
|
||||
$app->get("/user/login", function (Request $request, Response $response)
|
||||
{
|
||||
// get request data
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (empty($data["username"]) || empty($data["password"]))
|
||||
{
|
||||
// uh oh user sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($this->user->checkUser($data["username"], $data["password"]))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
|
||||
return $response->withStatus(401);
|
||||
$this->samlAuth->login();
|
||||
});
|
||||
|
||||
$app->get("/user/logout", function (Request $request, Response $response)
|
||||
@@ -92,6 +75,20 @@ class userRoutes implements routesInterface
|
||||
|
||||
});
|
||||
|
||||
$app->get("/user/metadata", function (Request $request, Response $response)
|
||||
{
|
||||
$settings = $this->samlAuth->getSettings();
|
||||
$metadata = $settings->getSPMetadata();
|
||||
$errors = $settings->validateMetadata($metadata);
|
||||
if (empty($errors))
|
||||
{
|
||||
$response->getBody()->write($metadata);
|
||||
return $response->withHeader("Content-Type", "text/xml");
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => $errors)));
|
||||
return $response->withStatus(500);
|
||||
});
|
||||
|
||||
$app->get("/user/checkResetEmail/{email}", function (Request $request, Response $response, array $args)
|
||||
{
|
||||
if (empty($args["email"]))
|
||||
@@ -139,6 +136,58 @@ class userRoutes implements routesInterface
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->post("/user/login", function (Request $request, Response $response)
|
||||
{
|
||||
// get request data
|
||||
$data = $request->getParsedBody();
|
||||
|
||||
if (empty($data["username"]) || empty($data["password"]))
|
||||
{
|
||||
// uh oh user sent empty data
|
||||
return $response->withStatus(400);
|
||||
}
|
||||
|
||||
if ($this->user->checkUser($data["username"], $data["password"]))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->post("/user/acs", function (Request $request, Response $response)
|
||||
{
|
||||
$this->samlAuth->processResponse();
|
||||
|
||||
$attributes = $this->samlAuth->getAttributes();
|
||||
$username = $attributes["username"][0];
|
||||
$email = $attributes["email"][0];
|
||||
|
||||
if ($this->user->checkSAMLUser($username, $email))
|
||||
{
|
||||
// yay, user is logged in
|
||||
$_SESSION["token"] = $this->user->createToken($username);
|
||||
$_SESSION["username"] = $username;
|
||||
$_SESSION["email"] = $email;
|
||||
|
||||
$inactive = 60 * 60 * 48; // 2 days
|
||||
$_SESSION["timeout"] = time() + $inactive;
|
||||
|
||||
return $response->withHeader("Location", "https://rohitpai.co.uk/editor/")->withStatus(302);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
|
||||
return $response->withStatus(401);
|
||||
});
|
||||
|
||||
$app->post("/user/changePassword", function (Request $request, Response $response)
|
||||
{
|
||||
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
|
||||
@@ -165,4 +214,4 @@ class userRoutes implements routesInterface
|
||||
return $response->withStatus(500);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace api\utils\feedGenerator;
|
||||
|
||||
/**
|
||||
* Universal Feed Writer
|
||||
*
|
||||
* FeedItem class - Used as a feed element in FeedWriter class
|
||||
*
|
||||
* @package UniversalFeedWriter
|
||||
* @author Anis uddin Ahmad <anisniit@gmail.com>
|
||||
* @link http://www.ajaxray.com/projects/rss
|
||||
*/
|
||||
class FeedItem
|
||||
{
|
||||
private array $elements = []; // Collection of feed elements
|
||||
private string $version;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $version (RSS1/RSS2/ATOM) RSS2 is the default.
|
||||
*/
|
||||
public function __construct(string $version = RSS2)
|
||||
{
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an element to elements array
|
||||
*
|
||||
* @param string $elementName The tag name of an element
|
||||
* @param string $content The content of the tag
|
||||
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
|
||||
*/
|
||||
public function addElement(string $elementName, string $content, ?array $attributes = null): void
|
||||
{
|
||||
$this->elements[$elementName]['name'] = $elementName;
|
||||
$this->elements[$elementName]['content'] = $content;
|
||||
$this->elements[$elementName]['attributes'] = $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiple feed elements from an array.
|
||||
* Elements that have attributes cannot be added by this method
|
||||
*
|
||||
* @param array $elementArray Array of elements in 'tagName' => 'tagContent' format.
|
||||
*/
|
||||
public function addElementArray(array $elementArray): void
|
||||
{
|
||||
foreach ($elementArray as $elementName => $content)
|
||||
{
|
||||
$this->addElement($elementName, $content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the collection of elements in this feed item
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getElements(): array
|
||||
{
|
||||
return $this->elements;
|
||||
}
|
||||
|
||||
// Wrapper functions ------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Set the 'description' element of the feed item
|
||||
*
|
||||
* @param string $description The content of the 'description' element
|
||||
*/
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$tag = ($this->version === ATOM) ? 'summary' : 'description';
|
||||
$this->addElement($tag, $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'title' element of the feed item
|
||||
*
|
||||
* @param string $title The content of the 'title' element
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->addElement('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'date' element of the feed item
|
||||
*
|
||||
* @param string|int $date The content of the 'date' element
|
||||
*/
|
||||
public function setDate(string|int $date): void
|
||||
{
|
||||
if (!is_numeric($date))
|
||||
{
|
||||
$date = strtotime($date);
|
||||
}
|
||||
|
||||
if ($this->version === ATOM)
|
||||
{
|
||||
$tag = 'updated';
|
||||
$value = date(DATE_ATOM, $date);
|
||||
}
|
||||
elseif ($this->version === RSS2)
|
||||
{
|
||||
$tag = 'pubDate';
|
||||
$value = date(DATE_RSS, $date);
|
||||
}
|
||||
else
|
||||
{
|
||||
$tag = 'dc:date';
|
||||
$value = date("Y-m-d", $date);
|
||||
}
|
||||
|
||||
$this->addElement($tag, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'link' element of the feed item
|
||||
*
|
||||
* @param string $link The content of the 'link' element
|
||||
*/
|
||||
public function setLink(string $link): void
|
||||
{
|
||||
if ($this->version === RSS2 || $this->version === RSS1)
|
||||
{
|
||||
$this->addElement('link', $link);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->addElement('link', '', ['href' => $link]);
|
||||
$this->addElement('id', FeedWriter::uuid($link, 'urn:uuid:'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'encloser' element of the feed item
|
||||
* For RSS 2.0 only
|
||||
*
|
||||
* @param string $url The url attribute of the encloser tag
|
||||
* @param string $length The length attribute of the encloser tag
|
||||
* @param string $type The type attribute of the encloser tag
|
||||
*/
|
||||
public function setEncloser(string $url, string $length, string $type): void
|
||||
{
|
||||
$attributes = ['url' => $url, 'length' => $length, 'type' => $type];
|
||||
$this->addElement('enclosure', '', $attributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
<?php
|
||||
|
||||
namespace api\utils\feedGenerator;
|
||||
|
||||
require_once "FeedItem.php";
|
||||
|
||||
/**
|
||||
* Universal Feed Writer class
|
||||
*
|
||||
* Generate RSS 1.0, RSS 2.0, and Atom Feed
|
||||
*
|
||||
* @package UniversalFeedWriter
|
||||
* @link http://www.ajaxray.com/projects/rss
|
||||
*/
|
||||
class FeedWriter
|
||||
{
|
||||
private array $channels = []; // Collection of channel elements
|
||||
private array $items = []; // Collection of items as objects of FeedItem class
|
||||
private array $data = []; // Store some other version-wise data
|
||||
private array $CDATAEncoding = []; // The tag names that need to be encoded as CDATA
|
||||
|
||||
private string $version;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $version The version (RSS1, RSS2, ATOM).
|
||||
*/
|
||||
public function __construct(string $version = RSS2)
|
||||
{
|
||||
$this->version = $version;
|
||||
|
||||
// Setting default values for essential channel elements
|
||||
$this->channels['title'] = $version . ' Feed';
|
||||
$this->channels['link'] = 'http://www.ajaxray.com/blog';
|
||||
$this->channels["feedUrl"] = "http://example.com/feed";
|
||||
|
||||
// Tag names to encode in CDATA
|
||||
$this->CDATAEncoding = ['description', 'content:encoded', 'summary'];
|
||||
}
|
||||
|
||||
// Public functions
|
||||
|
||||
/**
|
||||
* Set a channel element
|
||||
*
|
||||
* @param string $elementName Name of the channel tag
|
||||
* @param string $content Content of the channel tag
|
||||
*/
|
||||
public function setChannelElement(string $elementName, string|array $content): void
|
||||
{
|
||||
$this->channels[$elementName] = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the actual RSS/Atom file
|
||||
*/
|
||||
public function generateFeed(): void
|
||||
{
|
||||
$this->printHead();
|
||||
$this->printChannels();
|
||||
$this->printItems();
|
||||
$this->printTail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new FeedItem.
|
||||
*
|
||||
* @return FeedItem An instance of FeedItem class
|
||||
*/
|
||||
public function createNewItem(): FeedItem
|
||||
{
|
||||
$item = new FeedItem($this->version);
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a FeedItem to the main class
|
||||
*
|
||||
* @param FeedItem $feedItem An instance of FeedItem class
|
||||
*/
|
||||
public function addItem(FeedItem $feedItem): void
|
||||
{
|
||||
$this->items[] = $feedItem;
|
||||
}
|
||||
|
||||
// Wrapper functions
|
||||
|
||||
/**
|
||||
* Set the 'title' channel element
|
||||
*
|
||||
* @param string $title Value of 'title' channel tag
|
||||
*/
|
||||
public function setTitle(string $title): void
|
||||
{
|
||||
$this->setChannelElement('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'description' channel element
|
||||
*
|
||||
* @param string $description Value of 'description' channel tag
|
||||
*/
|
||||
public function setDescription(string $description): void
|
||||
{
|
||||
$this->setChannelElement('description', $description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'link' channel element
|
||||
*
|
||||
* @param string $link Value of 'link' channel tag
|
||||
*/
|
||||
public function setLink(string $link): void
|
||||
{
|
||||
$this->setChannelElement('link', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'image' channel element
|
||||
*
|
||||
* @param string $title Title of the image
|
||||
* @param string $link Link URL of the image
|
||||
* @param string $url Path URL of the image
|
||||
*/
|
||||
public function setImage(string $title, string $link, string $url): void
|
||||
{
|
||||
$this->setChannelElement('image', ['title' => $title, 'link' => $link, 'url' => $url]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the 'about' channel element. Only for RSS 1.0
|
||||
*
|
||||
* @param string $url Value of 'about' channel tag
|
||||
*/
|
||||
public function setChannelAbout(string $url): void
|
||||
{
|
||||
$this->data['ChannelAbout'] = $url;
|
||||
}
|
||||
|
||||
// Other functions
|
||||
|
||||
/**
|
||||
* Generates a UUID
|
||||
*
|
||||
* @param string $key An optional prefix
|
||||
* @param string $prefix A prefix
|
||||
* @return string The formatted UUID
|
||||
*/
|
||||
public static function uuid(?string $key = null, string $prefix = ''): string
|
||||
{
|
||||
$key = $key ?? uniqid((string)rand());
|
||||
$chars = md5($key);
|
||||
$uuid = substr($chars, 0, 8) . '-';
|
||||
$uuid .= substr($chars, 8, 4) . '-';
|
||||
$uuid .= substr($chars, 12, 4) . '-';
|
||||
$uuid .= substr($chars, 16, 4) . '-';
|
||||
$uuid .= substr($chars, 20, 12);
|
||||
|
||||
return $prefix . $uuid;
|
||||
}
|
||||
|
||||
// Private functions
|
||||
|
||||
/**
|
||||
* Prints the XML and RSS namespace
|
||||
*/
|
||||
private function printHead(): void
|
||||
{
|
||||
$out = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
|
||||
|
||||
if ($this->version == RSS2)
|
||||
{
|
||||
$out .= '<rss version="2.0"
|
||||
xmlns:content="http://purl.org/rss/1.0/modules/content/"
|
||||
xmlns:wfw="http://wellformedweb.org/CommentAPI/"
|
||||
>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == RSS1)
|
||||
{
|
||||
$out .= '<rdf:RDF
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://purl.org/rss/1.0/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
$out .= '<feed xmlns="http://www.w3.org/2005/Atom">' . PHP_EOL;
|
||||
}
|
||||
echo $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the open tags at the end of the file
|
||||
*/
|
||||
private function printTail(): void
|
||||
{
|
||||
if ($this->version == RSS2)
|
||||
{
|
||||
echo '</channel>' . PHP_EOL . '</rss>';
|
||||
}
|
||||
elseif ($this->version == RSS1)
|
||||
{
|
||||
echo '</rdf:RDF>';
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
echo '</feed>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single node in XML format
|
||||
*
|
||||
* @param string $tagName Name of the tag
|
||||
* @param mixed $tagContent Tag value as a string or an array of nested tags in 'tagName' => 'tagValue' format
|
||||
* @param array|null $attributes Attributes (if any) in 'attrName' => 'attrValue' format
|
||||
* @return string Formatted XML tag
|
||||
*/
|
||||
private function makeNode(string $tagName, $tagContent, ?array $attributes = null): string
|
||||
{
|
||||
$nodeText = '';
|
||||
$attrText = '';
|
||||
|
||||
if (is_array($attributes))
|
||||
{
|
||||
foreach ($attributes as $key => $value)
|
||||
{
|
||||
$attrText .= " $key=\"$value\"";
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($tagContent) && $this->version == RSS1)
|
||||
{
|
||||
$attrText = ' rdf:parseType="Resource"';
|
||||
}
|
||||
|
||||
$attrText .= (in_array($tagName, $this->CDATAEncoding) && $this->version == ATOM) ? ' type="html" ' : '';
|
||||
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "<{$tagName}{$attrText}><![CDATA[" : "<{$tagName}{$attrText}>";
|
||||
|
||||
if (is_array($tagContent))
|
||||
{
|
||||
foreach ($tagContent as $key => $value)
|
||||
{
|
||||
$nodeText .= $this->makeNode($key, $value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? $tagContent : htmlentities($tagContent);
|
||||
}
|
||||
|
||||
$nodeText .= (in_array($tagName, $this->CDATAEncoding)) ? "]]></$tagName>" : "</$tagName>";
|
||||
|
||||
return $nodeText . PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints channel elements
|
||||
*/
|
||||
private function printChannels(): void
|
||||
{
|
||||
// Start channel tag
|
||||
switch ($this->version)
|
||||
{
|
||||
case RSS2:
|
||||
echo '<channel>' . PHP_EOL;
|
||||
break;
|
||||
case RSS1:
|
||||
echo (isset($this->data['ChannelAbout'])) ? "<channel rdf:about=\"{$this->data['ChannelAbout']}\">" : "<channel rdf:about=\"{$this->channels['link']}\">";
|
||||
break;
|
||||
}
|
||||
|
||||
// Print items of channel
|
||||
foreach ($this->channels as $key => $value)
|
||||
{
|
||||
if ($this->version == ATOM && $key == 'link')
|
||||
{
|
||||
// ATOM prints the link element as an href attribute
|
||||
echo $this->makeNode($key, '', ['href' => $value]);
|
||||
// Add the id for ATOM
|
||||
echo $this->makeNode('id', $this->uuid($value, 'urn:uuid:'));
|
||||
}
|
||||
else if ($this->version == ATOM && $key == 'feedUrl')
|
||||
{
|
||||
echo $this->makeNode('link', '', ['rel' => 'self', 'href' => $value]);
|
||||
}
|
||||
else
|
||||
{
|
||||
echo $this->makeNode($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
// RSS 1.0 has a special tag <rdf:Seq> with channel
|
||||
if ($this->version == RSS1)
|
||||
{
|
||||
echo "<items>" . PHP_EOL . "<rdf:Seq>" . PHP_EOL;
|
||||
foreach ($this->items as $item)
|
||||
{
|
||||
$thisItems = $item->getElements();
|
||||
echo "<rdf:li resource=\"{$thisItems['link']['content']}\"/>" . PHP_EOL;
|
||||
}
|
||||
echo "</rdf:Seq>" . PHP_EOL . "</items>" . PHP_EOL . "</channel>" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints formatted feed items
|
||||
*/
|
||||
private function printItems(): void
|
||||
{
|
||||
foreach ($this->items as $item)
|
||||
{
|
||||
$thisItems = $item->getElements();
|
||||
|
||||
// The argument is printed as rdf:about attribute of item in RSS 1.0
|
||||
echo $this->startItem($thisItems['link']['content']);
|
||||
|
||||
foreach ($thisItems as $feedItem)
|
||||
{
|
||||
echo $this->makeNode($feedItem['name'], $feedItem['content'], $feedItem['attributes']);
|
||||
}
|
||||
echo $this->endItem();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the starting tag of items
|
||||
*
|
||||
* @param string|false $about The value of about tag, which is used only for RSS 1.0
|
||||
*/
|
||||
private function startItem($about = false): void
|
||||
{
|
||||
if ($this->version == RSS2)
|
||||
{
|
||||
echo '<item>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == RSS1)
|
||||
{
|
||||
if ($about)
|
||||
{
|
||||
echo "<item rdf:about=\"$about\">" . PHP_EOL;
|
||||
}
|
||||
else
|
||||
{
|
||||
die("link element is not set.\n It's required for RSS 1.0 to be used as the about attribute of the item");
|
||||
}
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
echo "<entry>" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the feed item tag
|
||||
*/
|
||||
private function endItem(): void
|
||||
{
|
||||
if ($this->version == RSS2 || $this->version == RSS1)
|
||||
{
|
||||
echo '</item>' . PHP_EOL;
|
||||
}
|
||||
elseif ($this->version == ATOM)
|
||||
{
|
||||
echo "</entry>" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Feed URL
|
||||
* @param string $string - The URL of the feed
|
||||
* @return void
|
||||
*/
|
||||
public function setFeedURL(string $string): void
|
||||
{
|
||||
$this->setChannelElement("feedUrl", $string);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Define constants for RSS 1.0, RSS 2.0, and Atom
|
||||
const RSS1 = 'RSS 1.0';
|
||||
const RSS2 = 'RSS 2.0';
|
||||
const ATOM = 'ATOM';
|
||||
@@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException;
|
||||
use Slim\Exception\HttpMethodNotAllowedException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Slim\Psr7\Response;
|
||||
use Throwable;
|
||||
use Tuupola\Middleware\JwtAuthentication;
|
||||
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
|
||||
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
|
||||
@@ -65,7 +66,12 @@ class middleware
|
||||
$app->add(function ($request, $handler)
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
return $response->withHeader("Content-Type", "application/json");
|
||||
$contentType = $response->getHeaderLine("Content-Type");
|
||||
if (empty($contentType) || $contentType === "text/html")
|
||||
{
|
||||
return $response->withHeader("Content-Type", "application/json");
|
||||
}
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,8 +85,8 @@ class middleware
|
||||
$app->add(new JwtAuthentication([
|
||||
"rules" => [
|
||||
new RequestPathRule([
|
||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
|
||||
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"]
|
||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"],
|
||||
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
|
||||
]),
|
||||
new RequestMethodRule([
|
||||
"ignore" => ["OPTIONS", "GET"]
|
||||
@@ -128,8 +134,27 @@ class middleware
|
||||
return $response;
|
||||
}
|
||||
});
|
||||
$app->addErrorMiddleware(true, true, true);
|
||||
|
||||
|
||||
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
|
||||
|
||||
|
||||
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
|
||||
|
||||
$errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception,
|
||||
bool $displayErrorDetails,
|
||||
bool $logErrors,
|
||||
bool $logErrorDetails
|
||||
) use ($app, $errorHandler)
|
||||
{
|
||||
$statusCode = $exception->getCode() ?: 500;
|
||||
|
||||
// Create a JSON response with the error message
|
||||
$response = $app->getResponseFactory()->createResponse($statusCode);
|
||||
$response->getBody()->write(json_encode(['error' => $exception->getMessage()]));
|
||||
|
||||
return $response;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
/**** Individual Blog Posts *****/
|
||||
|
||||
.profile {
|
||||
-webkit-border-radius: 50%;
|
||||
-moz-border-radius: 50%;
|
||||
border-radius: 50%;
|
||||
width: 70%;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
svg {
|
||||
@@ -15,6 +17,13 @@ footer {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
section#individualPost {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
div.byLine {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -46,13 +55,6 @@ div.cover {
|
||||
box-shadow: 0 4px 2px 0 var(--mutedBlack);
|
||||
}
|
||||
|
||||
section#individualPost {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
div.mainContent {
|
||||
border-right: 5px solid var(--mutedGrey);
|
||||
min-height: 100%;
|
||||
@@ -71,15 +73,24 @@ article {
|
||||
padding: 0 2em;
|
||||
}
|
||||
|
||||
article a {
|
||||
padding: 0 1em;
|
||||
}
|
||||
|
||||
article a::before,
|
||||
article a::after {
|
||||
visibility: hidden;
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
article a.btn::before,
|
||||
article a.btn::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
article a::before {
|
||||
content: '<';
|
||||
content: ' <';
|
||||
margin-left: -0.5em;
|
||||
}
|
||||
|
||||
@@ -87,9 +98,9 @@ article a::after {
|
||||
content: '>';
|
||||
}
|
||||
|
||||
article a:hover::before,
|
||||
article a:hover::after {
|
||||
visibility: visible;
|
||||
article a:hover,
|
||||
article a:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
article h1 {
|
||||
@@ -100,6 +111,31 @@ article h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
article h3:not(div.byLine > h3), .otherPosts h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
article .media {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
article table td, article table th {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
article table tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
article table tr:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
article .table {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
aside.sideContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -116,6 +152,7 @@ div.authorInfo {
|
||||
padding-left: 1em;
|
||||
padding-top: 0.5em;
|
||||
border-bottom: 5px solid var(--mutedGrey);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.authorInfo .picture {
|
||||
@@ -130,7 +167,7 @@ div.authorInfo h3 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
div.otherPosts {
|
||||
div.otherPosts, div.newsletter, div.feeds {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
@@ -140,10 +177,36 @@ div.otherPosts {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.otherPosts a {
|
||||
div.otherPosts a, div.feeds a {
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
div.newsletter div.form input[type="submit"] {
|
||||
margin-top: 1em;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
div.feeds .icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
align-items: flex-start;
|
||||
gap: 0.5em;
|
||||
flex-wrap: wrap-reverse;
|
||||
}
|
||||
|
||||
div.feeds h2 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.feeds i.fa-solid.fa-rss {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
div.feeds img.atom, div.feeds img.json {
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
div.categories {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -153,6 +216,10 @@ div.categories {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.form input[type="submit"] {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.image img, .image_resized img {
|
||||
max-width: 100%;
|
||||
-webkit-border-radius: 10px;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/**** Categories Pages ****/
|
||||
|
||||
main > h1 {
|
||||
padding-left: 3em;
|
||||
@@ -9,13 +10,21 @@ section.catPosts .largePost {
|
||||
|
||||
|
||||
section.categories {
|
||||
display: flex
|
||||
flex-direction row;
|
||||
justify-content: center;
|
||||
align-items;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
margin-bottom: 5em;
|
||||
row-gap: 1em;
|
||||
}
|
||||
|
||||
section.categories .btnContainer {
|
||||
flex-basis: 33.3333333%
|
||||
flex-basis: 33.3333333%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/**** Blog Home page *****/
|
||||
|
||||
.banner {
|
||||
max-width: 30%;
|
||||
box-shadow: 0 6px 4px 0 var(--mutedBlack);
|
||||
@@ -24,8 +26,47 @@ h3 {
|
||||
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 {
|
||||
/*margin: 0 5em;*/
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
@@ -67,14 +108,117 @@ section.largePost .outerContent .postContent a {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
#main .error {
|
||||
#main .errorFof, #main .unsubscribe {
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fof {
|
||||
#main .unsubscribe {
|
||||
height: 50dvh;
|
||||
}
|
||||
|
||||
.centered {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
section#olderPosts {
|
||||
/*max-width: 90%;*/
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
section#olderPosts .carousel {
|
||||
position: relative;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
section#olderPosts .arrow {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
display: flex;
|
||||
width: 2.5em;
|
||||
height: 2.5em;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
font-size: 1.625em;
|
||||
color: white;
|
||||
background: var(--mutedBlack);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
section#olderPosts .arrow:hover {
|
||||
background: var(--grey);
|
||||
}
|
||||
|
||||
section#olderPosts #prev {
|
||||
left: -4em;
|
||||
}
|
||||
|
||||
section#olderPosts #next {
|
||||
right: -4em;
|
||||
}
|
||||
|
||||
section#olderPosts .carouselOuter {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
section#olderPosts #allCarouselItems {
|
||||
display: none;
|
||||
}
|
||||
|
||||
section#olderPosts #carouselInner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: center;
|
||||
gap: 1em;
|
||||
transition: transform 0.5s ease-in-out;
|
||||
left: 0;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
section#olderPosts #carouselInner .cardItem {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border: 2px solid var(--primaryDefault);
|
||||
-webkit-border-radius: 0.625rem;
|
||||
-moz-border-radius: 0.625rem;
|
||||
border-radius: 0.625rem;
|
||||
width: 30em;
|
||||
height: 40rem;
|
||||
}
|
||||
|
||||
section#olderPosts #carouselInner .cardItem img {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
object-fit: cover;
|
||||
object-position: left;
|
||||
-webkit-border-radius: 0.625rem;
|
||||
-moz-border-radius: 0.625rem;
|
||||
border-radius: 0.625rem;
|
||||
color: #FFFFFF;
|
||||
|
||||
}
|
||||
|
||||
section#olderPosts #carouselInner .cardItem .content {
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
background: #FFFFFF;
|
||||
margin: 0 2em 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -8,5 +8,323 @@
|
||||
@import "blogPosts.css";
|
||||
@import "home.css";
|
||||
@import "category.css";
|
||||
@import "prism.css";
|
||||
|
||||
div.menuBar a.link::before, main a.link::before,
|
||||
div.menuBar a.link::after, main a.link::after {
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
/* Modal Styling */
|
||||
.policy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding-left: 5em;
|
||||
padding-bottom: 5em;
|
||||
}
|
||||
|
||||
.policy h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.4); /* Background color for the entire screen */
|
||||
box-sizing: border-box; /* Include padding and border in the element's total width and height */
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
.modal-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 40%;
|
||||
height: auto;
|
||||
max-height: 90dvh;
|
||||
overflow: auto;
|
||||
margin: 1.25em;
|
||||
padding: 1.25em;
|
||||
box-sizing: border-box; /* Include padding and border in the element's total width and height */
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #DDDDDD;
|
||||
margin: auto;
|
||||
padding: 20px;
|
||||
border: 5px solid var(--primaryHover);
|
||||
width: 100%;
|
||||
box-shadow: 0 6px 4px 0 var(--mutedBlack);
|
||||
}
|
||||
|
||||
.modal-content .flexRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: 1em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/**** Media Queries *****/
|
||||
|
||||
@media screen and (max-width: 90em) {
|
||||
|
||||
|
||||
section#olderPosts #carouselInner .cardItem {
|
||||
width: 25em;
|
||||
}
|
||||
|
||||
/***** Individual Blog Posts ***/
|
||||
|
||||
div.mainContent {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
aside.sideContent {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 75em) {
|
||||
|
||||
/***** Menu bar ***/
|
||||
div.menuBar .menu ul {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-left: 1.25em;
|
||||
}
|
||||
|
||||
div.menuBar .menu ul li input[type="search"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*** Cookie popup ***/
|
||||
.modal-content .flexRow {
|
||||
width: 50%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/*** Large Post for Home and Category ***/
|
||||
section.largePost {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
section.largePost .outerContent {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.banner {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
section.largePost .outerContent > .content,
|
||||
section.largePost .outerContent > img {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
section#olderPosts .arrow {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
|
||||
section#olderPosts #prev {
|
||||
left: -3em;
|
||||
}
|
||||
|
||||
section#olderPosts #next {
|
||||
right: -3em;
|
||||
}
|
||||
|
||||
section#olderPosts #carouselInner .cardItem {
|
||||
width: 20em;
|
||||
}
|
||||
|
||||
/***** Individual Blog Posts ***/
|
||||
section#individualPost {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.mainContent {
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
aside.sideContent {
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
justify-content: space-around;
|
||||
border-bottom: 5px solid var(--mutedGrey);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
aside.sideContent > div.authorInfo {
|
||||
grid-template-columns: 4fr 1fr 1fr 1fr;
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
aside.sideContent > div.authorInfo .picture {
|
||||
grid-row: span 3;
|
||||
}
|
||||
|
||||
div.byLine {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
div.byLine h3:last-child {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
aside.sideContent > div.authorInfo h3 {
|
||||
grid-column-start: 2;
|
||||
grid-column-end: end;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
aside.sideContent > div.categories {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
aside.sideContent > div.otherPosts {
|
||||
border-bottom: 5px solid var(--mutedGrey);
|
||||
}
|
||||
|
||||
|
||||
div.otherPosts, div.newsletter, div.feeds {
|
||||
justify-content: space-between;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
aside.sideContent > div {
|
||||
border-right: 5px solid var(--mutedGrey);
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
aside.sideContent > div:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
/***** Categories and Search Pages ***/
|
||||
main > h1 {
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-width: 55em) {
|
||||
|
||||
/*** Cookie Popup ***/
|
||||
.modal {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/*** Large Post for Home and Category ***/
|
||||
.banner {
|
||||
max-width: 75%;
|
||||
}
|
||||
|
||||
section#olderPosts .arrow {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
section#olderPosts #prev {
|
||||
left: -1.75em;
|
||||
}
|
||||
|
||||
section#olderPosts #next {
|
||||
right: -1.75em;
|
||||
}
|
||||
|
||||
section#olderPosts #carouselInner .cardItem {
|
||||
width: 15em;
|
||||
}
|
||||
|
||||
/***** Individual Blog Posts ***/
|
||||
|
||||
aside.sideContent > div.authorInfo {
|
||||
grid-template-columns: 2fr 1fr 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
}
|
||||
|
||||
aside.sideContent > div.authorInfo h3 {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media screen and (max-height: 38em) and (orientation: landscape) {
|
||||
/*** cookie popup ***/
|
||||
.modal {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.modal-content .flexRow {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 30em) {
|
||||
|
||||
/***** Individual Blog Posts ***/
|
||||
.profile {
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
section#individualPost {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
aside.sideContent {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
aside.sideContent > div.authorInfo {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
grid-template-rows: repeat(3, auto);
|
||||
border-top: 5px solid var(--mutedGrey);
|
||||
}
|
||||
|
||||
aside.sideContent > div.authorInfo .picture {
|
||||
grid-row: 1;
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
aside.sideContent > div.authorInfo h3 {
|
||||
grid-column: span 3;
|
||||
}
|
||||
|
||||
div.otherPosts, div.newsletter, div.feeds {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
/* PrismJS 1.29.0
|
||||
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers+autolinker+show-language+previewers+toolbar+match-braces+diff-highlight */
|
||||
code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers+show-language+inline-color+previewers+unescaped-markup+toolbar+copy-to-clipboard+download-button+match-braces */
|
||||
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
|
||||
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
|
||||
.token a{color:inherit}
|
||||
div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}
|
||||
span.inline-color-wrapper{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=);background-position:center;background-size:110%;display:inline-block;height:1.333ch;width:1.333ch;margin:0 .333ch;box-sizing:border-box;border:1px solid #fff;outline:1px solid rgba(0,0,0,.5);overflow:hidden}span.inline-color{display:block;height:120%;width:120%}
|
||||
.prism-previewer,.prism-previewer:after,.prism-previewer:before{position:absolute;pointer-events:none}.prism-previewer,.prism-previewer:after{left:50%}.prism-previewer{margin-top:-48px;width:32px;height:32px;margin-left:-16px;z-index:10;opacity:0;-webkit-transition:opacity .25s;-o-transition:opacity .25s;transition:opacity .25s}.prism-previewer.flipped{margin-top:0;margin-bottom:-48px}.prism-previewer:after,.prism-previewer:before{content:'';position:absolute;pointer-events:none}.prism-previewer:before{top:-5px;right:-5px;left:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer:after{top:100%;width:0;height:0;margin:5px 0 0 -7px;border:7px solid transparent;border-color:rgba(255,0,0,0);border-top-color:#fff}.prism-previewer.flipped:after{top:auto;bottom:100%;margin-top:0;margin-bottom:5px;border-top-color:rgba(255,0,0,0);border-bottom-color:#fff}.prism-previewer.active{opacity:1}.prism-previewer-angle:before{border-radius:50%;background:#fff}.prism-previewer-angle:after{margin-top:4px}.prism-previewer-angle svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-angle[data-negative] svg{-webkit-transform:scaleX(-1) rotate(-90deg);-moz-transform:scaleX(-1) rotate(-90deg);-ms-transform:scaleX(-1) rotate(-90deg);-o-transform:scaleX(-1) rotate(-90deg);transform:scaleX(-1) rotate(-90deg)}.prism-previewer-angle circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500}.prism-previewer-gradient{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px;width:64px;margin-left:-32px}.prism-previewer-gradient:before{content:none}.prism-previewer-gradient div{position:absolute;top:-5px;left:-5px;right:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer-color{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px}.prism-previewer-color:before{background-color:inherit;background-clip:padding-box}.prism-previewer-easing{margin-top:-76px;margin-left:-30px;width:60px;height:60px;background:#333}.prism-previewer-easing.flipped{margin-bottom:-116px}.prism-previewer-easing svg{width:60px;height:60px}.prism-previewer-easing circle{fill:#2d3438;stroke:#fff}.prism-previewer-easing path{fill:none;stroke:#fff;stroke-linecap:round;stroke-width:4}.prism-previewer-easing line{stroke:#fff;stroke-opacity:.5;stroke-width:2}@-webkit-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-o-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-moz-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}.prism-previewer-time:before{border-radius:50%;background:#fff}.prism-previewer-time:after{margin-top:4px}.prism-previewer-time svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-time circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500;stroke-dashoffset:0;-webkit-animation:prism-previewer-time linear infinite 3s;-moz-animation:prism-previewer-time linear infinite 3s;-o-animation:prism-previewer-time linear infinite 3s;animation:prism-previewer-time linear infinite 3s}
|
||||
[class*=lang-] script[type='text/plain'],[class*=language-] script[type='text/plain'],script[type='text/plain'][class*=lang-],script[type='text/plain'][class*=language-]{display:block;font:100% Consolas,Monaco,monospace;white-space:pre;overflow:auto}
|
||||
.token.punctuation.brace-hover,.token.punctuation.brace-selected{outline:solid 1px}.rainbow-braces .token.punctuation.brace-level-1,.rainbow-braces .token.punctuation.brace-level-5,.rainbow-braces .token.punctuation.brace-level-9{color:#e50;opacity:1}.rainbow-braces .token.punctuation.brace-level-10,.rainbow-braces .token.punctuation.brace-level-2,.rainbow-braces .token.punctuation.brace-level-6{color:#0b3;opacity:1}.rainbow-braces .token.punctuation.brace-level-11,.rainbow-braces .token.punctuation.brace-level-3,.rainbow-braces .token.punctuation.brace-level-7{color:#26f;opacity:1}.rainbow-braces .token.punctuation.brace-level-12,.rainbow-braces .token.punctuation.brace-level-4,.rainbow-braces .token.punctuation.brace-level-8{color:#e0e;opacity:1}
|
||||
pre.diff-highlight>code .token.deleted:not(.prefix),pre>code.diff-highlight .token.deleted:not(.prefix){background-color:rgba(255,0,0,.1);color:inherit;display:block}pre.diff-highlight>code .token.inserted:not(.prefix),pre>code.diff-highlight .token.inserted:not(.prefix){background-color:rgba(0,255,128,.1);color:inherit;display:block}
|
||||
|
||||
/*gruvbox light*/
|
||||
code[class*=language-],pre[class*=language-]{color:#3c3836;font-family:Consolas,Monaco,"Andale Mono",monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{color:#282828;background:#a89984}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{color:#282828;background:#a89984}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f9f5d7}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em}.token.attr-name,.token.attr-value,.token.attr-value .punctuation,.token.cdata,.token.comment,.token.operator,.token.prolog,.token.punctuation{color:#7c6f64}.token.atrule,.token.boolean,.token.constant,.token.delimiter,.token.important,.token.keyword,.token.property,.token.selector,.token.variable{color:#9d0006}.token.builtin,.token.doctype,.token.function,.token.tag,.token.tag .punctuation{color:#b57614}.token.entity,.token.number,.token.symbol{color:#8f3f71}.token.char,.token.string,.token.url{color:#797403}.token.url{text-decoration:underline}.token.regex{background:#797403}.token.bold{font-weight:700}.token.italic{font-style:italic}.token.inserted{background:#7c6f64}.token.deleted{background:#9d0006}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
id="svg2"
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-filename="C:\Users\Ilia\SVG\Atom_icon.png"
|
||||
viewBox="0 0 285.45 280.91"
|
||||
inkscape:export-xdpi="90"
|
||||
version="1.1"
|
||||
inkscape:version="0.91pre3 r13670"
|
||||
sodipodi:docname="_svgclean2.svg"
|
||||
>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
fit-margin-left="10"
|
||||
inkscape:zoom="1.979899"
|
||||
borderopacity="1.0"
|
||||
inkscape:current-layer="layer1"
|
||||
inkscape:cx="30.185218"
|
||||
inkscape:cy="330.70792"
|
||||
inkscape:snap-bbox-midpoints="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:snap-bbox="true"
|
||||
showgrid="false"
|
||||
fit-margin-right="10"
|
||||
inkscape:snap-nodes="false"
|
||||
units="mm"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:snap-smooth-nodes="true"
|
||||
inkscape:window-width="718"
|
||||
fit-margin-bottom="10"
|
||||
inkscape:snap-page="false"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
pagecolor="#ffffff"
|
||||
inkscape:window-height="645"
|
||||
fit-margin-top="10"
|
||||
/>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(-.31824 -771.33)"
|
||||
>
|
||||
<g
|
||||
id="g8434"
|
||||
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
|
||||
>
|
||||
<ellipse
|
||||
id="path3089"
|
||||
style="fill:#FFFFFF"
|
||||
rx="2.0633"
|
||||
ry="2.0618"
|
||||
cy="1037.7"
|
||||
cx="14.936"
|
||||
/>
|
||||
<path
|
||||
id="circle3412"
|
||||
style="fill:#FFFFFF"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,1020.4)"
|
||||
d="m15.045 2.7051v3.3496a11.266 11.258 0 0 1 11.154 11.213h3.3516a14.617 14.606 0 0 0 -14.506 -14.563z"
|
||||
/>
|
||||
<path
|
||||
id="circle3428"
|
||||
style="fill:#FFFFFF"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,1020.4)"
|
||||
d="m15.045 9.4453v2.9492a4.9226 4.919 0 0 1 4.8105 4.873h2.9492a7.8711 7.8654 0 0 0 -7.76 -7.8217z"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
<g
|
||||
id="g8430"
|
||||
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
|
||||
>
|
||||
<path
|
||||
id="path3302"
|
||||
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(0,1020.4)"
|
||||
d="m14.865 12.807c-3.944 0.0057-7.5137 0.47474-10.127 1.2383-1.3146 0.3841-2.3876 0.844-3.1562 1.375-0.76861 0.5309-1.2617 1.1648-1.2617 1.8906 0 0.7259 0.49311 1.3617 1.2617 1.8926 0.76861 0.5309 1.8417 0.97918 3.1562 1.3633 2.6292 0.7682 6.2246 1.2402 10.197 1.2402 3.9727 0 7.57-0.47204 10.199-1.2402 1.3146-0.3841 2.3857-0.83238 3.1543-1.3633 0.71878-0.49648 1.1813-1.0881 1.2402-1.7559h-0.64453c-0.05963 0.38822-0.35991 0.79958-0.9668 1.2188-0.67801 0.4684-1.6967 0.90955-2.9688 1.2812-2.5442 0.7433-6.0921 1.209-10.014 1.209-3.9216 0-7.4675-0.46568-10.012-1.209-1.2721-0.3717-2.3005-0.81285-2.9785-1.2812-0.67801-0.4683-0.98438-0.92797-0.98438-1.3555 0-0.4274 0.30637-0.89488 0.98438-1.3633 0.67801-0.4683 1.7064-0.90965 2.9785-1.2812 2.5286-0.73883 6.0485-1.2033 9.9414-1.209v-0.65039z"
|
||||
/>
|
||||
<circle
|
||||
id="path8416"
|
||||
cy="1034.4"
|
||||
cx="6.1108"
|
||||
r=".75893"
|
||||
style="fill:#FFFFFF"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
<g
|
||||
id="g8439"
|
||||
transform="matrix(9.7655,0,0,9.7655,-2.8077,-9219.8)"
|
||||
>
|
||||
<g
|
||||
id="g8426"
|
||||
>
|
||||
<path
|
||||
id="path3304"
|
||||
d="m14.865 12.236c-0.72049 0.36795-1.4472 0.75012-2.1816 1.1738-3.4404 1.9848-6.3203 4.1911-8.2129 6.1699-0.94628 0.9894-1.6489 1.914-2.0488 2.7578-0.39997 0.8438-0.50968 1.639-0.14648 2.2676s1.1078 0.93201 2.0391 1.0078c0.93131 0.076 2.0893-0.06106 3.4199-0.38476 2.6613-0.6484 6.0127-2.0366 9.4531-4.0215 2.0899-1.2056 3.9354-2.4914 5.5156-3.7598h-1.0293c-1.411 1.0794-3.0286 2.1662-4.8125 3.1953-3.3962 1.9593-6.7001 3.3277-9.2754 3.9551-1.2876 0.3137-2.3914 0.43995-3.2129 0.37305-0.82153-0.067-1.3291-0.31535-1.543-0.68555-0.21387-0.3702-0.16922-0.92367 0.18359-1.668 0.35282-0.7444 1.014-1.6364 1.9297-2.5938 1.8314-1.9149 4.6702-4.0894 8.0664-6.0488 0.62532-0.36075 1.2403-0.68024 1.8555-1v-0.73828z"
|
||||
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
|
||||
transform="translate(0,1020.4)"
|
||||
inkscape:connector-curvature="0"
|
||||
/>
|
||||
<circle
|
||||
id="circle8418"
|
||||
cy="1045.1"
|
||||
cx="2.8068"
|
||||
r=".75893"
|
||||
style="fill:#FFFFFF"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
<g
|
||||
id="g8422"
|
||||
>
|
||||
<path
|
||||
id="path3300"
|
||||
d="m11.682 3.1523c-0.17928-0.012267-0.35779 0.00188-0.5332 0.048828-0.70164 0.1879-1.1876 0.82968-1.502 1.709-0.31431 0.8792-0.47091 2.0283-0.50195 3.3965-0.062086 2.7365 0.4132 6.3316 1.4414 10.166s2.4148 7.1846 3.8379 9.5234c0.71153 1.1695 1.4246 2.087 2.1367 2.6914 0.71217 0.6045 1.4527 0.91642 2.1543 0.72852 0.70164-0.1879 1.1876-0.82783 1.502-1.707 0.31431-0.8793 0.48067-2.034 0.51172-3.4023 0.05491-2.42-0.33654-5.5249-1.1328-8.8594h-0.66797c0.81117 3.3483 1.2082 6.4642 1.1543 8.8418-0.03005 1.3241-0.19344 2.4196-0.4707 3.1953-0.27726 0.7756-0.64933 1.2019-1.0625 1.3125s-0.93818-0.07442-1.5664-0.60742c-0.62822-0.5332-1.3193-1.4036-2.0078-2.5352-1.377-2.2632-2.7448-5.5645-3.7598-9.3496-1.015-3.785-1.482-7.3305-1.4219-9.9785 0.030055-1.324 0.18954-2.4314 0.4668-3.207s0.64152-1.1882 1.0547-1.2988c0.41317-0.1106 0.946 0.062403 1.5742 0.5957 0.6182 0.52469 1.2989 1.3866 1.9766 2.4922v-1.1836c-0.521-0.7474-1.043-1.3562-1.561-1.7957-0.534-0.4534-1.086-0.7406-1.623-0.7774z"
|
||||
style="color:#000000;text-indent:0;block-progression:tb;text-decoration-line:none;text-transform:none;fill:#FFFFFF"
|
||||
transform="translate(0,1020.4)"
|
||||
inkscape:connector-curvature="0"
|
||||
/>
|
||||
<circle
|
||||
id="circle8420"
|
||||
cx="20.264"
|
||||
style="fill:#FFFFFF"
|
||||
r=".75893"
|
||||
cy="1043.4"
|
||||
/>
|
||||
</g
|
||||
>
|
||||
</g
|
||||
>
|
||||
</g
|
||||
>
|
||||
<metadata
|
||||
id="metadata19"
|
||||
>
|
||||
<rdf:RDF
|
||||
>
|
||||
<cc:Work
|
||||
>
|
||||
<dc:format
|
||||
>image/svg+xml
|
||||
</dc:format
|
||||
>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage"
|
||||
/>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/publicdomain/"
|
||||
/>
|
||||
<dc:publisher
|
||||
>
|
||||
<cc:Agent
|
||||
rdf:about="http://openclipart.org/"
|
||||
>
|
||||
<dc:title
|
||||
>Openclipart
|
||||
</dc:title
|
||||
>
|
||||
</cc:Agent
|
||||
>
|
||||
</dc:publisher
|
||||
>
|
||||
</cc:Work
|
||||
>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/publicdomain/"
|
||||
>
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction"
|
||||
/>
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution"
|
||||
/>
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks"
|
||||
/>
|
||||
</cc:License
|
||||
>
|
||||
</rdf:RDF
|
||||
>
|
||||
</metadata
|
||||
>
|
||||
</svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
|
||||
<path fill="#FFFFFF"
|
||||
d="M854 175q-27 0 -46 19t-19 45.5t18.5 45t45 18.5t45.5 -18.5t19 -45t-18.5 -45.5t-44.5 -19zM205 192l-34 34q-83 83 -88 154t68 144l82 82q45 46 48.5 78t-33.5 69v0q-16 19 -15.5 44.5t18.5 43.5t43.5 18.5t44.5 -15.5l1 1q25 -25 47 -32t45.5 4.5t53.5 41.5l95 96
|
||||
q75 74 147.5 70t155.5 -87l33 -34l-71 -72l-18 18q-47 47 -84 47.5t-82 -44.5l-112 -112q-86 -86 -169 -17l-11 -11q35 -42 31.5 -83t-45.5 -82l-100 -101q-31 -31 -40.5 -56.5t1 -51.5t42.5 -59l17 -17zM703 326q-28 0 -46.5 19t-18.5 45.5t18.5 45.5t45 19t45.5 -19
|
||||
t19 -45.5t-18.5 -45t-44.5 -19.5zM551 477q-27 0 -46 19t-19 45.5t19 45.5t45.5 19t45.5 -19t19 -45.5t-19 -45t-45 -19.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -15,8 +15,12 @@
|
||||
<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/prism.css">
|
||||
<link rel="stylesheet" href="/blog/css/main.css">
|
||||
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
|
||||
<script type='text/javascript'
|
||||
src='https://platform-api.sharethis.com/js/sharethis.js#property=6550cdc47a115e0012964576&product=sop'
|
||||
async='async'></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
@@ -49,12 +53,47 @@
|
||||
</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>
|
||||
|
||||
<div class="modal-container" id="cookiePopup">
|
||||
<div class="modal">
|
||||
<div class="modal-content">
|
||||
<h2><i class="fas fa-cookie-bite"></i> Hey I use cookies btw</h2>
|
||||
<p>Just to let you know, I use cookies to give you the best experience on my blog. By clicking agree
|
||||
I'll assume that you are happy with it. <a href="/blog/policy/cookie" class="link">Read more</a>
|
||||
</p>
|
||||
<div class="flexRow">
|
||||
<button class="btn btnPrimary" id="cookieAccept">agree</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="flexRow">
|
||||
<div class="spacer"></div>
|
||||
<div class="nav">
|
||||
<ul>
|
||||
<li><a href="/blog/policy/privacy" class="link">privacy policy</a></li>
|
||||
<li><a href="/blog/policy/cookie" class="link">cookie policy</a></li>
|
||||
</ul>
|
||||
</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>
|
||||
@@ -62,6 +101,7 @@
|
||||
</footer>
|
||||
|
||||
<script src="/js/typewriter.js"></script>
|
||||
<script src="/blog/js/prism.js"></script>
|
||||
<script src="/blog/js/index.js"></script>
|
||||
<script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script>
|
||||
</body>
|
||||
|
||||
@@ -33,10 +33,14 @@ function goToURL(url)
|
||||
// Get the current URL and split it into an array
|
||||
let urlArray = url.split('/');
|
||||
|
||||
if (url === '/blog/' || url === '/blog')
|
||||
if (localStorage.getItem('cookiePopup') === 'accepted')
|
||||
{
|
||||
document.querySelector('#cookiePopup').classList.add('hidden');
|
||||
}
|
||||
|
||||
if (url === '/blog' || url === 'blog' || url === '/blog/')
|
||||
{
|
||||
loadHomeContent();
|
||||
// window.history.pushState(null, null, url);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,7 +48,6 @@ function goToURL(url)
|
||||
if (urlArray[2] === 'post')
|
||||
{
|
||||
// Create a new URL with the dynamic part
|
||||
// window.history.pushState(null, null, url);
|
||||
loadIndividualPost(urlArray[urlArray.length - 1]).catch(err => console.log(err));
|
||||
return;
|
||||
}
|
||||
@@ -52,19 +55,150 @@ function goToURL(url)
|
||||
if (urlArray[2] === 'category')
|
||||
{
|
||||
// Create a new URL with the dynamic part
|
||||
// window.history.pushState(null, null, url);
|
||||
if (urlArray[3])
|
||||
{
|
||||
loadPostsByCategory(urlArray[urlArray.length - 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
loadAllCategories();
|
||||
loadAllCategories().catch(err => console.log(err));
|
||||
return;
|
||||
}
|
||||
|
||||
show404();
|
||||
if (urlArray[2] === 'search' && urlArray[3])
|
||||
{
|
||||
// Create a new URL with the dynamic part
|
||||
loadSearchResults(urlArray[urlArray.length - 1]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (urlArray[2] === 'policy')
|
||||
{
|
||||
if (urlArray[3] === 'privacy')
|
||||
{
|
||||
loadPrivacyPolicy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (urlArray[3] === 'cookie')
|
||||
{
|
||||
loadCookiePolicy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (urlArray[2] === 'unsubscribe')
|
||||
{
|
||||
if (urlArray[3])
|
||||
{
|
||||
unsubscribe(urlArray[urlArray.length - 1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
show404();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#cookieAccept').addEventListener('click', _ =>
|
||||
{
|
||||
document.querySelector('#cookiePopup').classList.add('hidden');
|
||||
localStorage.setItem('cookiePopup', 'accepted');
|
||||
});
|
||||
|
||||
/**
|
||||
* Submits the newsletter form
|
||||
*/
|
||||
function submitNewsletter()
|
||||
{
|
||||
fetch(`/api/blog/newsletter/${document.querySelector('#email').value}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(res => res.json().then(json =>
|
||||
{
|
||||
|
||||
document.querySelector('#newsletterMessage').classList.remove('hidden');
|
||||
|
||||
if (json.message.includes('exists'))
|
||||
{
|
||||
document.querySelector('#newsletterMessage').classList.add('error');
|
||||
document.querySelector('#newsletterMessage').classList.remove('success');
|
||||
document.querySelector('#newsletterMessage div').innerHTML = 'You"ve already signed up you silly goose!';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!res.ok)
|
||||
{
|
||||
document.querySelector('#newsletterMessage').classList.add('error');
|
||||
document.querySelector('#newsletterMessage').classList.remove('success');
|
||||
document.querySelector('#newsletterMessage div').innerHTML = json.error;
|
||||
return;
|
||||
}
|
||||
|
||||
document.querySelector('#newsletterMessage div').innerHTML = json.message;
|
||||
document.querySelector('#newsletterMessage').classList.add('success');
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* unsubscribe by email
|
||||
* @param email the email to unsubscribe
|
||||
*/
|
||||
function unsubscribe(email)
|
||||
{
|
||||
fetch(`/api/blog/newsletter/${email}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
}).then(res => res.json().then(json =>
|
||||
{
|
||||
document.querySelector('#main').innerHTML = '';
|
||||
let post = document.createElement('section');
|
||||
post.classList.add('unsubscribe');
|
||||
post.id = 'unsubscribe';
|
||||
let mainContent = document.createElement('div');
|
||||
mainContent.classList.add('centered');
|
||||
mainContent.innerHTML = `
|
||||
<h1>Unsubscribe</h1>
|
||||
<p>${json.message}</p>
|
||||
<a href="/blog/" class="btn btnPrimary">See all blog posts</a>
|
||||
`;
|
||||
post.appendChild(mainContent);
|
||||
document.querySelector('#main').appendChild(post);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted date
|
||||
* @param {string} dateString - the date string
|
||||
* @returns {string} the formatted date
|
||||
*/
|
||||
function createFormattedDate(dateString)
|
||||
{
|
||||
let formattedDate = new Date(dateString);
|
||||
return formattedDate.toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +212,7 @@ function createLargePost(post)
|
||||
outerContent.classList.add('outerContent');
|
||||
let img = document.createElement('img');
|
||||
img.className = 'banner';
|
||||
img.src = post.headerImg;
|
||||
img.src = post.headerImg.replaceAll('%2F', '/');
|
||||
img.alt = post.title;
|
||||
outerContent.appendChild(img);
|
||||
let content = document.createElement('div');
|
||||
@@ -99,18 +233,45 @@ function createLargePost(post)
|
||||
{
|
||||
categories = categories.substring(0, categories.length - 2);
|
||||
}
|
||||
let dateModifiedString = createFormattedDate(post.dateModified);
|
||||
|
||||
postContent.innerHTML = `
|
||||
<h2>${post.title}</h2>
|
||||
<h3>Last updated: ${post.dateModified} | ${categories}</h3>
|
||||
<h3>Last updated: ${dateModifiedString} | ${categories}</h3>
|
||||
<p>${post.abstract}</p>
|
||||
<a href="/blog/post/${post.title}#disqus_thread" class="btn btnPrimary">See Post</a>
|
||||
<a href="/blog/post/${post.title}" class="btn btnPrimary">See Post</a>
|
||||
`;
|
||||
content.appendChild(postContent);
|
||||
outerContent.appendChild(content);
|
||||
return outerContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a card post element
|
||||
* @param post the object
|
||||
* @returns {HTMLDivElement} the outer content of the post
|
||||
*/
|
||||
function createCardPost(post)
|
||||
{
|
||||
let cardItem = document.createElement('div');
|
||||
cardItem.classList.add('cardItem');
|
||||
cardItem.id = 'post' + post.ID;
|
||||
let img = document.createElement('img');
|
||||
img.className = 'cardImg';
|
||||
img.src = post.headerImg.replaceAll('%2F', '/');
|
||||
img.alt = post.title;
|
||||
cardItem.appendChild(img);
|
||||
let content = document.createElement('div');
|
||||
content.classList.add('content');
|
||||
content.innerHTML = `
|
||||
<h2>${post.title}</h2>
|
||||
<h3>Last updated: ${createFormattedDate(post.dateModified)}</h3>
|
||||
<a href="/blog/post/${post.title}" class="btn btnPrimary">See Post</a>
|
||||
`;
|
||||
cardItem.appendChild(content);
|
||||
return cardItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the home content
|
||||
*/
|
||||
@@ -118,6 +279,30 @@ function loadHomeContent()
|
||||
{
|
||||
fetch('/api/blog/post').then(res => res.json().then(json =>
|
||||
{
|
||||
// older posts outside the carousel and loop
|
||||
let olderPosts = document.createElement('section');
|
||||
olderPosts.classList.add('largePost');
|
||||
olderPosts.id = 'olderPosts';
|
||||
let h1 = document.createElement('h1');
|
||||
h1.innerHTML = 'older posts';
|
||||
olderPosts.appendChild(h1);
|
||||
|
||||
let carousel = document.createElement('div');
|
||||
carousel.classList.add('carousel');
|
||||
carousel.innerHTML += `<div class="arrow" id="prev"><i class="fa-solid fa-chevron-left"></i></div>
|
||||
<div class="arrow" id="next"><i class="fa-solid fa-chevron-right"></i></div>
|
||||
`;
|
||||
let carouselOuter = document.createElement('div');
|
||||
let carouselInner = document.createElement('div');
|
||||
let allCarouselItems = document.createElement('div');
|
||||
carouselOuter.classList.add('carouselOuter');
|
||||
carouselInner.id = 'carouselInner';
|
||||
allCarouselItems.id = 'allCarouselItems';
|
||||
carouselOuter.appendChild(allCarouselItems);
|
||||
carouselOuter.appendChild(carouselInner);
|
||||
carousel.appendChild(carouselOuter);
|
||||
olderPosts.appendChild(carousel);
|
||||
|
||||
for (let i = 0; i < json.length; i++)
|
||||
{
|
||||
if (json[i].featured === 1)
|
||||
@@ -130,10 +315,10 @@ function loadHomeContent()
|
||||
featuredPost.appendChild(h1);
|
||||
let outerContent = createLargePost(json[i]);
|
||||
featuredPost.appendChild(outerContent);
|
||||
document.querySelector('#main').prepend(featuredPost);
|
||||
document.querySelector('#main').appendChild(featuredPost);
|
||||
}
|
||||
|
||||
if (i === 0)
|
||||
if (i === 1)
|
||||
{
|
||||
let latestPost = document.createElement('section');
|
||||
latestPost.classList.add('largePost');
|
||||
@@ -143,12 +328,153 @@ function loadHomeContent()
|
||||
latestPost.appendChild(h1);
|
||||
let outerContent = createLargePost(json[i]);
|
||||
latestPost.appendChild(outerContent);
|
||||
document.querySelector('#main').prepend(latestPost);
|
||||
document.querySelector('#main').appendChild(latestPost);
|
||||
}
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
allCarouselItems.appendChild(createCardPost(json[i]));
|
||||
}
|
||||
}
|
||||
document.querySelector('#main').appendChild(olderPosts);
|
||||
//carousel loop
|
||||
carouselLoop(carouselInner, allCarouselItems);
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the loop for the carousel
|
||||
* @param {HTMLDivElement} carouselInner
|
||||
* @param {HTMLDivElement} allItems
|
||||
*/
|
||||
function carouselLoop(carouselInner, allItems)
|
||||
{
|
||||
const prev = document.querySelector('#prev');
|
||||
const next = document.querySelector('#next');
|
||||
const mediaBig = window.matchMedia('(max-width: 75em)');
|
||||
const mediaSmall = window.matchMedia('(max-width: 30em)');
|
||||
let cards = document.querySelectorAll('#allCarouselItems .cardItem');
|
||||
let visibleCardsCount = 3;
|
||||
|
||||
if (mediaBig.matches)
|
||||
{
|
||||
visibleCardsCount = 2; // only show 2 cards if on a slightly smaller screen e.g. tablet/laptop
|
||||
}
|
||||
|
||||
if (mediaSmall.matches)
|
||||
{
|
||||
visibleCardsCount = 1; // only show 1 card if on a mobile, although it'll only work on portrait
|
||||
}
|
||||
|
||||
let visibleCards = [];
|
||||
|
||||
// put the first n (3, 2, or 1) cards in the carousel
|
||||
for (let i = 0; i < visibleCardsCount; i++)
|
||||
{
|
||||
carouselInner.appendChild(cards[i]);
|
||||
visibleCards.push(cards[i]);
|
||||
}
|
||||
|
||||
if (allItems.children.length === 0)
|
||||
{
|
||||
// if there are no cards in the carousel, don't show the arrows
|
||||
prev.style.visibility = 'hidden';
|
||||
next.style.visibility = 'hidden';
|
||||
return;
|
||||
}
|
||||
|
||||
next.addEventListener('click', () =>
|
||||
{
|
||||
const firstCard = visibleCards.shift();
|
||||
firstCard.style.visibility = 'hidden'; // hide the first card
|
||||
|
||||
// add the next card to the end and off the screen
|
||||
const nextCard = allItems.querySelector('.cardItem');
|
||||
nextCard.style.transform = 'translateX(100%)';
|
||||
nextCard.style.transition = 'none';
|
||||
carouselInner.appendChild(nextCard);
|
||||
visibleCards.push(nextCard);
|
||||
|
||||
// transition all the cards to the left after 50ms
|
||||
// i.e. after the next card is added hence the setTimeout
|
||||
setTimeout(() =>
|
||||
{
|
||||
// move all the cards to the left
|
||||
for (let i = 0; i < carouselInner.children.length; i++)
|
||||
{
|
||||
carouselInner.children[i].style.transition = 'transform 0.5s ease-out';
|
||||
carouselInner.children[i].style.transform = 'translateX(-100%)';
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// after 500ms move all cards back to the center and move
|
||||
// the first card to the hidden div
|
||||
setTimeout(() =>
|
||||
{
|
||||
// move the first card back to the center
|
||||
// instantly and move it to the hidden div
|
||||
firstCard.style.transition = 'none';
|
||||
firstCard.style.transform = 'translateX(0)';
|
||||
allItems.appendChild(firstCard);
|
||||
firstCard.style.visibility = 'visible'; // make it visible again
|
||||
|
||||
// for the remaining cards, reset them
|
||||
for (let i = 0; i < carouselInner.children.length; i++)
|
||||
{
|
||||
carouselInner.children[i].style.transition = 'none';
|
||||
carouselInner.children[i].style.transform = 'translateX(0%)';
|
||||
}
|
||||
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// everything is done in reverse
|
||||
prev.addEventListener('click', () =>
|
||||
{
|
||||
const lastCard = visibleCards.pop();
|
||||
lastCard.style.visibility = 'hidden'; // hide the last card
|
||||
|
||||
// add the previous card to the beginning and off the screen
|
||||
const prevCard = allItems.querySelector('.cardItem:last-child');
|
||||
prevCard.style.transform = 'translateX(-100%)';
|
||||
prevCard.style.transition = 'none';
|
||||
carouselInner.insertBefore(prevCard, carouselInner.firstChild);
|
||||
visibleCards.unshift(prevCard);
|
||||
|
||||
// transition all the cards to the right after 50ms
|
||||
// i.e. after the previous card is added hence the setTimeout
|
||||
setTimeout(() =>
|
||||
{
|
||||
// move all the cards to the right
|
||||
for (let i = 0; i < carouselInner.children.length; i++)
|
||||
{
|
||||
carouselInner.children[i].style.transition = 'transform 0.5s ease-out';
|
||||
carouselInner.children[i].style.transform = 'translateX(100%)';
|
||||
}
|
||||
}, 50);
|
||||
|
||||
// after 500ms move all cards back to the center and move
|
||||
// the last card to the hidden div
|
||||
setTimeout(() =>
|
||||
{
|
||||
// move the last card back to the center
|
||||
// instantly and move it to the hidden div
|
||||
lastCard.style.transition = 'none';
|
||||
lastCard.style.transform = 'translateX(0)';
|
||||
allItems.insertBefore(lastCard, allItems.firstChild);
|
||||
lastCard.style.visibility = 'visible';
|
||||
|
||||
// for the remaining cards reset them
|
||||
for (let i = 0; i < carouselInner.children.length; i++)
|
||||
{
|
||||
carouselInner.children[i].style.transition = 'none';
|
||||
carouselInner.children[i].style.transform = 'translateX(0%)';
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest and featured posts
|
||||
* @returns {Promise<any[]>} the latest and featured posts
|
||||
@@ -257,7 +583,7 @@ async function createSideContent()
|
||||
<div class="authorInfo">
|
||||
<div class="picture">
|
||||
<img src="/imgs/profile.jpg"
|
||||
alt="My professional picture taken in brighton near
|
||||
alt="My professional picture taken in brighton near
|
||||
north street at night wearing a beige jacket and checkered shirt"
|
||||
class="profile">
|
||||
<p>Rohit Pai</p>
|
||||
@@ -289,8 +615,8 @@ async function createSideContent()
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<h3>Avid Full Stack Dev | Uni of Notts Grad | Amateur Blogger</h3>
|
||||
|
||||
</div>
|
||||
<div class="otherPosts">
|
||||
<h2>latest post</h2>
|
||||
@@ -304,14 +630,92 @@ async function createSideContent()
|
||||
<p>${featuredPost.abstract}</p>
|
||||
<a href="/blog/post/${featuredPost.title}" class="btn btnPrimary boxShadowIn boxShadowOut">See Post</a>
|
||||
</div>
|
||||
<div class="newsletter">
|
||||
<h3>Sign up to the newsletter to never miss a new post!</h3>
|
||||
<div id="newsletterForm" class="form">
|
||||
<div class="formControl">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" id="email" name="email" placeholder="Email" required>
|
||||
</div>
|
||||
<div class="success hidden" id="newsletterMessage">
|
||||
<button class="close" type="button" onclick="this.parentElement.classList.toggle('hidden')">×</button>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<input type="submit" value="Sign Up" onclick="submitNewsletter()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="feeds">
|
||||
<h2>feeds</h2>
|
||||
<div class="icons">
|
||||
<a href="https://rohitpai.co.uk/api/blog/feed/rss" class="btn btnPrimary" title="RSS"><i class="fa-solid fa-rss"></i></a>
|
||||
<a href="https://rohitpai.co.uk/api/blog/feed/atom" class="btn btnPrimary" title="Atom"><img class="atom" src="/blog/imgs/atomFeed.svg" alt="Atom"></a>
|
||||
<a href="https://rohitpai.co.uk/api/blog/feed/json" class="btn btnPrimary" title="JSON"><img class="json" src="/blog/imgs/jsonFeed.svg" alt="JSON"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="categories">
|
||||
<h2>categories</h2>
|
||||
${categories}
|
||||
</div>
|
||||
|
||||
`;
|
||||
return sideContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the meta tags
|
||||
* @param nameOrProperty - the name or property
|
||||
* @param attribute - the attribute
|
||||
* @param value - the value
|
||||
*/
|
||||
function createMetaTag(nameOrProperty, attribute, value)
|
||||
{
|
||||
let existingTag = document.querySelector(`meta[name="${nameOrProperty}"], meta[property="${nameOrProperty}"]`);
|
||||
|
||||
if (!existingTag)
|
||||
{
|
||||
// If the meta tag doesn't exist, create it
|
||||
let newTag = document.createElement('meta');
|
||||
newTag.setAttribute(nameOrProperty.includes('name') ? 'name' : 'property', nameOrProperty);
|
||||
newTag.setAttribute(attribute, value);
|
||||
document.head.appendChild(newTag);
|
||||
return;
|
||||
}
|
||||
|
||||
existingTag.setAttribute(attribute, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* inserts the meta tags
|
||||
* @param json - the json
|
||||
*/
|
||||
function insertMetaTags(json)
|
||||
{
|
||||
let metaDesc = document.querySelector('meta[name=\'description\']');
|
||||
metaDesc.setAttribute('content', json.abstract);
|
||||
|
||||
// Twitter meta tags
|
||||
createMetaTag('twitter:title', 'content', json.title);
|
||||
createMetaTag('twitter:description', 'content', json.abstract);
|
||||
createMetaTag('twitter:image', 'content', json.headerImg);
|
||||
|
||||
// Open Graph (Facebook) meta tags
|
||||
createMetaTag('og:title', 'content', json.title);
|
||||
createMetaTag('og:description', 'content', json.abstract);
|
||||
createMetaTag('og:image', 'content', json.headerImg);
|
||||
createMetaTag('og:url', 'content', window.location.href);
|
||||
createMetaTag('og:type', 'content', 'blog');
|
||||
createMetaTag('og:site_name', 'content', 'Rohit Pai"s Blog');
|
||||
createMetaTag('og:locale', 'content', 'en_GB');
|
||||
|
||||
//Keywords
|
||||
let metKeywords = document.querySelector('meta[name=\'keywords\']');
|
||||
let keywords = metKeywords.getAttribute('content');
|
||||
keywords += `, ${json.keywords}`;
|
||||
metKeywords.setAttribute('content', keywords);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Trys to load the individual post if not runs the 404 function
|
||||
* @param title
|
||||
@@ -329,6 +733,8 @@ async function loadIndividualPost(title)
|
||||
|
||||
await res.json().then(async json =>
|
||||
{
|
||||
//replace meta description
|
||||
insertMetaTags(json);
|
||||
// create the post
|
||||
let post = document.createElement('section');
|
||||
post.classList.add('post');
|
||||
@@ -336,13 +742,17 @@ async function loadIndividualPost(title)
|
||||
let mainContent = document.createElement('div');
|
||||
mainContent.classList.add('mainContent');
|
||||
let article = document.createElement('article');
|
||||
let headerImg = json.headerImg.replaceAll('%2F', '/');
|
||||
article.innerHTML = `
|
||||
<h1>${json.title}</h1>
|
||||
<div class="byLine">
|
||||
<h3>Last updated: ${json.dateModified}</h3>
|
||||
<h3>Published: ${createFormattedDate(json.dateCreated)} | Last updated: ${createFormattedDate(json.dateModified)}</h3>
|
||||
<h3>${createButtonCategories([csvToArray(json.categories.replace(/\s*,\s*/g, ','))])}</h3>
|
||||
<div class="sharethis-inline-share-buttons" data-url="https://rohitpai.co.uk/blog/post/${title}"
|
||||
data-title="${json.title}" data-description="${json.abstract}"
|
||||
data-image="https://rohitpai.co.uk/${headerImg}" data-username="@rohitpai123"></div>
|
||||
</div>
|
||||
<div class="cover" style="background-image: url('${json.headerImg}')"></div>
|
||||
<div class="cover" style="background-image: url('${headerImg}')"></div>
|
||||
${json.body}
|
||||
`;
|
||||
let comments = document.createElement('section');
|
||||
@@ -372,6 +782,7 @@ async function loadIndividualPost(title)
|
||||
s.setAttribute('data-timestamp', +new Date());
|
||||
d.body.appendChild(s);
|
||||
})();
|
||||
Prism.highlightAll();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -438,14 +849,223 @@ function loadPostsByCategory(category)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the search results
|
||||
* @param searchTerm the search term
|
||||
*/
|
||||
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);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the privacy policy
|
||||
*/
|
||||
function loadPrivacyPolicy()
|
||||
{
|
||||
document.querySelector('#main').innerHTML = `
|
||||
<div class="policy">
|
||||
<h2>Privacy Policy</h2>
|
||||
<p>Last Updated: Nov 12, 2023</p>
|
||||
<p>
|
||||
Thank you for visiting the Privacy Policy of Rohit Pai"s Blog. This Privacy Policy explains how I, Rohit Pai, collect, use, and share information about you (“you”, “yours” or “user”) when you access or use my website (“Services”). You are responsible for any third-party data you provid'e or share through the Services and confirm that you have the third partys consent to provide such data to me.
|
||||
</p>
|
||||
<br>
|
||||
|
||||
<h3>Sources of Information and Tracking Technologies</h3>
|
||||
<p>
|
||||
I collect information that you directly provide to me, such as when you submit a form or send me a message with your information. I may also receive information about you from my partners, including but not limited to distribution partners, data services, and marketing firms. I may combine this information with other information I collect from or about you. In these cases, my Privacy Policy governs the handling of the combined information. I and my partners may collect the information noted in this privacy policy using cookies, web beacons, pixels, and other similar technologies. These technologies are used for authentication, to store your preferences or progress, for analytics, and for advertising and analytics. Cookies are small text files stored on your computer. You can set your browser to reject cookies altogether, to reject my cookies in particular, or to delete cookies. However, this may cause some or all of my Services not to function on your computer or device.
|
||||
</p>
|
||||
<br>
|
||||
<h3>How I Use Your Information</h3>
|
||||
<p>
|
||||
I use information I collect about you to provide, maintain, and improve my Services and other interactions I have with you. For example, I use the information collected to:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Facilitate and improve your online experience;</li>
|
||||
<li>Provide and deliver products and services, perform authentication, process transactions and returns, and send you related information, including confirmations, receipts, invoices, customer experience surveys, and product or Services-related notices;</li>
|
||||
<li>Process and deliver promotions;</li>
|
||||
<li>Respond to your comments and questions and provide customer service;</li>
|
||||
<li>If you have indicated to me that you wish to receive notifications or promotional messages;</li>
|
||||
<li>Detect, investigate and prevent fraudulent transactions and other illegal activities and protect my rights and property and others;</li>
|
||||
<li>Comply with my legal and financial obligations;</li>
|
||||
<li>Monitor and analyze trends, usage, and activities;</li>
|
||||
<li>Provide and allow my partners to provide advertising and marketing targeted toward your interests.</li>
|
||||
</ul>
|
||||
<br>
|
||||
<h3>How I May Share Information</h3>
|
||||
<p>
|
||||
I may share your Personal Information in the following situations:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong><em>Third Party Services Providers.</em></strong>
|
||||
I may share data with service providers, vendors, contractors, or agents who complete transactions or perform services on my behalf, such as those that assist me with my business and internal operations like shipping and delivery, payment processing, fraud prevention, customer service, gift cards, experiences, personalization, marketing, and advertising;
|
||||
</li>
|
||||
<li>
|
||||
<strong><em>Change in Business.</em></strong>
|
||||
I may share data in connection with a corporate business transaction, such as a merger or acquisition of all or a portion of my business to another company, joint venture, corporate reorganization, insolvency or bankruptcy, financing or sale of company assets;
|
||||
</li>
|
||||
<li>
|
||||
<strong><em>To Comply with Law.</em></strong>
|
||||
I may share data to facilitate legal process from lawful requests by public authorities, including to meet national security or law enforcement demands as permitted by law.
|
||||
</li>
|
||||
<li>
|
||||
<strong><em>With Your Consent.</em></strong>
|
||||
I may share data with third parties when I have your consent.
|
||||
</li>
|
||||
<li>
|
||||
<strong><em>With Advertising and Analytics Partners.</em></strong>
|
||||
See the section entitled “Advertising and Analytics” below.
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<h3>Advertising and Analytics</h3>
|
||||
<p>
|
||||
I use advertising and analytics technologies to better understand your online activity on my Services to provide personalized products and services that may interest you. I may allow third-party companies, including ad networks, to serve advertisements, provide other advertising services, and/or collect certain information when you visit my website. Third-party companies may use pseudonymized personal data (e.g., click stream information, browser type, time and date, subject of advertisements clicked or scrolled over) during your visit to this website in order to provide advertisements about goods and services likely to be of interest to you, on this website and others. To learn more about Interest-Based Advertising or to opt-out of this type of advertising, you can visit <a href="http://optout.aboutads.info/?c=2&lang=EN">AboutAds.info/choices</a> or <a href="http://optout.networkadvertising.org/?c=1">www.networkadvertising.org/choices</a>. Some third-party companies may also use non-cookie technologies, such as statistical IDs. Please keep in mind that your web browser may not permit you to block the use of these non-cookie technologies, and those browser settings that block cookies may have no effect on such techniques. If the third-party company uses the non-cookie technologies for interest-based advertising, you can opt out at <a href="http://optout.networkadvertising.org/?c=1">www.networkadvertising.org/choices</a>. Please note the industry opt out only applies to use for interest-based advertising and may not apply to use for analytics or attribution. Some websites have “do not track” features that allow you to tell a website not to track you. These features are not all uniform. I do not currently respond to those signals.
|
||||
</p>
|
||||
<br>
|
||||
<h3>Data Security</h3>
|
||||
<p>
|
||||
I implement commercially reasonable security measures designed to protect your information. Despite my best efforts, however, no security measures are completely impenetrable.
|
||||
</p>
|
||||
<br>
|
||||
<h3>Data Retention</h3>
|
||||
<p>
|
||||
I store the information I collect about you for as long as necessary for the purpose(s) for which I collected it or for other legitimate business purposes, including to meet my legal, regulatory, or other compliance obligations.
|
||||
</p>
|
||||
<br>
|
||||
|
||||
<h3>EU Privacy Rights</h3>
|
||||
|
||||
<p>Individuals located in certain countries, including the European Economic Area (EEA) and the United Kingdom, have certain statutory rights under the General Data Protection Regulation (GDPR) in relation to their personal data.</p>
|
||||
<p>To the extent information I collect is associated with an identified or identifiable natural person and is protected as personal data under GDPR, it is referred to in this Privacy Policy as “Personal Data”.</p>
|
||||
<p>
|
||||
<strong><em>Data Subject Access Requests</em></strong>
|
||||
</p>
|
||||
<p>
|
||||
Subject to any exemptions provided by law, you may have the right to request:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
a copy of the Personal Data I hold about you;
|
||||
</li>
|
||||
<li>
|
||||
to correct the Personal Data I hold about you;
|
||||
</li>
|
||||
<li>
|
||||
to delete your Account or Personal Data;
|
||||
</li>
|
||||
<li>
|
||||
to object to processing of your Personal Data for certain purposes;
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
To access your privacy rights, send me an email at rohit@rohitpai.co.uk.
|
||||
</p>
|
||||
<p>
|
||||
I will generally process requests within one month. I may need to request specific information from you to help me confirm your identity and/or the jurisdiction in which you reside. If your request is complicated or if you have made a large number of requests, it may take me longer. I will let you know if I need longer than one month to respond.
|
||||
</p>
|
||||
<p>
|
||||
<strong><em>Legal Bases For Processing Personal Data</em></strong>
|
||||
</p>
|
||||
<p>
|
||||
I may process your Personal Data under applicable data protection law on the following legal grounds:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong><em>Contractual Necessity:</em></strong>
|
||||
I may process your Personal Data to enter into or perform a contract with you.
|
||||
</li>
|
||||
<li>
|
||||
<strong><em>Consent:</em></strong>
|
||||
where you have provided consent to process your Personal Data. You may withdraw your consent at any time.
|
||||
</li>
|
||||
<li>
|
||||
<strong><em>Legitimate interest:</em></strong>
|
||||
I process your Personal Data to provide my Services to you such as to provide my online user experience, communicate with you, provide customer service, market, analyze and improve my business, and to protect my Services.
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<h3>Age Limitations</h3>
|
||||
<p>
|
||||
My Service is intended for adults ages 18 years and above. I do not knowingly collect personally identifiable information from children. If you are a parent or legal guardian and think your child under 13 has given me information, please email or write to me at the address listed at the end of this Privacy Policy. Please mark your inquiries “COPPA Information Request.”
|
||||
</p>
|
||||
<br>
|
||||
<h3>Changes to this Privacy Policy</h3>
|
||||
<p>
|
||||
Rohit Pai may change this Privacy Policy from time to time. I encourage you to visit this page to stay informed. If the changes are material, I may provide you additional notice to your email address or through my Services. Your continued use of the Services indicates your acceptance of the modified Privacy Policy.
|
||||
</p>
|
||||
<br>
|
||||
<h3>Newsletters</h3>
|
||||
<p>
|
||||
You can opt in to receive my marketing emails and/or newsletters by below. I may still send you transactional messages, which include Services-related communications and responses to your questions.
|
||||
</p>
|
||||
<br>
|
||||
<h3>Storage of Information in the United States</h3>
|
||||
<p>
|
||||
Information I maintain may be stored both within and outside of the United States. If you live outside of the United States, you understand and agree that I may transfer your information to the United States, and that U.S. laws may not afford the same level of protection as those in your country.
|
||||
</p>
|
||||
<br>
|
||||
<h3>Contact Me</h3>
|
||||
<p>
|
||||
If you have questions, comments, or concerns about this Privacy Policy, you may contact me at:
|
||||
</p>
|
||||
<a href="https://rohitpai.co.uk/#contact" class="link">Contact</a>
|
||||
<a href="mailto:rohit@rohitpai.co.uk" class="link">rohit@rohitpai.co.uk</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the cookie policy
|
||||
*/
|
||||
function loadCookiePolicy()
|
||||
{
|
||||
document.querySelector('#main').innerHTML = `
|
||||
<div class="policy">
|
||||
<h3>Cookies Policy</h3>
|
||||
<p>I only use functional cookies for the blog which includes PHP Session ID, disqus. a cookie to disable the cookie popup, and maybe share this. I think that these are functional cookies, if you don't, you're welcome to exit the site or tell me by emailing me through the email address below, or the contact form on the contact section of my main website.</p>
|
||||
<br>
|
||||
<a href="mailto:rohit@rohitpai.co.uk" class="link">rohit@rohitpai.co.uk</a>
|
||||
<br>
|
||||
<a href="https://rohitpai.co.uk/#contact" class="link">contact</a>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the 404 page
|
||||
*/
|
||||
function show404()
|
||||
{
|
||||
document.querySelector('#main').innerHTML = `
|
||||
<div class="error">
|
||||
<div class="fof">
|
||||
<div class="errorFof">
|
||||
<div class="centered">
|
||||
<h1>Blog post, Category or page not found</h1>
|
||||
<a href="/blog/" class="btn btnPrimary">See all blog posts</a>
|
||||
</div>
|
||||
|
||||
@@ -120,4 +120,4 @@ div.message.hidden {
|
||||
|
||||
div.message button:hover {
|
||||
text-shadow: -1px 2px var(--mutedBlack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,28 @@ footer .spacer {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
footer .nav {
|
||||
width: 100%;
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
footer .nav ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
footer .nav ul a {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
footer p {
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
|
||||
@@ -19,7 +19,7 @@ nav {
|
||||
width: 100%;
|
||||
transition: background-color 0.4s ease-in;
|
||||
color: #FFFFFF;
|
||||
z-index: 1;
|
||||
z-index: 100000000000000000000000000;
|
||||
}
|
||||
|
||||
nav.scrolled {
|
||||
@@ -64,10 +64,6 @@ nav ul li span {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/*nav ul li a:hover span, nav ul li .active span {*/
|
||||
/* visibility: visible;*/
|
||||
/*}*/
|
||||
|
||||
nav ul li .active::before,
|
||||
nav ul li .active::after {
|
||||
visibility: visible;
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
--grey: hsla(0, 0%, 39%, 1);
|
||||
--notAvailableDefault: hsla(0, 0%, 39%, 1);
|
||||
--notAvailableHover: hsla(0, 0%, 32%, 1);
|
||||
--mutedGrey: hsla(0, 0%, 78%, 1);
|
||||
--mutedGrey: hsla(0, 0%, 75%, 1);
|
||||
--mutedBlack: hsla(0, 0%, 0%, 0.25);
|
||||
--mutedGreen: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);
|
||||
--mutedGreen: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 1);
|
||||
--navBack: hsla(0, 0%, 30%, 1);
|
||||
|
||||
/* Font Sizes */
|
||||
@@ -63,7 +63,7 @@ h2 {
|
||||
line-height: 2.1875rem;
|
||||
}
|
||||
|
||||
a.btn, button.btn, form input[type="submit"] {
|
||||
a.btn, button.btn, form input[type="submit"], div.form input[type="submit"] {
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
padding: 1em 2em;
|
||||
@@ -75,11 +75,15 @@ a.btn, button.btn, form input[type="submit"] {
|
||||
max-height: 4em;
|
||||
}
|
||||
|
||||
form input[type="submit"] {
|
||||
button.btn {
|
||||
padding: 1.2em 2.2em;
|
||||
}
|
||||
|
||||
form input[type="submit"], div.form input[type="submit"] {
|
||||
padding: 1.1em 2em;
|
||||
}
|
||||
|
||||
a.btn:hover, button.btn:hover form input[type="submit"]:hover {
|
||||
a.btn:hover, button.btn:hover, form input[type="submit"]:hover, div.form input[type="submit"]:hover {
|
||||
border: 0.3215em solid var(--primaryHover);
|
||||
}
|
||||
|
||||
@@ -87,7 +91,7 @@ a.btn:hover::before, a.btn:hover::after {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
a.btnPrimary, button.btnPrimary, form input[type="submit"] {
|
||||
a.btnPrimary, button.btnPrimary, form input[type="submit"], div.form input[type="submit"] {
|
||||
background-color: var(--primaryDefault);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -108,11 +112,12 @@ a.btnPrimary[disabled]:hover, button.btnPrimary[disabled]:hover {
|
||||
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, div.form input[type="submit"]:hover {
|
||||
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, div.form input[type="submit"]:active {
|
||||
padding: 0.8rem 1.8rem;
|
||||
}
|
||||
|
||||
@@ -128,75 +133,82 @@ a.btn:active, button.btn:active, form input[type="submit"]:active {
|
||||
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, form .formControl textarea.invalid:invalid,
|
||||
div.form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
||||
border: 0.3125em solid var(--errorDefault);
|
||||
}
|
||||
|
||||
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
|
||||
border: 4px solid var(--errorHover);
|
||||
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
|
||||
div.form .formControl input:not([type="submit"]).invalid:invalid:focus, div.form .formControl textarea.invalid:invalid:focus {
|
||||
border: 0.3125em 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"]) {
|
||||
form .formControl input:not([type="submit"]),
|
||||
div.form .formControl input:not([type="submit"]) {
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
form .formControl {
|
||||
form .formControl,
|
||||
div.form .formControl {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
/*align-items: flex-start;*/
|
||||
}
|
||||
|
||||
form .formControl.passwordControl {
|
||||
form .formControl.passwordControl,
|
||||
div.form .formControl.passwordControl {
|
||||
display: block;
|
||||
}
|
||||
|
||||
form input[type="submit"] {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
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__main .ck-content {
|
||||
form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]),
|
||||
div.form .formControl input:not([type="submit"]), form .formControl textarea,
|
||||
div.form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
|
||||
div.form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]) {
|
||||
width: 100%;
|
||||
border: 4px solid var(--primaryDefault);
|
||||
border: 0.3125em solid var(--primaryDefault);
|
||||
background: none;
|
||||
outline: none;
|
||||
-webkit-border-radius: 1em;
|
||||
-moz-border-radius: 1em;
|
||||
-webkit-border-radius: 0.5em;
|
||||
-moz-border-radius: 0.5em;
|
||||
border-radius: 0.5em;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
form .formControl textarea {
|
||||
form .formControl textarea,
|
||||
div.form .formControl textarea {
|
||||
padding: 0.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, form .formControl textarea.invalid:invalid,
|
||||
div.form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
||||
border: 0.3125em solid var(--errorDefault);
|
||||
}
|
||||
|
||||
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
|
||||
border: 4px solid var(--errorHover);
|
||||
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
|
||||
div.form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
|
||||
border: 0.3125em solid var(--errorHover);
|
||||
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"]):hover, form .formControl textarea:hover {
|
||||
border: 4px solid var(--primaryHover);
|
||||
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover,
|
||||
div.menu input:not([type="submit"]):focus, div.menu input:not([type="submit"]):hover,
|
||||
div.form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
|
||||
div.form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover {
|
||||
border: 0.3125em solid var(--primaryHover);
|
||||
}
|
||||
|
||||
form .formControl input:not([type="submit"]) {
|
||||
form .formControl input:not([type="submit"]),
|
||||
div.form .formControl input:not([type="submit"]) {
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
form .formControl i.fa-eye, form .formControl i.fa-eye-slash {
|
||||
form .formControl i.fa-eye, form .formControl i.fa-eye-slash,
|
||||
div.form .formControl i.fa-eye, form .formControl i.fa-eye-slash {
|
||||
margin-left: -40px;
|
||||
cursor: pointer;
|
||||
color: var(--primaryDefault);
|
||||
@@ -207,7 +219,8 @@ form .formControl input:not([type="submit"]):focus + i.fa-eye-slash {
|
||||
color: var(--primaryHover);
|
||||
}
|
||||
|
||||
form .formControl .checkContainer {
|
||||
form .formControl .checkContainer,
|
||||
div.form .formControl .checkContainer {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin-bottom: 1.25em;
|
||||
@@ -218,7 +231,8 @@ form .formControl .checkContainer {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
form .formControl .checkContainer input {
|
||||
form .formControl .checkContainer input,
|
||||
div.form .formControl .checkContainer input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
@@ -226,7 +240,8 @@ form .formControl .checkContainer input {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
form .formControl .checkContainer .checkmark {
|
||||
form .formControl .checkContainer .checkmark,
|
||||
div.form .formControl .checkContainer .checkmark {
|
||||
position: absolute;
|
||||
top: 1.25em;
|
||||
left: 0;
|
||||
@@ -235,29 +250,35 @@ form .formControl .checkContainer .checkmark {
|
||||
background-color: var(--mutedGrey);
|
||||
}
|
||||
|
||||
form .formControl .checkContainer:hover input ~ .checkmark {
|
||||
form .formControl .checkContainer:hover input ~ .checkmark,
|
||||
div.form .formControl .checkContainer:hover input ~ .checkmark {
|
||||
background-color: var(--grey);
|
||||
}
|
||||
|
||||
form .formControl .checkContainer input:checked ~ .checkmark {
|
||||
form .formControl .checkContainer input:checked ~ .checkmark,
|
||||
div.form .formControl .checkContainer input:checked ~ .checkmark {
|
||||
background-color: var(--primaryDefault);
|
||||
}
|
||||
|
||||
form .formControl .checkContainer input:checked:hover ~ .checkmark {
|
||||
form .formControl .checkContainer input:checked:hover ~ .checkmark,
|
||||
div.form .formControl .checkContainer input:checked:hover ~ .checkmark {
|
||||
background-color: var(--primaryHover);
|
||||
}
|
||||
|
||||
form .formControl .checkContainer .checkmark:after {
|
||||
form .formControl .checkContainer .checkmark:after,
|
||||
div.form .formControl .checkContainer .checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
form .formControl .checkContainer input:checked ~ .checkmark:after {
|
||||
form .formControl .checkContainer input:checked ~ .checkmark:after,
|
||||
div.form .formControl .checkContainer input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
form .formControl .checkContainer .checkmark:after {
|
||||
form .formControl .checkContainer .checkmark:after,
|
||||
div.form .formControl .checkContainer .checkmark:after {
|
||||
left: 9px;
|
||||
top: 5px;
|
||||
width: 5px;
|
||||
@@ -302,32 +323,103 @@ a {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
a.link {
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
a.link::before,
|
||||
a.link::after {
|
||||
visibility: hidden;
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
nav a.link::before,
|
||||
nav a.link::after,
|
||||
.nav a.link::before,
|
||||
.nav a.link::after {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
a.link::before {
|
||||
content: '<';
|
||||
content: ' <';
|
||||
margin-left: -0.5em;
|
||||
}
|
||||
|
||||
a.link::after {
|
||||
content: '>';
|
||||
content: '> ';
|
||||
}
|
||||
|
||||
a.link:hover::before,
|
||||
a.link:hover::after {
|
||||
a.link:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav a.link:hover::before,
|
||||
nav a.link:hover::after,
|
||||
.nav a.link:hover::before,
|
||||
.nav a.link:hover::after {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/*.link span {*/
|
||||
/* visibility: hidden;*/
|
||||
/*}*/
|
||||
nav a.link:hover, .nav a.link:hover {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/*.link:hover span {*/
|
||||
/* visibility: visible;*/
|
||||
/*}*/
|
||||
div.error, div.success {
|
||||
color: #FFFFFF;
|
||||
padding: 0.5em 0.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 0.5s ease-in-out;
|
||||
-moz-transition: all 0.5s ease-in-out;
|
||||
-ms-transition: all 0.5s ease-in-out;
|
||||
-o-transition: all 0.5s ease-in-out;
|
||||
transition: all 0.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: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: #FFFFFF;
|
||||
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);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ main.editor section {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
section#editPost {
|
||||
section#curriculumVitae {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ section#projects form.projItem:not(.editing) div.formControl.infoContainer texta
|
||||
}
|
||||
|
||||
section#addPost form, section#editPost form {
|
||||
margin: auto 4rem;
|
||||
margin: auto 4rem 4rem;
|
||||
}
|
||||
|
||||
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar {
|
||||
@@ -276,6 +276,6 @@ section#editPost table td, th {
|
||||
min-width: 10rem;
|
||||
}
|
||||
|
||||
section#editPost form {
|
||||
margin-bottom: 2em;
|
||||
section#newsletter form {
|
||||
margin: 0 5em;
|
||||
}
|
||||
@@ -66,71 +66,22 @@ div#login input[type=submit]{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.error, div.success {
|
||||
color: #FFFFFF;
|
||||
padding: 0.5em 0.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 0.5s ease-in-out;
|
||||
-moz-transition: all 0.5s ease-in-out;
|
||||
-ms-transition: all 0.5s ease-in-out;
|
||||
-o-transition: all 0.5s ease-in-out;
|
||||
transition: all 0.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: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
color: #FFFFFF;
|
||||
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%;
|
||||
width: 60%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
div.btnContainer a {
|
||||
justify-content: center;
|
||||
text-transform: unset;
|
||||
}
|
||||
|
||||
form div.btnContainer input[type="submit"] {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
div.btnContainer a:not(.btn) {
|
||||
|
||||
@@ -61,7 +61,7 @@ nav.sideNav ul li.dropdown ul {
|
||||
|
||||
nav.sideNav ul li.dropdown ul.active {
|
||||
transition: max-height ease-in 400ms;
|
||||
max-height: 15rem;
|
||||
max-height: 20rem;
|
||||
}
|
||||
|
||||
nav.sideNav ul li.dropdown ul li {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
|
||||
<script src="js/CKEditor/ckeditor.js"></script>
|
||||
<link rel="stylesheet" href="css/main.css">
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="sideNav">
|
||||
@@ -40,6 +39,11 @@
|
||||
Edit Blog Post
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="goToNewsletter" class="link">
|
||||
Send Newsletter
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#" id="logout">Logout</a></li>
|
||||
@@ -135,7 +139,7 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -200,10 +204,7 @@
|
||||
</div>
|
||||
<div class="formControl">
|
||||
<label for="postCategories">Categories</label>
|
||||
<input type="text" name="postCategories" id="postCategories"
|
||||
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
|
||||
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
|
||||
oninput="this.setCustomValidity('')" required>
|
||||
<input type="text" name="postCategories" id="postCategories" title="CSV format" required>
|
||||
</div>
|
||||
|
||||
<div class="formControl">
|
||||
@@ -264,10 +265,7 @@
|
||||
</div>
|
||||
<div class="formControl">
|
||||
<label for="editPostCategories">Categories</label>
|
||||
<input type="text" name="editPostCategories" id="editPostCategories"
|
||||
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
|
||||
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
|
||||
oninput="this.setCustomValidity('')" required>
|
||||
<input type="text" name="editPostCategories" id="editPostCategories" title="CSV format" required>
|
||||
</div>
|
||||
|
||||
<div class="formControl">
|
||||
@@ -294,6 +292,33 @@
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section id="newsletter">
|
||||
<h2>newsletter</h2>
|
||||
<form action="" id="sendNewsletterForm" method="POST">
|
||||
<div class="formControl">
|
||||
<label for="newsletterSubject">Subject</label>
|
||||
<input type="text" id="newsletterSubject" name="newsletterSubject" required>
|
||||
</div>
|
||||
<div class="formControl">
|
||||
<label for="CKEditorNewsletter">Message</label>
|
||||
<div id="CKEditorNewsletter">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="error hidden" id="newsletterError">
|
||||
<button class="close" type="button">×</button>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div class="success hidden" id="newsletterSuccess">
|
||||
<button class="close" type="button">×</button>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<input type="submit" class="btn btnPrimary boxShadowIn boxShadowOut" value="Send newsletter">
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="js/editor.js"></script>
|
||||
|
||||
@@ -32,8 +32,9 @@
|
||||
|
||||
<div class="btnContainer">
|
||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
||||
|
||||
<a href="#" id="resetPwd">Reset Password</a>
|
||||
<a href="/api/user/login" class="btn btnPrimary boxShadowIn boxShadowOut">Login with Jump Cloud</a>
|
||||
<button type="button" id="resetPwd" class="btn btnPrimary boxShadowIn boxShadowOut">Reset Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -54,7 +55,8 @@
|
||||
|
||||
<div class="btnContainer">
|
||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
||||
<a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="loginBtn">Login</a>
|
||||
<button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="loginBtn">Login
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -75,7 +77,9 @@
|
||||
|
||||
<div class="btnContainer">
|
||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
||||
<a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="resendEmail">Resend Email</a>
|
||||
<button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="resendEmail">Resend
|
||||
Email
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -102,7 +106,7 @@
|
||||
|
||||
<div class="btnContainer">
|
||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
||||
<a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut">Login</a>
|
||||
<button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ let dateOptions = {month: 'short', year: 'numeric'};
|
||||
let textareaLoaded = false;
|
||||
let editors = {};
|
||||
let posts = null;
|
||||
const smallPaddingElements = ['figcaption', 'li'];
|
||||
document.addEventListener('DOMContentLoaded', () =>
|
||||
{
|
||||
// check if the userData is logged in, if not redirect to log in
|
||||
@@ -69,7 +70,7 @@ document.addEventListener('DOMContentLoaded', () =>
|
||||
}));
|
||||
|
||||
// CKEditor stuff
|
||||
createEditors("CKEditorAddPost", "CKEditorEditPost");
|
||||
createEditors('CKEditorAddPost', 'CKEditorEditPost', 'CKEditorNewsletter');
|
||||
|
||||
});
|
||||
|
||||
@@ -262,6 +263,7 @@ document.querySelector("#addPostForm").addEventListener("submit", e =>
|
||||
data.append("featured", document.querySelector("#isFeatured").checked ? "1" : "0");
|
||||
data.append("abstract", document.querySelector("#postAbstract").value);
|
||||
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('categories', document.querySelector('#postCategories').value.toLowerCase());
|
||||
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["abstract"] = document.querySelector("#editPostAbstract").value;
|
||||
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['categories'] = document.querySelector('#editPostCategories').value.toLowerCase();
|
||||
|
||||
@@ -364,6 +367,7 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
|
||||
{
|
||||
document.querySelector("#editPostForm").reset();
|
||||
document.querySelector("#editPostForm input[type='submit']").id = "";
|
||||
console.log();
|
||||
editors["CKEditorEditPost"].setData("");
|
||||
showSuccessMessage("Post edited successfully", "editPost");
|
||||
return;
|
||||
@@ -374,10 +378,43 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
|
||||
window.location.href = "./";
|
||||
return;
|
||||
}
|
||||
|
||||
showErrorMessage(json.error.message, "editPost");
|
||||
|
||||
showErrorMessage(json.error, 'editPost');
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
document.querySelector('#sendNewsletterForm').addEventListener('submit', e =>
|
||||
{
|
||||
e.preventDefault();
|
||||
let data = new FormData();
|
||||
data.append('subject', document.querySelector('#newsletterSubject').value);
|
||||
data.append('message', editors['CKEditorNewsletter'].getData());
|
||||
|
||||
fetch('/api/blog/newsletter', {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token'),
|
||||
},
|
||||
}).then(res => res.json().then(json =>
|
||||
{
|
||||
if (res.ok)
|
||||
{
|
||||
document.querySelector('#sendNewsletterForm').reset();
|
||||
editors['CKEditorNewsletter'].setData('');
|
||||
showSuccessMessage('Newsletter sent successfully', 'newsletter');
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === 401)
|
||||
{
|
||||
window.location.href = './';
|
||||
return;
|
||||
}
|
||||
|
||||
showErrorMessage(json.error, 'newsletter');
|
||||
}));
|
||||
});
|
||||
|
||||
document.querySelector("#goToCV").addEventListener("click", () =>
|
||||
@@ -418,6 +455,13 @@ document.querySelector("#goToEditPost").addEventListener("click", () =>
|
||||
document.querySelector("#blog").classList.add("active");
|
||||
});
|
||||
|
||||
document.querySelector('#goToNewsletter').addEventListener('click', () =>
|
||||
{
|
||||
textareaLoaded = false;
|
||||
addActiveClass('goToNewsletter');
|
||||
goToPage('newsletter');
|
||||
});
|
||||
|
||||
document.querySelector("#logout").addEventListener("click", () =>
|
||||
{
|
||||
fetch("/api/user/logout").then(res =>
|
||||
@@ -468,6 +512,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
|
||||
* @param {string} id - The id to add the active class to
|
||||
@@ -758,6 +858,31 @@ function createEditors(...ids)
|
||||
{language: 'zephir', label: 'Zephir'},
|
||||
],
|
||||
},
|
||||
mediaEmbed: {
|
||||
previewsInData: true,
|
||||
providers: [
|
||||
{
|
||||
name: 'youtube',
|
||||
url: [
|
||||
/^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)(?:&t=(\d+))?/,
|
||||
/^(?:m\.)?youtube\.com\/v\/([\w-]+)(?:\?t=(\d+))?/,
|
||||
/^youtube\.com\/embed\/([\w-]+)(?:\?start=(\d+))?/,
|
||||
/^youtu\.be\/([\w-]+)(?:\?t=(\d+))?/,
|
||||
],
|
||||
html: match =>
|
||||
{
|
||||
const id = match[1];
|
||||
const time = match[2];
|
||||
|
||||
return (
|
||||
`<iframe width="560" height="315" style="text-align:center;" src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` +
|
||||
'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>' +
|
||||
'</iframe>'
|
||||
);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}).then(CKEditor =>
|
||||
{
|
||||
editors[id] = CKEditor;
|
||||
@@ -768,7 +893,7 @@ function createEditors(...ids)
|
||||
console.warn('Build id: 1eo8ioyje2om-vgar4aghypdm');
|
||||
console.error(error);
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1097,15 +1222,15 @@ function updateProjectItem(id, e)
|
||||
{
|
||||
e.preventDefault();
|
||||
let data = {}
|
||||
data["title"] = document.querySelector(`#title${id}`).value;
|
||||
data["isMainProject"] = document.querySelector(`#isMainProject${id}`).checked ? "true" : "false";
|
||||
data["information"] = document.querySelector(`#info${id}`).value;
|
||||
data["projectLink"] = document.querySelector(`#viewProj${id}`).value;
|
||||
data["gitLink"] = document.querySelector(`#git${id}`).value;
|
||||
data['title'] = document.querySelector(`#title${id}proj`).value;
|
||||
data['isMainProject'] = document.querySelector(`#isMainProject${id}proj`).checked ? 'true' : 'false';
|
||||
data['information'] = document.querySelector(`#info${id}proj`).value;
|
||||
data['projectLink'] = document.querySelector(`#viewProj${id}proj`).value;
|
||||
data['gitLink'] = document.querySelector(`#git${id}proj`).value;
|
||||
|
||||
let imgData = new FormData();
|
||||
imgData.append("img", document.querySelector(`#img${id}`).files[0]);
|
||||
|
||||
imgData.append('img', document.querySelector(`#img${id}proj`).files[0]);
|
||||
|
||||
fetch("/api/projectData/" + id, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(data),
|
||||
@@ -1129,8 +1254,8 @@ function updateProjectItem(id, e)
|
||||
}
|
||||
|
||||
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
|
||||
document.querySelector(`#title${id}`).setAttribute("disabled", "");
|
||||
document.querySelector(`#info${id}`).setAttribute("disabled", "");
|
||||
document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
|
||||
document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
|
||||
return;
|
||||
}
|
||||
console.log("updating image")
|
||||
@@ -1168,8 +1293,8 @@ function updateProjectItem(id, e)
|
||||
}
|
||||
|
||||
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
|
||||
document.querySelector(`#title${id}`).setAttribute("disabled", "");
|
||||
document.querySelector(`#info${id}`).setAttribute("disabled", "");
|
||||
document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
|
||||
document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
|
||||
document.querySelector(`#projectImage${id}`).src = updatedProjectImage.imgLocation;
|
||||
return;
|
||||
}
|
||||
@@ -1250,7 +1375,7 @@ function addProject(ID, isMainProject, imgLocation, title, information, projectL
|
||||
<input type="checkbox" id="isMainProject${id}" name="isMainProject${id}" ${(isMainProject === "true" ? "checked=''" : "")}>
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formControl infoContainer">
|
||||
<textarea name="info${id}" id="info${id}" disabled>${information}</textarea>
|
||||
</div>
|
||||
|
||||
@@ -63,7 +63,13 @@ document.querySelector("#login form").addEventListener("submit", e =>
|
||||
showErrorMessage("Please type in a username and password.", "login");
|
||||
return;
|
||||
}
|
||||
showErrorMessage("Invalid username or password.", "login");
|
||||
if (res.status === 401)
|
||||
{
|
||||
showErrorMessage('Invalid username or password.', 'login');
|
||||
return;
|
||||
}
|
||||
|
||||
showErrorMessage(json.error, 'login');
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 87 KiB |
@@ -32,6 +32,7 @@
|
||||
<li><a href="/blog" class="textShadow link">blog</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<header>
|
||||
<div>
|
||||
<h1>full stack developer</h1>
|
||||
@@ -44,11 +45,13 @@
|
||||
<section id="about">
|
||||
<h1>about</h1>
|
||||
<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
|
||||
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>
|
||||
<a href="other/rohitpaicv.pdf" class="btn btnOutline boxShadowIn boxShadowOut" download>Download CV</a>
|
||||
<a href="https://cloud.rohitpai.co.uk/index.php/s/e4LkFiE3wMK8x3C"
|
||||
class="btn btnOutline boxShadowIn boxShadowOut" download>Download
|
||||
CV</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -106,7 +109,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="#">
|
||||
<a href="https://rohitpai.co.uk/blog">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M15.5 14.625c0 .484-.387.875-.864.875h-5.273c-.477 0-.863-.392-.863-.875s.387-.875.863-.875h5.272c.478 0 .865.391.865.875zm-6.191-4.375h2.466c.448 0 .809-.392.809-.875s-.361-.875-.81-.875h-2.465c-.447 0-.809.392-.809.875s.362.875.809.875zm14.691 1.75c0 6.627-5.373 12-12 12s-12-5.373-12-12 5.373-12 12-12 12 5.373 12 12zm-5-1.039c0-.383-.311-.692-.691-.692h-1.138c-.583 0-.69-.446-.69-.996-.001-2.36-1.91-4.273-4.265-4.273h-2.952c-2.355 0-4.264 1.913-4.264 4.272v5.455c0 2.36 1.909 4.273 4.264 4.273h5.474c2.353 0 4.262-1.913 4.262-4.272v-3.767z"/>
|
||||
</svg>
|
||||
@@ -171,15 +174,16 @@
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<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>
|
||||
</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/index.js"></script>
|
||||
<script src="js/typewriter.js"></script>
|
||||
</body>
|
||||
|
||||