Everywhere: Add initial cut of the blogging functionality
This commit is contained in:
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use App\Model\BlogModel;
|
||||||
|
|
||||||
|
class BlogController extends PageController {
|
||||||
|
|
||||||
|
public function indexAction(): void
|
||||||
|
{
|
||||||
|
$query = $this->router->request()->param('search', '');
|
||||||
|
$posts = $this->search($query);
|
||||||
|
|
||||||
|
$this->defineHelpers();
|
||||||
|
|
||||||
|
$this->router->service()->search = $query;
|
||||||
|
$this->router->service()->posts = $posts;
|
||||||
|
$this->router->service()->injectView = $this->views . '/partials/blog-posts.php';
|
||||||
|
parent::view('blog', 'Hello');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchAction(): void
|
||||||
|
{
|
||||||
|
$query = $this->router->request()->param('query', '');
|
||||||
|
$posts = $this->search($query);
|
||||||
|
|
||||||
|
$this->defineHelpers();
|
||||||
|
|
||||||
|
$this->router->service()->posts = $posts;
|
||||||
|
$this->router->service()->partial($this->views . '/partials/blog-posts.php', $posts);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------//
|
||||||
|
|
||||||
|
private function search(string $query): ?array
|
||||||
|
{
|
||||||
|
return BlogModel::selectAll(
|
||||||
|
'blog_post.*, media.filename, media.extension, page.page, section.section, log.created_at', '
|
||||||
|
LEFT JOIN media ON blog_post.media_id = media.id
|
||||||
|
LEFT JOIN page ON blog_post.page_id = page.id
|
||||||
|
LEFT JOIN section ON page.section_id = section.id
|
||||||
|
LEFT JOIN log ON blog_post.log_id = log.id
|
||||||
|
WHERE blog_post.archived = 0 AND
|
||||||
|
(blog_post.title LIKE :query OR blog_post.tag LIKE :query)
|
||||||
|
', [[':query', "%$query%", \PDO::PARAM_STR]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function defineHelpers(): void
|
||||||
|
{
|
||||||
|
$this->router->service()->prettyTimestamp = function (string $timestamp): string {
|
||||||
|
$date = date_create($timestamp);
|
||||||
|
$date = date_format($date, 'd M Y');
|
||||||
|
return "<u class=\"text-decoration-none text-reset\" title=\"{$timestamp}\">{$date}</u>";
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->router->service()->tags = function (string $tags): array {
|
||||||
|
// Remove empty elements via array_filter()
|
||||||
|
return array_filter(explode(':', $tags));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Model;
|
||||||
|
|
||||||
|
class BlogModel extends Model {
|
||||||
|
|
||||||
|
protected $table = 'blog_post';
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
use \App\Classes\Config;
|
||||||
use \App\Classes\Config;
|
|
||||||
?>
|
?>
|
||||||
<div class="content shadow p-4 mb-4">
|
<div class="content shadow p-4 mb-4">
|
||||||
<h3 class="mb-4">Cache</h3>
|
<h3 class="mb-4">Cache</h3>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<div class="row mt-4">
|
||||||
|
<?php $size = $this->sideContent ? '8' : '12'; ?>
|
||||||
|
<div class="col-12 col-md-<?= $size; ?> col-lg-<?= $size; ?>">
|
||||||
|
|
||||||
|
<div class="input-group mb-4">
|
||||||
|
<input type="text" name="blog-search" id="js-blog-search" class="form-control"
|
||||||
|
autofocus="" placeholder="Search" value="<?= $this->search; ?>" onfocus="this.select();" data-url="<?= $this->url; ?>">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="button" id="js-blog-search-button" class="btn btn-dark"><i class="fa fa-search"></i> Search</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="blog-posts">
|
||||||
|
<?= $this->partial($this->injectView); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
use App\Classes\Config;
|
||||||
|
?>
|
||||||
|
<?php foreach ($this->posts as $post) { ?>
|
||||||
|
<a class="clear" href="<?= Config::c('APP_URL') . '/' . $post['section'] . '/' . $post['page']; ?>">
|
||||||
|
<div class="content shadow p-4 mb-4" style="min-height: 0;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 <?= _exists($post, 'media_id') ? 'col-md-9' : ''; ?>">
|
||||||
|
<div class="d-flex flex-wrap align-items-baseline">
|
||||||
|
<h4 class="mr-3"><strong><?= $post['title']; ?></strong></h4>
|
||||||
|
<small class="mb-2 text-muted"><?= ($this->prettyTimestamp)($post['created_at']); ?></small>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<?= $post['content']; ?>
|
||||||
|
</p>
|
||||||
|
<?php if (_exists($post, 'tag')) { ?>
|
||||||
|
<small>
|
||||||
|
<i>
|
||||||
|
tags:
|
||||||
|
<?php $tags = ($this->tags)($post['tag']); ?>
|
||||||
|
<?php foreach ($tags as $key => $tag) { ?>
|
||||||
|
<?= $tag . (($key === array_key_last($tags)) ? '' : ', '); ?>
|
||||||
|
<?php } ?>
|
||||||
|
</i>
|
||||||
|
</small>
|
||||||
|
<div class="d-md-none mb-3"></div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
<?php if (_exists($post, 'media_id')) { ?>
|
||||||
|
<div class="col-12 col-md-3">
|
||||||
|
<img src="<?= Config::c('APP_URL'). '/media/' . $post['filename'] . '.' . $post['extension']; ?>"
|
||||||
|
loading="lazy" class="w-100" style="height: 125px; object-fit: cover;">
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
@@ -57,6 +57,11 @@ nav.shadow {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.clear {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Anchor offset */
|
/* Anchor offset */
|
||||||
h3 {
|
h3 {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -12,6 +12,39 @@ $(document).ready(function() {
|
|||||||
return elementBottom > viewportTop && elementTop < viewportBottom;
|
return elementBottom > viewportTop && elementTop < viewportBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//------------------------------------------//
|
||||||
|
// Blog search
|
||||||
|
|
||||||
|
function blogSearch(input)
|
||||||
|
{
|
||||||
|
var url = input.data("url");
|
||||||
|
var search = input.val();
|
||||||
|
window.location.href = url + '?search=' + search;
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#js-blog-search").keydown(function(e) {
|
||||||
|
if (e.key == 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
blogSearch($(this));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#js-blog-search-button").click(function() {
|
||||||
|
blogSearch($("#js-blog-search"));
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#js-blog-search").on("input", function() {
|
||||||
|
var url = $(this).data("url");
|
||||||
|
var search = $(this).val();
|
||||||
|
if (search.length == 0 || search.length >= 3) {
|
||||||
|
fetch(url + '/search?query=' + search)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
$("#blog-posts").empty().append(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//------------------------------------------//
|
//------------------------------------------//
|
||||||
|
|
||||||
// Image hover mouseenter
|
// Image hover mouseenter
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ return [
|
|||||||
['/img/captcha.jpg', 'IndexController', 'captcha'],
|
['/img/captcha.jpg', 'IndexController', 'captcha'],
|
||||||
['/robots.txt', 'IndexController', 'robots'],
|
['/robots.txt', 'IndexController', 'robots'],
|
||||||
['/sitemap.xml', 'IndexController', 'sitemap'],
|
['/sitemap.xml', 'IndexController', 'sitemap'],
|
||||||
|
['/blog', 'BlogController'],
|
||||||
|
['/blog/search', 'BlogController', 'search'],
|
||||||
['/login', 'LoginController', 'login', ['', 'Sign in', '']],
|
['/login', 'LoginController', 'login', ['', 'Sign in', '']],
|
||||||
['/reset-password', 'LoginController', 'reset', ['', 'Reset password', '']],
|
['/reset-password', 'LoginController', 'reset', ['', 'Reset password', '']],
|
||||||
['/logout', 'LoginController', 'logout'],
|
['/logout', 'LoginController', 'logout'],
|
||||||
|
|||||||
Reference in New Issue
Block a user