Compare commits
45 Commits
cdead14bfd
..
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 | |||
| 3db2520339 | |||
| 4c871c20a0 | |||
| 3812f99259 | |||
| a697ea2ac8 | |||
| e106f89dcb | |||
| da791c8866 | |||
| a0567a25f5 |
@@ -1,4 +1,7 @@
|
|||||||
on: push
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
name: 🚀 Deploy website on push
|
name: 🚀 Deploy website on push
|
||||||
jobs:
|
jobs:
|
||||||
web-deploy:
|
web-deploy:
|
||||||
@@ -11,7 +14,7 @@ jobs:
|
|||||||
- name: Use Node.js Latest
|
- name: Use Node.js Latest
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 'latest'
|
node-version: 20
|
||||||
|
|
||||||
- name: 🔨Run Gulp
|
- name: 🔨Run Gulp
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
"slim/slim-skeleton": "^4.3",
|
"slim/slim-skeleton": "^4.3",
|
||||||
"ext-pdo": "*",
|
"ext-pdo": "*",
|
||||||
"slim/psr7": "^1.4",
|
"slim/psr7": "^1.4",
|
||||||
@@ -15,6 +15,16 @@
|
|||||||
"rbdwllr/psr-jwt": "^2.0",
|
"rbdwllr/psr-jwt": "^2.0",
|
||||||
"tuupola/slim-jwt-auth": "^3.6",
|
"tuupola/slim-jwt-auth": "^3.6",
|
||||||
"ext-dom": "*",
|
"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 __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "blogData.php";
|
require_once "blogData.php";
|
||||||
|
|
||||||
|
|
||||||
use api\utils\routesInterface;
|
use api\utils\routesInterface;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
@@ -12,6 +13,7 @@ use Slim\App;
|
|||||||
class blogRoutes implements routesInterface
|
class blogRoutes implements routesInterface
|
||||||
{
|
{
|
||||||
private blogData $blogData;
|
private blogData $blogData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constructor used to instantiate a base blog routes, to be used in the index.php file.
|
* constructor used to instantiate a base blog routes, to be used in the index.php file.
|
||||||
* @param App $app - the slim app used to create the routes
|
* @param App $app - the slim app used to create the routes
|
||||||
@@ -79,35 +81,15 @@ class blogRoutes implements routesInterface
|
|||||||
|
|
||||||
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
|
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
|
||||||
{
|
{
|
||||||
if ($args["type"] != null)
|
if ($args["type"] == null)
|
||||||
{
|
{
|
||||||
if ($args["type"] == "latest")
|
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||||
{
|
return $response->withStatus(400);
|
||||||
$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($post));
|
if ($args["type"] == "latest")
|
||||||
return $response;
|
{
|
||||||
}
|
$post = $this->blogData->getLatestBlogPost();
|
||||||
|
|
||||||
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))
|
if (array_key_exists("errorMessage", $post))
|
||||||
{
|
{
|
||||||
$response->getBody()->write(json_encode($post));
|
$response->getBody()->write(json_encode($post));
|
||||||
@@ -118,120 +100,233 @@ class blogRoutes implements routesInterface
|
|||||||
return $response;
|
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);
|
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)
|
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if ($args["id"] != null)
|
if ($args["id"] == null)
|
||||||
{
|
{
|
||||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
{
|
return $response->withStatus(400);
|
||||||
// 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('/(?:^|,)(?=[^"]|(")?)"?((?(1)(?:[^"]|"")*|[^,"]*))"?(?=,|$)/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")));
|
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
||||||
return $response->withStatus(400);
|
{
|
||||||
|
// 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)
|
$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"]);
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
|
return $response->withStatus(400);
|
||||||
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")));
|
$message = $this->blogData->deletePost($args["id"]);
|
||||||
return $response->withStatus(400);
|
|
||||||
|
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)
|
$app->post("/blog/post", function (Request $request, Response $response)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
$files = $request->getUploadedFiles();
|
$files = $request->getUploadedFiles();
|
||||||
$headerImg = $files["headerImg"];
|
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
|
||||||
{
|
{
|
||||||
// uh oh sent some empty data
|
// uh oh sent some empty data
|
||||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preg_match('/(?:^|,)(?=[^"]|(")?)"?((?(1)(?:[^"]|"")*|[^,"]*))"?(?=,|$)/mx', $data["categories"]))
|
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
|
||||||
{
|
{
|
||||||
// uh oh sent some empty data
|
// uh oh sent some empty data
|
||||||
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (array_key_exists("headerImg", $files))
|
||||||
|
{
|
||||||
|
$headerImg = $files["headerImg"];
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($files["headerImg"]))
|
if (empty($files["headerImg"]))
|
||||||
{
|
{
|
||||||
$headerImg = null;
|
$headerImg = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$featured = $data["featured"] === "true";
|
// $featured = $data["featured"] === "true";
|
||||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], intval($data["featured"]), $data["categories"], $headerImg);
|
||||||
if (!is_int($insertedID))
|
if (!is_int($insertedID))
|
||||||
{
|
{
|
||||||
// uh oh something went wrong
|
// uh oh something went wrong
|
||||||
@@ -267,28 +362,75 @@ class blogRoutes implements routesInterface
|
|||||||
{
|
{
|
||||||
$files = $request->getUploadedFiles();
|
$files = $request->getUploadedFiles();
|
||||||
|
|
||||||
if ($args["id"] != null)
|
if ($args["id"] == null)
|
||||||
{
|
{
|
||||||
if (empty($files))
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
{
|
return $response->withStatus(400);
|
||||||
// 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")));
|
if (empty($files))
|
||||||
return $response->withStatus(400);
|
{
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ new userRoutes($app);
|
|||||||
$app->post("/contact", function (Request $request, Response $response)
|
$app->post("/contact", function (Request $request, Response $response)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if(empty($data["fName"]) || empty($data["lName"]) || empty($data["email"]) || empty($data["subject"]) || empty($data["message"]))
|
if (empty($data["fName"]) || empty($data["lName"]) || empty($data["email"]) || empty($data["subject"]) || empty($data["message"]))
|
||||||
{
|
{
|
||||||
$response->getBody()->write(json_encode(array("errorMessage" => "Please fill out all the fields")));
|
$response->getBody()->write(json_encode(array("errorMessage" => "Please fill out all the fields")));
|
||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\project;
|
namespace api\project;
|
||||||
|
|
||||||
use api\utils\imgUtils;
|
use api\utils\imgUtils;
|
||||||
use PDO;
|
use PDO;
|
||||||
use Psr\Http\Message\UploadedFileInterface;
|
use Psr\Http\Message\UploadedFileInterface;
|
||||||
|
use function api\utils\dbConn;
|
||||||
|
|
||||||
require_once __DIR__ . "/../utils/config.php";
|
require_once __DIR__ . "/../utils/config.php";
|
||||||
require_once __DIR__ . "/../utils/imgUtils.php";
|
require_once __DIR__ . "/../utils/imgUtils.php";
|
||||||
@@ -15,7 +18,7 @@ class projectData
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get all project data
|
* 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
|
public function getProjectData(): array
|
||||||
{
|
{
|
||||||
@@ -162,7 +165,7 @@ class projectData
|
|||||||
* @param UploadedFileInterface $img - Image preview of the project
|
* @param UploadedFileInterface $img - Image preview of the project
|
||||||
* @return string|array - String with error message or array with the new image location
|
* @return string|array - String with error message or array with the new image location
|
||||||
*/
|
*/
|
||||||
public function uploadImage(int $ID, UploadedFileInterface $img): string | array
|
public function uploadImage(int $ID, UploadedFileInterface $img): string|array
|
||||||
{
|
{
|
||||||
|
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\project;
|
namespace api\project;
|
||||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "projectData.php";
|
require_once "projectData.php";
|
||||||
@@ -37,7 +38,7 @@ class projectRoutes implements routesInterface
|
|||||||
|
|
||||||
$response->getBody()->write($json);
|
$response->getBody()->write($json);
|
||||||
|
|
||||||
if(array_key_exists("errorMessage", $result))
|
if (array_key_exists("errorMessage", $result))
|
||||||
{
|
{
|
||||||
$response->withStatus(404);
|
$response->withStatus(404);
|
||||||
}
|
}
|
||||||
@@ -49,78 +50,78 @@ class projectRoutes implements routesInterface
|
|||||||
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
|
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if ($args["id"] != null)
|
if ($args["id"] == null)
|
||||||
{
|
{
|
||||||
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
{
|
return $response->withStatus(400);
|
||||||
// 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")));
|
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
||||||
return $response->withStatus(400);
|
{
|
||||||
|
// 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)
|
$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"]);
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
|
return $response->withStatus(400);
|
||||||
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")));
|
$message = $this->projectData->deleteProjectData($args["id"]);
|
||||||
return $response->withStatus(400);
|
|
||||||
|
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)
|
$app->post("/projectData", function (Request $request, Response $response)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace api\timeline;
|
namespace api\timeline;
|
||||||
|
|
||||||
use PDO;
|
use PDO;
|
||||||
|
use function api\utils\dbConn;
|
||||||
|
|
||||||
require_once __DIR__ . "/../utils/config.php";
|
require_once __DIR__ . "/../utils/config.php";
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ class timelineData
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get all education data
|
* 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
|
public function getEduData(): array
|
||||||
{
|
{
|
||||||
@@ -31,7 +32,7 @@ class timelineData
|
|||||||
}
|
}
|
||||||
return array("errorMessage" => "Error, edu data not found");
|
return array("errorMessage" => "Error, edu data not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all work data
|
* Get all work data
|
||||||
* @return array - Array of all work data or error message
|
* @return array - Array of all work data or error message
|
||||||
@@ -178,7 +179,7 @@ class timelineData
|
|||||||
|
|
||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new education data
|
* Create new education data
|
||||||
* @param string $dateFrom - Start date
|
* @param string $dateFrom - Start date
|
||||||
@@ -202,7 +203,7 @@ class timelineData
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new work data
|
* Create new work data
|
||||||
* @param string $dateFrom - Start date
|
* @param string $dateFrom - Start date
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\timeline;
|
namespace api\timeline;
|
||||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "timelineData.php";
|
require_once "timelineData.php";
|
||||||
@@ -39,7 +40,7 @@ class timelineRoutes implements routesInterface
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($args["timeline"] == "work")
|
if ($args["timeline"] == "work")
|
||||||
{
|
{
|
||||||
$response->getBody()->write(json_encode($this->timelineData->getWorkData()));
|
$response->getBody()->write(json_encode($this->timelineData->getWorkData()));
|
||||||
return $response;
|
return $response;
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\user;
|
namespace api\user;
|
||||||
|
|
||||||
use Firebase\JWT\JWT;
|
use Firebase\JWT\JWT;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
use function api\utils\dbConn;
|
||||||
|
use function api\utils\getSAMLSettings;
|
||||||
|
use function api\utils\getSecretKey;
|
||||||
|
|
||||||
require_once __DIR__ . "/../utils/config.php";
|
require_once __DIR__ . "/../utils/config.php";
|
||||||
|
|
||||||
@@ -54,7 +59,7 @@ class userData
|
|||||||
"exp" => $future
|
"exp" => $future
|
||||||
];
|
];
|
||||||
|
|
||||||
return JWT::encode($payload,$secretKey,"HS256");
|
return JWT::encode($payload, $secretKey, "HS256");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,7 +113,7 @@ class userData
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
";
|
";
|
||||||
|
|
||||||
mail($email, "Reset Password Verification Code", $message, $headers1);
|
mail($email, "Reset Password Verification Code", $message, $headers1);
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
@@ -116,7 +121,7 @@ class userData
|
|||||||
/**
|
/**
|
||||||
* Change password for an email with new password
|
* Change password for an email with new password
|
||||||
* @param $email string Email
|
* @param $email string Email
|
||||||
* @param $password string Password
|
* @param $password string Password
|
||||||
* @return bool - true if the password was changed, false if not
|
* @return bool - true if the password was changed, false if not
|
||||||
*/
|
*/
|
||||||
public function changePassword(string $email, string $password): bool
|
public function changePassword(string $email, string $password): bool
|
||||||
@@ -134,5 +139,38 @@ class userData
|
|||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\user;
|
namespace api\user;
|
||||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "userData.php";
|
require_once "userData.php";
|
||||||
|
|
||||||
use api\utils\routesInterface;
|
use api\utils\routesInterface;
|
||||||
|
use OneLogin\Saml2\Auth;
|
||||||
|
use OneLogin\Saml2\Error;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Slim\App;
|
use Slim\App;
|
||||||
@@ -11,14 +14,17 @@ use Slim\App;
|
|||||||
class userRoutes implements routesInterface
|
class userRoutes implements routesInterface
|
||||||
{
|
{
|
||||||
private userData $user;
|
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
|
* @param App $app - the slim app used to create the routes
|
||||||
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public function __construct(App $app)
|
public function __construct(App $app)
|
||||||
{
|
{
|
||||||
$this->user = new userData();
|
$this->user = new userData();
|
||||||
|
$this->samlAuth = new Auth($this->user->getSamlConf());
|
||||||
$this->createRoutes($app);
|
$this->createRoutes($app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,31 +35,9 @@ class userRoutes implements routesInterface
|
|||||||
*/
|
*/
|
||||||
public function createRoutes(App $app): void
|
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
|
$this->samlAuth->login();
|
||||||
$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->get("/user/logout", function (Request $request, Response $response)
|
$app->get("/user/logout", function (Request $request, Response $response)
|
||||||
@@ -91,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)
|
$app->get("/user/checkResetEmail/{email}", function (Request $request, Response $response, array $args)
|
||||||
{
|
{
|
||||||
if (empty($args["email"]))
|
if (empty($args["email"]))
|
||||||
@@ -138,6 +136,58 @@ class userRoutes implements routesInterface
|
|||||||
return $response->withStatus(401);
|
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)
|
$app->post("/user/changePassword", function (Request $request, Response $response)
|
||||||
{
|
{
|
||||||
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
|
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
|
||||||
@@ -164,4 +214,4 @@ class userRoutes implements routesInterface
|
|||||||
return $response->withStatus(500);
|
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\HttpMethodNotAllowedException;
|
||||||
use Slim\Exception\HttpNotFoundException;
|
use Slim\Exception\HttpNotFoundException;
|
||||||
use Slim\Psr7\Response;
|
use Slim\Psr7\Response;
|
||||||
|
use Throwable;
|
||||||
use Tuupola\Middleware\JwtAuthentication;
|
use Tuupola\Middleware\JwtAuthentication;
|
||||||
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
|
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
|
||||||
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
|
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
|
||||||
@@ -45,7 +46,7 @@ class middleware
|
|||||||
$app->addBodyParsingMiddleware();
|
$app->addBodyParsingMiddleware();
|
||||||
$app->addRoutingMiddleware();
|
$app->addRoutingMiddleware();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SameSite Cookie Configuration
|
* SameSite Cookie Configuration
|
||||||
* @param App $app - Slim App
|
* @param App $app - Slim App
|
||||||
@@ -65,10 +66,15 @@ class middleware
|
|||||||
$app->add(function ($request, $handler)
|
$app->add(function ($request, $handler)
|
||||||
{
|
{
|
||||||
$response = $handler->handle($request);
|
$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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT Authentication
|
* JWT Authentication
|
||||||
* @param App $app - Slim App
|
* @param App $app - Slim App
|
||||||
@@ -79,8 +85,8 @@ class middleware
|
|||||||
$app->add(new JwtAuthentication([
|
$app->add(new JwtAuthentication([
|
||||||
"rules" => [
|
"rules" => [
|
||||||
new RequestPathRule([
|
new RequestPathRule([
|
||||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
|
"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"]
|
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
|
||||||
]),
|
]),
|
||||||
new RequestMethodRule([
|
new RequestMethodRule([
|
||||||
"ignore" => ["OPTIONS", "GET"]
|
"ignore" => ["OPTIONS", "GET"]
|
||||||
@@ -128,8 +134,27 @@ class middleware
|
|||||||
return $response;
|
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",
|
"author": "Rohit Pai",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ckeditor/ckeditor5-clipboard": "^40.0.0",
|
||||||
"browser-sync": "^2.27.5",
|
"browser-sync": "^2.27.5",
|
||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"gulp-clean-css": "^4.3.0",
|
"gulp-clean-css": "^4.3.0",
|
||||||
@@ -25,5 +26,11 @@
|
|||||||
"require": "^0.4.4",
|
"require": "^0.4.4",
|
||||||
"source-map-generator": "^0.8.0",
|
"source-map-generator": "^0.8.0",
|
||||||
"vinyl-ftp": "^0.6.1"
|
"vinyl-ftp": "^0.6.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"terser-webpack-plugin": "^5.3.9",
|
||||||
|
"vinyl-named-with-path": "^1.0.0",
|
||||||
|
"webpack-cli": "^5.1.4",
|
||||||
|
"webpack-stream": "^7.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace api\blog;
|
|||||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "blogData.php";
|
require_once "blogData.php";
|
||||||
|
|
||||||
|
|
||||||
use api\utils\routesInterface;
|
use api\utils\routesInterface;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
@@ -12,6 +13,7 @@ use Slim\App;
|
|||||||
class blogRoutes implements routesInterface
|
class blogRoutes implements routesInterface
|
||||||
{
|
{
|
||||||
private blogData $blogData;
|
private blogData $blogData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constructor used to instantiate a base blog routes, to be used in the index.php file.
|
* constructor used to instantiate a base blog routes, to be used in the index.php file.
|
||||||
* @param App $app - the slim app used to create the routes
|
* @param App $app - the slim app used to create the routes
|
||||||
@@ -29,9 +31,11 @@ class blogRoutes implements routesInterface
|
|||||||
*/
|
*/
|
||||||
public function createRoutes(App $app): void
|
public function createRoutes(App $app): void
|
||||||
{
|
{
|
||||||
$app->get("/blog/categories", function (Request $request, Response $response) {
|
$app->get("/blog/categories", function (Request $request, Response $response)
|
||||||
|
{
|
||||||
$post = $this->blogData->getCategories();
|
$post = $this->blogData->getCategories();
|
||||||
if (array_key_exists("errorMessage", $post)) {
|
if (array_key_exists("errorMessage", $post))
|
||||||
|
{
|
||||||
$response->getBody()->write(json_encode($post));
|
$response->getBody()->write(json_encode($post));
|
||||||
return $response->withStatus(404);
|
return $response->withStatus(404);
|
||||||
}
|
}
|
||||||
@@ -40,10 +44,13 @@ class blogRoutes implements routesInterface
|
|||||||
return $response;
|
return $response;
|
||||||
});
|
});
|
||||||
|
|
||||||
$app->get("/blog/categories/{category}", function (Request $request, Response $response, $args) {
|
$app->get("/blog/categories/{category}", function (Request $request, Response $response, $args)
|
||||||
if ($args["category"] != null) {
|
{
|
||||||
|
if ($args["category"] != null)
|
||||||
|
{
|
||||||
$post = $this->blogData->getPostsByCategory($args["category"]);
|
$post = $this->blogData->getPostsByCategory($args["category"]);
|
||||||
if (array_key_exists("errorMessage", $post)) {
|
if (array_key_exists("errorMessage", $post))
|
||||||
|
{
|
||||||
$response->getBody()->write(json_encode($post));
|
$response->getBody()->write(json_encode($post));
|
||||||
return $response->withStatus(404);
|
return $response->withStatus(404);
|
||||||
}
|
}
|
||||||
@@ -72,31 +79,17 @@ class blogRoutes implements routesInterface
|
|||||||
return $response;
|
return $response;
|
||||||
});
|
});
|
||||||
|
|
||||||
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args) {
|
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
|
||||||
if ($args["type"] != null) {
|
{
|
||||||
if ($args["type"] == "latest") {
|
if ($args["type"] == null)
|
||||||
$post = $this->blogData->getLatestBlogPost();
|
{
|
||||||
if (array_key_exists("errorMessage", $post)) {
|
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
|
||||||
$response->getBody()->write(json_encode($post));
|
return $response->withStatus(400);
|
||||||
return $response->withStatus(404);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$response->getBody()->write(json_encode($post));
|
if ($args["type"] == "latest")
|
||||||
return $response;
|
{
|
||||||
}
|
$post = $this->blogData->getLatestBlogPost();
|
||||||
|
|
||||||
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))
|
if (array_key_exists("errorMessage", $post))
|
||||||
{
|
{
|
||||||
$response->getBody()->write(json_encode($post));
|
$response->getBody()->write(json_encode($post));
|
||||||
@@ -107,118 +100,233 @@ class blogRoutes implements routesInterface
|
|||||||
return $response;
|
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);
|
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)
|
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if ($args["id"] != null)
|
if ($args["id"] == null)
|
||||||
{
|
{
|
||||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
{
|
return $response->withStatus(400);
|
||||||
// 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")));
|
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
|
||||||
return $response->withStatus(400);
|
{
|
||||||
|
// 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)
|
$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"]);
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
|
return $response->withStatus(400);
|
||||||
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")));
|
$message = $this->blogData->deletePost($args["id"]);
|
||||||
return $response->withStatus(400);
|
|
||||||
|
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)
|
$app->post("/blog/post", function (Request $request, Response $response)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
$files = $request->getUploadedFiles();
|
$files = $request->getUploadedFiles();
|
||||||
$headerImg = $files["headerImg"];
|
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
||||||
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
|
|
||||||
{
|
{
|
||||||
// uh oh sent some empty data
|
// uh oh sent some empty data
|
||||||
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
|
||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"])) {
|
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"]))
|
||||||
|
{
|
||||||
// uh oh sent some empty data
|
// uh oh sent some empty data
|
||||||
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
|
||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (array_key_exists("headerImg", $files))
|
||||||
|
{
|
||||||
|
$headerImg = $files["headerImg"];
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($files["headerImg"]))
|
if (empty($files["headerImg"]))
|
||||||
{
|
{
|
||||||
$headerImg = null;
|
$headerImg = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$featured = $data["featured"] === "true";
|
// $featured = $data["featured"] === "true";
|
||||||
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
|
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], intval($data["featured"]), $data["categories"], $headerImg);
|
||||||
if (!is_int($insertedID))
|
if (!is_int($insertedID))
|
||||||
{
|
{
|
||||||
// uh oh something went wrong
|
// uh oh something went wrong
|
||||||
@@ -254,28 +362,75 @@ class blogRoutes implements routesInterface
|
|||||||
{
|
{
|
||||||
$files = $request->getUploadedFiles();
|
$files = $request->getUploadedFiles();
|
||||||
|
|
||||||
if ($args["id"] != null)
|
if ($args["id"] == null)
|
||||||
{
|
{
|
||||||
if (empty($files))
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
{
|
return $response->withStatus(400);
|
||||||
// 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")));
|
if (empty($files))
|
||||||
return $response->withStatus(400);
|
{
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,7 +39,7 @@ new userRoutes($app);
|
|||||||
$app->post("/contact", function (Request $request, Response $response)
|
$app->post("/contact", function (Request $request, Response $response)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if(empty($data["fName"]) || empty($data["lName"]) || empty($data["email"]) || empty($data["subject"]) || empty($data["message"]))
|
if (empty($data["fName"]) || empty($data["lName"]) || empty($data["email"]) || empty($data["subject"]) || empty($data["message"]))
|
||||||
{
|
{
|
||||||
$response->getBody()->write(json_encode(array("errorMessage" => "Please fill out all the fields")));
|
$response->getBody()->write(json_encode(array("errorMessage" => "Please fill out all the fields")));
|
||||||
return $response->withStatus(400);
|
return $response->withStatus(400);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\project;
|
namespace api\project;
|
||||||
|
|
||||||
use api\utils\imgUtils;
|
use api\utils\imgUtils;
|
||||||
use PDO;
|
use PDO;
|
||||||
use Psr\Http\Message\UploadedFileInterface;
|
use Psr\Http\Message\UploadedFileInterface;
|
||||||
|
use function api\utils\dbConn;
|
||||||
|
|
||||||
require_once __DIR__ . "/../utils/config.php";
|
require_once __DIR__ . "/../utils/config.php";
|
||||||
require_once __DIR__ . "/../utils/imgUtils.php";
|
require_once __DIR__ . "/../utils/imgUtils.php";
|
||||||
@@ -15,7 +18,7 @@ class projectData
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get all project data
|
* 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
|
public function getProjectData(): array
|
||||||
{
|
{
|
||||||
@@ -162,7 +165,7 @@ class projectData
|
|||||||
* @param UploadedFileInterface $img - Image preview of the project
|
* @param UploadedFileInterface $img - Image preview of the project
|
||||||
* @return string|array - String with error message or array with the new image location
|
* @return string|array - String with error message or array with the new image location
|
||||||
*/
|
*/
|
||||||
public function uploadImage(int $ID, UploadedFileInterface $img): string | array
|
public function uploadImage(int $ID, UploadedFileInterface $img): string|array
|
||||||
{
|
{
|
||||||
|
|
||||||
$conn = dbConn();
|
$conn = dbConn();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\project;
|
namespace api\project;
|
||||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "projectData.php";
|
require_once "projectData.php";
|
||||||
@@ -37,7 +38,7 @@ class projectRoutes implements routesInterface
|
|||||||
|
|
||||||
$response->getBody()->write($json);
|
$response->getBody()->write($json);
|
||||||
|
|
||||||
if(array_key_exists("errorMessage", $result))
|
if (array_key_exists("errorMessage", $result))
|
||||||
{
|
{
|
||||||
$response->withStatus(404);
|
$response->withStatus(404);
|
||||||
}
|
}
|
||||||
@@ -49,78 +50,78 @@ class projectRoutes implements routesInterface
|
|||||||
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
|
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
|
||||||
{
|
{
|
||||||
$data = $request->getParsedBody();
|
$data = $request->getParsedBody();
|
||||||
if ($args["id"] != null)
|
if ($args["id"] == null)
|
||||||
{
|
{
|
||||||
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
{
|
return $response->withStatus(400);
|
||||||
// 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")));
|
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
|
||||||
return $response->withStatus(400);
|
{
|
||||||
|
// 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)
|
$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"]);
|
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
|
||||||
|
return $response->withStatus(400);
|
||||||
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")));
|
$message = $this->projectData->deleteProjectData($args["id"]);
|
||||||
return $response->withStatus(400);
|
|
||||||
|
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)
|
$app->post("/projectData", function (Request $request, Response $response)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace api\timeline;
|
namespace api\timeline;
|
||||||
|
|
||||||
use PDO;
|
use PDO;
|
||||||
|
use function api\utils\dbConn;
|
||||||
|
|
||||||
require_once __DIR__ . "/../utils/config.php";
|
require_once __DIR__ . "/../utils/config.php";
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ class timelineData
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get all education data
|
* 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
|
public function getEduData(): array
|
||||||
{
|
{
|
||||||
@@ -31,7 +32,7 @@ class timelineData
|
|||||||
}
|
}
|
||||||
return array("errorMessage" => "Error, edu data not found");
|
return array("errorMessage" => "Error, edu data not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all work data
|
* Get all work data
|
||||||
* @return array - Array of all work data or error message
|
* @return array - Array of all work data or error message
|
||||||
@@ -178,7 +179,7 @@ class timelineData
|
|||||||
|
|
||||||
return "error";
|
return "error";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new education data
|
* Create new education data
|
||||||
* @param string $dateFrom - Start date
|
* @param string $dateFrom - Start date
|
||||||
@@ -202,7 +203,7 @@ class timelineData
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new work data
|
* Create new work data
|
||||||
* @param string $dateFrom - Start date
|
* @param string $dateFrom - Start date
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\timeline;
|
namespace api\timeline;
|
||||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "timelineData.php";
|
require_once "timelineData.php";
|
||||||
@@ -39,7 +40,7 @@ class timelineRoutes implements routesInterface
|
|||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($args["timeline"] == "work")
|
if ($args["timeline"] == "work")
|
||||||
{
|
{
|
||||||
$response->getBody()->write(json_encode($this->timelineData->getWorkData()));
|
$response->getBody()->write(json_encode($this->timelineData->getWorkData()));
|
||||||
return $response;
|
return $response;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\user;
|
namespace api\user;
|
||||||
|
|
||||||
use Firebase\JWT\JWT;
|
use Firebase\JWT\JWT;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
use function api\utils\dbConn;
|
||||||
|
use function api\utils\getSAMLSettings;
|
||||||
|
use function api\utils\getSecretKey;
|
||||||
|
|
||||||
require_once __DIR__ . "/../utils/config.php";
|
require_once __DIR__ . "/../utils/config.php";
|
||||||
|
|
||||||
@@ -54,7 +59,7 @@ class userData
|
|||||||
"exp" => $future
|
"exp" => $future
|
||||||
];
|
];
|
||||||
|
|
||||||
return JWT::encode($payload,$secretKey,"HS256");
|
return JWT::encode($payload, $secretKey, "HS256");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,7 +113,7 @@ class userData
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
";
|
";
|
||||||
|
|
||||||
mail($email, "Reset Password Verification Code", $message, $headers1);
|
mail($email, "Reset Password Verification Code", $message, $headers1);
|
||||||
return $token;
|
return $token;
|
||||||
}
|
}
|
||||||
@@ -116,7 +121,7 @@ class userData
|
|||||||
/**
|
/**
|
||||||
* Change password for an email with new password
|
* Change password for an email with new password
|
||||||
* @param $email string Email
|
* @param $email string Email
|
||||||
* @param $password string Password
|
* @param $password string Password
|
||||||
* @return bool - true if the password was changed, false if not
|
* @return bool - true if the password was changed, false if not
|
||||||
*/
|
*/
|
||||||
public function changePassword(string $email, string $password): bool
|
public function changePassword(string $email, string $password): bool
|
||||||
@@ -134,5 +139,38 @@ class userData
|
|||||||
return false;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace api\user;
|
namespace api\user;
|
||||||
require_once __DIR__ . "/../utils/routesInterface.php";
|
require_once __DIR__ . "/../utils/routesInterface.php";
|
||||||
require_once "userData.php";
|
require_once "userData.php";
|
||||||
|
|
||||||
use api\utils\routesInterface;
|
use api\utils\routesInterface;
|
||||||
|
use OneLogin\Saml2\Auth;
|
||||||
|
use OneLogin\Saml2\Error;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Slim\App;
|
use Slim\App;
|
||||||
@@ -11,14 +14,17 @@ use Slim\App;
|
|||||||
class userRoutes implements routesInterface
|
class userRoutes implements routesInterface
|
||||||
{
|
{
|
||||||
private userData $user;
|
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
|
* @param App $app - the slim app used to create the routes
|
||||||
|
* @throws Error
|
||||||
*/
|
*/
|
||||||
public function __construct(App $app)
|
public function __construct(App $app)
|
||||||
{
|
{
|
||||||
$this->user = new userData();
|
$this->user = new userData();
|
||||||
|
$this->samlAuth = new Auth($this->user->getSamlConf());
|
||||||
$this->createRoutes($app);
|
$this->createRoutes($app);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,31 +35,9 @@ class userRoutes implements routesInterface
|
|||||||
*/
|
*/
|
||||||
public function createRoutes(App $app): void
|
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
|
$this->samlAuth->login();
|
||||||
$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->get("/user/logout", function (Request $request, Response $response)
|
$app->get("/user/logout", function (Request $request, Response $response)
|
||||||
@@ -91,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)
|
$app->get("/user/checkResetEmail/{email}", function (Request $request, Response $response, array $args)
|
||||||
{
|
{
|
||||||
if (empty($args["email"]))
|
if (empty($args["email"]))
|
||||||
@@ -138,6 +136,58 @@ class userRoutes implements routesInterface
|
|||||||
return $response->withStatus(401);
|
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)
|
$app->post("/user/changePassword", function (Request $request, Response $response)
|
||||||
{
|
{
|
||||||
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
|
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
|
||||||
@@ -164,4 +214,4 @@ class userRoutes implements routesInterface
|
|||||||
return $response->withStatus(500);
|
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\HttpMethodNotAllowedException;
|
||||||
use Slim\Exception\HttpNotFoundException;
|
use Slim\Exception\HttpNotFoundException;
|
||||||
use Slim\Psr7\Response;
|
use Slim\Psr7\Response;
|
||||||
|
use Throwable;
|
||||||
use Tuupola\Middleware\JwtAuthentication;
|
use Tuupola\Middleware\JwtAuthentication;
|
||||||
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
|
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
|
||||||
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
|
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
|
||||||
@@ -45,7 +46,7 @@ class middleware
|
|||||||
$app->addBodyParsingMiddleware();
|
$app->addBodyParsingMiddleware();
|
||||||
$app->addRoutingMiddleware();
|
$app->addRoutingMiddleware();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SameSite Cookie Configuration
|
* SameSite Cookie Configuration
|
||||||
* @param App $app - Slim App
|
* @param App $app - Slim App
|
||||||
@@ -65,10 +66,15 @@ class middleware
|
|||||||
$app->add(function ($request, $handler)
|
$app->add(function ($request, $handler)
|
||||||
{
|
{
|
||||||
$response = $handler->handle($request);
|
$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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JWT Authentication
|
* JWT Authentication
|
||||||
* @param App $app - Slim App
|
* @param App $app - Slim App
|
||||||
@@ -79,8 +85,8 @@ class middleware
|
|||||||
$app->add(new JwtAuthentication([
|
$app->add(new JwtAuthentication([
|
||||||
"rules" => [
|
"rules" => [
|
||||||
new RequestPathRule([
|
new RequestPathRule([
|
||||||
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
|
"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"]
|
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
|
||||||
]),
|
]),
|
||||||
new RequestMethodRule([
|
new RequestMethodRule([
|
||||||
"ignore" => ["OPTIONS", "GET"]
|
"ignore" => ["OPTIONS", "GET"]
|
||||||
@@ -128,8 +134,27 @@ class middleware
|
|||||||
return $response;
|
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 {
|
.profile {
|
||||||
-webkit-border-radius: 50%;
|
-webkit-border-radius: 50%;
|
||||||
-moz-border-radius: 50%;
|
-moz-border-radius: 50%;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
width: 70%;
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
@@ -15,6 +17,13 @@ footer {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
section#individualPost {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
div.byLine {
|
div.byLine {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -46,13 +55,6 @@ div.cover {
|
|||||||
box-shadow: 0 4px 2px 0 var(--mutedBlack);
|
box-shadow: 0 4px 2px 0 var(--mutedBlack);
|
||||||
}
|
}
|
||||||
|
|
||||||
section#individualPost {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.mainContent {
|
div.mainContent {
|
||||||
border-right: 5px solid var(--mutedGrey);
|
border-right: 5px solid var(--mutedGrey);
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
@@ -71,15 +73,24 @@ article {
|
|||||||
padding: 0 2em;
|
padding: 0 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article a {
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
article a::before,
|
article a::before,
|
||||||
article a::after {
|
article a::after {
|
||||||
visibility: hidden;
|
visibility: visible;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
article a.btn::before,
|
||||||
|
article a.btn::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
article a::before {
|
article a::before {
|
||||||
content: '<';
|
content: ' <';
|
||||||
margin-left: -0.5em;
|
margin-left: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,9 +98,9 @@ article a::after {
|
|||||||
content: '>';
|
content: '>';
|
||||||
}
|
}
|
||||||
|
|
||||||
article a:hover::before,
|
article a:hover,
|
||||||
article a:hover::after {
|
article a:hover {
|
||||||
visibility: visible;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
article h1 {
|
article h1 {
|
||||||
@@ -100,6 +111,31 @@ article h3 {
|
|||||||
margin-top: 0;
|
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 {
|
aside.sideContent {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -116,6 +152,7 @@ div.authorInfo {
|
|||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
padding-top: 0.5em;
|
padding-top: 0.5em;
|
||||||
border-bottom: 5px solid var(--mutedGrey);
|
border-bottom: 5px solid var(--mutedGrey);
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.authorInfo .picture {
|
div.authorInfo .picture {
|
||||||
@@ -130,7 +167,7 @@ div.authorInfo h3 {
|
|||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.otherPosts {
|
div.otherPosts, div.newsletter, div.feeds {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -140,10 +177,36 @@ div.otherPosts {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.otherPosts a {
|
div.otherPosts a, div.feeds a {
|
||||||
padding: 0.5em 1em;
|
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 {
|
div.categories {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -153,6 +216,10 @@ div.categories {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.form input[type="submit"] {
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
.image img, .image_resized img {
|
.image img, .image_resized img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
-webkit-border-radius: 10px;
|
-webkit-border-radius: 10px;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**** Categories Pages ****/
|
||||||
|
|
||||||
|
main > h1 {
|
||||||
|
padding-left: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.catPosts .largePost {
|
||||||
|
margin-bottom: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
section.categories {
|
||||||
|
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%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
/**** Blog Home page *****/
|
||||||
|
|
||||||
.banner {
|
.banner {
|
||||||
max-width: 30%;
|
max-width: 30%;
|
||||||
box-shadow: 0 6px 4px 0 var(--mutedBlack);
|
box-shadow: 0 6px 4px 0 var(--mutedBlack);
|
||||||
@@ -24,8 +26,47 @@ h3 {
|
|||||||
line-height: 2.1875rem;
|
line-height: 2.1875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.menu {
|
||||||
|
width: 100%;
|
||||||
|
border-bottom: 5px solid var(--mutedGrey);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu input:not([type="submit"]) {
|
||||||
|
width: auto;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu > ul {
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li button.btn {
|
||||||
|
padding: initial;
|
||||||
|
border-radius: 0 0.5em 0.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li input:not([type="submit"]):focus + button.btn,
|
||||||
|
div.menu ul li:hover button.btn,
|
||||||
|
div.menu ul li:focus button.btn {
|
||||||
|
background: var(--primaryHover);
|
||||||
|
border: 0.3215em solid var(--primaryHover);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.menu ul li:hover input:not([type="submit"]),
|
||||||
|
div.menu ul li:focus input:not([type="submit"]) {
|
||||||
|
border: 0.3215em solid var(--primaryHover);
|
||||||
|
}
|
||||||
|
|
||||||
section.largePost {
|
section.largePost {
|
||||||
/*margin: 0 5em;*/
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
@@ -35,7 +76,7 @@ section.largePost {
|
|||||||
padding: 0 5em 1em;
|
padding: 0 5em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.largePost:first-child {
|
section.largePost:not(:last-child) {
|
||||||
border-bottom: 5px solid var(--mutedGrey);
|
border-bottom: 5px solid var(--mutedGrey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,14 +108,117 @@ section.largePost .outerContent .postContent a {
|
|||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main .error {
|
#main .errorFof, #main .unsubscribe {
|
||||||
display: table;
|
display: table;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100dvh;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fof {
|
#main .unsubscribe {
|
||||||
|
height: 50dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
vertical-align: middle;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,5 +7,324 @@
|
|||||||
@import "../../css/footer.css";
|
@import "../../css/footer.css";
|
||||||
@import "blogPosts.css";
|
@import "blogPosts.css";
|
||||||
@import "home.css";
|
@import "home.css";
|
||||||
@import "prism.css";
|
@import "category.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
|
/* 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 */
|
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:#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}
|
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}
|
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}
|
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}
|
.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}
|
.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 http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
<meta name="language" content="English">
|
<meta name="language" content="English">
|
||||||
<meta name="author" content="Rohit Pai">
|
<meta name="author" content="Rohit Pai">
|
||||||
|
<link rel="stylesheet" href="/blog/css/prism.css">
|
||||||
<link rel="stylesheet" href="/blog/css/main.css">
|
<link rel="stylesheet" href="/blog/css/main.css">
|
||||||
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav>
|
||||||
@@ -49,12 +53,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
<div class="menuBar">
|
||||||
|
<div class="menu">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/blog" class="link active">All posts</a></li>
|
||||||
|
<li><a href="/blog/category" class="link">categories</a></li>
|
||||||
|
<li>
|
||||||
|
<label for="searchField" aria-hidden="true" hidden>search</label>
|
||||||
|
<input type="search" name="search" id="searchField"
|
||||||
|
placeholder="Search...">
|
||||||
|
<button type="submit" id="searchBtn" class="btn btnPrimary"><i
|
||||||
|
class="fa fa-search"></i></button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<main id="main">
|
<main id="main">
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<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">
|
<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>
|
<p>© <span id="year"></span> Rohit Pai all rights reserved</p>
|
||||||
<div class="button">
|
<div class="button">
|
||||||
<button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button>
|
<button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button>
|
||||||
@@ -62,6 +101,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/js/typewriter.js"></script>
|
<script src="/js/typewriter.js"></script>
|
||||||
|
<script src="/blog/js/prism.js"></script>
|
||||||
<script src="/blog/js/index.js"></script>
|
<script src="/blog/js/index.js"></script>
|
||||||
<script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script>
|
<script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -120,4 +120,4 @@ div.message.hidden {
|
|||||||
|
|
||||||
div.message button:hover {
|
div.message button:hover {
|
||||||
text-shadow: -1px 2px var(--mutedBlack);
|
text-shadow: -1px 2px var(--mutedBlack);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,28 @@ footer .spacer {
|
|||||||
margin-right: auto;
|
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 {
|
footer p {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ nav {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
transition: background-color 0.4s ease-in;
|
transition: background-color 0.4s ease-in;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
z-index: 1;
|
z-index: 100000000000000000000000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.scrolled {
|
nav.scrolled {
|
||||||
@@ -64,10 +64,6 @@ nav ul li span {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*nav ul li a:hover span, nav ul li .active span {*/
|
|
||||||
/* visibility: visible;*/
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
nav ul li .active::before,
|
nav ul li .active::before,
|
||||||
nav ul li .active::after {
|
nav ul li .active::after {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
--grey: hsla(0, 0%, 39%, 1);
|
--grey: hsla(0, 0%, 39%, 1);
|
||||||
--notAvailableDefault: hsla(0, 0%, 39%, 1);
|
--notAvailableDefault: hsla(0, 0%, 39%, 1);
|
||||||
--notAvailableHover: hsla(0, 0%, 32%, 1);
|
--notAvailableHover: hsla(0, 0%, 32%, 1);
|
||||||
--mutedGrey: hsla(0, 0%, 78%, 1);
|
--mutedGrey: hsla(0, 0%, 75%, 1);
|
||||||
--mutedBlack: hsla(0, 0%, 0%, 0.25);
|
--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);
|
--navBack: hsla(0, 0%, 30%, 1);
|
||||||
|
|
||||||
/* Font Sizes */
|
/* Font Sizes */
|
||||||
@@ -63,7 +63,7 @@ h2 {
|
|||||||
line-height: 2.1875rem;
|
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;
|
text-decoration: none;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
padding: 1em 2em;
|
padding: 1em 2em;
|
||||||
@@ -75,11 +75,15 @@ a.btn, button.btn, form input[type="submit"] {
|
|||||||
max-height: 4em;
|
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;
|
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);
|
border: 0.3215em solid var(--primaryHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ a.btn:hover::before, a.btn:hover::after {
|
|||||||
visibility: hidden;
|
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);
|
background-color: var(--primaryDefault);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -108,11 +112,12 @@ a.btnPrimary[disabled]:hover, button.btnPrimary[disabled]:hover {
|
|||||||
border: 0.3215em solid var(--notAvailableHover);
|
border: 0.3215em solid var(--notAvailableHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btnPrimary:hover, button.btnPrimary:hover form input[type="submit"]:hover {
|
a.btnPrimary:hover, button.btnPrimary:hover, form input[type="submit"]:hover, div.form input[type="submit"]:hover {
|
||||||
background: var(--primaryHover);
|
background: var(--primaryHover);
|
||||||
|
border: 0.3215em solid var(--primaryHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.btn:active, button.btn:active, form input[type="submit"]:active {
|
a.btn:active, button.btn:active, form input[type="submit"]:active, div.form input[type="submit"]:active {
|
||||||
padding: 0.8rem 1.8rem;
|
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);
|
text-shadow: 0 6px 4px var(--mutedBlack);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid,
|
||||||
border: 4px solid var(--errorDefault);
|
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 {
|
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
|
||||||
border: 4px solid var(--errorHover);
|
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);
|
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"]),
|
||||||
border: 4px solid var(--primaryHover);
|
div.form .formControl input:not([type="submit"]) {
|
||||||
}
|
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]) {
|
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl {
|
form .formControl,
|
||||||
|
div.form .formControl {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
/*align-items: flex-start;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl.passwordControl {
|
form .formControl.passwordControl,
|
||||||
|
div.form .formControl.passwordControl {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
form input[type="submit"] {
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]), form .formControl textarea,
|
form .formControl input:not([type="submit"]), form .formControl textarea,
|
||||||
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
|
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
|
||||||
form .formControl .ck.ck-editor__main .ck-content {
|
form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]),
|
||||||
|
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%;
|
width: 100%;
|
||||||
border: 4px solid var(--primaryDefault);
|
border: 0.3125em solid var(--primaryDefault);
|
||||||
background: none;
|
background: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
-webkit-border-radius: 1em;
|
-webkit-border-radius: 0.5em;
|
||||||
-moz-border-radius: 1em;
|
-moz-border-radius: 0.5em;
|
||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl textarea {
|
form .formControl textarea,
|
||||||
|
div.form .formControl textarea {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
|
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid,
|
||||||
border: 4px solid var(--errorDefault);
|
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 {
|
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
|
||||||
border: 4px solid var(--errorHover);
|
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);
|
box-shadow: 0 4px 2px 0 var(--mutedBlack);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
|
form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
|
||||||
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover {
|
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover,
|
||||||
border: 4px solid var(--primaryHover);
|
div.menu input:not([type="submit"]):focus, div.menu input:not([type="submit"]):hover,
|
||||||
|
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;
|
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;
|
margin-left: -40px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--primaryDefault);
|
color: var(--primaryDefault);
|
||||||
@@ -207,7 +219,8 @@ form .formControl input:not([type="submit"]):focus + i.fa-eye-slash {
|
|||||||
color: var(--primaryHover);
|
color: var(--primaryHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl .checkContainer {
|
form .formControl .checkContainer,
|
||||||
|
div.form .formControl .checkContainer {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 1.25em;
|
margin-bottom: 1.25em;
|
||||||
@@ -218,7 +231,8 @@ form .formControl .checkContainer {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl .checkContainer input {
|
form .formControl .checkContainer input,
|
||||||
|
div.form .formControl .checkContainer input {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -226,7 +240,8 @@ form .formControl .checkContainer input {
|
|||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl .checkContainer .checkmark {
|
form .formControl .checkContainer .checkmark,
|
||||||
|
div.form .formControl .checkContainer .checkmark {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1.25em;
|
top: 1.25em;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -235,29 +250,35 @@ form .formControl .checkContainer .checkmark {
|
|||||||
background-color: var(--mutedGrey);
|
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);
|
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);
|
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);
|
background-color: var(--primaryHover);
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl .checkContainer .checkmark:after {
|
form .formControl .checkContainer .checkmark:after,
|
||||||
|
div.form .formControl .checkContainer .checkmark:after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: none;
|
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;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl .checkContainer .checkmark:after {
|
form .formControl .checkContainer .checkmark:after,
|
||||||
|
div.form .formControl .checkContainer .checkmark:after {
|
||||||
left: 9px;
|
left: 9px;
|
||||||
top: 5px;
|
top: 5px;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
@@ -302,32 +323,103 @@ a {
|
|||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.link {
|
||||||
|
padding: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
a.link::before,
|
a.link::before,
|
||||||
a.link::after {
|
a.link::after {
|
||||||
visibility: hidden;
|
visibility: visible;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav a.link::before,
|
||||||
|
nav a.link::after,
|
||||||
|
.nav a.link::before,
|
||||||
|
.nav a.link::after {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
a.link::before {
|
a.link::before {
|
||||||
content: '<';
|
content: ' <';
|
||||||
margin-left: -0.5em;
|
margin-left: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link::after {
|
a.link::after {
|
||||||
content: '>';
|
content: '> ';
|
||||||
}
|
}
|
||||||
|
|
||||||
a.link:hover::before,
|
a.link:hover {
|
||||||
a.link:hover::after {
|
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;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*.link span {*/
|
nav a.link:hover, .nav a.link:hover {
|
||||||
/* visibility: hidden;*/
|
font-weight: normal;
|
||||||
/*}*/
|
}
|
||||||
|
|
||||||
/*.link:hover span {*/
|
div.error, div.success {
|
||||||
/* visibility: visible;*/
|
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;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
section#editPost {
|
section#curriculumVitae {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +234,7 @@ section#projects form.projItem:not(.editing) div.formControl.infoContainer texta
|
|||||||
}
|
}
|
||||||
|
|
||||||
section#addPost form, section#editPost form {
|
section#addPost form, section#editPost form {
|
||||||
margin: auto 4rem;
|
margin: auto 4rem 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar {
|
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar {
|
||||||
@@ -276,6 +276,6 @@ section#editPost table td, th {
|
|||||||
min-width: 10rem;
|
min-width: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
section#editPost form {
|
section#newsletter form {
|
||||||
margin-bottom: 2em;
|
margin: 0 5em;
|
||||||
}
|
}
|
||||||
@@ -66,71 +66,22 @@ div#login input[type=submit]{
|
|||||||
margin: 0;
|
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 {
|
div.btnContainer {
|
||||||
width: 100%;
|
width: 60%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
align-items: 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) {
|
div.btnContainer a:not(.btn) {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ nav.sideNav ul li.dropdown ul {
|
|||||||
|
|
||||||
nav.sideNav ul li.dropdown ul.active {
|
nav.sideNav ul li.dropdown ul.active {
|
||||||
transition: max-height ease-in 400ms;
|
transition: max-height ease-in 400ms;
|
||||||
max-height: 15rem;
|
max-height: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav.sideNav ul li.dropdown ul li {
|
nav.sideNav ul li.dropdown ul li {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
|
||||||
<script src="js/CKEditor/ckeditor.js"></script>
|
<script src="js/CKEditor/ckeditor.js"></script>
|
||||||
<link rel="stylesheet" href="css/main.css">
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="sideNav">
|
<nav class="sideNav">
|
||||||
@@ -40,6 +39,11 @@
|
|||||||
Edit Blog Post
|
Edit Blog Post
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#" id="goToNewsletter" class="link">
|
||||||
|
Send Newsletter
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="#" id="logout">Logout</a></li>
|
<li><a href="#" id="logout">Logout</a></li>
|
||||||
@@ -135,7 +139,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -200,10 +204,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
<label for="postCategories">Categories</label>
|
<label for="postCategories">Categories</label>
|
||||||
<input type="text" name="postCategories" id="postCategories"
|
<input type="text" name="postCategories" id="postCategories" title="CSV format" required>
|
||||||
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
|
|
||||||
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
|
|
||||||
oninput="this.setCustomValidity('')" required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
@@ -264,10 +265,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
<label for="editPostCategories">Categories</label>
|
<label for="editPostCategories">Categories</label>
|
||||||
<input type="text" name="editPostCategories" id="editPostCategories"
|
<input type="text" name="editPostCategories" id="editPostCategories" title="CSV format" required>
|
||||||
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
|
|
||||||
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
|
|
||||||
oninput="this.setCustomValidity('')" required>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="formControl">
|
<div class="formControl">
|
||||||
@@ -294,6 +292,33 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</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>
|
</main>
|
||||||
|
|
||||||
<script src="js/editor.js"></script>
|
<script src="js/editor.js"></script>
|
||||||
|
|||||||
@@ -32,8 +32,9 @@
|
|||||||
|
|
||||||
<div class="btnContainer">
|
<div class="btnContainer">
|
||||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
<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>
|
||||||
<a href="#" id="resetPwd">Reset Password</a>
|
<button type="button" id="resetPwd" class="btn btnPrimary boxShadowIn boxShadowOut">Reset Password
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +55,8 @@
|
|||||||
|
|
||||||
<div class="btnContainer">
|
<div class="btnContainer">
|
||||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +77,9 @@
|
|||||||
|
|
||||||
<div class="btnContainer">
|
<div class="btnContainer">
|
||||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,7 +106,7 @@
|
|||||||
|
|
||||||
<div class="btnContainer">
|
<div class="btnContainer">
|
||||||
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ let dateOptions = {month: 'short', year: 'numeric'};
|
|||||||
let textareaLoaded = false;
|
let textareaLoaded = false;
|
||||||
let editors = {};
|
let editors = {};
|
||||||
let posts = null;
|
let posts = null;
|
||||||
|
const smallPaddingElements = ['figcaption', 'li'];
|
||||||
document.addEventListener('DOMContentLoaded', () =>
|
document.addEventListener('DOMContentLoaded', () =>
|
||||||
{
|
{
|
||||||
// check if the userData is logged in, if not redirect to log in
|
// check if the userData is logged in, if not redirect to log in
|
||||||
@@ -69,7 +70,7 @@ document.addEventListener('DOMContentLoaded', () =>
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// CKEditor stuff
|
// CKEditor stuff
|
||||||
createEditors("CKEditorAddPost", "CKEditorEditPost");
|
createEditors('CKEditorAddPost', 'CKEditorEditPost', 'CKEditorNewsletter');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,8 +263,9 @@ document.querySelector("#addPostForm").addEventListener("submit", e =>
|
|||||||
data.append("featured", document.querySelector("#isFeatured").checked ? "1" : "0");
|
data.append("featured", document.querySelector("#isFeatured").checked ? "1" : "0");
|
||||||
data.append("abstract", document.querySelector("#postAbstract").value);
|
data.append("abstract", document.querySelector("#postAbstract").value);
|
||||||
data.append("body", editors["CKEditorAddPost"].getData());
|
data.append("body", editors["CKEditorAddPost"].getData());
|
||||||
|
data.append('bodyText', viewToPlainText(editors['CKEditorAddPost'].editing.view.document.getRoot()));
|
||||||
data.append("dateCreated", new Date().toISOString().slice(0, 19).replace('T', ' '));
|
data.append("dateCreated", new Date().toISOString().slice(0, 19).replace('T', ' '));
|
||||||
data.append("categories", document.querySelector("#postCategories").value);
|
data.append('categories', document.querySelector('#postCategories').value.toLowerCase());
|
||||||
data.append("headerImg", document.querySelector("#headerImg").files[0]);
|
data.append("headerImg", document.querySelector("#headerImg").files[0]);
|
||||||
|
|
||||||
fetch("/api/blog/post", {
|
fetch("/api/blog/post", {
|
||||||
@@ -315,8 +317,9 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
|
|||||||
data["featured"] = document.querySelector("#editIsFeatured").checked ? "1" : "0";
|
data["featured"] = document.querySelector("#editIsFeatured").checked ? "1" : "0";
|
||||||
data["abstract"] = document.querySelector("#editPostAbstract").value;
|
data["abstract"] = document.querySelector("#editPostAbstract").value;
|
||||||
data["body"] = editors["CKEditorEditPost"].getData();
|
data["body"] = editors["CKEditorEditPost"].getData();
|
||||||
|
data['bodyText'] = viewToPlainText(editors['CKEditorEditPost'].editing.view.document.getRoot());
|
||||||
data["dateModified"] = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
data["dateModified"] = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||||
data["categories"] = document.querySelector("#editPostCategories").value;
|
data['categories'] = document.querySelector('#editPostCategories').value.toLowerCase();
|
||||||
|
|
||||||
let imgData = new FormData();
|
let imgData = new FormData();
|
||||||
imgData.append("headerImg", document.querySelector("#editHeaderImg").files[0]);
|
imgData.append("headerImg", document.querySelector("#editHeaderImg").files[0]);
|
||||||
@@ -364,6 +367,7 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
|
|||||||
{
|
{
|
||||||
document.querySelector("#editPostForm").reset();
|
document.querySelector("#editPostForm").reset();
|
||||||
document.querySelector("#editPostForm input[type='submit']").id = "";
|
document.querySelector("#editPostForm input[type='submit']").id = "";
|
||||||
|
console.log();
|
||||||
editors["CKEditorEditPost"].setData("");
|
editors["CKEditorEditPost"].setData("");
|
||||||
showSuccessMessage("Post edited successfully", "editPost");
|
showSuccessMessage("Post edited successfully", "editPost");
|
||||||
return;
|
return;
|
||||||
@@ -374,10 +378,43 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
|
|||||||
window.location.href = "./";
|
window.location.href = "./";
|
||||||
return;
|
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", () =>
|
document.querySelector("#goToCV").addEventListener("click", () =>
|
||||||
@@ -418,6 +455,13 @@ document.querySelector("#goToEditPost").addEventListener("click", () =>
|
|||||||
document.querySelector("#blog").classList.add("active");
|
document.querySelector("#blog").classList.add("active");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.querySelector('#goToNewsletter').addEventListener('click', () =>
|
||||||
|
{
|
||||||
|
textareaLoaded = false;
|
||||||
|
addActiveClass('goToNewsletter');
|
||||||
|
goToPage('newsletter');
|
||||||
|
});
|
||||||
|
|
||||||
document.querySelector("#logout").addEventListener("click", () =>
|
document.querySelector("#logout").addEventListener("click", () =>
|
||||||
{
|
{
|
||||||
fetch("/api/user/logout").then(res =>
|
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
|
* Removes the active class from all nav items and adds it to the one with the given id
|
||||||
* @param {string} id - The id to add the active class to
|
* @param {string} id - The id to add the active class to
|
||||||
@@ -758,6 +858,31 @@ function createEditors(...ids)
|
|||||||
{language: 'zephir', label: 'Zephir'},
|
{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 =>
|
}).then(CKEditor =>
|
||||||
{
|
{
|
||||||
editors[id] = CKEditor;
|
editors[id] = CKEditor;
|
||||||
@@ -768,7 +893,7 @@ function createEditors(...ids)
|
|||||||
console.warn('Build id: 1eo8ioyje2om-vgar4aghypdm');
|
console.warn('Build id: 1eo8ioyje2om-vgar4aghypdm');
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1097,15 +1222,15 @@ function updateProjectItem(id, e)
|
|||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
let data = {}
|
let data = {}
|
||||||
data["title"] = document.querySelector(`#title${id}`).value;
|
data['title'] = document.querySelector(`#title${id}proj`).value;
|
||||||
data["isMainProject"] = document.querySelector(`#isMainProject${id}`).checked ? "true" : "false";
|
data['isMainProject'] = document.querySelector(`#isMainProject${id}proj`).checked ? 'true' : 'false';
|
||||||
data["information"] = document.querySelector(`#info${id}`).value;
|
data['information'] = document.querySelector(`#info${id}proj`).value;
|
||||||
data["projectLink"] = document.querySelector(`#viewProj${id}`).value;
|
data['projectLink'] = document.querySelector(`#viewProj${id}proj`).value;
|
||||||
data["gitLink"] = document.querySelector(`#git${id}`).value;
|
data['gitLink'] = document.querySelector(`#git${id}proj`).value;
|
||||||
|
|
||||||
let imgData = new FormData();
|
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, {
|
fetch("/api/projectData/" + id, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -1129,8 +1254,8 @@ function updateProjectItem(id, e)
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
|
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
|
||||||
document.querySelector(`#title${id}`).setAttribute("disabled", "");
|
document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
|
||||||
document.querySelector(`#info${id}`).setAttribute("disabled", "");
|
document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("updating image")
|
console.log("updating image")
|
||||||
@@ -1168,8 +1293,8 @@ function updateProjectItem(id, e)
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
|
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
|
||||||
document.querySelector(`#title${id}`).setAttribute("disabled", "");
|
document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
|
||||||
document.querySelector(`#info${id}`).setAttribute("disabled", "");
|
document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
|
||||||
document.querySelector(`#projectImage${id}`).src = updatedProjectImage.imgLocation;
|
document.querySelector(`#projectImage${id}`).src = updatedProjectImage.imgLocation;
|
||||||
return;
|
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=''" : "")}>
|
<input type="checkbox" id="isMainProject${id}" name="isMainProject${id}" ${(isMainProject === "true" ? "checked=''" : "")}>
|
||||||
<span class="checkmark"></span>
|
<span class="checkmark"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="formControl infoContainer">
|
<div class="formControl infoContainer">
|
||||||
<textarea name="info${id}" id="info${id}" disabled>${information}</textarea>
|
<textarea name="info${id}" id="info${id}" disabled>${information}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,7 +63,13 @@ document.querySelector("#login form").addEventListener("submit", e =>
|
|||||||
showErrorMessage("Please type in a username and password.", "login");
|
showErrorMessage("Please type in a username and password.", "login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showErrorMessage("Invalid username or password.", "login");
|
if (res.status === 401)
|
||||||
|
{
|
||||||
|
showErrorMessage('Invalid username or password.', 'login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showErrorMessage(json.error, 'login');
|
||||||
}));
|
}));
|
||||||
return;
|
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>
|
<li><a href="/blog" class="textShadow link">blog</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<div>
|
<div>
|
||||||
<h1>full stack developer</h1>
|
<h1>full stack developer</h1>
|
||||||
@@ -44,11 +45,13 @@
|
|||||||
<section id="about">
|
<section id="about">
|
||||||
<h1>about</h1>
|
<h1>about</h1>
|
||||||
<div>
|
<div>
|
||||||
<p>Hi, I'm Rohit, a computer science student at The University of Nottingham with experience in multiple
|
<p>Hi, I'm Rohit, a Full Stack Developer at Cadonix with experience in multiple
|
||||||
programming languages such as Java, C#, Python, HTML, CSS, JS, PHP. Bringing forth a motivated
|
programming languages such as Java, C#, Python, HTML, CSS, JS, PHP. Bringing forth a motivated
|
||||||
attitude and a variety of powerful skills. Very good at bringing a team together to get a project
|
attitude and a variety of powerful skills. Very good at bringing a team together to get a project
|
||||||
finished. Below are some of my projects that I have worked on. </p>
|
finished. Below are some of my projects that I have worked on. </p>
|
||||||
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<a href="#">
|
<a href="https://rohitpai.co.uk/blog">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<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"/>
|
<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>
|
</svg>
|
||||||
@@ -171,15 +174,16 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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>
|
</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/index.js"></script>
|
||||||
<script src="js/typewriter.js"></script>
|
<script src="js/typewriter.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||