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

View file

@ -2,6 +2,7 @@
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;
use function _\find;
class IndexControl
{
@ -11,9 +12,85 @@ class IndexControl
array $args
): ResponseInterface {
//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);
$html = file_get_contents("../public/index.html");
$response->getBody()->write($html);
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;
}
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)
{
$content = $this->getContents();

View file

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

View file

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

View file

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

View file

@ -1,4 +1,5 @@
<?php
use Mni\FrontYAML\Parser;
use function _\find;
use function _\filter;
@ -93,4 +94,136 @@ class Sorting
}
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 %}
{% 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 %}
{% block mainContent %}
@ -29,58 +29,79 @@
</button>
</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-wrapper">
<div id="member-settings" class="columns">
<div id="member-settings-1" class="column is-one-third">
<div id="member-settings">
<div id="member-images" class="columns">
<div class="column is-one-third">
<div id="member-avatar-drop">
<img id="avatar" src="{{member['avatar']}}" for="avatar-upload"/>
<input id="avatar-upload" type="file" name="avatar-upload" />
</div>
</div>
<div class="column is-three-fifths">
<div class="columns">
<div id="member-settings-2" class="column">
<div class="column is-two-thirds">
<div id="site-background">
<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='email' id='settings-email' placeholder='email' value="{{member['email']}}" autofocus />
</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-title' id='settings-title' placeholder='site title' value="{{siteTitle}}" autofocus />
</div>
</div>
<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 class="column is-one-third">
<textarea id="settings-desc" type='text' name='settings_desc' class='settings-dec' placeholder='description stuff', autofocus>{{desc}}</textarea>
</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 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 %}
<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 %}
</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 id="option-settings" class="columns">
<div id="theme-settings" class="column">
@ -117,28 +138,43 @@
{% endapply %}
<button id="send-mail">TEST MAIL</button>
<br /><br />
<label>API SETTINGS</label><br />
<div id="settings-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>
</div>
</div>
<div id="token-settings">
<div id="keys-tokens" class="columns">
<div class="column">
<label>API KEY</label>
<div id="member-api-key">
{{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 %}
<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>
<span>span No back ups. Frowny face.</span>
{% endif %}
</div>
</div>
</div>
</div>

View file

@ -9,7 +9,8 @@
"theme": "fipamo-default",
"display_limit": 5,
"last_backup": null,
"externalAPI": "false"
"externalAPI": "false",
"dynamicRender": "false"
},
"library_stats": {
"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
.getElementById("api-access-toggle")
.getAttribute("data-enabled");
let dynamicRenderStatus = document
.getElementById("dynamic-render-toggle")
.getAttribute("data-enabled");
var i, count;
for (i = 0, count = selects.length; i < count; i++) {
if (selects[i].getAttribute("data-enabled") == "true")
@ -46,7 +49,8 @@ export default class SettingsActions {
private: false,
renderOnSave: render,
theme: selected,
externalAPI: apiStatus
externalAPI: apiStatus,
dynamicRender: dynamicRenderStatus
},
member: { handle: handle, 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
.getElementById("send-mail")
.addEventListener("click", (e) => this.handleMailer(e));

View file

@ -30,45 +30,25 @@
svg
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
width: 94%
max-width: 900px
margin: 0 auto
overflow: hidden
#settings-index-wrapper
padding: 0.75rem
padding: 0
button
margin-top: 5px
width: 100%
height: 33px
#member-settings, #site-settings, #option-settings, #member-utils
#member-settings, #feature-settings, #option-settings, #token-settings
background: $white
padding: 5px
padding: 0px
border-radius: 5px 0 5px 0
width: 100%
margin: 20px auto
label
font-family: $baseType
@ -93,8 +73,8 @@
margin: 5px 0 0 0
text-align: center
width: 100%
margin: 20px auto
#member-images
padding: 10px 15px 0 15px
#member-avatar-drop
display: inline-block
@ -102,7 +82,6 @@
img
width: 100%
// border 5px solid $white
border-radius: 5px
overflow: hidden
cursor: pointer
@ -117,19 +96,33 @@
#render-toggle
width: 50%
#member-info
vertical-align: top
display: inline-block
width: 100%
#site-background
margin: 0 0 10px 0
img
width: 92.1%
height: 292px
border-radius: 3px
overflow: hidden
cursor: pointer
input
width: 95%
margin: 0 5px 10px 0
visibility: hidden
display: none
#member-meta
padding: 10px 15px 0 15px
position: relative
top: -30px
#features
padding: 10px 15px 0 15px
textarea
background: $primary
width: 93%
height: 80px
width: 70%
height: 89.5px
color: $tertiary
padding: 10px
display: inline-block
@ -144,6 +137,50 @@
width: 95%
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
#theme-settings
a