added dynamic page rendering for index, archives, tages and blog pages

This commit is contained in:
Ro 2021-11-02 16:19:03 -07:00
parent e5cffd39d7
commit 35c780bba6
14 changed files with 461 additions and 3804 deletions

View file

@ -24,6 +24,7 @@ class DashControl
$themes = $config->getThemes(); $themes = $config->getThemes();
$template = "dash/settings.twig"; $template = "dash/settings.twig";
$member = Session::get("member"); $member = Session::get("member");
$form_token = Session::get("form_token");
$updated = new \Moment\Moment($settings["global"]["last_backup"]); $updated = new \Moment\Moment($settings["global"]["last_backup"]);
$pageOptions = [ $pageOptions = [
"title" => "Dash Settings", "title" => "Dash Settings",
@ -31,6 +32,7 @@ class DashControl
"render" => $settings["global"]["renderOnSave"], "render" => $settings["global"]["renderOnSave"],
"background" => $settings["global"]["background"], "background" => $settings["global"]["background"],
"member" => $member, "member" => $member,
"ftoken" => $form_token,
"siteTitle" => $settings["global"]["title"], "siteTitle" => $settings["global"]["title"],
"baseUrl" => $settings["global"]["base_url"], "baseUrl" => $settings["global"]["base_url"],
"desc" => $settings["global"]["descriptions"], "desc" => $settings["global"]["descriptions"],
@ -40,6 +42,11 @@ class DashControl
"apiStatus" => isset($settings["global"]["externalAPI"]) "apiStatus" => isset($settings["global"]["externalAPI"])
? $settings["global"]["externalAPI"] ? $settings["global"]["externalAPI"]
: "false", : "false",
"dynamicRenderStatus" => isset(
$settings["global"]["dynamicRender"]
)
? $settings["global"]["dynamicRender"]
: "false",
"mailOption" => $settings["email"]["active"], "mailOption" => $settings["email"]["active"],
"mailConfig" => $settings["email"], "mailConfig" => $settings["email"],
"status" => Session::active(), "status" => Session::active(),

View file

@ -2,6 +2,7 @@
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig; use Slim\Views\Twig;
use function _\find;
class IndexControl class IndexControl
{ {
@ -11,9 +12,85 @@ class IndexControl
array $args array $args
): ResponseInterface { ): ResponseInterface {
//unset($_SESSION); //unset($_SESSION);
$config = new Settings();
$settings = $config->getSettings();
$view = Twig::fromRequest($request);
//checks dynamic render flag for site render status
if ($settings["global"]["dynamicRender"]) {
if ($settings["global"]["dynamicRender"] == "true") {
$loader = new \Twig\Loader\FilesystemLoader("../content/themes");
$display = new \Twig\Environment($loader, []);
$template = "";
$pageOptions = [];
$pageInfo = [
"keywords" => isset($settings["global"]["keywords"])
? $settings["global"]["keywords"]
: "fipamo, blog, jamstack, php, markdown, js",
"description" => $settings["global"]["descriptions"],
"image" =>
$settings["global"]["base_url"] . $settings["global"]["background"],
"baseURL" => $settings["global"]["base_url"],
];
if (isset($args["first"])) {
switch ($args["first"]) {
case "tags":
$template = $settings["global"]["theme"] . "/tags.twig";
$tag = trim($args["second"]);
$taglist = Sorting::tags();
$item = find($taglist, ["tag_name" => $tag]);
$pageOptions = [
"title" => "Pages Tagged as " . $item["tag_name"],
"background" => $pageInfo["image"],
"tag_list" => $item["pages"],
"info" => $pageInfo,
"menu" => $settings["menu"],
];
break;
case "archives":
$archive = Sorting::archive();
$template = $settings["global"]["theme"] . "/archive.twig";
$pageOptions = [
"title" => "Archive",
"background" => $pageInfo["image"],
"archives" => $archive,
"info" => $pageInfo,
"menu" => $settings["menu"],
];
break;
default:
$template = $settings["global"]["theme"] . "/page.twig";
$book = new Book("../content/pages");
$page = $book->findPageBySlug($args["third"]);
$pageOptions = Sorting::page($page);
break;
}
} else {
//index
$template = $settings["global"]["theme"] . "/index.twig";
$book = new Book("../content/pages");
$page = $book->findPageBySlug();
$pageOptions = Sorting::page($page);
}
$html = $display->render($template, $pageOptions);
$response->getBody()->write($html);
return $response;
} else {
$view = Twig::fromRequest($request); $view = Twig::fromRequest($request);
$html = file_get_contents("../public/index.html"); $html = file_get_contents("../public/index.html");
$response->getBody()->write($html); $response->getBody()->write($html);
return $response; return $response;
} }
} else {
//if flag is not present, default to static html
$view = Twig::fromRequest($request);
$html = file_get_contents("../public/index.html");
$response->getBody()->write($html);
return $response;
}
}
} }

