Compare commits

...

4 Commits

Author SHA1 Message Date
a697ea2ac8 Merge pull request 'categories-page' (#44) from categories-page into master
All checks were successful
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 16s
Reviewed-on: #44
2023-10-24 15:54:20 +01:00
e106f89dcb All categories page created with flex
Some checks reported warnings
🚀 Deploy website on push / 🎉 Deploy (push) Has been cancelled
2023-10-21 23:26:00 +01:00
da791c8866 Started work on all categories page
Some checks reported warnings
🚀 Deploy website on push / 🎉 Deploy (push) Has been cancelled
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-10-21 09:06:03 +01:00
a0567a25f5 Created individual categories page
Signed-off-by: rodude123 <rodude123@gmail.com>
2023-10-18 23:58:21 +01:00
28 changed files with 410 additions and 280 deletions

View File

@ -1,5 +1,7 @@
<?php
namespace api\blog;
use api\utils\imgUtils;
use DOMDocument;
use PDO;
@ -125,7 +127,7 @@ class blogData
public function getCategories(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT categories FROM blog;");
$stmt = $conn->prepare("SELECT DISTINCT categories FROM blog;");
$stmt->execute();
// set the resulting array to associative
@ -430,17 +432,15 @@ class blogData
/**
* Get all posts with the given category
* @param mixed $category - Category of the post
* @param string $category - Category of the post
* @return array - Array of all posts with the given category or error message
*/
public function getPostsByCategory(mixed $category)
public function getPostsByCategory(string $category): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog WHERE categories = :category;");
$stmt = $conn->prepare("SELECT * FROM blog WHERE LOCATE(:category, categories) > 0;");
$stmt->bindParam(":category", $category);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}

View File

@ -12,6 +12,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
@ -134,7 +135,7 @@ class blogRoutes implements routesInterface
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")));
@ -218,7 +219,7 @@ class blogRoutes implements routesInterface
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")));

View File

@ -1,5 +1,7 @@
<?php
namespace api\project;
use api\utils\imgUtils;
use PDO;
use Psr\Http\Message\UploadedFileInterface;

View File

@ -1,4 +1,5 @@
<?php
namespace api\project;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "projectData.php";

View File

@ -1,4 +1,5 @@
<?php
namespace api\timeline;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "timelineData.php";

View File

@ -1,5 +1,7 @@
<?php
namespace api\user;
use Firebase\JWT\JWT;
use PDO;

View File

@ -1,4 +1,5 @@
<?php
namespace api\user;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "userData.php";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,7 @@
<?php
namespace api\blog;
use api\utils\imgUtils;
use DOMDocument;
use PDO;
@ -91,7 +93,8 @@ class blogData
$result = $stmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
if ($result)
{
return $result;
}
@ -113,7 +116,8 @@ class blogData
// set the resulting array to associative
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
if ($result) {
if ($result)
{
return $result;
}
@ -123,7 +127,7 @@ class blogData
public function getCategories(): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT categories FROM blog;");
$stmt = $conn->prepare("SELECT DISTINCT categories FROM blog;");
$stmt->execute();
// set the resulting array to associative
@ -428,17 +432,15 @@ class blogData
/**
* Get all posts with the given category
* @param mixed $category - Category of the post
* @param string $category - Category of the post
* @return array - Array of all posts with the given category or error message
*/
public function getPostsByCategory(mixed $category)
public function getPostsByCategory(string $category): array
{
$conn = dbConn();
$stmt = $conn->prepare("SELECT * FROM blog WHERE categories = :category;");
$stmt = $conn->prepare("SELECT * FROM blog WHERE LOCATE(:category, categories) > 0;");
$stmt->bindParam(":category", $category);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}

View File

@ -12,6 +12,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 +30,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 +43,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 +78,15 @@ 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)
{
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 +95,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);
}
@ -123,7 +135,8 @@ class blogRoutes implements routesInterface
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);
@ -206,7 +219,8 @@ class blogRoutes implements routesInterface
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);

View File

@ -1,5 +1,7 @@
<?php
namespace api\project;
use api\utils\imgUtils;
use PDO;
use Psr\Http\Message\UploadedFileInterface;

View File

@ -1,4 +1,5 @@
<?php
namespace api\project;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "projectData.php";

View File

@ -1,4 +1,5 @@
<?php
namespace api\timeline;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "timelineData.php";

View File

@ -1,5 +1,7 @@
<?php
namespace api\user;
use Firebase\JWT\JWT;
use PDO;

View File

@ -1,4 +1,5 @@
<?php
namespace api\user;
require_once __DIR__ . "/../utils/routesInterface.php";
require_once "userData.php";

21
src/blog/css/category.css Normal file
View File

