added frontend search functionality with a small menubar
🚀 Deploy website on push / 🎉 Deploy (push) Successful in 23s

Signed-off-by: rodude123 <rodude123@gmail.com>
This commit is contained in:
2023-11-05 17:47:44 +00:00
parent b4ab7900db
commit f54ed2f8fb
13 changed files with 235 additions and 62 deletions
+75 -10
View File
@@ -444,22 +444,87 @@ class blogData
if ($result)
{
$bodyText = array_column($result, "bodyText");
// go through each body plain text and get the sentence before, the sentence with the search term and the sentence after and append it to a new array
foreach ($bodyText as $key => $text)
for ($i = 0; $i < count($result); $i++)
{
$text = strtolower($text);
$searchTerm = strtolower($searchTerm);
$pos = strpos($text, $searchTerm);
$start = strrpos(substr($text, 0, $pos), ".") + 1;
$end = strpos($text, ".", $pos);
$result[$key]["bodyText"] = substr($text, $start, $end - $start);
$result[$i]["abstract"] = $this->getShortPost($searchTerm, stripcslashes($result[$i]["bodyText"]));
}
return $result;
}
return array("errorMessage" => "Error, could not find posts");
}
/**
* Get the short post with the search term
* @param string $searchTerm - Search term
* @param $text - Body of the post as plain text
* @return string - Short post with the search term
*/
private function getShortPost(string $searchTerm, $text): string
{
$pattern = '/([,:;!?.-]+)/u';
$parts = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$cleanedParts = [];
foreach ($parts as $part)
{
$part = trim($part); // Remove leading/trailing spaces and newline characters
if (!empty($part))
{
$cleanedParts[] = $part;
}
}
$combinedParts = [];
$currentPart = '';
foreach ($cleanedParts as $part)
{
if (preg_match('/[,:;!?.-]/u', $part))
{
$currentPart .= $part;
}
else
{
if (!empty($currentPart))
{
$combinedParts[] = trim($currentPart);
}
$currentPart = rtrim($part);
}
}
if (!empty($currentPart))
{
$combinedParts[] = trim($currentPart);
}
$result = "";
for ($i = 0; $i < count($combinedParts); $i++)
{
$part = $combinedParts[$i];
if (stripos($part, $searchTerm) !== false)
{
$before = ($i > 0) ? $combinedParts[$i - 1] : "";
$after = ($i < count($combinedParts) - 1) ? $combinedParts[$i + 1] : "";
if ($before === "" && $i > 0)
{
$before = $combinedParts[$i - 1];
}
$result = $before . " " . $part . " " . $after;
// If the search term is found, we don't need to continue checking subsequent parts
break;
}
}
return $result;
}
}
+1 -1
View File
@@ -254,7 +254,7 @@ class blogRoutes implements routesInterface
}
$featured = $data["featured"] === "true";
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
$insertedID = $this->blogData->createPost($data["title"], $data["abstract"], $data["body"], $data["bodyText"], $data["dateCreated"], $featured, $data["categories"], $headerImg);
if (!is_int($insertedID))
{
// uh oh something went wrong
+16
View File
@@ -49,6 +49,22 @@
</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>
+56 -22
View File
@@ -44,7 +44,6 @@ function goToURL(url)
if (urlArray[2] === 'post')
{
// Create a new URL with the dynamic part
// window.history.pushState(null, null, url);
loadIndividualPost(urlArray[urlArray.length - 1]).catch(err => console.log(err));
return;
}
@@ -52,14 +51,20 @@ function goToURL(url)
if (urlArray[2] === 'category')
{
// Create a new URL with the dynamic part
// window.history.pushState(null, null, url);
if (urlArray[3])
{
loadPostsByCategory(urlArray[urlArray.length - 1]);
return;
}
loadAllCategories();
loadAllCategories().catch(err => console.log(err));
return;
}
if (urlArray[2] === 'search' && urlArray[3])
{
// Create a new URL with the dynamic part
loadSearchResults(urlArray[urlArray.length - 1]);
return;
}
@@ -67,6 +72,26 @@ function goToURL(url)
}
document.querySelector('#searchBtn').addEventListener('click', _ =>
{
let searchTerm = document.querySelector('#searchField').value;
if (searchTerm.length > 0)
{
window.history.pushState(null, null, `/blog/search/${searchTerm}`);
document.querySelector('#searchField').value = '';
document.querySelector('#main').innerHTML = '';
goToURL(`/blog/search/${searchTerm}`);
}
});
document.querySelector('#searchField').addEventListener('keyup', e =>
{
if (e.key === 'Enter')
{
document.querySelector('#searchBtn').click();
}
});
/**
* Creates a large post element
* @param post the post object
@@ -116,25 +141,6 @@ function createLargePost(post)
*/
function loadHomeContent()
{
let menuBar = document.createElement('div');
menuBar.classList.add('menuBar');
// language=HTML
menuBar.innerHTML = `
<div class="menu">
<ul>
<li><a href="/blog/category" class="link">categories</a></li>
<li>
<label for="search" aria-hidden="true" hidden>search</label>
<input type="search" name="search" id="search"
placeholder="Search...">
<button type="submit" class="btn btnPrimary"><i
class="fa fa-search"></i></button>
</li>
</ul>
</div>
`;
document.querySelector('#main').appendChild(menuBar);
fetch('/api/blog/post').then(res => res.json().then(json =>
{
for (let i = 0; i < json.length; i++)
@@ -458,6 +464,34 @@ function loadPostsByCategory(category)
}));
}
function loadSearchResults(searchTerm)
{
document.title = 'Rohit Pai - Search Results for ' + decodeURI(searchTerm);
fetch(`/api/blog/search/${searchTerm}`).then(res => res.json().then(json =>
{
let main = document.querySelector('#main');
let posts = document.createElement('section');
posts.classList.add('catPosts');
posts.id = 'searchResults';
let h1 = document.createElement('h1');
h1.innerHTML = 'Search Results';
main.appendChild(h1);
for (let i = 0; i < json.length; i++)
{
let largePost = document.createElement('section');
largePost.classList.add('largePost');
if (i < json.length - 1)
{
largePost.classList.add('categoryPost');
}
let outerContent = createLargePost(json[i]);
largePost.appendChild(outerContent);
posts.appendChild(largePost);
}
main.appendChild(posts);
}));
}
/**
* Shows the 404 page
*/
-1
View File
@@ -64,7 +64,6 @@ nav ul li span {
visibility: hidden;
}
nav ul li .active::before,
nav ul li .active::after {
visibility: visible;
+2 -8
View File
@@ -200,10 +200,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 +261,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">
+5 -5
View File
@@ -486,11 +486,11 @@ function viewToPlainText(viewItem)
// 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', '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).