View file

@ -33,6 +33,18 @@ class Book
return $page; return $page;
} }
public function findPageBySlug(string $slug = null)
{
$content = $this->getContents();
if (isset($slug)) {
$page = find($content, ["slug" => $slug]);
} else {
$page = find($content, ["layout" => "index"]);
}
return $page;
}
public function editPage($task, $request) public function editPage($task, $request)
{ {
$content = $this->getContents(); $content = $this->getContents();

View file

@ -47,6 +47,7 @@ class Member
"email" => $found["email"], "email" => $found["email"],
"role" => $found["role"], "role" => $found["role"],
"avatar" => $found["avi"], "avatar" => $found["avi"],
"key" => $found["key"],
]; ];
Session::set("member", $member); Session::set("member", $member);
} }

View file

@ -74,6 +74,7 @@ class Render
$featured = []; $featured = [];
$limit = 4; $limit = 4;
foreach ($pages as $page) { foreach ($pages as $page) {
//TODO: Move page data organization to render to utility class
if (!$page["deleted"] && $page["published"]) { if (!$page["deleted"] && $page["published"]) {
if (count($recent) < $limit) { if (count($recent) < $limit) {
array_push($recent, [ array_push($recent, [

View file

@ -39,6 +39,7 @@ class Settings
$settings["global"]["renderOnSave"] = $data["global"]["renderOnSave"]; $settings["global"]["renderOnSave"] = $data["global"]["renderOnSave"];
$settings["global"]["theme"] = $data["global"]["theme"]; $settings["global"]["theme"] = $data["global"]["theme"];
$settings["global"]["externalAPI"] = $data["global"]["externalAPI"]; $settings["global"]["externalAPI"] = $data["global"]["externalAPI"];
$settings["global"]["dynamicRender"] = $data["global"]["dynamicRender"];
Member::updateData("handle", $data["member"]["handle"]); Member::updateData("handle", $data["member"]["handle"]);
Member::updateData("email", $data["member"]["email"]); Member::updateData("email", $data["member"]["email"]);

View file

@ -1,4 +1,5 @@
<?php <?php
use Mni\FrontYAML\Parser;
use function _\find; use function _\find;
use function _\filter; use function _\filter;
@ -93,4 +94,136 @@ class Sorting
} }
return self::$_archive; return self::$_archive;
} }
public static function page($page)
{
$config = new Settings();
$settings = $config->getSettings();
$pageOption = [];
$pageInfo = [
"keywords" => isset($settings["global"]["keywords"])
? $settings["global"]["keywords"]
: "fipamo, blog, jamstack, php, markdown, js",
"description" => $settings["global"]["descriptions"],
"image" =>
$settings["global"]["base_url"] . $settings["global"]["background"],
"baseURL" => $settings["global"]["base_url"],
];
$taglist = explode(",", $page["tags"]);
$tags = [];
foreach ($taglist as $tag) {
$label = trim($tag);
array_push($tags, [
"label" => $label . " ",
"slug" => StringTools::safeString($label),
]);
}
$meta = [
"who" => $page["author"],
"when" => $page["created"],
"tags" => $tags,
];
//render markdown content and clean it
$parser = new Parser();
$rendered = $parser->parse($page["content"]);
$sanitizer = HtmlSanitizer\Sanitizer::create([
"extensions" => ["basic", "image", "list", "code"],
"tags" => [
"img" => [
"allowed_attributes" => ["src", "alt", "title", "class"],
"allowed_hosts" => null,
],
],
]);
$preclean = $sanitizer->sanitize($rendered->getContent());
//just clean renderd string for now, Sanitize doesn't like relative img urls
//so another option is needed
$cleaned = strip_tags($rendered->getContent(), [
"a",
"br",
"p",
"strong",
"br",
"img",
"iframe",
"ul",
"li",
"i",
"h1",
"h2",
"h3",
"pre",
"code",
]);
//$cleaned = preg_replace('/(?:\r\n|[\r\n]){2,}/', "\n\n", $cleaned);
//$cleaned = html_entity_decode($cleaned, ENT_QUOTES, "UTF-8");
//if page feature isn't empty, replace page info meta image
if ($page["feature"] != "" || $page["feature"] != null) {
$pageInfo["image"] = $pageInfo["baseURL"] . $page["feature"];
}
if ($page["layout"] == "index") {
//$template = $this->theme . "/index.twig";
//$location = "../public/index.html";
//$dir = null;
$recent = [];
$featured = [];
$limit = 4;
$pages = (new Book("../content/pages"))->getContents();
foreach ($pages as $item) {
//TODO: Move page data organization to render to utility class
if (!$item["deleted"] && $item["published"]) {
if (count($recent) < $limit) {
array_push($recent, [
"path" => $item["path"],
"slug" => $item["slug"],
"title" => $item["title"],
]);
}
if ($item["featured"] == true) {
if (count($featured) < $limit) {
array_push($featured, [
"path" => $item["path"],
"slug" => $item["slug"],
"title" => $item["title"],
]);
}
}
}
}
$pageOptions = [
"title" => $page["title"],
"background" => $page["feature"],
"content" => $cleaned,
"meta" => $meta,
"recent" => $recent,
"featured" => $featured,
"info" => $pageInfo,
"menu" => $settings["menu"],
];
} else {
//$template = $this->theme . "/page.twig";
//$location = "../public/" . $page["path"] . "/" . $page["slug"] . ".html";
//$dir = "../public/" . $page["path"];
$pageOptions = [
"title" => $page["title"],
"background" => $page["feature"],
"content" => $cleaned,
"meta" => $meta,
"info" => $pageInfo,
"menu" => $settings["menu"],
];
}
return $pageOptions;
}
} }

View file

@ -11,7 +11,7 @@
{% endblock %} {% endblock %}
{% block stylesheets %} {% block stylesheets %}
<link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=asdfdf"> <link rel="stylesheet" type="text/css" href="/assets/css/dash.css?=cvbvbv">
{% endblock %} {% endblock %}
{% block mainContent %} {% block mainContent %}
@ -29,58 +29,79 @@
</button> </button>
</div> </div>
</div> </div>
<div id="site-background">
<label>Site Header</label>
<img id="background" src="{{background}}" alt="image for site background" for="background-upload"/>
<input id="background-upload" type="file" name="backgrond-upload" />
</div>
<div id="settings-index"> <div id="settings-index">
<div id="settings-index-wrapper"> <div id="settings-index-wrapper">
<div id="member-settings" class="columns"> <div id="member-settings">
<div id="member-settings-1" class="column is-one-third"> <div id="member-images" class="columns">
<div class="column is-one-third">
<div id="member-avatar-drop"> <div id="member-avatar-drop">
<img id="avatar" src="{{member['avatar']}}" for="avatar-upload"/> <img id="avatar" src="{{member['avatar']}}" for="avatar-upload"/>
<input id="avatar-upload" type="file" name="avatar-upload" /> <input id="avatar-upload" type="file" name="avatar-upload" />
</div> </div>
</div> </div>
<div class="column is-three-fifths"> <div class="column is-two-thirds">
<div class="columns"> <div id="site-background">
<div id="member-settings-2" class="column"> <img id="background" src="{{background}}" alt="image for site background" for="background-upload"/>
<input id="background-upload" type="file" name="backgrond-upload" />
</div>
</div>
</div>
<div id="member-meta" class="columns">
<div class="column is-one-third">
<input type='text' name='handle' id='settings-handle' placeholder='handle' value="{{member['handle']}}" autofocus /> <input type='text' name='handle' id='settings-handle' placeholder='handle' value="{{member['handle']}}" autofocus />
<input type='text' name='email' id='settings-email' placeholder='email' value="{{member['email']}}" autofocus /> <input type='text' name='email' id='settings-email' placeholder='email' value="{{member['email']}}" autofocus />
</div> </div>
<div id="member-settings-3" class="column"> <div class="column is-one-third">
<input type='text' name='base-url' id='settings-url' placeholder='url' value="{{baseUrl}}" autofocus /> <input type='text' name='base-url' id='settings-url' placeholder='url' value="{{baseUrl}}" autofocus />
<input type='text' name='base-title' id='settings-title' placeholder='site title' value="{{siteTitle}}" autofocus /> <input type='text' name='base-title' id='settings-title' placeholder='site title' value="{{siteTitle}}" autofocus />
</div> </div>
<div class="column is-one-third">
</div> <textarea id="settings-desc" type='text' name='settings_desc' class='settings-dec' placeholder='description stuff', autofocus>{{desc}}</textarea>
<div class="columns">
<div class="column is-full">
<textarea id="settings-desc" type='text' name='settings_desc' class='settings-dec' placeholder='description stuff', autofocus>{{desc}}</textarea><br />
<label>YOUR API KEY</label><br />
<span id="key">{{member['key']}}</span>
</div> </div>
</div> </div>
</div>
</div>
<div id="member-utils" class="columns">
<div id="util-1" class="column is-one-third">
<button id="create-backup">BACK UP YOUR SITE</button><br />
</div>
<div id="util-2" class="column is-three-fifths">
{% if lastBackup != '' %}
<div class="backup-meta">
LAST BACK UP <a href="/api/v1/files">{{lastBackup}}</a><br />
</div> </div>
<div id="feature-settings">
<div id="features" class="columns">
<div class="column">
<div id="feature-api">
{% if apiStatus is defined and apiStatus == "true" %}
<button id="api-access-toggle" title="allow external api" data-enabled="true">
<svg id="api-access-toggle" class="icons">
<use id="api-access-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-landline"/>
</svg>
</button>
<span id="api-status">EXTERNAL API ACCESS ENABLED</span>
{% else %} {% else %}
<span>span No back ups. Frowny face.</span> <button id="api-access-toggle" title="allow external api" data-enabled="false">
<svg id="api-access-toggle" class="icons">
<use id="api-access-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-landline"/>
</svg>
</button>
<span id="api-status">EXTERNAL API ACCESS NOT ENABLED</span>
{% endif %} {% endif %}
</div> </div>
</div>
<div class="column">
<div id="dynamic-api">
{% if dynamicRenderStatus is defined and dynamicRenderStatus == "true" %}
<button id="dynamic-render-toggle" title="allow external api" data-enabled="true">
<svg id="dynamic-render-toggle" class="icons">
<use id="dynamic-render-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-text-document-inverted"/>
</svg>
</button>
<span id="dynamic-render-status">DYNAMIC PAGE RENDERING</span>
{% else %}
<button id="dynamic-render-toggle" title="allow external api" data-enabled="false">
<svg id="dynamic-render-toggle" class="icons">
<use id="dynamic-render-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-text-document-inverted"/>
</svg>
</button>
<span id="dynamic-render-status">STATIC PAGE RENDERING</span>
{% endif %}
</div>
</div>
</div>
</div> </div>
<div id="option-settings" class="columns"> <div id="option-settings" class="columns">
<div id="theme-settings" class="column"> <div id="theme-settings" class="column">
@ -117,28 +138,43 @@
{% endapply %} {% endapply %}
<button id="send-mail">TEST MAIL</button> <button id="send-mail">TEST MAIL</button>
<br /><br /> <br /><br />
<label>API SETTINGS</label><br />
<div id="settings-api"> </div>
{% if apiStatus is defined and apiStatus == "true" %} </div>
<button id="api-access-toggle" title="allow external api" data-enabled="true"> <div id="token-settings">
<svg id="api-access-toggle" class="icons"> <div id="keys-tokens" class="columns">
<use id="api-access-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-landline"/> <div class="column">
</svg> <label>API KEY</label>
</button> <div id="member-api-key">
<span id="api-status">EXTERNAL API ACCESS ENABLED</span> {{member['key']}}
</div>
</div>
<div class="column">
<label>FORM TOKEN</label>
<div id="form-token">
{{ftoken}}
</div>
</div>
</div>
</div>
<div id="backup-settings">
<div id="util-1" class="column is-one-third">
<button id="create-backup">BACK UP YOUR SITE</button><br />
</div>
<div id="util-2" class="column is-three-fifths">
{% if lastBackup != '' %}
<div class="backup-meta">
LAST BACK UP <a href="/api/v1/files">{{lastBackup}}</a><br />
</div>
{% else %} {% else %}
<button id="api-access-toggle" title="allow external api" data-enabled="false"> <span>span No back ups. Frowny face.</span>
<svg id="api-access-toggle" class="icons">
<use id="api-access-toggle" xlink:href="/assets/images/global/sprite.svg#entypo-landline"/>
</svg>
</button>
<span id="api-status">EXTERNAL API ACCESS NOT ENABLED</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
</div> </div>

View file

@ -9,7 +9,8 @@
"theme": "fipamo-default", "theme": "fipamo-default",
"display_limit": 5, "display_limit": 5,
"last_backup": null, "last_backup": null,
"externalAPI": "false" "externalAPI": "false",
"dynamicRender": "false"
}, },
"library_stats": { "library_stats": {
"current_index": 1 "current_index": 1

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -27,6 +27,9 @@ export default class SettingsActions {
let apiStatus = document let apiStatus = document
.getElementById("api-access-toggle") .getElementById("api-access-toggle")
.getAttribute("data-enabled"); .getAttribute("data-enabled");
let dynamicRenderStatus = document
.getElementById("dynamic-render-toggle")
.getAttribute("data-enabled");
var i, count; var i, count;
for (i = 0, count = selects.length; i < count; i++) { for (i = 0, count = selects.length; i < count; i++) {
if (selects[i].getAttribute("data-enabled") == "true") if (selects[i].getAttribute("data-enabled") == "true")
@ -46,7 +49,8 @@ export default class SettingsActions {
private: false, private: false,
renderOnSave: render, renderOnSave: render,
theme: selected, theme: selected,
externalAPI: apiStatus externalAPI: apiStatus,
dynamicRender: dynamicRenderStatus
}, },
member: { handle: handle, email: email }, member: { handle: handle, email: email },
email: { email: {

View file

@ -76,6 +76,21 @@ export default class SettingsIndex {
} }
}); });
//handle dynamic page rendering
var dynamicRenderButton = document.getElementById("dynamic-render-toggle");
var dynamicRenderStatus = document.getElementById("dynamic-render-status");
dynamicRenderButton.addEventListener("click", (e) => {
e.stopPropagation();
e.preventDefault();
if (dynamicRenderButton.getAttribute("data-enabled") == "false") {
dynamicRenderButton.setAttribute("data-enabled", "true");
dynamicRenderStatus.innerHTML = "DYNAMIC PAGE RENDERING";
} else {
dynamicRenderButton.setAttribute("data-enabled", "false");
dynamicRenderStatus.innerHTML = "STATIC PAGE RENDERING";
}
});
document document
.getElementById("send-mail") .getElementById("send-mail")
.addEventListener("click", (e) => this.handleMailer(e)); .addEventListener("click", (e) => this.handleMailer(e));

View file

@ -30,45 +30,25 @@
svg svg
fill: color.adjust($primary, $lightness: -60%) fill: color.adjust($primary, $lightness: -60%)
#site-background
margin: 0 0 10px 0
img
width: 100%
// border 5px solid $white
border-radius: 0
overflow: hidden
cursor: pointer
label
position: absolute
color: $white
margin: 5px
background: color.adjust($primary, $lightness: -60%)
padding: 5px
border-radius: 3px
input
visibility: hidden
display: none
#settings-index #settings-index
width: 94% width: 94%
max-width: 900px max-width: 900px
margin: 0 auto margin: 0 auto
overflow: hidden
#settings-index-wrapper #settings-index-wrapper
padding: 0.75rem padding: 0
button button
margin-top: 5px margin-top: 5px
width: 100% width: 100%
height: 33px height: 33px
#member-settings, #site-settings, #option-settings, #member-utils #member-settings, #feature-settings, #option-settings, #token-settings
background: $white background: $white
padding: 5px padding: 0px
border-radius: 5px 0 5px 0 border-radius: 5px 0 5px 0
width: 100%
margin: 20px auto
label label
font-family: $baseType font-family: $baseType
@ -93,8 +73,8 @@
margin: 5px 0 0 0 margin: 5px 0 0 0
text-align: center text-align: center
width: 100% #member-images
margin: 20px auto padding: 10px 15px 0 15px
#member-avatar-drop #member-avatar-drop
display: inline-block display: inline-block
@ -102,7 +82,6 @@
img img
width: 100% width: 100%
// border 5px solid $white
border-radius: 5px border-radius: 5px
overflow: hidden overflow: hidden
cursor: pointer cursor: pointer
@ -117,19 +96,33 @@
#render-toggle #render-toggle
width: 50% width: 50%
#member-info #site-background
vertical-align: top margin: 0 0 10px 0
display: inline-block
width: 100% img
width: 92.1%
height: 292px
border-radius: 3px
overflow: hidden
cursor: pointer
input input
width: 95% visibility: hidden
margin: 0 5px 10px 0 display: none
#member-meta
padding: 10px 15px 0 15px
position: relative
top: -30px
#features
padding: 10px 15px 0 15px
textarea textarea
background: $primary background: $primary
width: 93% width: 70%
height: 80px height: 89.5px
color: $tertiary color: $tertiary
padding: 10px padding: 10px
display: inline-block display: inline-block
@ -144,6 +137,50 @@
width: 95% width: 95%
overflow: hidden overflow: hidden
#feature-settings
#feature-api, #dynamic-api
background: $primary
border-radius: 3px
padding: 5px
width: 95%
span
color: $white !important
margin: 6px 0 0 5px
position: relative
vertical-align: middle
display: inline-block
font-weight: bold
button
color: $white
border-radius: 3px
width: 40px
margin: 0
height: 25px
svg
width: 25px
height: 20px
fill: $white
position: relative
display: block
margin: -12px auto
button[data-enabled='false']
background: $secondary
svg
fill: $primary
button[data-enabled='true']
background: $highlight
svg
fill: $white
#token-settings
#keys-tokens
padding: 10px 15px 0 15px
#member-api-key, #form-token
background: $primary
border-radius: 3px
padding: 5px
color: $white
#option-settings #option-settings
#theme-settings #theme-settings
a a