@ -0,0 +1,21 @@
main > h1 {
padding-left: 3em;
}
section.catPosts .largePost {
margin-bottom: 3em;
}
section.categories {
display: flex
flex-direction row;
justify-content: center;
align-items;
flex-wrap: wrap;
}
section.categories .btnContainer {
flex-basis: 33.3333333%
}

View File

@ -35,7 +35,7 @@ section.largePost {
padding: 0 5em 1em;
}
section.largePost:first-child {
section.largePost:not(:last-child) {
border-bottom: 5px solid var(--mutedGrey);
}

View File

@ -7,5 +7,6 @@
@import "../../css/footer.css";
@import "blogPosts.css";
@import "home.css";
@import "category.css";
@import "prism.css";

View File

@ -11,12 +11,16 @@ window.addEventListener('popstate', _ =>
goToURL(window.history.state);
});
window.onscroll = () => {
window.onscroll = () =>
{
// check if scrolled past limit if so add scrolled class to change background of nav
if (document.body.scrollTop >= scrollLimit || document.documentElement.scrollTop >= scrollLimit) {
document.querySelector("nav").classList.add("scrolled");
} else {
document.querySelector("nav").classList.remove("scrolled");
if (document.body.scrollTop >= scrollLimit || document.documentElement.scrollTop >= scrollLimit)
{
document.querySelector('nav').classList.add('scrolled');
}
else
{
document.querySelector('nav').classList.remove('scrolled');
}
};
@ -29,7 +33,7 @@ function goToURL(url)
// Get the current URL and split it into an array
let urlArray = url.split('/');
if (url === "/blog/" || url === "/blog")
if (url === '/blog/' || url === '/blog')
{
loadHomeContent();
// window.history.pushState(null, null, url);
@ -45,10 +49,12 @@ function goToURL(url)
return;
}
if (urlArray[2] === 'category') {
if (urlArray[2] === 'category')
{
// Create a new URL with the dynamic part
// window.history.pushState(null, null, url);
if (urlArray[3]) {
if (urlArray[3])
{
loadPostsByCategory(urlArray[urlArray.length - 1]);
return;
}
@ -66,27 +72,33 @@ function goToURL(url)
* @param post the post object
* @returns {HTMLDivElement} the outer content of the post
*/
function createLargePost(post) {
let outerContent = document.createElement("div");
outerContent.classList.add("outerContent");
let img = document.createElement("img");
img.className = "banner";
function createLargePost(post)
{
let outerContent = document.createElement('div');
outerContent.classList.add('outerContent');
let img = document.createElement('img');
img.className = 'banner';
img.src = post.headerImg;
img.alt = post.title;
outerContent.appendChild(img);
let content = document.createElement("div");
content.classList.add("content");
let postContent = document.createElement("div");
postContent.classList.add("postContent");
let categories = "";
post.categories.split(", ").forEach(category => {
categories += `<a href="/blog/category/${category}" class="link">${category}</a>`
if (post.categories.split(", ").length > 1) {
categories += ", ";
let content = document.createElement('div');
content.classList.add('content');
let postContent = document.createElement('div');
postContent.classList.add('postContent');
let categories = '';
post.categories.split(', ').forEach(category =>
{
categories += `<a href="/blog/category/${category}" class="link">${category}</a>`;
if (post.categories.split(', ').length > 1)
{
categories += ', ';
}
});
window.categories = categories;
if (categories.endsWith(', '))
{
categories = categories.substring(0, categories.length - 2);
}
postContent.innerHTML = `
<h2>${post.title}</h2>
@ -102,43 +114,49 @@ function createLargePost(post) {
/**
* Loads the home content
*/
function loadHomeContent() {
fetch("/api/blog/post").then(res => res.json().then(json => {
for (let i = 0; i < json.length; i++) {
if (json[i].featured === 1) {
let featuredPost = document.createElement("section");
featuredPost.classList.add("largePost");
featuredPost.id = "featuredPost";
let h1 = document.createElement("h1");
h1.innerHTML = "featured post";
function loadHomeContent()
{
fetch('/api/blog/post').then(res => res.json().then(json =>
{
for (let i = 0; i < json.length; i++)
{
if (json[i].featured === 1)
{
let featuredPost = document.createElement('section');
featuredPost.classList.add('largePost');
featuredPost.id = 'featuredPost';
let h1 = document.createElement('h1');
h1.innerHTML = 'featured post';
featuredPost.appendChild(h1);
let outerContent = createLargePost(json[i]);
featuredPost.appendChild(outerContent);
document.querySelector("#main").prepend(featuredPost);
document.querySelector('#main').prepend(featuredPost);
}
if (i === 0) {
let latestPost = document.createElement("section");
latestPost.classList.add("largePost");
latestPost.id = "latestPost";
let h1 = document.createElement("h1");
h1.innerHTML = "latest post";
if (i === 0)
{
let latestPost = document.createElement('section');
latestPost.classList.add('largePost');
latestPost.id = 'latestPost';
let h1 = document.createElement('h1');
h1.innerHTML = 'latest post';
latestPost.appendChild(h1);
let outerContent = createLargePost(json[i]);
latestPost.appendChild(outerContent);
document.querySelector("#main").prepend(latestPost);
document.querySelector('#main').prepend(latestPost);
}
}
}))
}));
}
/**
* Gets the latest and featured posts
* @returns {Promise<any[]>} the latest and featured posts
*/
async function getLatestAndFeaturedPosts() {
let latestPost = await fetch("/api/blog/post/latest").then(res => res.json());
let featuredPost = await fetch("/api/blog/post/featured").then(res => res.json());
async function getLatestAndFeaturedPosts()
{
let latestPost = await fetch('/api/blog/post/latest').then(res => res.json());
let featuredPost = await fetch('/api/blog/post/featured').then(res => res.json());
return [latestPost, featuredPost];
}
@ -147,25 +165,38 @@ async function getLatestAndFeaturedPosts() {
* @param text the csv text
* @returns {string[]} the array
*/
function csvToArray(text) {
function csvToArray(text)
{
let p = '';
let arr = [''];
let i = 0;
let s = true;
let l = null;
for (l of text) {
if ('"' === l) {
if (s && l === p) {
for (l of text)
{
if ('"' === l)
{
if (s && l === p)
{
arr[i] += l;
}
s = !s;
} else if (',' === l && s) {
}
else if (',' === l && s)
{
l = arr[++i] = '';
} else if ('\n' === l && s) {
if ('\r' === p) row[i] = row[i].slice(0, -1);
}
else if ('\n' === l && s)
{
if ('\r' === p)
{
row[i] = row[i].slice(0, -1);
}
arr = arr[++r] = [l = ''];
i = 0;
} else {
}
else
{
arr[i] += l;
}
p = l;
@ -177,31 +208,32 @@ function csvToArray(text) {
* Gets the categories
* @returns {Promise<*[]>} the categories
*/
async function getCategories() {
let categories = await fetch("/api/blog/categories").then(res => res.json());
async function getCategories()
{
let categories = await fetch('/api/blog/categories').then(res => res.json());
let modifiedCategories = [];
categories.forEach(category => modifiedCategories.push(csvToArray(category.categories)));
return modifiedCategories;
categories.forEach(category => modifiedCategories = modifiedCategories.concat(csvToArray(category.categories.replace(/\s*,\s*/g, ','))));
return [...new Set(modifiedCategories)];
}
/**
* Creates the categories
* @param {*[]} categoriesList the categories
*/
function createCategories(categoriesList) {
let categories = "";
categoriesList.forEach(lst => lst.forEach(category => categories += `<a href="/blog/category/${category}" class="link">${category}</a>`));
function createCategories(categoriesList)
{
let categories = '';
categoriesList.forEach(category => categories += `<a href="/blog/category/${category}" class="link">${category}</a>`);
return categories;
}
/**
* Creates the button categories
* @param {string[][]} categoriesList - the categories
*/
function createButtonCategories(categoriesList) {
let categories = "";
function createButtonCategories(categoriesList)
{
let categories = '';
categoriesList.forEach(lst => lst.forEach(category => categories += `<a href="/blog/category/${category}" class="btn btnOutline">${category}</a>`));
return categories;
}
@ -210,7 +242,8 @@ function createButtonCategories(categoriesList) {
* Creates the side content
* @returns {HTMLElement} the aside element
*/
async function createSideContent() {
async function createSideContent()
{
let posts = await getLatestAndFeaturedPosts();
let latestPost = posts[0];
@ -218,8 +251,8 @@ async function createSideContent() {
let categoriesList = await getCategories();
let categories = createCategories(categoriesList);
let sideContent = document.createElement("aside");
sideContent.classList.add("sideContent");
let sideContent = document.createElement('aside');
sideContent.classList.add('sideContent');
sideContent.innerHTML = `
<div class="authorInfo">
<div class="picture">
@ -283,33 +316,37 @@ async function createSideContent() {
* Trys to load the individual post if not runs the 404 function
* @param title
*/
async function loadIndividualPost(title) {
document.title = "Rohit Pai - " + decodeURI(title);
await fetch(`/api/blog/post/${title}`).then(async res => {
if (!res.ok) {
async function loadIndividualPost(title)
{
document.title = 'Rohit Pai - ' + decodeURI(title);
await fetch(`/api/blog/post/${title}`).then(async res =>
{
if (!res.ok)
{
show404();
return;
}
await res.json().then(async json => {
await res.json().then(async json =>
{
// create the post
let post = document.createElement("section");
post.classList.add("post");
post.id = "individualPost";
let mainContent = document.createElement("div");
mainContent.classList.add("mainContent");
let article = document.createElement("article");
let post = document.createElement('section');
post.classList.add('post');
post.id = 'individualPost';
let mainContent = document.createElement('div');
mainContent.classList.add('mainContent');
let article = document.createElement('article');
article.innerHTML = `
<h1>${json.title}</h1>
<div class="byLine">
<h3>Last updated: ${json.dateModified}</h3>
<h3>${createButtonCategories([csvToArray(json.categories)])}</h3>
<h3>${createButtonCategories([csvToArray(json.categories.replace(/\s*,\s*/g, ','))])}</h3>
</div>
<div class="cover" style="background-image: url('${json.headerImg}')"></div>
${json.body}
`;
let comments = document.createElement("section");
comments.classList.add("comments");
let comments = document.createElement('section');
comments.classList.add('comments');
comments.innerHTML = `<h2>Comments</h2>
<div id="disqus_thread"></div>
`;
@ -320,14 +357,16 @@ async function loadIndividualPost(title) {
post.appendChild(mainContent);
post.appendChild(sideContent);
document.querySelector("#main").appendChild(post);
document.querySelector('#main').appendChild(post);
let disqus_config = _ => {
let disqus_config = _ =>
{
this.page.url = window.location.href; // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = window.location.href.substring(window.location.href.lastIndexOf("/") + 1); // Replace PAGE_IDENTIFIER with your page's unique identifier variable
this.page.identifier = window.location.href.substring(window.location.href.lastIndexOf('/') + 1); // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};
(function () { // DON'T EDIT BELOW THIS LINE
(function ()
{ // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://rohitpaiportfolio.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
@ -337,36 +376,74 @@ async function loadIndividualPost(title) {
});
}
function loadAllCategories() {
/**
* Loads all the categories
*/
async function loadAllCategories()
{
document.title = 'Rohit Pai - Categories';
let categoriesList = await getCategories();
let main = document.querySelector('#main');
let categories = document.createElement('section');
categories.classList.add('categories');
categories.id = 'allCategories';
let h1 = document.createElement('h1');
h1.innerHTML = 'Categories';
main.appendChild(h1);
for (let category of categoriesList)
{
let btnContainer = document.createElement('div');
btnContainer.classList.add('btnContainer');
let btn = document.createElement('a');
btn.classList.add('btn');
btn.classList.add('btnPrimary');
btn.innerHTML = category;
btn.href = `/blog/category/${category}`;
btnContainer.appendChild(btn);
categories.appendChild(btnContainer);
}
main.appendChild(categories);
}
/**
* Loads the posts by category
* @param category the category
*/
function loadPostsByCategory(category) {
document.title = "Rohit Pai - " + decodeURI(category);
fetch(`/api/blog/post/category/${category}`).then(res => res.json().then(json => {
let posts = document.createElement("section");
posts.classList.add("posts");
posts.id = "postsByCategory";
let h1 = document.createElement("h1");
h1.innerHTML = category;
posts.appendChild(h1);
for (let i = 0; i < json.length; i++) {
let outerContent = createLargePost(json[i]);
posts.appendChild(outerContent);
function loadPostsByCategory(category)
{
document.title = 'Rohit Pai - ' + decodeURI(category);
fetch(`/api/blog/categories/${category}`).then(res => res.json().then(json =>
{
let main = document.querySelector('#main');
let posts = document.createElement('section');
posts.classList.add('catPosts');
posts.id = 'postsByCategory';
let h1 = document.createElement('h1');
h1.innerHTML = decodeURI(category);
main.appendChild(h1);
for (let i = 0; i < json.length; i++)
{
let largePost = document.createElement('section');
largePost.classList.add('largePost');
if (i < json.length - 1)
{
largePost.classList.add('categoryPost');
}
document.querySelector("#main").appendChild(posts);
largePost.id = `lp-${i + 1}`;
let outerContent = createLargePost(json[i]);
largePost.appendChild(outerContent);
posts.appendChild(largePost);
}
main.appendChild(posts);
}));
}
/**
* Shows the 404 page
*/
function show404() {
document.querySelector("#main").innerHTML = `
function show404()
{
document.querySelector('#main').innerHTML = `
<div class="error">
<div class="fof">
<h1>Blog post, Category or page not found</h1>

View File

@ -263,7 +263,7 @@ document.querySelector("#addPostForm").addEventListener("submit", e =>
data.append("abstract", document.querySelector("#postAbstract").value);
data.append("body", editors["CKEditorAddPost"].getData());
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", {
@ -316,7 +316,7 @@ document.querySelector("#editPostForm").addEventListener("submit", e =>
data["abstract"] = document.querySelector("#editPostAbstract").value;
data["body"] = editors["CKEditorEditPost"].getData();
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]);