Compare commits

...

45 Commits

Author SHA1 Message Date
rodude123 00851d37b8 Merge pull request 'Fixed small tiny issue with the carousel having less the maximum number of visible items i.e. none in the hidden div' (#57) from older-blog-posts-fix into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
Reviewed-on: #57
2024-11-04 22:39:51 +00:00
rodude123 a55ba6ce2f Fixed small tiny issue with the carousel having less the maximum number of visible items i.e. none in the hidden div
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 20s
Signed-off-by: rodude123 <rodude123@gmail.com>
2024-11-04 22:39:04 +00:00
rodude123 c2e01dd1e8 Merge pull request 'older-blog-posts' (#56) from older-blog-posts into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 18s
Reviewed-on: #56
2024-11-04 22:30:01 +00:00
rodude123 860c52e829 Removed commented code from blogData.php
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s
Signed-off-by: rodude123 <rodude123@gmail.com>
2024-11-04 22:28:23 +00:00
rodude123 b28e7b2da5 Various fixes for the blog and editor. As well as finally adding in the carousel!
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 24s
Signed-off-by: rodude123 <rodude123@gmail.com>
2024-11-04 22:17:42 +00:00
rodude123 7d6eeb2310 Merge pull request 'various-fixes' (#55) from various-fixes into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 20s
Reviewed-on: #55
2024-06-22 19:02:57 +01:00
rodude123 558ac03fbb various fixes for the blog including prismjs highlighting, colour schemes and embedding media
🚀 Deploy website on push / 🎉 Deploy (push) Failing after 25s
Signed-off-by: rodude123 <rodude123@gmail.com>
2024-06-22 17:46:17 +01:00
rodude123 646cfa6561 various fixes for the blog including prismjs highlighting, colour schemes and embedding media
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 23s
Signed-off-by: rodude123 <rodude123@gmail.com>
2024-02-11 23:13:58 +00:00
rodude123 591db4dfa3 Uncommented the code, shouldn't have been commented in the first place
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 30s
Signed-off-by: rodude123 <rodude123@gmail.com>
2024-01-01 19:09:23 +00:00
rodude123 7f96aa9277 Merge pull request 'Impleted SAML SSO' (#54) from SAML-auth into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 28s
Reviewed-on: #54
2024-01-01 13:55:12 +00:00
rodude123 7b8e81e1f7 Added SAML-Toolkits php-saml to composer and installed it to the vendor folder. Implemented SSO with JumpCloud
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 31s
Signed-off-by: rodude123 <rodude123@gmail.com>
2024-01-01 13:52:30 +00:00
rodude123 430e1c65ca Merge pull request 'fixed main url' (#53) from url-fix into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 33s
Reviewed-on: #53
2023-12-28 18:04:59 +00:00
rodude123 364e2d2675 fixed main url
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
2023-12-13 15:39:39 +00:00
rodude123 804d8a9390 Merge pull request 'Fixed some bugs in the newsletter images, made some minor improvements in other areas. Added in unsubscribe functionality' (#52) from bug-fixes-and-unsubscribe into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s
Reviewed-on: #52
2023-12-13 03:11:40 +00:00
rodude123 a5f17a70ed Fixed some bugs in the newsletter images, made some minor improvements in other areas. Added in unsubscribe functionality
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 21s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-12-13 03:10:23 +00:00
rodude123 62f871f4ca Merge pull request 'newsletter-and-cookies' (#51) from newsletter-and-cookies into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
Reviewed-on: #51
2023-12-06 23:32:09 +00:00
rodude123 0cb57d0813 Made cookie popup work for most browsers
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-12-06 23:30:25 +00:00
rodude123 52614e5835 Added in a cookie popup and proper newsletter functionality
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 21s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-12-06 00:38:33 +00:00
rodude123 e6522fb05e Merge pull request 'feed-and-misc' (#50) from feed-and-misc into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 18s
Reviewed-on: #50
2023-11-18 13:14:08 +00:00
rodude123 5b063afad3 Got keywords from text using rake-php-plus and then stored in the DB, then used it to append the keywords to the meta tag.
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 20s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-11-18 13:06:27 +00:00
rodude123 f27a5113b1 Created feeds and UI for feeds and newsletter
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-11-14 01:03:17 +00:00
rodude123 6cfea3fc98 Merge pull request 'fixed mobile view of menubar, search page and category page' (#49) from search-fix into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 18s
Reviewed-on: #49
2023-11-08 22:24:43 +00:00
rodude123 d8a7901574 fixed mobile view of menubar, search page and category page
🚀 Deploy website on push / 🎉 Deploy (push) Failing after 0s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-11-06 19:37:26 +00:00
rodude123 f3f68717ee Merge pull request 'Search' (#48) from search into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
Reviewed-on: #48
2023-11-05 17:50:00 +00:00
rodude123 f54ed2f8fb added frontend search functionality with a small menubar
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 23s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-11-05 17:47:44 +00:00
rodude123 b4ab7900db added backend search functionality
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-10-31 19:36:51 +00:00
rodude123 929060ce70 Merge pull request 'mobile-friendly-blog' (#47) from mobile-friendly-blog into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 16s
Reviewed-on: #47
2023-10-28 19:02:34 +01:00
rodude123 03f14ba174 Completed mobile view and changed some minor things
🚀 Deploy website on push / 🎉 Deploy (push) Failing after 19s
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-10-28 19:01:14 +01:00
rodude123 801e336c29 added comments
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s
2023-10-28 13:14:59 +01:00
rodude123 d3a8ff927c changed width of the main content
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s
2023-10-28 12:17:30 +01:00
rodude123 5878dbaf9a Merge pull request 'categories-fix' (#46) from categories-fix into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 16s
Reviewed-on: #46
2023-10-28 11:29:42 +01:00
rodude123 edabd92c17 fixed css errors
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 19s
2023-10-28 11:26:39 +01:00
rodude123 730b822a2b centered button in container
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
2023-10-24 17:58:18 +01:00
rodude123 9e94f6cade added bottom margin
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 46s
2023-10-24 16:25:32 +01:00
rodude123 a868136a99 margin centered
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 16s
2023-10-24 16:22:16 +01:00
rodude123 fbf9449116 set width to 100%
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
2023-10-24 16:20:35 +01:00
rodude123 9349f73016 centered items
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 16s
2023-10-24 16:19:11 +01:00
rodude123 b06614a8c7 fixed category page
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
2023-10-24 16:17:30 +01:00
rodude123 3db2520339 Merge pull request 'node-fix' (#45) from node-fix into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 17s
Reviewed-on: #45
2023-10-24 16:14:31 +01:00
rodude123 4c871c20a0 chnaged to run all branches
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 16s
2023-10-24 16:08:02 +01:00
rodude123 3812f99259 changed node version
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 22s
2023-10-24 16:04:17 +01:00
rodude123 a697ea2ac8 Merge pull request 'categories-page' (#44) from categories-page into master
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 16s
Reviewed-on: #44
2023-10-24 15:54:20 +01:00
rodude123 e106f89dcb All categories page created with flex
🚀 Deploy website on push / 🎉 Deploy (push) Has been cancelled
2023-10-21 23:26:00 +01:00
rodude123 da791c8866 Started work on all categories page
🚀 Deploy website on push / 🎉 Deploy (push) Has been cancelled
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-10-21 09:06:03 +01:00
rodude123 a0567a25f5 Created individual categories page
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-10-18 23:58:21 +01:00
77 changed files with 10005 additions and 2405 deletions
+5 -2
View File
@@ -1,4 +1,7 @@
on: push
on:
push:
branches:
- '*'
name: 🚀 Deploy website on push
jobs:
web-deploy:
@@ -11,7 +14,7 @@ jobs:
- name: Use Node.js Latest
uses: actions/setup-node@v3
with:
node-version: 'latest'
node-version: 20
- name: 🔨Run Gulp
run: |
+11 -1
View File
@@ -15,6 +15,16 @@
"rbdwllr/psr-jwt": "^2.0",
"tuupola/slim-jwt-auth": "^3.6",
"ext-dom": "*",
"ext-libxml": "*"
"ext-libxml": "*",
"donatello-za/rake-php-plus": "^1.0",
"phpmailer/phpmailer": "^6.9",
"onelogin/php-saml": "^4.1",
"ext-mbstring": "*"
},
"repositories": [
{
"type": "composer",
"url": "https:\/\/www.phpclasses.org\/"
}
]
}
Generated
+360 -182
View File
File diff suppressed because it is too large Load Diff
+1134 -51
View File
File diff suppressed because it is too large Load Diff
+165 -23
View File
@@ -4,6 +4,7 @@ namespace api\blog;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "blogData.php";
use api\utils\routesInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -12,6 +13,7 @@ use Slim\App;
class blogRoutes implements routesInterface
{
private blogData $blogData;
/**
* 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
@@ -79,8 +81,12 @@ class blogRoutes implements routesInterface
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args)
{
if ($args["type"] != null)
if ($args["type"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
return $response->withStatus(400);
}
if ($args["type"] == "latest")
{
$post = $this->blogData->getLatestBlogPost();
@@ -116,32 +122,93 @@ class blogRoutes implements routesInterface
$response->getBody()->write(json_encode($post));
return $response;
}
});
$app->get("/blog/feed/{type}", function (Request $request, Response $response, $args)
{
if ($args["type"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
return $response->withStatus(400);
}
$feed = $this->blogData->getFeed($args["type"]);
if (is_array($feed))
{
$response->getBody()->write(json_encode($feed));
return $response->withStatus(404);
}
if ($args["type"] == "atom")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/atom+xml");
}
if ($args["type"] == "rss")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/rss+xml");
}
if ($args["type"] == "json")
{
$response->getBody()->write(json_encode($feed));
return $response->withHeader("Content-Type", "application/feed+json");
}
$response->getBody()->write(json_encode(array("error" => "Invalid feed type")));
return $response->withStatus(400);
});
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
{
if ($args["searchTerm"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
return $response->withStatus(400);
}
$posts = $this->blogData->searchBlog($args["searchTerm"]);
$json = json_encode($posts);
$response->getBody()->write($json);
if (array_key_exists("errorMessage", $posts))
{
$response->withStatus(404);
}
return $response;
});
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"]))
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
if (!preg_match('/(?:^|,)(?=[^"]|(")?)"?((?(1)(?:[^"]|"")*|[^,"]*))"?(?=,|$)/mx', $data["categories"]))
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"]);
$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")
{
@@ -167,16 +234,16 @@ class blogRoutes implements routesInterface
$response->withStatus(201);
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
});
$app->delete("/blog/post/{id}", function (Request $request, Response $response, $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$message = $this->blogData->deletePost($args["id"]);
if ($message === "post not found")
@@ -200,38 +267,66 @@ class blogRoutes implements routesInterface
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);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
$message = $this->blogData->deleteNewsletterEmail($args["email"]);
if ($message === "email not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("message" => "Woah, you're already trying to leave without signing up?")));
return $response->withStatus(404);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("message" => "Error, something went wrong")));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode(array("message" => "Sorry to see you go! You'll no longer receive any emails from me. If you change your mind, you can always sign up again.")));
return $response;
});
$app->post("/blog/post", function (Request $request, Response $response)
{
$data = $request->getParsedBody();
$files = $request->getUploadedFiles();
$headerImg = $files["headerImg"];
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
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
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
return $response->withStatus(400);
}
if (array_key_exists("headerImg", $files))
{
$headerImg = $files["headerImg"];
}
if (empty($files["headerImg"]))
{
$headerImg = null;
}
$featured = $data["featured"] === "true";
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
// $featured = $data["featured"] === "true";
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], intval($data["featured"]), $data["categories"], $headerImg);
if (!is_int($insertedID))
{
// uh oh something went wrong
@@ -267,28 +362,75 @@ class blogRoutes implements routesInterface
{
$files = $request->getUploadedFiles();
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
if (empty($files))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent"))));
$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" => array("message" => $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);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
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);
});
}
}
+4 -1
View File
@@ -1,8 +1,11 @@
<?php
namespace api\project;
use api\utils\imgUtils;
use PDO;
use Psr\Http\Message\UploadedFileInterface;
use function api\utils\dbConn;
require_once __DIR__ . "/../utils/config.php";
require_once __DIR__ . "/../utils/imgUtils.php";
@@ -15,7 +18,7 @@ class projectData
{
/**
* Get all project data
* @return array - Array of all project data or error message
* @return array<array> - Array of all project data or error message
*/
public function getProjectData(): array
{
+11 -10
View File
@@ -1,4 +1,5 @@
<?php
namespace api\project;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "projectData.php";
@@ -49,8 +50,12 @@ class projectRoutes implements routesInterface
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
{
// uh oh sent some empty data
@@ -83,16 +88,16 @@ class projectRoutes implements routesInterface
}
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
});
$app->delete("/projectData/{id}", function (Request $request, Response $response, array $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$message = $this->projectData->deleteProjectData($args["id"]);
if ($message === "project not found")
@@ -117,10 +122,6 @@ class projectRoutes implements routesInterface
}
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
});
$app->post("/projectData", function (Request $request, Response $response)
+2 -1
View File
@@ -3,6 +3,7 @@
namespace api\timeline;
use PDO;
use function api\utils\dbConn;
require_once __DIR__ . "/../utils/config.php";
@@ -14,7 +15,7 @@ class timelineData
{
/**
* Get all education data
* @return array - Array of all education data or error message
* @return array<array> - Array of all education data or error message
*/
public function getEduData(): array
{
+1
View File
@@ -1,4 +1,5 @@
<?php
namespace api\timeline;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "timelineData.php";
-138
View File
@@ -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;
}
}
+38
View File
@@ -1,7 +1,12 @@
<?php
namespace api\user;
use Firebase\JWT\JWT;
use PDO;
use function api\utils\dbConn;
use function api\utils\getSAMLSettings;
use function api\utils\getSecretKey;
require_once __DIR__ . "/../utils/config.php";
@@ -134,5 +139,38 @@ class userData
return false;
}
/**
* Get the SAML settings
* @return array - SAML settings
*/
public function getSamlConf(): array
{
return getSAMLSettings();
}
/**
* Check if the SAML user exists
* @param string $username - Username
* @param string $email - Email
* @return bool - True if the user exists, false if not
*/
public function checkSAMLUser(string $username, string $email): bool
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM users WHERE username = :username AND email = :email");
$stmt->bindParam(":username", $username);
$stmt->bindParam(":email", $email);
$stmt->execute();
// set the resulting array to associative
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($result)
{
return true;
}
return false;
}
}
+75 -25
View File
@@ -1,9 +1,12 @@
<?php
namespace api\user;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "userData.php";
use api\utils\routesInterface;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Error;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
@@ -11,14 +14,17 @@ use Slim\App;
class userRoutes implements routesInterface
{
private userData $user;
private Auth $samlAuth;
/**
* constructor used to instantiate a base user routes, to be used in the index.php file.
* constructor used to instantiate base user routes, to be used in the index.php file.
* @param App $app - the slim app used to create the routes
* @throws Error
*/
public function __construct(App $app)
{
$this->user = new userData();
$this->samlAuth = new Auth($this->user->getSamlConf());
$this->createRoutes($app);
}
@@ -29,31 +35,9 @@ class userRoutes implements routesInterface
*/
public function createRoutes(App $app): void
{
$app->post("/user/login", function (Request $request, Response $response)
$app->get("/user/login", function (Request $request, Response $response)
{
// get request data
$data = $request->getParsedBody();
if (empty($data["username"]) || empty($data["password"]))
{
// uh oh user sent empty data
return $response->withStatus(400);
}
if ($this->user->checkUser($data["username"], $data["password"]))
{
// yay, user is logged in
$_SESSION["token"] = $this->user->createToken($data["username"]);
$_SESSION["username"] = $data["username"];
$inactive = 60 * 60 * 48; // 2 days
$_SESSION["timeout"] = time() + $inactive;
$response->getBody()->write(json_encode(array("token" => $_SESSION["token"])));
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
return $response->withStatus(401);
$this->samlAuth->login();
});
$app->get("/user/logout", function (Request $request, Response $response)
@@ -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)
{
if (empty($args["email"]))
@@ -138,6 +136,58 @@ class userRoutes implements routesInterface
return $response->withStatus(401);
});
$app->post("/user/login", function (Request $request, Response $response)
{
// get request data
$data = $request->getParsedBody();
if (empty($data["username"]) || empty($data["password"]))
{
// uh oh user sent empty data
return $response->withStatus(400);
}
if ($this->user->checkUser($data["username"], $data["password"]))
{
// yay, user is logged in
$_SESSION["token"] = $this->user->createToken($data["username"]);
$_SESSION["username"] = $data["username"];
$inactive = 60 * 60 * 48; // 2 days
$_SESSION["timeout"] = time() + $inactive;
$response->getBody()->write(json_encode(array("token" => $_SESSION["token"])));
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
return $response->withStatus(401);
});
$app->post("/user/acs", function (Request $request, Response $response)
{
$this->samlAuth->processResponse();
$attributes = $this->samlAuth->getAttributes();
$username = $attributes["username"][0];
$email = $attributes["email"][0];
if ($this->user->checkSAMLUser($username, $email))
{
// yay, user is logged in
$_SESSION["token"] = $this->user->createToken($username);
$_SESSION["username"] = $username;
$_SESSION["email"] = $email;
$inactive = 60 * 60 * 48; // 2 days
$_SESSION["timeout"] = time() + $inactive;
return $response->withHeader("Location", "https://rohitpai.co.uk/editor/")->withStatus(302);
}
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
return $response->withStatus(401);
});
$app->post("/user/changePassword", function (Request $request, Response $response)
{
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
+152
View File
@@ -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);
}
}
+386
View File
@@ -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';
+28 -3
View File
@@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException;
use Slim\Exception\HttpMethodNotAllowedException;
use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Response;
use Throwable;
use Tuupola\Middleware\JwtAuthentication;
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
@@ -65,7 +66,12 @@ class middleware
$app->add(function ($request, $handler)
{
$response = $handler->handle($request);
$contentType = $response->getHeaderLine("Content-Type");
if (empty($contentType) || $contentType === "text/html")
{
return $response->withHeader("Content-Type", "application/json");
}
return $response;
});
}
@@ -79,8 +85,8 @@ class middleware
$app->add(new JwtAuthentication([
"rules" => [
new RequestPathRule([
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"]
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"],
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
]),
new RequestMethodRule([
"ignore" => ["OPTIONS", "GET"]
@@ -128,8 +134,27 @@ class middleware
return $response;
}
});
$app->addErrorMiddleware(true, true, true);
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
$errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails
) use ($app, $errorHandler)
{
$statusCode = $exception->getCode() ?: 500;
// Create a JSON response with the error message
$response = $app->getResponseFactory()->createResponse($statusCode);
$response->getBody()->write(json_encode(['error' => $exception->getMessage()]));
return $response;
});
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+12
View File
@@ -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}
+197
View File
@@ -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

+9
View File
@@ -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
View File
@@ -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>&copy; <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>&copy; <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
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -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")}));
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 87 KiB

+2 -2
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
+2867 -1062
View File
File diff suppressed because it is too large Load Diff
+7
View File
@@ -12,6 +12,7 @@
"author": "Rohit Pai",
"license": "ISC",
"dependencies": {
"@ckeditor/ckeditor5-clipboard": "^40.0.0",
"browser-sync": "^2.27.5",
"gulp": "^4.0.2",
"gulp-clean-css": "^4.3.0",
@@ -25,5 +26,11 @@
"require": "^0.4.4",
"source-map-generator": "^0.8.0",
"vinyl-ftp": "^0.6.1"
},
"devDependencies": {
"terser-webpack-plugin": "^5.3.9",
"vinyl-named-with-path": "^1.0.0",
"webpack-cli": "^5.1.4",
"webpack-stream": "^7.0.0"
}
}
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
npm start
+1136 -51
View File
File diff suppressed because it is too large Load Diff
+188 -33
View File
@@ -4,6 +4,7 @@ namespace api\blog;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "blogData.php";
use api\utils\routesInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -12,6 +13,7 @@ use Slim\App;
class blogRoutes implements routesInterface
{
private blogData $blogData;
/**
* 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
@@ -29,9 +31,11 @@ class blogRoutes implements routesInterface
*/
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();
if (array_key_exists("errorMessage", $post)) {
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
@@ -40,10 +44,13 @@ class blogRoutes implements routesInterface
return $response;
});
$app->get("/blog/categories/{category}", function (Request $request, Response $response, $args) {
if ($args["category"] != null) {
$app->get("/blog/categories/{category}", function (Request $request, Response $response, $args)
{
if ($args["category"] != null)
{
$post = $this->blogData->getPostsByCategory($args["category"]);
if (array_key_exists("errorMessage", $post)) {
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
@@ -72,11 +79,19 @@ class blogRoutes implements routesInterface
return $response;
});
$app->get("/blog/post/{type}", function (Request $request, Response $response, $args) {
if ($args["type"] != null) {
if ($args["type"] == "latest") {
$app->get("/blog/post/{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);
}
if ($args["type"] == "latest")
{
$post = $this->blogData->getLatestBlogPost();
if (array_key_exists("errorMessage", $post)) {
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
@@ -85,9 +100,11 @@ class blogRoutes implements routesInterface
return $response;
}
if ($args["type"] == "featured") {
if ($args["type"] == "featured")
{
$post = $this->blogData->getFeaturedBlogPost();
if (array_key_exists("errorMessage", $post)) {
if (array_key_exists("errorMessage", $post))
{
$response->getBody()->write(json_encode($post));
return $response->withStatus(404);
}
@@ -105,31 +122,93 @@ class blogRoutes implements routesInterface
$response->getBody()->write(json_encode($post));
return $response;
}
});
$app->get("/blog/feed/{type}", function (Request $request, Response $response, $args)
{
if ($args["type"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide a title")));
return $response->withStatus(400);
}
$feed = $this->blogData->getFeed($args["type"]);
if (is_array($feed))
{
$response->getBody()->write(json_encode($feed));
return $response->withStatus(404);
}
if ($args["type"] == "atom")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/atom+xml");
}
if ($args["type"] == "rss")
{
$response->getBody()->write($feed);
return $response->withHeader("Content-Type", "application/rss+xml");
}
if ($args["type"] == "json")
{
$response->getBody()->write(json_encode($feed));
return $response->withHeader("Content-Type", "application/feed+json");
}
$response->getBody()->write(json_encode(array("error" => "Invalid feed type")));
return $response->withStatus(400);
});
$app->get("/blog/search/{searchTerm}", function (Request $request, $response, $args)
{
if ($args["searchTerm"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide a search term")));
return $response->withStatus(400);
}
$posts = $this->blogData->searchBlog($args["searchTerm"]);
$json = json_encode($posts);
$response->getBody()->write($json);
if (array_key_exists("errorMessage", $posts))
{
$response->withStatus(404);
}
return $response;
});
$app->patch("/blog/post/{id}", function (Request $request, Response $response, $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["dateModified"]) || empty($data["categories"]))
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["dateModified"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Only some of the data was sent")));
return $response->withStatus(400);
}
if (!preg_match('/[a-zA-Z0-9 ]+, |\w+/mx', $data["categories"])) {
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"]);
$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")
{
@@ -155,16 +234,16 @@ class blogRoutes implements routesInterface
$response->withStatus(201);
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
});
$app->delete("/blog/post/{id}", function (Request $request, Response $response, $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$message = $this->blogData->deletePost($args["id"]);
if ($message === "post not found")
@@ -188,37 +267,66 @@ class blogRoutes implements routesInterface
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);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
$message = $this->blogData->deleteNewsletterEmail($args["email"]);
if ($message === "email not found")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("message" => "Woah, you're already trying to leave without signing up?")));
return $response->withStatus(404);
}
if ($message === "error")
{
// uh oh something went wrong
$response->getBody()->write(json_encode(array("message" => "Error, something went wrong")));
return $response->withStatus(500);
}
$response->getBody()->write(json_encode(array("message" => "Sorry to see you go! You'll no longer receive any emails from me. If you change your mind, you can always sign up again.")));
return $response;
});
$app->post("/blog/post", function (Request $request, Response $response)
{
$data = $request->getParsedBody();
$files = $request->getUploadedFiles();
$headerImg = $files["headerImg"];
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
if (empty($data["title"]) || strlen($data["featured"]) == 0 || empty($data["body"]) || empty($data["bodyText"]) || empty($data["abstract"]) || empty($data["dateCreated"]) || empty($data["categories"]))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => "Error, empty data sent")));
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
$response->getBody()->write(json_encode(array("error" => "Categories must be in a CSV format")));
return $response->withStatus(400);
}
if (array_key_exists("headerImg", $files))
{
$headerImg = $files["headerImg"];
}
if (empty($files["headerImg"]))
{
$headerImg = null;
}
$featured = $data["featured"] === "true";
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
// $featured = $data["featured"] === "true";
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], intval($data["featured"]), $data["categories"], $headerImg);
if (!is_int($insertedID))
{
// uh oh something went wrong
@@ -254,28 +362,75 @@ class blogRoutes implements routesInterface
{
$files = $request->getUploadedFiles();
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
if (empty($files))
{
// uh oh sent some empty data
$response->getBody()->write(json_encode(array("error" => array("message" => "Error, empty data sent"))));
$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" => array("message" => $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);
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
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);
});
}
}
+4 -1
View File
@@ -1,8 +1,11 @@
<?php
namespace api\project;
use api\utils\imgUtils;
use PDO;
use Psr\Http\Message\UploadedFileInterface;
use function api\utils\dbConn;
require_once __DIR__ . "/../utils/config.php";
require_once __DIR__ . "/../utils/imgUtils.php";
@@ -15,7 +18,7 @@ class projectData
{
/**
* Get all project data
* @return array - Array of all project data or error message
* @return array<array> - Array of all project data or error message
*/
public function getProjectData(): array
{
+11 -10
View File
@@ -1,4 +1,5 @@
<?php
namespace api\project;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "projectData.php";
@@ -49,8 +50,12 @@ class projectRoutes implements routesInterface
$app->patch("/projectData/{id}", function (Request $request, Response $response, array $args)
{
$data = $request->getParsedBody();
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
if (empty($data["title"]) || empty($data["isMainProject"]) || empty($data["information"]) || empty($data["gitLink"]))
{
// uh oh sent some empty data
@@ -83,16 +88,16 @@ class projectRoutes implements routesInterface
}
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
});
$app->delete("/projectData/{id}", function (Request $request, Response $response, array $args)
{
if ($args["id"] != null)
if ($args["id"] == null)
{
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
}
$message = $this->projectData->deleteProjectData($args["id"]);
if ($message === "project not found")
@@ -117,10 +122,6 @@ class projectRoutes implements routesInterface
}
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Please provide an ID")));
return $response->withStatus(400);
});
$app->post("/projectData", function (Request $request, Response $response)
+2 -1
View File
@@ -3,6 +3,7 @@
namespace api\timeline;
use PDO;
use function api\utils\dbConn;
require_once __DIR__ . "/../utils/config.php";
@@ -14,7 +15,7 @@ class timelineData
{
/**
* Get all education data
* @return array - Array of all education data or error message
* @return array<array> - Array of all education data or error message
*/
public function getEduData(): array
{
+1
View File
@@ -1,4 +1,5 @@
<?php
namespace api\timeline;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "timelineData.php";
+38
View File
@@ -1,7 +1,12 @@
<?php
namespace api\user;
use Firebase\JWT\JWT;
use PDO;
use function api\utils\dbConn;
use function api\utils\getSAMLSettings;
use function api\utils\getSecretKey;
require_once __DIR__ . "/../utils/config.php";
@@ -134,5 +139,38 @@ class userData
return false;
}
/**
* Get the SAML settings
* @return array - SAML settings
*/
public function getSamlConf(): array
{
return getSAMLSettings();
}
/**
* Check if the SAML user exists
* @param string $username - Username
* @param string $email - Email
* @return bool - True if the user exists, false if not
*/
public function checkSAMLUser(string $username, string $email): bool
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM users WHERE username = :username AND email = :email");
$stmt->bindParam(":username", $username);
$stmt->bindParam(":email", $email);
$stmt->execute();
// set the resulting array to associative
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($result)
{
return true;
}
return false;
}
}
+75 -25
View File
@@ -1,9 +1,12 @@
<?php
namespace api\user;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "userData.php";
use api\utils\routesInterface;
use OneLogin\Saml2\Auth;
use OneLogin\Saml2\Error;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
@@ -11,14 +14,17 @@ use Slim\App;
class userRoutes implements routesInterface
{
private userData $user;
private Auth $samlAuth;
/**
* constructor used to instantiate a base user routes, to be used in the index.php file.
* constructor used to instantiate base user routes, to be used in the index.php file.
* @param App $app - the slim app used to create the routes
* @throws Error
*/
public function __construct(App $app)
{
$this->user = new userData();
$this->samlAuth = new Auth($this->user->getSamlConf());
$this->createRoutes($app);
}
@@ -29,31 +35,9 @@ class userRoutes implements routesInterface
*/
public function createRoutes(App $app): void
{
$app->post("/user/login", function (Request $request, Response $response)
$app->get("/user/login", function (Request $request, Response $response)
{
// get request data
$data = $request->getParsedBody();
if (empty($data["username"]) || empty($data["password"]))
{
// uh oh user sent empty data
return $response->withStatus(400);
}
if ($this->user->checkUser($data["username"], $data["password"]))
{
// yay, user is logged in
$_SESSION["token"] = $this->user->createToken($data["username"]);
$_SESSION["username"] = $data["username"];
$inactive = 60 * 60 * 48; // 2 days
$_SESSION["timeout"] = time() + $inactive;
$response->getBody()->write(json_encode(array("token" => $_SESSION["token"])));
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
return $response->withStatus(401);
$this->samlAuth->login();
});
$app->get("/user/logout", function (Request $request, Response $response)
@@ -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)
{
if (empty($args["email"]))
@@ -138,6 +136,58 @@ class userRoutes implements routesInterface
return $response->withStatus(401);
});
$app->post("/user/login", function (Request $request, Response $response)
{
// get request data
$data = $request->getParsedBody();
if (empty($data["username"]) || empty($data["password"]))
{
// uh oh user sent empty data
return $response->withStatus(400);
}
if ($this->user->checkUser($data["username"], $data["password"]))
{
// yay, user is logged in
$_SESSION["token"] = $this->user->createToken($data["username"]);
$_SESSION["username"] = $data["username"];
$inactive = 60 * 60 * 48; // 2 days
$_SESSION["timeout"] = time() + $inactive;
$response->getBody()->write(json_encode(array("token" => $_SESSION["token"])));
return $response;
}
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
return $response->withStatus(401);
});
$app->post("/user/acs", function (Request $request, Response $response)
{
$this->samlAuth->processResponse();
$attributes = $this->samlAuth->getAttributes();
$username = $attributes["username"][0];
$email = $attributes["email"][0];
if ($this->user->checkSAMLUser($username, $email))
{
// yay, user is logged in
$_SESSION["token"] = $this->user->createToken($username);
$_SESSION["username"] = $username;
$_SESSION["email"] = $email;
$inactive = 60 * 60 * 48; // 2 days
$_SESSION["timeout"] = time() + $inactive;
return $response->withHeader("Location", "https://rohitpai.co.uk/editor/")->withStatus(302);
}
$response->getBody()->write(json_encode(array("error" => "Unauthorised")));
return $response->withStatus(401);
});
$app->post("/user/changePassword", function (Request $request, Response $response)
{
if (empty($_SESSION["resetToken"]) && empty($_SESSION["resetEmail"]))
+152
View File
@@ -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);
}
}
+386
View File
@@ -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';
+28 -3
View File
@@ -13,6 +13,7 @@ use Slim\Exception\HttpInternalServerErrorException;
use Slim\Exception\HttpMethodNotAllowedException;
use Slim\Exception\HttpNotFoundException;
use Slim\Psr7\Response;
use Throwable;
use Tuupola\Middleware\JwtAuthentication;
use Tuupola\Middleware\JwtAuthentication\RequestMethodRule;
use Tuupola\Middleware\JwtAuthentication\RequestPathRule;
@@ -65,7 +66,12 @@ class middleware
$app->add(function ($request, $handler)
{
$response = $handler->handle($request);
$contentType = $response->getHeaderLine("Content-Type");
if (empty($contentType) || $contentType === "text/html")
{
return $response->withHeader("Content-Type", "application/json");
}
return $response;
});
}
@@ -79,8 +85,8 @@ class middleware
$app->add(new JwtAuthentication([
"rules" => [
new RequestPathRule([
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout"],
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword"]
"path" => ["/api/projectData", "/api/timelineData/[a-z]*", "/api/projectImage/[0-9]*", "/api/logout", "/api/blog/[a-z]*"],
"ignore" => ["/api/contact", "/api/userData/login", "/api/userData/changePassword", "/api/blog/newsletter/\S*", "/api/blog/newsletter/unsubscribe/\S*"]
]),
new RequestMethodRule([
"ignore" => ["OPTIONS", "GET"]
@@ -128,8 +134,27 @@ class middleware
return $response;
}
});
$app->addErrorMiddleware(true, true, true);
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
$errorHandler = $errorMiddleware->getDefaultErrorHandler();
$errorMiddleware->setDefaultErrorHandler(function (ServerRequestInterface $request, Throwable $exception,
bool $displayErrorDetails,
bool $logErrors,
bool $logErrorDetails
) use ($app, $errorHandler)
{
$statusCode = $exception->getCode() ?: 500;
// Create a JSON response with the error message
$response = $app->getResponseFactory()->createResponse($statusCode);
$response->getBody()->write(json_encode(['error' => $exception->getMessage()]));
return $response;
});
}
}
+81 -14
View File
@@ -1,8 +1,10 @@
/**** Individual Blog Posts *****/
.profile {
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
width: 70%;
max-width: 70%;
}
svg {
@@ -15,6 +17,13 @@ footer {
margin-top: 0;
}
section#individualPost {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
}
div.byLine {
display: flex;
flex-direction: row;
@@ -46,13 +55,6 @@ div.cover {
box-shadow: 0 4px 2px 0 var(--mutedBlack);
}
section#individualPost {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: stretch;
}
div.mainContent {
border-right: 5px solid var(--mutedGrey);
min-height: 100%;
@@ -71,13 +73,22 @@ article {
padding: 0 2em;
}
article a {
padding: 0 1em;
}
article a::before,
article a::after {
visibility: hidden;
visibility: visible;
position: absolute;
margin-top: 1px;
}
article a.btn::before,
article a.btn::after {
display: none;
}
article a::before {
content: ' <';
margin-left: -0.5em;
@@ -87,9 +98,9 @@ article a::after {
content: '>';
}
article a:hover::before,
article a:hover::after {
visibility: visible;
article a:hover,
article a:hover {
font-weight: bold;
}
article h1 {
@@ -100,6 +111,31 @@ article h3 {
margin-top: 0;
}
article h3:not(div.byLine > h3), .otherPosts h3 {
font-weight: bold;
}
article .media {
align-self: center;
}
article table td, article table th {
border: 1px solid #ddd;
padding: 8px;
}
article table tr:nth-child(even) {
background-color: #f2f2f2;
}
article table tr:hover {
background-color: #ddd;
}
article .table {
margin: 0;
}
aside.sideContent {
display: flex;
flex-direction: column;
@@ -116,6 +152,7 @@ div.authorInfo {
padding-left: 1em;
padding-top: 0.5em;
border-bottom: 5px solid var(--mutedGrey);
width: 100%;
}
div.authorInfo .picture {
@@ -130,7 +167,7 @@ div.authorInfo h3 {
grid-column: span 2;
}
div.otherPosts {
div.otherPosts, div.newsletter, div.feeds {
display: flex;
flex-direction: column;
justify-content: flex-start;
@@ -140,10 +177,36 @@ div.otherPosts {
width: 100%;
}
div.otherPosts a {
div.otherPosts a, div.feeds a {
padding: 0.5em 1em;
}
div.newsletter div.form input[type="submit"] {
margin-top: 1em;
padding: 0.5em 1em;
}
div.feeds .icons {
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: flex-start;
gap: 0.5em;
flex-wrap: wrap-reverse;
}
div.feeds h2 {
margin-bottom: 0;
}
div.feeds i.fa-solid.fa-rss {
font-size: 2em;
}
div.feeds img.atom, div.feeds img.json {
width: 2em;
}
div.categories {
display: flex;
flex-direction: column;
@@ -153,6 +216,10 @@ div.categories {
width: 100%;
}
div.form input[type="submit"] {
align-self: flex-start;
}
.image img, .image_resized img {
max-width: 100%;
-webkit-border-radius: 10px;
+30
View File
@@ -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;
}
+149 -5
View File
@@ -1,3 +1,5 @@
/**** Blog Home page *****/
.banner {
max-width: 30%;
box-shadow: 0 6px 4px 0 var(--mutedBlack);
@@ -24,8 +26,47 @@ h3 {
line-height: 2.1875rem;
}
div.menu {
width: 100%;
border-bottom: 5px solid var(--mutedGrey);
}
div.menu input:not([type="submit"]) {
width: auto;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
div.menu > ul {
list-style: none;
display: flex;
flex-direction: row;
justify-content: space-around;
}
div.menu ul li {
display: flex;
flex-direction: row;
}
div.menu ul li button.btn {
padding: initial;
border-radius: 0 0.5em 0.5em 0;
}
div.menu ul li input:not([type="submit"]):focus + button.btn,
div.menu ul li:hover button.btn,
div.menu ul li:focus button.btn {
background: var(--primaryHover);
border: 0.3215em solid var(--primaryHover);
}
div.menu ul li:hover input:not([type="submit"]),
div.menu ul li:focus input:not([type="submit"]) {
border: 0.3215em solid var(--primaryHover);
}
section.largePost {
/*margin: 0 5em;*/
display: flex;
flex-direction: column;
justify-content: space-evenly;
@@ -35,7 +76,7 @@ section.largePost {
padding: 0 5em 1em;
}
section.largePost:first-child {
section.largePost:not(:last-child) {
border-bottom: 5px solid var(--mutedGrey);
}
@@ -67,14 +108,117 @@ section.largePost .outerContent .postContent a {
align-self: flex-end;
}
#main .error {
#main .errorFof, #main .unsubscribe {
display: table;
width: 100%;
height: 100vh;
height: 100dvh;
text-align: center;
}
.fof {
#main .unsubscribe {
height: 50dvh;
}
.centered {
display: table-cell;
vertical-align: middle;
}
section#olderPosts {
/*max-width: 90%;*/
margin: auto;
}
section#olderPosts .carousel {
position: relative;
margin: auto;
}
section#olderPosts .arrow {
position: absolute;
top: 50%;
display: flex;
width: 2.5em;
height: 2.5em;
justify-content: center;
align-items: center;
border-radius: 50%;
z-index: 1;
font-size: 1.625em;
color: white;
background: var(--mutedBlack);
cursor: pointer;
}
section#olderPosts .arrow:hover {
background: var(--grey);
}
section#olderPosts #prev {
left: -4em;
}
section#olderPosts #next {
right: -4em;
}
section#olderPosts .carouselOuter {
display: flex;
flex-direction: row;
align-items: center;
gap: 1em;
overflow: hidden;
position: relative;
margin: auto;
}
section#olderPosts #allCarouselItems {
display: none;
}
section#olderPosts #carouselInner {
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: center;
gap: 1em;
transition: transform 0.5s ease-in-out;
left: 0;
height: 50%;
}
section#olderPosts #carouselInner .cardItem {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
border: 2px solid var(--primaryDefault);
-webkit-border-radius: 0.625rem;
-moz-border-radius: 0.625rem;
border-radius: 0.625rem;
width: 30em;
height: 40rem;
}
section#olderPosts #carouselInner .cardItem img {
width: 100%;
height: 300px;
object-fit: cover;
object-position: left;
-webkit-border-radius: 0.625rem;
-moz-border-radius: 0.625rem;
border-radius: 0.625rem;
color: #FFFFFF;
}
section#olderPosts #carouselInner .cardItem .content {
height: auto;
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
background: #FFFFFF;
margin: 0 2em 1.5em;
padding: 0;
}
+320 -1
View File
@@ -7,5 +7,324 @@
@import "../../css/footer.css";
@import "blogPosts.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%;
}
}
+7 -4
View File
@@ -1,9 +1,12 @@
/* PrismJS 1.29.0
https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers+autolinker+show-language+previewers+toolbar+match-braces+diff-highlight */
code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#272822}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8292a2}.token.punctuation{color:#f8f8f2}.token.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#f92672}.token.boolean,.token.number{color:#ae81ff}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#a6e22e}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#e6db74}.token.keyword{color:#66d9ef}.token.important,.token.regex{color:#fd971f}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+abap+abnf+actionscript+ada+agda+al+antlr4+apacheconf+apex+apl+applescript+aql+arduino+arff+armasm+arturo+asciidoc+aspnet+asm6502+asmatmel+autohotkey+autoit+avisynth+avro-idl+awk+bash+basic+batch+bbcode+bbj+bicep+birb+bison+bnf+bqn+brainfuck+brightscript+bro+bsl+c+csharp+cpp+cfscript+chaiscript+cil+cilkc+cilkcpp+clojure+cmake+cobol+coffeescript+concurnas+csp+cooklang+coq+crystal+css-extras+csv+cue+cypher+d+dart+dataweave+dax+dhall+diff+django+dns-zone-file+docker+dot+ebnf+editorconfig+eiffel+ejs+elixir+elm+etlua+erb+erlang+excel-formula+fsharp+factor+false+firestore-security-rules+flow+fortran+ftl+gml+gap+gcode+gdscript+gedcom+gettext+gherkin+git+glsl+gn+linker-script+go+go-module+gradle+graphql+groovy+haml+handlebars+haskell+haxe+hcl+hlsl+hoon+http+hpkp+hsts+ichigojam+icon+icu-message-format+idris+ignore+inform7+ini+io+j+java+javadoc+javadoclike+javastacktrace+jexl+jolie+jq+jsdoc+js-extras+json+json5+jsonp+jsstacktrace+js-templates+julia+keepalived+keyman+kotlin+kumir+kusto+latex+latte+less+lilypond+liquid+lisp+livescript+llvm+log+lolcode+lua+magma+makefile+markdown+markup-templating+mata+matlab+maxscript+mel+mermaid+metafont+mizar+mongodb+monkey+moonscript+n1ql+n4js+nand2tetris-hdl+naniscript+nasm+neon+nevod+nginx+nim+nix+nsis+objectivec+ocaml+odin+opencl+openqasm+oz+parigp+parser+pascal+pascaligo+psl+pcaxis+peoplecode+perl+php+phpdoc+php-extras+plant-uml+plsql+powerquery+powershell+processing+prolog+promql+properties+protobuf+pug+puppet+pure+purebasic+purescript+python+qsharp+q+qml+qore+r+racket+cshtml+jsx+tsx+reason+regex+rego+renpy+rescript+rest+rip+roboconf+robotframework+ruby+rust+sas+sass+scss+scala+scheme+shell-session+smali+smalltalk+smarty+sml+solidity+solution-file+soy+sparql+splunk-spl+sqf+sql+squirrel+stan+stata+iecst+stylus+supercollider+swift+systemd+t4-templating+t4-cs+t4-vb+tap+tcl+tt2+textile+toml+tremor+turtle+twig+typescript+typoscript+unrealscript+uorazor+uri+v+vala+vbnet+velocity+verilog+vhdl+vim+visual-basic+warpscript+wasm+web-idl+wgsl+wiki+wolfram+wren+xeora+xml-doc+xojo+xquery+yaml+yang+zig&plugins=line-numbers+show-language+inline-color+previewers+unescaped-markup+toolbar+copy-to-clipboard+download-button+match-braces */
code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
pre[class*=language-].line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre[class*=language-].line-numbers>code{position:relative;white-space:inherit}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}
.token a{color:inherit}
div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}
span.inline-color-wrapper{background:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyIDIiPjxwYXRoIGZpbGw9ImdyYXkiIGQ9Ik0wIDBoMnYySDB6Ii8+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0wIDBoMXYxSDB6TTEgMWgxdjFIMXoiLz48L3N2Zz4=);background-position:center;background-size:110%;display:inline-block;height:1.333ch;width:1.333ch;margin:0 .333ch;box-sizing:border-box;border:1px solid #fff;outline:1px solid rgba(0,0,0,.5);overflow:hidden}span.inline-color{display:block;height:120%;width:120%}
.prism-previewer,.prism-previewer:after,.prism-previewer:before{position:absolute;pointer-events:none}.prism-previewer,.prism-previewer:after{left:50%}.prism-previewer{margin-top:-48px;width:32px;height:32px;margin-left:-16px;z-index:10;opacity:0;-webkit-transition:opacity .25s;-o-transition:opacity .25s;transition:opacity .25s}.prism-previewer.flipped{margin-top:0;margin-bottom:-48px}.prism-previewer:after,.prism-previewer:before{content:'';position:absolute;pointer-events:none}.prism-previewer:before{top:-5px;right:-5px;left:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer:after{top:100%;width:0;height:0;margin:5px 0 0 -7px;border:7px solid transparent;border-color:rgba(255,0,0,0);border-top-color:#fff}.prism-previewer.flipped:after{top:auto;bottom:100%;margin-top:0;margin-bottom:5px;border-top-color:rgba(255,0,0,0);border-bottom-color:#fff}.prism-previewer.active{opacity:1}.prism-previewer-angle:before{border-radius:50%;background:#fff}.prism-previewer-angle:after{margin-top:4px}.prism-previewer-angle svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-angle[data-negative] svg{-webkit-transform:scaleX(-1) rotate(-90deg);-moz-transform:scaleX(-1) rotate(-90deg);-ms-transform:scaleX(-1) rotate(-90deg);-o-transform:scaleX(-1) rotate(-90deg);transform:scaleX(-1) rotate(-90deg)}.prism-previewer-angle circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500}.prism-previewer-gradient{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px;width:64px;margin-left:-32px}.prism-previewer-gradient:before{content:none}.prism-previewer-gradient div{position:absolute;top:-5px;left:-5px;right:-5px;bottom:-5px;border-radius:10px;border:5px solid #fff;box-shadow:0 0 3px rgba(0,0,0,.5) inset,0 0 10px rgba(0,0,0,.75)}.prism-previewer-color{background-image:linear-gradient(45deg,#bbb 25%,transparent 25%,transparent 75%,#bbb 75%,#bbb),linear-gradient(45deg,#bbb 25%,#eee 25%,#eee 75%,#bbb 75%,#bbb);background-size:10px 10px;background-position:0 0,5px 5px}.prism-previewer-color:before{background-color:inherit;background-clip:padding-box}.prism-previewer-easing{margin-top:-76px;margin-left:-30px;width:60px;height:60px;background:#333}.prism-previewer-easing.flipped{margin-bottom:-116px}.prism-previewer-easing svg{width:60px;height:60px}.prism-previewer-easing circle{fill:#2d3438;stroke:#fff}.prism-previewer-easing path{fill:none;stroke:#fff;stroke-linecap:round;stroke-width:4}.prism-previewer-easing line{stroke:#fff;stroke-opacity:.5;stroke-width:2}@-webkit-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-o-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@-moz-keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}@keyframes prism-previewer-time{0%{stroke-dasharray:0,500;stroke-dashoffset:0}50%{stroke-dasharray:100,500;stroke-dashoffset:0}100%{stroke-dasharray:0,500;stroke-dashoffset:-100}}.prism-previewer-time:before{border-radius:50%;background:#fff}.prism-previewer-time:after{margin-top:4px}.prism-previewer-time svg{width:32px;height:32px;-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-ms-transform:rotate(-90deg);-o-transform:rotate(-90deg);transform:rotate(-90deg)}.prism-previewer-time circle{fill:transparent;stroke:#2d3438;stroke-opacity:.9;stroke-width:32;stroke-dasharray:0,500;stroke-dashoffset:0;-webkit-animation:prism-previewer-time linear infinite 3s;-moz-animation:prism-previewer-time linear infinite 3s;-o-animation:prism-previewer-time linear infinite 3s;animation:prism-previewer-time linear infinite 3s}
[class*=lang-] script[type='text/plain'],[class*=language-] script[type='text/plain'],script[type='text/plain'][class*=lang-],script[type='text/plain'][class*=language-]{display:block;font:100% Consolas,Monaco,monospace;white-space:pre;overflow:auto}
.token.punctuation.brace-hover,.token.punctuation.brace-selected{outline:solid 1px}.rainbow-braces .token.punctuation.brace-level-1,.rainbow-braces .token.punctuation.brace-level-5,.rainbow-braces .token.punctuation.brace-level-9{color:#e50;opacity:1}.rainbow-braces .token.punctuation.brace-level-10,.rainbow-braces .token.punctuation.brace-level-2,.rainbow-braces .token.punctuation.brace-level-6{color:#0b3;opacity:1}.rainbow-braces .token.punctuation.brace-level-11,.rainbow-braces .token.punctuation.brace-level-3,.rainbow-braces .token.punctuation.brace-level-7{color:#26f;opacity:1}.rainbow-braces .token.punctuation.brace-level-12,.rainbow-braces .token.punctuation.brace-level-4,.rainbow-braces .token.punctuation.brace-level-8{color:#e0e;opacity:1}
pre.diff-highlight>code .token.deleted:not(.prefix),pre>code.diff-highlight .token.deleted:not(.prefix){background-color:rgba(255,0,0,.1);color:inherit;display:block}pre.diff-highlight>code .token.inserted:not(.prefix),pre>code.diff-highlight .token.inserted:not(.prefix){background-color:rgba(0,255,128,.1);color:inherit;display:block}
/*gruvbox light*/
code[class*=language-],pre[class*=language-]{color:#3c3836;font-family:Consolas,Monaco,"Andale Mono",monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{color:#282828;background:#a89984}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{color:#282828;background:#a89984}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f9f5d7}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em}.token.attr-name,.token.attr-value,.token.attr-value .punctuation,.token.cdata,.token.comment,.token.operator,.token.prolog,.token.punctuation{color:#7c6f64}.token.atrule,.token.boolean,.token.constant,.token.delimiter,.token.important,.token.keyword,.token.property,.token.selector,.token.variable{color:#9d0006}.token.builtin,.token.doctype,.token.function,.token.tag,.token.tag .punctuation{color:#b57614}.token.entity,.token.number,.token.symbol{color:#8f3f71}.token.char,.token.string,.token.url{color:#797403}.token.url{text-decoration:underline}.token.regex{background:#797403}.token.bold{font-weight:700}.token.italic{font-style:italic}.token.inserted{background:#7c6f64}.token.deleted{background:#9d0006}
+199
View File
@@ -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

+10
View File
@@ -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

+41 -1
View File
@@ -15,8 +15,12 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="language" content="English">
<meta name="author" content="Rohit Pai">
<link rel="stylesheet" href="/blog/css/prism.css">
<link rel="stylesheet" href="/blog/css/main.css">
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
<script type='text/javascript'
src='https://platform-api.sharethis.com/js/sharethis.js#property=6550cdc47a115e0012964576&product=sop'
async='async'></script>
</head>
<body>
<nav>
@@ -49,12 +53,47 @@
</div>
</header>
<div class="menuBar">
<div class="menu">
<ul>
<li><a href="/blog" class="link active">All posts</a></li>
<li><a href="/blog/category" class="link">categories</a></li>
<li>
<label for="searchField" aria-hidden="true" hidden>search</label>
<input type="search" name="search" id="searchField"
placeholder="Search...">
<button type="submit" id="searchBtn" class="btn btnPrimary"><i
class="fa fa-search"></i></button>
</li>
</ul>
</div>
</div>
<main id="main">
</main>
<div class="modal-container" id="cookiePopup">
<div class="modal">
<div class="modal-content">
<h2><i class="fas fa-cookie-bite"></i> Hey I use cookies btw</h2>
<p>Just to let you know, I use cookies to give you the best experience on my blog. By clicking agree
I'll assume that you are happy with it. <a href="/blog/policy/cookie" class="link">Read more</a>
</p>
<div class="flexRow">
<button class="btn btnPrimary" id="cookieAccept">agree</button>
</div>
</div>
</div>
</div>
<footer class="flexRow">
<div class="spacer"></div>
<div class="nav">
<ul>
<li><a href="/blog/policy/privacy" class="link">privacy policy</a></li>
<li><a href="/blog/policy/cookie" class="link">cookie policy</a></li>
</ul>
</div>
<p>&copy; <span id="year"></span> Rohit Pai all rights reserved</p>
<div class="button">
<button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button>
@@ -62,6 +101,7 @@
</footer>
<script src="/js/typewriter.js"></script>
<script src="/blog/js/prism.js"></script>
<script src="/blog/js/index.js"></script>
<script id="dsq-count-scr" src="https://rohitpaiportfolio.disqus.com/count.js" async></script>
</body>
+811 -114
View File
File diff suppressed because it is too large Load Diff
+5 -3
View File
File diff suppressed because one or more lines are too long
+22
View File
@@ -11,6 +11,28 @@ footer .spacer {
margin-right: auto;
}
footer .nav {
width: 100%;
margin-right: auto;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
footer .nav ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: space-around;
}
footer .nav ul a {
color: #FFFFFF;
}
footer p {
margin: auto;
width: 100%;
+1 -5
View File
@@ -19,7 +19,7 @@ nav {
width: 100%;
transition: background-color 0.4s ease-in;
color: #FFFFFF;
z-index: 1;
z-index: 100000000000000000000000000;
}
nav.scrolled {
@@ -64,10 +64,6 @@ nav ul li span {
visibility: hidden;
}
/*nav ul li a:hover span, nav ul li .active span {*/
/* visibility: visible;*/
/*}*/
nav ul li .active::before,
nav ul li .active::after {
visibility: visible;
+147 -55
View File
@@ -15,9 +15,9 @@
--grey: hsla(0, 0%, 39%, 1);
--notAvailableDefault: hsla(0, 0%, 39%, 1);
--notAvailableHover: hsla(0, 0%, 32%, 1);
--mutedGrey: hsla(0, 0%, 78%, 1);
--mutedGrey: hsla(0, 0%, 75%, 1);
--mutedBlack: hsla(0, 0%, 0%, 0.25);
--mutedGreen: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 0.5);
--mutedGreen: hsla(var(--mainHue), var(--mainSat), calc(var(--mainLight) + 20%), 1);
--navBack: hsla(0, 0%, 30%, 1);
/* Font Sizes */
@@ -63,7 +63,7 @@ h2 {
line-height: 2.1875rem;
}
a.btn, button.btn, form input[type="submit"] {
a.btn, button.btn, form input[type="submit"], div.form input[type="submit"] {
text-decoration: none;
display: inline-flex;
padding: 1em 2em;
@@ -75,11 +75,15 @@ a.btn, button.btn, form input[type="submit"] {
max-height: 4em;
}
form input[type="submit"] {
button.btn {
padding: 1.2em 2.2em;
}
form input[type="submit"], div.form input[type="submit"] {
padding: 1.1em 2em;
}
a.btn:hover, button.btn:hover form input[type="submit"]:hover {
a.btn:hover, button.btn:hover, form input[type="submit"]:hover, div.form input[type="submit"]:hover {
border: 0.3215em solid var(--primaryHover);
}
@@ -87,7 +91,7 @@ a.btn:hover::before, a.btn:hover::after {
visibility: hidden;
}
a.btnPrimary, button.btnPrimary, form input[type="submit"] {
a.btnPrimary, button.btnPrimary, form input[type="submit"], div.form input[type="submit"] {
background-color: var(--primaryDefault);
cursor: pointer;
}
@@ -108,11 +112,12 @@ a.btnPrimary[disabled]:hover, button.btnPrimary[disabled]:hover {
border: 0.3215em solid var(--notAvailableHover);
}
a.btnPrimary:hover, button.btnPrimary:hover form input[type="submit"]:hover {
a.btnPrimary:hover, button.btnPrimary:hover, form input[type="submit"]:hover, div.form input[type="submit"]:hover {
background: var(--primaryHover);
border: 0.3215em solid var(--primaryHover);
}
a.btn:active, button.btn:active, form input[type="submit"]:active {
a.btn:active, button.btn:active, form input[type="submit"]:active, div.form input[type="submit"]:active {
padding: 0.8rem 1.8rem;
}
@@ -128,75 +133,82 @@ a.btn:active, button.btn:active, form input[type="submit"]:active {
text-shadow: 0 6px 4px var(--mutedBlack);
}
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
border: 4px solid var(--errorDefault);
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid,
div.form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
border: 0.3125em solid var(--errorDefault);
}
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
border: 4px solid var(--errorHover);
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
div.form .formControl input:not([type="submit"]).invalid:invalid:focus, div.form .formControl textarea.invalid:invalid:focus {
border: 0.3125em solid var(--errorHover);
box-shadow: 0 4px 2px 0 var(--mutedBlack);
}
form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus {
border: 4px solid var(--primaryHover);
}
form .formControl input:not([type="submit"]) {
form .formControl input:not([type="submit"]),
div.form .formControl input:not([type="submit"]) {
height: 3em;
}
form .formControl {
form .formControl,
div.form .formControl {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
/*align-items: flex-start;*/
}
form .formControl.passwordControl {
form .formControl.passwordControl,
div.form .formControl.passwordControl {
display: block;
}
form input[type="submit"] {
align-self: flex-start;
}
form .formControl input:not([type="submit"]), form .formControl textarea,
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
form .formControl .ck.ck-editor__main .ck-content {
form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]),
div.form .formControl input:not([type="submit"]), form .formControl textarea,
div.form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar,
div.form .formControl .ck.ck-editor__main .ck-content, div.menu input:not([type="submit"]) {
width: 100%;
border: 4px solid var(--primaryDefault);
border: 0.3125em solid var(--primaryDefault);
background: none;
outline: none;
-webkit-border-radius: 1em;
-moz-border-radius: 1em;
-webkit-border-radius: 0.5em;
-moz-border-radius: 0.5em;
border-radius: 0.5em;
padding: 0 0.5em;
}
form .formControl textarea {
form .formControl textarea,
div.form .formControl textarea {
padding: 0.5em;
}
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
border: 4px solid var(--errorDefault);
form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid,
div.form .formControl input:not([type="submit"]).invalid:invalid, form .formControl textarea.invalid:invalid {
border: 0.3125em solid var(--errorDefault);
}
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
border: 4px solid var(--errorHover);
form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus,
div.form .formControl input:not([type="submit"]).invalid:invalid:focus, form .formControl textarea.invalid:invalid:focus {
border: 0.3125em solid var(--errorHover);
box-shadow: 0 4px 2px 0 var(--mutedBlack);
}
form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover {
border: 4px solid var(--primaryHover);
form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover,
div.menu input:not([type="submit"]):focus, div.menu input:not([type="submit"]):hover,
div.form .formControl input:not([type="submit"]):focus, form .formControl textarea:focus,
div.form .formControl input:not([type="submit"]):hover, form .formControl textarea:hover {
border: 0.3125em solid var(--primaryHover);
}
form .formControl input:not([type="submit"]) {
form .formControl input:not([type="submit"]),
div.form .formControl input:not([type="submit"]) {
height: 3em;
}
form .formControl i.fa-eye, form .formControl i.fa-eye-slash {
form .formControl i.fa-eye, form .formControl i.fa-eye-slash,
div.form .formControl i.fa-eye, form .formControl i.fa-eye-slash {
margin-left: -40px;
cursor: pointer;
color: var(--primaryDefault);
@@ -207,7 +219,8 @@ form .formControl input:not([type="submit"]):focus + i.fa-eye-slash {
color: var(--primaryHover);
}
form .formControl .checkContainer {
form .formControl .checkContainer,
div.form .formControl .checkContainer {
display: block;
position: relative;
margin-bottom: 1.25em;
@@ -218,7 +231,8 @@ form .formControl .checkContainer {
user-select: none;
}
form .formControl .checkContainer input {
form .formControl .checkContainer input,
div.form .formControl .checkContainer input {
position: absolute;
opacity: 0;
cursor: pointer;
@@ -226,7 +240,8 @@ form .formControl .checkContainer input {
width: 0;
}
form .formControl .checkContainer .checkmark {
form .formControl .checkContainer .checkmark,
div.form .formControl .checkContainer .checkmark {
position: absolute;
top: 1.25em;
left: 0;
@@ -235,29 +250,35 @@ form .formControl .checkContainer .checkmark {
background-color: var(--mutedGrey);
}
form .formControl .checkContainer:hover input ~ .checkmark {
form .formControl .checkContainer:hover input ~ .checkmark,
div.form .formControl .checkContainer:hover input ~ .checkmark {
background-color: var(--grey);
}
form .formControl .checkContainer input:checked ~ .checkmark {
form .formControl .checkContainer input:checked ~ .checkmark,
div.form .formControl .checkContainer input:checked ~ .checkmark {
background-color: var(--primaryDefault);
}
form .formControl .checkContainer input:checked:hover ~ .checkmark {
form .formControl .checkContainer input:checked:hover ~ .checkmark,
div.form .formControl .checkContainer input:checked:hover ~ .checkmark {
background-color: var(--primaryHover);
}
form .formControl .checkContainer .checkmark:after {
form .formControl .checkContainer .checkmark:after,
div.form .formControl .checkContainer .checkmark:after {
content: "";
position: absolute;
display: none;
}
form .formControl .checkContainer input:checked ~ .checkmark:after {
form .formControl .checkContainer input:checked ~ .checkmark:after,
div.form .formControl .checkContainer input:checked ~ .checkmark:after {
display: block;
}
form .formControl .checkContainer .checkmark:after {
form .formControl .checkContainer .checkmark:after,
div.form .formControl .checkContainer .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
@@ -302,14 +323,24 @@ a {
text-transform: lowercase;
}
a.link {
padding: 0 0.5em;
}
a.link::before,
a.link::after {
visibility: hidden;
visibility: visible;
position: absolute;
margin-top: 1px;
}
nav a.link::before,
nav a.link::after,
.nav a.link::before,
.nav a.link::after {
visibility: hidden;
}
a.link::before {
content: ' <';
margin-left: -0.5em;
@@ -319,15 +350,76 @@ a.link::after {
content: '> ';
}
a.link:hover::before,
a.link:hover::after {
a.link:hover {
font-weight: bold;
}
nav a.link:hover::before,
nav a.link:hover::after,
.nav a.link:hover::before,
.nav a.link:hover::after {
visibility: visible;
}
/*.link span {*/
/* visibility: hidden;*/
/*}*/
nav a.link:hover, .nav a.link:hover {
font-weight: normal;
}
/*.link:hover span {*/
/* visibility: visible;*/
/*}*/
div.error, div.success {
color: #FFFFFF;
padding: 0.5em 0.8em;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
align-self: flex-start;
flex-direction: row-reverse;
position: relative;
height: 75px;
visibility: visible;
overflow: hidden;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
opacity: 1;
margin-top: 1em;
}
div.error {
background: var(--errorDefault);
}
div.success {
background-color: var(--primaryHover);
}
div.error button, div.success button {
border: none;
background: none;
outline: none;
cursor: pointer;
color: #FFFFFF;
font-size: 1.25rem;
margin-top: -5px;
position: absolute;
transform: translate(0, 0);
transform-origin: 0 0;
right: 10px;
top: 10px;
}
div.error.hidden, div.success.hidden {
opacity: 0;
visibility: hidden;
height: 0;
margin: 0;
padding: 0;
}
div.error button:hover, div.success button:hover {
text-shadow: -1px 2px var(--mutedBlack);
}
+4 -4
View File
@@ -45,7 +45,7 @@ main.editor section {
flex-direction: column;
}
section#editPost {
section#curriculumVitae {
display: flex;
}
@@ -234,7 +234,7 @@ section#projects form.projItem:not(.editing) div.formControl.infoContainer texta
}
section#addPost form, section#editPost form {
margin: auto 4rem;
margin: auto 4rem 4rem;
}
form .formControl .ck.ck-editor__top .ck-sticky-panel .ck-toolbar {
@@ -276,6 +276,6 @@ section#editPost table td, th {
min-width: 10rem;
}
section#editPost form {
margin-bottom: 2em;
section#newsletter form {
margin: 0 5em;
}
+14 -63
View File
@@ -66,71 +66,22 @@ div#login input[type=submit]{
margin: 0;
}
div.error, div.success {
color: #FFFFFF;
padding: 0.5em 0.8em;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
display: flex;
justify-content: center;
align-items: center;
align-self: flex-start;
flex-direction: row-reverse;
position: relative;
height: 75px;
visibility: visible;
overflow: hidden;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
opacity: 1;
margin-top: 1em;
}
div.error {
background: var(--errorDefault);
}
div.success {
background-color: var(--primaryHover);
}
div.error button, div.success button {
border: none;
background: none;
outline: none;
cursor: pointer;
color: #FFFFFF;
font-size: 1.25rem;
margin-top: -5px;
position: absolute;
transform: translate(0, 0);
transform-origin: 0 0;
right: 10px;
top: 10px;
}
div.error.hidden, div.success.hidden {
opacity: 0;
visibility: hidden;
height: 0;
margin: 0;
padding: 0;
}
div.error button:hover, div.success button:hover {
text-shadow: -1px 2px var(--mutedBlack);
}
div.btnContainer {
width: 100%;
width: 60%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
flex-direction: column;
justify-content: center;
align-items: stretch;
gap: 1em;
}
div.btnContainer a {
justify-content: center;
text-transform: unset;
}
form div.btnContainer input[type="submit"] {
align-self: stretch;
}
div.btnContainer a:not(.btn) {
+1 -1
View File
@@ -61,7 +61,7 @@ nav.sideNav ul li.dropdown ul {
nav.sideNav ul li.dropdown ul.active {
transition: max-height ease-in 400ms;
max-height: 15rem;
max-height: 20rem;
}
nav.sideNav ul li.dropdown ul li {
+34 -9
View File
@@ -7,7 +7,6 @@
<script src="https://kit.fontawesome.com/ed3c25598e.js" crossorigin="anonymous"></script>
<script src="js/CKEditor/ckeditor.js"></script>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<nav class="sideNav">
@@ -40,6 +39,11 @@
Edit Blog Post
</a>
</li>
<li>
<a href="#" id="goToNewsletter" class="link">
Send Newsletter
</a>
</li>
</ul>
</li>
<li><a href="#" id="logout">Logout</a></li>
@@ -200,10 +204,7 @@
</div>
<div class="formControl">
<label for="postCategories">Categories</label>
<input type="text" name="postCategories" id="postCategories"
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
oninput="this.setCustomValidity('')" required>
<input type="text" name="postCategories" id="postCategories" title="CSV format" required>
</div>
<div class="formControl">
@@ -264,10 +265,7 @@
</div>
<div class="formControl">
<label for="editPostCategories">Categories</label>
<input type="text" name="editPostCategories" id="editPostCategories"
pattern='[a-zA-Z0-9 ]+, |\w+' title="CSV format"
oninvalid="this.setCustomValidity('This field takes a CSV like format')"
oninput="this.setCustomValidity('')" required>
<input type="text" name="editPostCategories" id="editPostCategories" title="CSV format" required>
</div>
<div class="formControl">
@@ -294,6 +292,33 @@
</form>
</section>
<section id="newsletter">
<h2>newsletter</h2>
<form action="" id="sendNewsletterForm" method="POST">
<div class="formControl">
<label for="newsletterSubject">Subject</label>
<input type="text" id="newsletterSubject" name="newsletterSubject" required>
</div>
<div class="formControl">
<label for="CKEditorNewsletter">Message</label>
<div id="CKEditorNewsletter">
</div>
</div>
<div class="error hidden" id="newsletterError">
<button class="close" type="button">&times;</button>
<div></div>
</div>
<div class="success hidden" id="newsletterSuccess">
<button class="close" type="button">&times;</button>
<div></div>
</div>
<input type="submit" class="btn btnPrimary boxShadowIn boxShadowOut" value="Send newsletter">
</form>
</section>
</main>
<script src="js/editor.js"></script>
+9 -5
View File
@@ -32,8 +32,9 @@
<div class="btnContainer">
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
<a href="#" id="resetPwd">Reset Password</a>
<a href="/api/user/login" class="btn btnPrimary boxShadowIn boxShadowOut">Login with Jump Cloud</a>
<button type="button" id="resetPwd" class="btn btnPrimary boxShadowIn boxShadowOut">Reset Password
</button>
</div>
</form>
</div>
@@ -54,7 +55,8 @@
<div class="btnContainer">
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
<a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="loginBtn">Login</a>
<button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="loginBtn">Login
</button>
</div>
</form>
</div>
@@ -75,7 +77,9 @@
<div class="btnContainer">
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
<a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="resendEmail">Resend Email</a>
<button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut" id="resendEmail">Resend
Email
</button>
</div>
</form>
</div>
@@ -102,7 +106,7 @@
<div class="btnContainer">
<input type="submit" value="Submit" class="btn btnPrimary boxShadowIn boxShadowOut">
<a href="#" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut">Login</a>
<button type="button" class="loginBtn btn btnPrimary boxShadowIn boxShadowOut">Login</button>
</div>
</form>
</div>
+2 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+140 -15
View File
@@ -2,6 +2,7 @@ let dateOptions = {month: 'short', year: 'numeric'};
let textareaLoaded = false;
let editors = {};
let posts = null;
const smallPaddingElements = ['figcaption', 'li'];
document.addEventListener('DOMContentLoaded', () =>
{
// check if the userData is logged in, if not redirect to log in
@@ -69,7 +70,7 @@ document.addEventListener('DOMContentLoaded', () =>
}));
// CKEditor stuff
createEditors("CKEditorAddPost", "CKEditorEditPost");
createEditors('CKEditorAddPost', 'CKEditorEditPost', 'CKEditorNewsletter');
});
@@ -262,8 +263,9 @@ document.querySelector("#addPostForm").addEventListener("submit", e =>
data.append("featured", document.querySelector("#isFeatured").checked ? "1" : "0");
data.append("abstract", document.querySelector("#postAbstract").value);
data.append("body", editors["CKEditorAddPost"].getData());
data.append('bodyText', viewToPlainText(editors['CKEditorAddPost'].editing.view.document.getRoot()));
data.append("dateCreated", new Date().toISOString().slice(0, 19).replace('T', ' '));
data.append("categories", document.querySelector("#postCategories").value);
data.append('categories', document.querySelector('#postCategories').value.toLowerCase());
data.append("headerImg", document.querySelector("#headerImg").files[0]);
fetch("/api/blog/post", {
@@ -315,8 +317,9 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
data["featured"] = document.querySelector("#editIsFeatured").checked ? "1" : "0";
data["abstract"] = document.querySelector("#editPostAbstract").value;
data["body"] = editors["CKEditorEditPost"].getData();
data['bodyText'] = viewToPlainText(editors['CKEditorEditPost'].editing.view.document.getRoot());
data["dateModified"] = new Date().toISOString().slice(0, 19).replace('T', ' ');
data["categories"] = document.querySelector("#editPostCategories").value;
data['categories'] = document.querySelector('#editPostCategories').value.toLowerCase();
let imgData = new FormData();
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 input[type='submit']").id = "";
console.log();
editors["CKEditorEditPost"].setData("");
showSuccessMessage("Post edited successfully", "editPost");
return;
@@ -375,11 +379,44 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
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", () =>
{
textareaLoaded = false;
@@ -418,6 +455,13 @@ document.querySelector("#goToEditPost").addEventListener("click", () =>
document.querySelector("#blog").classList.add("active");
});
document.querySelector('#goToNewsletter').addEventListener('click', () =>
{
textareaLoaded = false;
addActiveClass('goToNewsletter');
goToPage('newsletter');
});
document.querySelector("#logout").addEventListener("click", () =>
{
fetch("/api/user/logout").then(res =>
@@ -468,6 +512,62 @@ function goToPage(id)
}
/**
* Converts th CKEditor data to plain text
* @param viewItem - The CKEditor data
* @returns {string} - The plain text
*/
function viewToPlainText(viewItem)
{
let text = '';
if (viewItem.is('$text') || viewItem.is('$textProxy'))
{
// If item is `Text` or `TextProxy` simple take its text data.
text = viewItem.data;
}
// else if (viewItem.is('element', 'img') && viewItem.hasAttribute('alt'))
// {
// // Special case for images - use alt attribute if it is provided.
// text = viewItem.getAttribute('alt');
// }
else if (viewItem.is('element', 'br'))
{
// A soft break should be converted into a single line break (#8045).
text = '\n';
}
else
{
// Other elements are document fragments, attribute elements or container elements.
// They don't have their own text value, so convert their children.
let prev = null;
for (const child of viewItem.getChildren())
{
const childText = viewToPlainText(child);
// Separate container element children with one or more new-line characters.
if (prev && (prev.is('containerElement') || child.is('containerElement')))
{
if (smallPaddingElements.includes(prev.name) || smallPaddingElements.includes(child.name))
{
text += '\n';
}
else
{
text += '\n\n';
}
}
text += childText;
prev = child;
}
}
return text;
}
/**
* Removes the active class from all nav items and adds it to the one with the given id
* @param {string} id - The id to add the active class to
@@ -758,6 +858,31 @@ function createEditors(...ids)
{language: 'zephir', label: 'Zephir'},
],
},
mediaEmbed: {
previewsInData: true,
providers: [
{
name: 'youtube',
url: [
/^(?:m\.)?youtube\.com\/watch\?v=([\w-]+)(?:&t=(\d+))?/,
/^(?:m\.)?youtube\.com\/v\/([\w-]+)(?:\?t=(\d+))?/,
/^youtube\.com\/embed\/([\w-]+)(?:\?start=(\d+))?/,
/^youtu\.be\/([\w-]+)(?:\?t=(\d+))?/,
],
html: match =>
{
const id = match[1];
const time = match[2];
return (
`<iframe width="560" height="315" style="text-align:center;" src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` +
'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>' +
'</iframe>'
);
},
},
],
},
}).then(CKEditor =>
{
editors[id] = CKEditor;
@@ -768,7 +893,7 @@ function createEditors(...ids)
console.warn('Build id: 1eo8ioyje2om-vgar4aghypdm');
console.error(error);
});
})
});
}
/**
@@ -1097,14 +1222,14 @@ function updateProjectItem(id, e)
{
e.preventDefault();
let data = {}
data["title"] = document.querySelector(`#title${id}`).value;
data["isMainProject"] = document.querySelector(`#isMainProject${id}`).checked ? "true" : "false";
data["information"] = document.querySelector(`#info${id}`).value;
data["projectLink"] = document.querySelector(`#viewProj${id}`).value;
data["gitLink"] = document.querySelector(`#git${id}`).value;
data['title'] = document.querySelector(`#title${id}proj`).value;
data['isMainProject'] = document.querySelector(`#isMainProject${id}proj`).checked ? 'true' : 'false';
data['information'] = document.querySelector(`#info${id}proj`).value;
data['projectLink'] = document.querySelector(`#viewProj${id}proj`).value;
data['gitLink'] = document.querySelector(`#git${id}proj`).value;
let imgData = new FormData();
imgData.append("img", document.querySelector(`#img${id}`).files[0]);
imgData.append('img', document.querySelector(`#img${id}proj`).files[0]);
fetch("/api/projectData/" + id, {
method: "PATCH",
@@ -1129,8 +1254,8 @@ function updateProjectItem(id, e)
}
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
document.querySelector(`#title${id}`).setAttribute("disabled", "");
document.querySelector(`#info${id}`).setAttribute("disabled", "");
document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
return;
}
console.log("updating image")
@@ -1168,8 +1293,8 @@ function updateProjectItem(id, e)
}
document.querySelector(`#projectItem${id}`).classList.toggle("editing");
document.querySelector(`#title${id}`).setAttribute("disabled", "");
document.querySelector(`#info${id}`).setAttribute("disabled", "");
document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
document.querySelector(`#projectImage${id}`).src = updatedProjectImage.imgLocation;
return;
}
+7 -1
View File
@@ -63,7 +63,13 @@ document.querySelector("#login form").addEventListener("submit", e =>
showErrorMessage("Please type in a username and password.", "login");
return;
}
showErrorMessage("Invalid username or password.", "login");
if (res.status === 401)
{
showErrorMessage('Invalid username or password.', 'login');
return;
}
showErrorMessage(json.error, 'login');
}));
return;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 87 KiB

+8 -4
View File
@@ -32,6 +32,7 @@
<li><a href="/blog" class="textShadow link">blog</a></li>
</ul>
</nav>
<header>
<div>
<h1>full stack developer</h1>
@@ -44,11 +45,13 @@
<section id="about">
<h1>about</h1>
<div>
<p>Hi, I'm Rohit, a computer science student at The University of Nottingham with experience in multiple
<p>Hi, I'm Rohit, a Full Stack Developer at Cadonix with experience in multiple
programming languages such as Java, C#, Python, HTML, CSS, JS, PHP. Bringing forth a motivated
attitude and a variety of powerful skills. Very good at bringing a team together to get a project
finished. Below are some of my projects that I have worked on. </p>
<a href="other/rohitpaicv.pdf" class="btn btnOutline boxShadowIn boxShadowOut" download>Download CV</a>
<a href="https://cloud.rohitpai.co.uk/index.php/s/e4LkFiE3wMK8x3C"
class="btn btnOutline boxShadowIn boxShadowOut" download>Download
CV</a>
</div>
</section>
@@ -106,7 +109,7 @@
</a>
</div>
<div>
<a href="#">
<a href="https://rohitpai.co.uk/blog">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M15.5 14.625c0 .484-.387.875-.864.875h-5.273c-.477 0-.863-.392-.863-.875s.387-.875.863-.875h5.272c.478 0 .865.391.865.875zm-6.191-4.375h2.466c.448 0 .809-.392.809-.875s-.361-.875-.81-.875h-2.465c-.447 0-.809.392-.809.875s.362.875.809.875zm14.691 1.75c0 6.627-5.373 12-12 12s-12-5.373-12-12 5.373-12 12-12 12 5.373 12 12zm-5-1.039c0-.383-.311-.692-.691-.692h-1.138c-.583 0-.69-.446-.69-.996-.001-2.36-1.91-4.273-4.265-4.273h-2.952c-2.355 0-4.264 1.913-4.264 4.272v5.455c0 2.36 1.909 4.273 4.264 4.273h5.474c2.353 0 4.262-1.913 4.262-4.272v-3.767z"/>
</svg>
@@ -171,6 +174,7 @@
</form>
</div>
</section>
</main>
<footer class="flexRow">
<div class="spacer"></div>
@@ -179,7 +183,7 @@
<button id="goBackToTop"><i class="fa-solid fa-chevron-up"></i></button>
</div>
</footer>
</main>
<script src="js/index.js"></script>
<script src="js/typewriter.js"></script>
</body>
Binary file not shown.