Merge pull request 'older-blog-posts' (#56) from older-blog-posts into master
All checks were successful
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 18s

Reviewed-on: #56
This commit is contained in:
Rohit Pai 2024-11-04 22:30:01 +00:00
commit c2e01dd1e8
13 changed files with 2282 additions and 944 deletions

View File

@ -673,9 +673,17 @@ EOD;
*/ */
public function changeHTMLSrc(string $body, string $to, string $from): string public function changeHTMLSrc(string $body, string $to, string $from): string
{ {
// $body = preg_replace_callback('/>([^<]+)</', function($matches) {
// // Convert special characters to HTML entities
// return '>' . htmlentities(trim($matches[1]), ENT_QUOTES | ENT_HTML5, 'UTF-8') . '<';
// }, $body);
$htmlDoc = new DOMDocument(); $htmlDoc = new DOMDocument();
$body = mb_convert_encoding($body, "HTML-ENTITIES", "UTF-8");
$htmlDoc->loadHTML($body, LIBXML_NOERROR); // Load the raw HTML content into DOMDocument
@$htmlDoc->loadHTML($body, LIBXML_NOERROR);
// Get the body and process images
$doc = $htmlDoc->getElementsByTagName('body')->item(0); $doc = $htmlDoc->getElementsByTagName('body')->item(0);
$imgs = $doc->getElementsByTagName('img'); $imgs = $doc->getElementsByTagName('img');
@ -688,9 +696,11 @@ EOD;
$srcList[] = $src; $srcList[] = $src;
$fileName = basename($src); $fileName = basename($src);
// Update the src attribute to the new location
$img->setAttribute("src", substr($to, 2) . $fileName); $img->setAttribute("src", substr($to, 2) . $fileName);
} }
// Rename files and clean up old ones
$files = scandir($from); $files = scandir($from);
foreach ($files as $file) foreach ($files as $file)
{ {
@ -706,15 +716,36 @@ EOD;
} }
} }
// Process the HTML content for output
$newBody = ''; $newBody = '';
foreach ($doc->childNodes as $node) foreach ($doc->childNodes as $node)
{ {
$newHTML = $htmlDoc->saveHTML($node); // Only convert text nodes to HTML entities
$newBody .= mb_convert_encoding($newHTML, "UTF-8", mb_detect_encoding($newHTML)); if ($node->nodeType === XML_TEXT_NODE)
{
$newBody .= $this->convertToHtmlEntities($node->nodeValue); // Convert text nodes
} }
else
{
$newBody .= $htmlDoc->saveHTML($node); // Keep HTML tags intact
}
}
return $newBody; return $newBody;
} }
/**
* Convert all characters in a string to HTML entities while leaving HTML tags intact.
* @param string $text - The text to convert
* @return string - The converted text with HTML entities
*/
private function convertToHtmlEntities(string $text): string
{
// Convert characters to HTML entities using mb_encode_numericentity
return htmlentities($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
/** /**
* Get all posts with the given category * Get all posts with the given category
* @param string $category - Category of the post * @param string $category - Category of the post

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2786
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -674,8 +674,11 @@ EOD;
public function changeHTMLSrc(string $body, string $to, string $from): string public function changeHTMLSrc(string $body, string $to, string $from): string
{ {
$htmlDoc = new DOMDocument(); $htmlDoc = new DOMDocument();
$body = mb_convert_encoding($body, "HTML-ENTITIES", "UTF-8");
$htmlDoc->loadHTML($body, LIBXML_NOERROR); // Load the raw HTML content into DOMDocument
@$htmlDoc->loadHTML($body, LIBXML_NOERROR);
// Get the body and process images
$doc = $htmlDoc->getElementsByTagName('body')->item(0); $doc = $htmlDoc->getElementsByTagName('body')->item(0);
$imgs = $doc->getElementsByTagName('img'); $imgs = $doc->getElementsByTagName('img');
@ -688,9 +691,11 @@ EOD;
$srcList[] = $src; $srcList[] = $src;
$fileName = basename($src); $fileName = basename($src);
// Update the src attribute to the new location
$img->setAttribute("src", substr($to, 2) . $fileName); $img->setAttribute("src", substr($to, 2) . $fileName);
} }
// Rename files and clean up old ones
$files = scandir($from); $files = scandir($from);
foreach ($files as $file) foreach ($files as $file)
{ {
@ -706,15 +711,36 @@ EOD;
} }
} }
// Process the HTML content for output
$newBody = ''; $newBody = '';
foreach ($doc->childNodes as $node) foreach ($doc->childNodes as $node)
{ {
$newHTML = $htmlDoc->saveHTML($node); // Only convert text nodes to HTML entities
$newBody .= mb_convert_encoding($newHTML, "UTF-8", mb_detect_encoding($newHTML)); if ($node->nodeType === XML_TEXT_NODE)
{
$newBody .= $this->convertToHtmlEntities($node->nodeValue); // Convert text nodes
} }
else
{
$newBody .= $htmlDoc->saveHTML($node); // Keep HTML tags intact
}
}
return $newBody; return $newBody;
} }
/**
* Convert all characters in a string to HTML entities while leaving HTML tags intact.
* @param string $text - The text to convert
* @return string - The converted text with HTML entities
*/
private function convertToHtmlEntities(string $text): string
{
// Convert characters to HTML entities using mb_encode_numericentity
return htmlentities($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
/** /**
* Get all posts with the given category * Get all posts with the given category
* @param string $category - Category of the post * @param string $category - Category of the post

View File

@ -123,3 +123,102 @@ section.largePost .outerContent .postContent a {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
} }
section#olderPosts {
/*max-width: 90%;*/
margin: auto;
}
section#olderPosts .carousel {
position: relative;
margin: auto;
}
section#olderPosts .arrow {
position: absolute;
top: 50%;
display: flex;
width: 2.5em;
height: 2.5em;
justify-content: center;
align-items: center;
border-radius: 50%;
z-index: 1;
font-size: 1.625em;
color: white;
background: var(--mutedBlack);
cursor: pointer;
}
section#olderPosts .arrow:hover {
background: var(--grey);
}
section#olderPosts #prev {
left: -4em;
}
section#olderPosts #next {
right: -4em;
}
section#olderPosts .carouselOuter {
display: flex;
flex-direction: row;
align-items: center;
gap: 1em;
overflow: hidden;
position: relative;
margin: auto;
}
section#olderPosts #allCarouselItems {
display: none;
}
section#olderPosts #carouselInner {
display: flex;
flex-direction: row;
align-items: stretch;
justify-content: center;
gap: 1em;
transition: transform 0.5s ease-in-out;
left: 0;
height: 50%;
}
section#olderPosts #carouselInner .cardItem {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
border: 2px solid var(--primaryDefault);
-webkit-border-radius: 0.625rem;
-moz-border-radius: 0.625rem;
border-radius: 0.625rem;
width: 30em;
height: 40rem;
}
section#olderPosts #carouselInner .cardItem img {
width: 100%;
height: 300px;
object-fit: cover;
object-position: left;
-webkit-border-radius: 0.625rem;
-moz-border-radius: 0.625rem;
border-radius: 0.625rem;
color: #FFFFFF;
}
section#olderPosts #carouselInner .cardItem .content {
height: auto;
display: flex;
flex-direction: column;
justify-content: space-evenly;
align-items: center;
background: #FFFFFF;
margin: 0 2em 1.5em;
padding: 0;
}

View File

@ -9,6 +9,12 @@
@import "home.css"; @import "home.css";
@import "category.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 { .policy {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -73,6 +79,11 @@
@media screen and (max-width: 90em) { @media screen and (max-width: 90em) {
section#olderPosts #carouselInner .cardItem {
width: 25em;
}
/***** Individual Blog Posts ***/ /***** Individual Blog Posts ***/
div.mainContent { div.mainContent {
@ -124,6 +135,25 @@
width: 90%; 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 ***/ /***** Individual Blog Posts ***/
section#individualPost { section#individualPost {
flex-direction: column-reverse; flex-direction: column-reverse;
@ -219,6 +249,24 @@
max-width: 75%; 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 ***/ /***** Individual Blog Posts ***/
aside.sideContent > div.authorInfo { aside.sideContent > div.authorInfo {

View File

@ -41,7 +41,6 @@ function goToURL(url)
if (url === '/blog' || url === 'blog' || url === '/blog/') if (url === '/blog' || url === 'blog' || url === '/blog/')
{ {
loadHomeContent(); loadHomeContent();
// window.history.pushState(null, null, url);
return; return;
} }
@ -162,6 +161,10 @@ function submitNewsletter()
})); }));
} }
/**
* unsubscribe by email
* @param email the email to unsubscribe
*/
function unsubscribe(email) function unsubscribe(email)
{ {
fetch(`/api/blog/newsletter/${email}`, { fetch(`/api/blog/newsletter/${email}`, {
@ -243,6 +246,32 @@ function createLargePost(post)
return outerContent; return outerContent;
} }
/**
* Creates a card post element
* @param post the object
* @returns {HTMLDivElement} the outer content of the post
*/
function createCardPost(post)
{
let cardItem = document.createElement('div');
cardItem.classList.add('cardItem');
cardItem.id = 'post' + post.ID;
let img = document.createElement('img');
img.className = 'cardImg';
img.src = post.headerImg.replaceAll('%2F', '/');
img.alt = post.title;
cardItem.appendChild(img);
let content = document.createElement('div');
content.classList.add('content');
content.innerHTML = `
<h2>${post.title}</h2>
<h3>Last updated: ${createFormattedDate(post.dateModified)}</h3>
<a href="/blog/post/${post.title}" class="btn btnPrimary">See Post</a>
`;
cardItem.appendChild(content);
return cardItem;
}
/** /**
* Loads the home content * Loads the home content
*/ */
@ -250,6 +279,30 @@ function loadHomeContent()
{ {
fetch('/api/blog/post').then(res => res.json().then(json => fetch('/api/blog/post').then(res => res.json().then(json =>
{ {
// older posts outside the carousel and loop
let olderPosts = document.createElement('section');
olderPosts.classList.add('largePost');
olderPosts.id = 'olderPosts';
let h1 = document.createElement('h1');
h1.innerHTML = 'older posts';
olderPosts.appendChild(h1);
let carousel = document.createElement('div');
carousel.classList.add('carousel');
carousel.innerHTML += `<div class="arrow" id="prev"><i class="fa-solid fa-chevron-left"></i></div>
<div class="arrow" id="next"><i class="fa-solid fa-chevron-right"></i></div>
`;
let carouselOuter = document.createElement('div');
let carouselInner = document.createElement('div');
let allCarouselItems = document.createElement('div');
carouselOuter.classList.add('carouselOuter');
carouselInner.id = 'carouselInner';
allCarouselItems.id = 'allCarouselItems';
carouselOuter.appendChild(allCarouselItems);
carouselOuter.appendChild(carouselInner);
carousel.appendChild(carouselOuter);
olderPosts.appendChild(carousel);
for (let i = 0; i < json.length; i++) for (let i = 0; i < json.length; i++)
{ {
if (json[i].featured === 1) if (json[i].featured === 1)
@ -278,10 +331,142 @@ function loadHomeContent()
document.querySelector('#main').appendChild(latestPost); document.querySelector('#main').appendChild(latestPost);
} }
if (i > 1)
{
allCarouselItems.appendChild(createCardPost(json[i]));
} }
}
document.querySelector('#main').appendChild(olderPosts);
//carousel loop
carouselLoop(carouselInner, allCarouselItems);
})); }));
} }
/**
* Creates the loop for the carousel
* @param {HTMLDivElement} carouselInner
* @param {HTMLDivElement} allItems
*/
function carouselLoop(carouselInner, allItems)
{
const prev = document.querySelector('#prev');
const next = document.querySelector('#next');
const mediaBig = window.matchMedia('(max-width: 75em)');
const mediaSmall = window.matchMedia('(max-width: 30em)');
let cards = document.querySelectorAll('#allCarouselItems .cardItem');
let visibleCardsCount = 3;
if (mediaBig.matches)
{
visibleCardsCount = 2; // only show 2 cards if on a slightly smaller screen e.g. tablet/laptop
}
if (mediaSmall.matches)
{
visibleCardsCount = 1; // only show 1 card if on a mobile, although it'll only work on portrait
}
let visibleCards = [];
// put the first n (3, 2, or 1) cards in the carousel
for (let i = 0; i < visibleCardsCount; i++)
{
carouselInner.appendChild(cards[i]);
visibleCards.push(cards[i]);
}
next.addEventListener('click', () =>
{
const firstCard = visibleCards.shift();
firstCard.style.visibility = 'hidden'; // hide the first card
// add the next card to the end and off the screen
const nextCard = allItems.querySelector('.cardItem');
nextCard.style.transform = 'translateX(100%)';
nextCard.style.transition = 'none';
carouselInner.appendChild(nextCard);
visibleCards.push(nextCard);
// transition all the cards to the left after 50ms
// i.e. after the next card is added hence the setTimeout
setTimeout(() =>
{
// move all the cards to the left
for (let i = 0; i < carouselInner.children.length; i++)
{
carouselInner.children[i].style.transition = 'transform 0.5s ease-out';
carouselInner.children[i].style.transform = 'translateX(-100%)';
}
}, 50);
// after 500ms move all cards back to the center and move
// the first card to the hidden div
setTimeout(() =>
{
// move the first card back to the center
// instantly and move it to the hidden div
firstCard.style.transition = 'none';
firstCard.style.transform = 'translateX(0)';
allItems.appendChild(firstCard);
firstCard.style.visibility = 'visible'; // make it visible again
// for the remaining cards, reset them
for (let i = 0; i < carouselInner.children.length; i++)
{
carouselInner.children[i].style.transition = 'none';
carouselInner.children[i].style.transform = 'translateX(0%)';
}
}, 500);
});
// everything is done in reverse
prev.addEventListener('click', () =>
{
const lastCard = visibleCards.pop();
lastCard.style.visibility = 'hidden'; // hide the last card
// add the previous card to the beginning and off the screen
const prevCard = allItems.querySelector('.cardItem:last-child');
prevCard.style.transform = 'translateX(-100%)';
prevCard.style.transition = 'none';
carouselInner.insertBefore(prevCard, carouselInner.firstChild);
visibleCards.unshift(prevCard);
// transition all the cards to the right after 50ms
// i.e. after the previous card is added hence the setTimeout
setTimeout(() =>
{
// move all the cards to the right
for (let i = 0; i < carouselInner.children.length; i++)
{
carouselInner.children[i].style.transition = 'transform 0.5s ease-out';
carouselInner.children[i].style.transform = 'translateX(100%)';
}
}, 50);
// after 500ms move all cards back to the center and move
// the last card to the hidden div
setTimeout(() =>
{
// move the last card back to the center
// instantly and move it to the hidden div
lastCard.style.transition = 'none';
lastCard.style.transform = 'translateX(0)';
allItems.insertBefore(lastCard, allItems.firstChild);
lastCard.style.visibility = 'visible';
// for the remaining cards reset them
for (let i = 0; i < carouselInner.children.length; i++)
{
carouselInner.children[i].style.transition = 'none';
carouselInner.children[i].style.transform = 'translateX(0%)';
}
}, 500);
});
}
/** /**
* Gets the latest and featured posts * Gets the latest and featured posts
* @returns {Promise<any[]>} the latest and featured posts * @returns {Promise<any[]>} the latest and featured posts
@ -848,6 +1033,9 @@ function loadPrivacyPolicy()
`; `;
} }
/**
* Loads the cookie policy
*/
function loadCookiePolicy() function loadCookiePolicy()
{ {
document.querySelector('#main').innerHTML = ` document.querySelector('#main').innerHTML = `

View File

@ -876,7 +876,7 @@ function createEditors(...ids)
return ( return (
`<iframe width="560" height="315" style="text-align:center;" src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` + `<iframe width="560" height="315" style="text-align:center;" src="https://www.youtube.com/embed/${id}${time ? `?start=${time}` : ''}" ` +
'frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>' + 'allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen>' +
'</iframe>' '</iframe>'
); );
}, },
@ -1229,7 +1229,7 @@ function updateProjectItem(id, e)
data['gitLink'] = document.querySelector(`#git${id}proj`).value; data['gitLink'] = document.querySelector(`#git${id}proj`).value;
let imgData = new FormData(); let imgData = new FormData();
imgData.append("img", document.querySelector(`#img${id}`).files[0]); imgData.append('img', document.querySelector(`#img${id}proj`).files[0]);
fetch("/api/projectData/" + id, { fetch("/api/projectData/" + id, {
method: "PATCH", method: "PATCH",
@ -1254,8 +1254,8 @@ function updateProjectItem(id, e)
} }
document.querySelector(`#projectItem${id}`).classList.toggle("editing"); document.querySelector(`#projectItem${id}`).classList.toggle("editing");
document.querySelector(`#title${id}`).setAttribute("disabled", ""); document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
document.querySelector(`#info${id}`).setAttribute("disabled", ""); document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
return; return;
} }
console.log("updating image") console.log("updating image")
@ -1293,8 +1293,8 @@ function updateProjectItem(id, e)
} }
document.querySelector(`#projectItem${id}`).classList.toggle("editing"); document.querySelector(`#projectItem${id}`).classList.toggle("editing");
document.querySelector(`#title${id}`).setAttribute("disabled", ""); document.querySelector(`#title${id}proj`).setAttribute('disabled', '');
document.querySelector(`#info${id}`).setAttribute("disabled", ""); document.querySelector(`#info${id}proj`).setAttribute('disabled', '');
document.querySelector(`#projectImage${id}`).src = updatedProjectImage.imgLocation; document.querySelector(`#projectImage${id}`).src = updatedProjectImage.imgLocation;
return; return;
} }