From b420a6eafa011b57998165012074d350c942c28f Mon Sep 17 00:00:00 2001 From: David Bailey Date: Mon, 22 Jan 2024 17:05:11 +0100 Subject: [PATCH 01/46] feat: simplify README handling --- dragon_fire.code-workspace | 11 +++++++++++ www/post_adapter.php | 17 ++++------------- www/secrets/.gitignore | 1 + 3 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 dragon_fire.code-workspace diff --git a/dragon_fire.code-workspace b/dragon_fire.code-workspace new file mode 100644 index 0000000..533b5a2 --- /dev/null +++ b/dragon_fire.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "../dragon_fire_content" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/www/post_adapter.php b/www/post_adapter.php index 65b4ced..09027cf 100644 --- a/www/post_adapter.php +++ b/www/post_adapter.php @@ -97,6 +97,10 @@ class PostHandler extends MySQLAdapter { } function save_markdown_post($post_path, $post_data) { + if(basename($post_path) == "README.md") { + $post_path = dirname($post_path); + } + $frontmatter_post = YamlFrontMatter::parse($post_data); $post_path = $this->_sanitize_path($post_path); @@ -110,19 +114,6 @@ class PostHandler extends MySQLAdapter { $post_metadata['tags'][]= 'type:' . $post_metadata['type']; - if(basename($post_path) == "README.md") { - $readme_metadata = []; - if(isset($post_metadata['settings'])) { - $readme_metadata['settings'] = $post_metadata['settings']; - } - if(isset($post_metadata['directory'])) { - $readme_metadata = $post_metadata['directory']; - } - - $this->update_or_create_post(dirname($post_path), - $readme_metadata, $post_content); - } - $this->update_or_create_post($post_path, $post_metadata, $post_content); } diff --git a/www/secrets/.gitignore b/www/secrets/.gitignore index f573c46..bb1f315 100644 --- a/www/secrets/.gitignore +++ b/www/secrets/.gitignore @@ -1,2 +1,3 @@ *.json +*.yml api_admin_key \ No newline at end of file From a2c6842b714741931f9836c81b1100dc50af7937 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Thu, 25 Jan 2024 15:40:32 +0100 Subject: [PATCH 02/46] feat: :sparkles: add new Post wrapper and Blog overview page --- www/mysql_adapter.php | 2 +- www/post.php | 166 ++++++++++++++++++++++ www/post_adapter.php | 2 + www/router.php | 36 ++++- www/static/article_blop.css | 81 +++++++++++ www/static/dergstyle.css | 32 ++++- www/templates/fragments/article_blop.html | 23 +++ www/templates/post_types/blog_list.html | 48 +++++++ 8 files changed, 384 insertions(+), 6 deletions(-) create mode 100644 www/post.php create mode 100644 www/static/article_blop.css create mode 100644 www/templates/fragments/article_blop.html create mode 100644 www/templates/post_types/blog_list.html diff --git a/www/mysql_adapter.php b/www/mysql_adapter.php index 3d845b6..23904a7 100644 --- a/www/mysql_adapter.php +++ b/www/mysql_adapter.php @@ -268,7 +268,7 @@ class MySQLAdapter { ]; $qry = " - SELECT post_path, post_metadata + SELECT * FROM posts WHERE MATCH(post_tags) AGAINST (? IN BOOLEAN MODE) "; diff --git a/www/post.php b/www/post.php new file mode 100644 index 0000000..94c0da2 --- /dev/null +++ b/www/post.php @@ -0,0 +1,166 @@ + + -1, + 'post_path' => '/404.md', + 'post_title' => '404 Page', + 'post_metadata' => [ + 'type' => '404' + ] + ]; + } + + public static function _deduce_type($path) { + $ext = pathinfo($path, PATHINFO_EXTENSION); + + if(preg_match("/\.(\w+)\.md$/", $path, $ext_match)) { + $ext = $ext_match[1]; + } + + $ext_mapping = [ + '' => 'directory', + 'md' => 'text/markdown', + 'png' => 'image', + 'jpg' => 'image', + 'jpeg' => 'image' + ]; + + return $ext_mapping[$ext] ?? '?'; + } + + public static function _deduce_icon($type) { + $icon_mapping = [ + '' => 'question', + 'text/markdown' => 'markdown', + 'directory' => 'folder', + 'gallery' => 'images', + 'image' => 'image' + ]; + + return $icon_mapping[$type] ?? 'unknown'; + } + + function __construct($post_handler, $sql_row) { + $this->post_handler = $post_handler; + + $this->content_html = null; + + $sql_meta = null; + if(!isset($sql_row) or !$sql_row['found']) { + $sql_row = $this->_generate_404($sql_row); + $sql_meta = $sql_row['post_metadata']; + } + + + $sql_meta = $sql_row['post_metadata']; + if(is_string($sql_meta)) { + $sql_meta = json_decode($sql_meta, true); + } + + unset($sql_meta['settings']); + + $this->sql_row = $sql_row; + $this->sql_meta = $sql_meta ?? []; + $sql_settings = json_decode($sql_row['post_settings_cache'], true) ?? []; + + $data = [ + 'id' => $sql_row['post_id'], + 'path' => $sql_row['post_path'], + 'url' => 'https://' . $sql_row['host'] . $sql_row['post_path'], + 'created_at' => $sql_row['post_create_time'] ?? '', + 'updated_at' => $sql_row['post_update_time'] ?? '', + 'view_count' => $sql_row['post_access_count'] ?? 0 + ]; + + $data['title'] = $sql_meta['title'] ?? $sql_row['title']; + unset($sql_meta['title']); + + $data['tags'] = $sql_meta['tags'] ?? []; + unset($sql_meta['tags']); + + $data['type'] = $sql_meta['type'] + ?? self::_deduce_type($sql_row['post_path']); + unset($sql_meta['type']); + + $data['icon'] = $sql_meta['icon'] + ?? self::_deduce_icon($data['type']); + unset($sql_meta['icon']); + + if(isset($sql_meta['media_url'])) { + $data['media_url'] = $sql_meta['media_url']; + $data['thumb_url'] = $sql_meta['thumb_url'] ?? $data['media_url']; + + unset($sql_meta['media_url']); + unset($sql_meta['thumb_url']); + } + + $data['banners'] = $sql_meta['banners'] + ?? $sql_settings['banners'] + ?? self::$default_banners; + + unset($sql_meta['banners']); + unset($sql_settings['banners']); + + $data['preview_image'] = $sql_meta['preview_image'] ?? $data['banners'][0]['src'] ?? null; + unset($sql_meta['preview_image']); + + $data['brief'] = $sql_meta['brief'] + ?? $data['title']; + unset($sql_meta['brief']); + + $data['excerpt'] = $sql_meta['excerpt'] + ?? substr($sql_row['post_content'], 0, 256); + + $data['metadata'] = $sql_meta; + $data['settings'] = $sql_settings; + + $this->data = $data; + } + + public function __get($name) { + if($name == 'html') { + return $this->get_html(); + } + if($name == 'markdown') { + return $this->sql_row['post_content']; + } + if($name == 'json') { + return $this->to_json(); + } + + return $this->data[$name]; + } + + public function get_html() { + $fn = self::$markdown_engine; + $this->content_html ??= $fn($this); + + return $this->content_html; + } + + public function to_json($with_markdown = false, $with_html = false) { + $out_data = $this->data; + + if($with_markdown) { + $out_data['markdown'] = $this->sql_row['post_content']; + } + if($with_html) { + $out_data['html'] = $this->get_html(); + } + + return json_encode($out_data); + } +} + +?> \ No newline at end of file diff --git a/www/post_adapter.php b/www/post_adapter.php index 09027cf..314f0d7 100644 --- a/www/post_adapter.php +++ b/www/post_adapter.php @@ -118,12 +118,14 @@ class PostHandler extends MySQLAdapter { } function handle_upload($post_path, $file_path) { + $post_path = $this->_sanitize_path($post_path); $ext = pathinfo($post_path, PATHINFO_EXTENSION); switch($ext) { case "md": $this->save_markdown_post($post_path, file_get_contents($file_path)); + $this->make_post_directory(dirname($post_path)); move_uploaded_file($file_path, $this->data_directory . $post_path); break; default: diff --git a/www/router.php b/www/router.php index 18c4641..7994645 100644 --- a/www/router.php +++ b/www/router.php @@ -5,6 +5,9 @@ $data_time_start = microtime(true); require_once 'vendor/autoload.php'; require_once 'post_adapter.php'; + +require_once 'post.php'; + require_once 'fontawesome.php'; require_once 'dergdown.php'; @@ -40,6 +43,10 @@ function dergdown_to_html($text) { return $Parsedown->text($text); } +function post_to_html($post) { + return dergdown_to_html($post->markdown); +} +PostData::$markdown_engine = "post_to_html"; function deduce_user_agent() { $real_agent=$_SERVER['HTTP_USER_AGENT']; @@ -160,6 +167,7 @@ function try_render_post($SURI) { break; + case 'blog': case 'text/markdown': echo render_twig('post_types/markdown.html', [ "post" => $post @@ -185,6 +193,30 @@ function try_render_post($SURI) { ]); break; + case 'blog_list': + if(preg_match('/^(.*[^\/])((?:#.*)?)$/', $SURI, $match)) { + header('Location: ' . $match[1] . '/' . $match[2]); + + die(); + } + + $search_query = $post['post_metadata']['search_tags'] ?? + ('+type:blog +path:' . $post['post_path'] . '/*'); + + $search_result = $adapter->perform_post_search($search_query); + + $search_result = array_map(function($key) { + $post = new PostData(null, $key); + return $post->data; + }, $search_result['results']); + + echo render_twig('post_types/blog_list.html', [ + "post" => $post, + "subposts" => $adapter->get_subposts_by_path($SURI), + "blog_posts" => $search_result + ]); + break; + case 'image': echo render_twig('post_types/image.html', [ "post" => $post, @@ -233,7 +265,9 @@ function generate_website($SURI) { } elseif(preg_match('/^\/api\/posts(.*)$/', $SURI, $match)) { header('Content-Type: application/json'); - echo json_encode($adapter->get_post_by_path($match[1])); + + $post = new PostData(null, $adapter->get_post_by_path($match[1])); + echo $post->to_json(with_markdown: true, with_html: true); } elseif(preg_match('/^\/api\/subposts(.*)$/', $SURI, $match)) { diff --git a/www/static/article_blop.css b/www/static/article_blop.css new file mode 100644 index 0000000..1c46c37 --- /dev/null +++ b/www/static/article_blop.css @@ -0,0 +1,81 @@ + + +.article_blop { + position: relative; + overflow: clip; + z-index: 0; + + background: var(--bg_2); + + margin: 2rem; + padding: 0; + + border-radius: 1rem; + + box-shadow: 0px 5px 5px 0px #00000040; + transition: 0.3s; + + min-height: 10rem; +} + +.article_blop_bg { + position: absolute; + left: 0px; + right: 0; + top: 0; + bottom: 0; + + background-repeat: no-repeat; + background-position: center; + background-size: cover; + + opacity: 0.2; + + z-index: -1; +} + +.article_blop:hover { + box-shadow: 0px 8px 8px 0px #00000040; +} + +.article_blop_content { + padding: 1rem; + padding-top: 5rem; +} + +.article_blop_tags { + position: absolute; + top: 0.5rem; + right: 0.2rem; + + width: 40%; + height: auto; + + list-style: none; + display: flex; + flex-direction: row; + flex-flow: row wrap; + + justify-content: right; +} +.article_blop_tags :first-child { + margin-left: auto; +} +.article_blop_tags li { + margin-right: 0.4rem; + margin-bottom: 0.4rem; + + padding-left: 0.2rem; + padding-right: 0.2rem; + + font-size: 0.8rem; + + font-style: normal; + font-weight: bold; + + background-color: var(--highlight_1); + border-radius: 0.3rem; + color: var(--bg_2); + + +} \ No newline at end of file diff --git a/www/static/dergstyle.css b/www/static/dergstyle.css index ccde5fa..8ee91c8 100644 --- a/www/static/dergstyle.css +++ b/www/static/dergstyle.css @@ -16,6 +16,7 @@ body { --bg_2: #2c2943; --bg_3: #3f4148; + --highlight_0: #ee9015b1; --highlight_1: #ee9015; --highlight_2: #edd29e; @@ -185,6 +186,7 @@ a:hover { z-index: 5; } + #post_file_titles { display: flex; flex-direction: row; @@ -194,13 +196,35 @@ a:hover { padding: 0px; } -#post_file_titles * { - padding: 0.5rem; +#post_file_titles li { + padding: 0.2rem 0.8rem; + font-style: bold; font-size: 1.3rem; background: var(--highlight_1); } +#post_file_titles li:hover { + background: var(--highlight_0) !important; +} + +#post_file_bar_dirbtn { + margin-left: 0.15rem; + background: var(--bg_2) !important; +} +#post_file_bar #dir_collapse { + display: none; +} +#post_file_bar #dir_collapse:checked + #post_file_bar_dirbtn { + background: var(--highlight_1) !important; +} + +.directory { + display: none; +} +#dir_collapse:checked ~ .directory { + display: block; +} #post_file_path { width: 100%; @@ -223,7 +247,7 @@ a:hover { padding-right: 0.2rem; } -#main_content_wrapper article { +article { background: var(--bg_3); border-radius: 0rem 0rem 0.8rem 0.8rem; @@ -232,7 +256,7 @@ a:hover { padding: 0.75rem; } -#main_content_wrapper article img { +article img { display: block; max-width: 100%; diff --git a/www/templates/fragments/article_blop.html b/www/templates/fragments/article_blop.html new file mode 100644 index 0000000..0000271 --- /dev/null +++ b/www/templates/fragments/article_blop.html @@ -0,0 +1,23 @@ + + + +
+
+ +
+

+ {{post.title}} +

+ +
    + {% for tag in post.tags %} +
  • {{ tag }}
  • + {% endfor %} +
+ + + {{post.excerpt}} + +
+
+
\ No newline at end of file diff --git a/www/templates/post_types/blog_list.html b/www/templates/post_types/blog_list.html new file mode 100644 index 0000000..9fe2add --- /dev/null +++ b/www/templates/post_types/blog_list.html @@ -0,0 +1,48 @@ + + +{% extends "pathed_content.html" %} + +{% block extra_head %} + + +{%endblock%} + +{%block content_article%} + {% if subposts|length > 0 %} + +

Art Subfolders:

+ + + + + + + {% for subpost in subposts %} + + + + + + + {% endfor %} +
NameTitleModified
+ {{ fa[subpost.post_metadata.icon] | raw }} + + {{subpost.post_basename}} + + {{ subpost.post_metadata.title }} + + {{ subpost.post_update_time }} +
+ {% endif %} + + {{ content_html|raw }} + + {% if blog_posts|length > 0 %} + {% for post in blog_posts %} + {% include('fragments/article_blop.html') %} + {% endfor %} + {%else%} +

How sad. There are no blog posts yet... What a real shame :c

+ {% endif %} +{%endblock%} From b8e449006bdb8520cb8b57de0649379bef4c85cb Mon Sep 17 00:00:00 2001 From: David Bailey Date: Mon, 12 Feb 2024 23:07:58 +0100 Subject: [PATCH 03/46] style: :sparkles: add HTMX --- www/fontawesome.php | 15 +- www/router.php | 23 +++- www/static/.htaccess | 4 +- www/static/banner.js | 22 ++- www/static/dergstyle.css | 130 ++++++++++++++---- www/static/htmx.min.js | 1 + www/static/three-dots.svg | 15 ++ www/templates/ajax/closed_folder_listing.html | 13 ++ www/templates/ajax/folder_listing.html | 4 + www/templates/ajax/folder_listing_entry.html | 26 ++++ www/templates/ajax/open_folder_listing.html | 20 +++ www/templates/fragments/filepath_bar.html | 52 +++++-- www/templates/pathed_content.html | 2 +- www/templates/post_types/blog_list.html | 28 ---- www/templates/post_types/directory.html | 2 +- www/templates/root.html | 26 +++- 16 files changed, 299 insertions(+), 84 deletions(-) create mode 100644 www/static/htmx.min.js create mode 100644 www/static/three-dots.svg create mode 100644 www/templates/ajax/closed_folder_listing.html create mode 100644 www/templates/ajax/folder_listing.html create mode 100644 www/templates/ajax/folder_listing_entry.html create mode 100644 www/templates/ajax/open_folder_listing.html diff --git a/www/fontawesome.php b/www/fontawesome.php index 91b82d5..924e1f7 100644 --- a/www/fontawesome.php +++ b/www/fontawesome.php @@ -1,11 +1,16 @@ '', - 'image' => '', - 'images' => '', - 'folder' => '', - 'rss' => '' + 'bars' => '', + 'markdown' => '', + 'image' => '', + 'images' => '', + 'turn-up' => '', + 'rectangle-list' => '', + 'folder-tree' => '', + 'folder' => '', + 'folder-open' => '', + 'rss' => '' ]; ?> \ No newline at end of file diff --git a/www/router.php b/www/router.php index 7994645..68bdbd4 100644 --- a/www/router.php +++ b/www/router.php @@ -133,6 +133,25 @@ function render_twig($template, $args = []) { echo $twig->render($template, $args); } +function try_render_ajax($SURI) { + global $adapter; + + $match = null; + preg_match('/^\/ajax\/([^\/]+)(.*)$/', $SURI, $match); + + if(!isset($match)) { + die(); + } + + $post = $adapter->get_post_by_path($match[2]); + $subposts = $adapter->get_subposts_by_path($match[2]); + + echo render_twig('ajax/' . $match[1] . '.html', [ + "post" => $post, + "subposts" => $subposts + ]); +} + function try_render_post($SURI) { global $adapter; @@ -281,6 +300,8 @@ function generate_website($SURI) { header('Content-Type: application/json'); echo json_encode($adapter->perform_post_search($_GET['search_query'])); } + } elseif(preg_match('/^\/ajax\//', $SURI)) { + try_render_ajax($SURI); } elseif(preg_match('/^\/feed(?:\/(rss|atom)(.*))?$/', $SURI, $match)) { $feed = $adapter->get_laminas_feed($match[2] ?? '/', $match[1] ?? 'rss'); @@ -289,7 +310,7 @@ function generate_website($SURI) { header('Etag: W/"' . $SURI . '/' . strtotime($feed['feed_ts']) . '"'); echo $feed['feed']; - } elseif(true) { + } else { try_render_post($SURI); } } diff --git a/www/static/.htaccess b/www/static/.htaccess index 1c117e2..cf3ad7c 100644 --- a/www/static/.htaccess +++ b/www/static/.htaccess @@ -3,9 +3,9 @@ Allow from all Options +Indexes - Header set Cache-Control "max-age=315360, public" + Header set Cache-Control "max-age=60, public" - Header set Cache-Control "max-age=315360, public" + Header set Cache-Control "max-age=60, public" \ No newline at end of file diff --git a/www/static/banner.js b/www/static/banner.js index 1dc78c1..3da66de 100644 --- a/www/static/banner.js +++ b/www/static/banner.js @@ -61,6 +61,8 @@ class BannerHandler { } updateTranslation() { + this.bannerContainerDOM = document.getElementById("main_header") + const bannerTranslateMax = -this.bannerDOM.clientHeight + this.bannerContainerDOM.clientHeight const bannerPercentageFrom = this.currentBannerData.from || 0; @@ -120,7 +122,7 @@ class BannerHandler { } updateTick() { - console.log("tick") + const nextPhase = this.getPhase() % 1; @@ -150,11 +152,17 @@ class BannerHandler { } } -var bannerHandler = new BannerHandler( - document.getElementById("main_header"), - document.getElementById("main_banner_img"), - document.getElementById("main_banner_img_link")) +let bannerHandler = null; -bannerHandler.start() +function startBanner() { + if(bannerHandler !== null) { + return; + } -// addEventListener("resize", () => update_banner(banner, banner_container)); + bannerHandler = new BannerHandler( + document.getElementById("main_header"), + document.getElementById("main_banner_img"), + document.getElementById("main_banner_img_link")) + + bannerHandler.start() +} diff --git a/www/static/dergstyle.css b/www/static/dergstyle.css index 8ee91c8..2a4c1f9 100644 --- a/www/static/dergstyle.css +++ b/www/static/dergstyle.css @@ -6,9 +6,11 @@ padding: 0; } -svg { +.fa-icn { fill: var(--text_1); padding-top: 0.1rem; + height: 1em; + vertical-align: -0.125em; } body { @@ -175,6 +177,35 @@ a:hover { background: var(--bg_2); } +body::before { + content: ''; + + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + + width: 100%; + height: 100%; + + background-image: url('/static/three-dots.svg'); + background-repeat: no-repeat; + background-position: center; + + opacity: 0; + + z-index: 10; + pointer-events: none; + + transition: opacity 0.2s; +} +body.htmx-request::before { + opacity: 1; + + transition-delay: 0.5s; +} + #post_file_bar { position: sticky; @@ -196,35 +227,14 @@ a:hover { padding: 0px; } -#post_file_titles li { +#post_file_titles > li { padding: 0.2rem 0.8rem; font-style: bold; - font-size: 1.3rem; + font-size: 1.5rem; background: var(--highlight_1); } -#post_file_titles li:hover { - background: var(--highlight_0) !important; -} - -#post_file_bar_dirbtn { - margin-left: 0.15rem; - background: var(--bg_2) !important; -} -#post_file_bar #dir_collapse { - display: none; -} -#post_file_bar #dir_collapse:checked + #post_file_bar_dirbtn { - background: var(--highlight_1) !important; -} - -.directory { - display: none; -} -#dir_collapse:checked ~ .directory { - display: block; -} #post_file_path { width: 100%; @@ -232,6 +242,8 @@ a:hover { padding-left: 0.5rem; background: var(--highlight_1); + + font-size: 1.1rem; display: flex; flex-direction: row; @@ -247,6 +259,76 @@ a:hover { padding-right: 0.2rem; } +.navbar-expand { + background-color: var(--bg_3); + + border: 0.15rem solid var(--highlight_1); + border-top: none; + + border-radius: 0 0 0.2rem 0.2rem; + overflow: clip; + + padding-bottom: 0.2rem; + padding-left: 0.3rem; + + max-height: 80vh; + overflow-y: scroll; +} + +.navbar-full-path { + width: 100%; + font-style: italic; + padding-left: 0.2rem; + + display: flex; + flex-direction: row; + list-style-type: none; + + overflow-x: scroll; + overflow-y: hidden; + + white-space: nowrap; +} +.navbar-full-path li { + margin-left: 0.3rem; +} + +.navbar-folder-list span, .navbar-folder-list label { + width: 1rem; + display: inline-block; + cursor: pointer; +} +.navbar-folder-list ul, .navbar-folder-list { + list-style: none; + padding-left: 0.4rem; + margin-left: 0.4rem; + + border-left: 1px solid var(--text_border); +} +.navbar-folder-list a:hover { + background-color: rgba(255, 255, 255, 0.1); +} +.folder-listing input { + display: none; +} +.folder-listing input + ul { + display: none; +} +.folder-listing input:checked + ul { + display: block; +} + +#navbar-expand-label { + cursor: pointer; +} +#navbar-expander, .navbar-expand { + display: none; +} + +#navbar-expander:checked + .navbar-expand { + display: block; +} + article { background: var(--bg_3); diff --git a/www/static/htmx.min.js b/www/static/htmx.min.js new file mode 100644 index 0000000..47eb70f --- /dev/null +++ b/www/static/htmx.min.js @@ -0,0 +1 @@ +(function(e,t){if(typeof define==="function"&&define.amd){define([],t)}else if(typeof module==="object"&&module.exports){module.exports=t()}else{e.htmx=e.htmx||t()}})(typeof self!=="undefined"?self:this,function(){return function(){"use strict";var Q={onLoad:F,process:zt,on:de,off:ge,trigger:ce,ajax:Nr,find:C,findAll:f,closest:v,values:function(e,t){var r=dr(e,t||"post");return r.values},remove:_,addClass:z,removeClass:n,toggleClass:$,takeClass:W,defineExtension:Ur,removeExtension:Br,logAll:V,logNone:j,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:false,scrollBehavior:"smooth",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get"],selfRequestsOnly:false,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null},parseInterval:d,_:t,createEventSource:function(e){return new EventSource(e,{withCredentials:true})},createWebSocket:function(e){var t=new WebSocket(e,[]);t.binaryType=Q.config.wsBinaryType;return t},version:"1.9.10"};var r={addTriggerHandler:Lt,bodyContains:se,canAccessLocalStorage:U,findThisElement:xe,filterValues:yr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Hr,getHeaders:xr,getInputValues:dr,getInternalData:ae,getSwapSpecification:wr,getTriggerSpecs:it,getTarget:ye,makeFragment:l,mergeObjects:le,makeSettleInfo:T,oobSwap:Ee,querySelectorExt:ue,selectAndSwap:je,settleImmediately:nr,shouldCancel:ut,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:R};var w=["get","post","put","delete","patch"];var i=w.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");var S=e("head"),q=e("title"),H=e("svg",true);function e(e,t=false){return new RegExp(`<${e}(\\s[^>]*>|>)([\\s\\S]*?)<\\/${e}>`,t?"gim":"im")}function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e.getAttribute&&e.getAttribute(t)}function o(e,t){return e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function te(e,t){return ee(e,t)||ee(e,"data-"+t)}function u(e){return e.parentElement}function re(){return document}function c(e,t){while(e&&!t(e)){e=u(e)}return e?e:null}function L(e,t,r){var n=te(t,r);var i=te(t,"hx-disinherit");if(e!==t&&i&&(i==="*"||i.split(" ").indexOf(r)>=0)){return"unset"}else{return n}}function ne(t,r){var n=null;c(t,function(e){return n=L(t,e,r)});if(n!=="unset"){return n}}function h(e,t){var r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.oMatchesSelector;return r&&r.call(e,t)}function A(e){var t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;var r=t.exec(e);if(r){return r[1].toLowerCase()}else{return""}}function a(e,t){var r=new DOMParser;var n=r.parseFromString(e,"text/html");var i=n.body;while(t>0){t--;i=i.firstChild}if(i==null){i=re().createDocumentFragment()}return i}function N(e){return/",0);return i.querySelector("template").content}switch(r){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return a(""+n+"
",1);case"col":return a(""+n+"
",2);case"tr":return a(""+n+"
",2);case"td":case"th":return a(""+n+"
",3);case"script":case"style":return a("
"+n+"
",1);default:return a(n,0)}}function ie(e){if(e){e()}}function I(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return I(e,"Function")}function P(e){return I(e,"Object")}function ae(e){var t="htmx-internal-data";var r=e[t];if(!r){r=e[t]={}}return r}function M(e){var t=[];if(e){for(var r=0;r=0}function se(e){if(e.getRootNode&&e.getRootNode()instanceof window.ShadowRoot){return re().body.contains(e.getRootNode().host)}else{return re().body.contains(e)}}function D(e){return e.trim().split(/\s+/)}function le(e,t){for(var r in t){if(t.hasOwnProperty(r)){e[r]=t[r]}}return e}function E(e){try{return JSON.parse(e)}catch(e){b(e);return null}}function U(){var e="htmx:localStorageTest";try{localStorage.setItem(e,e);localStorage.removeItem(e);return true}catch(e){return false}}function B(t){try{var e=new URL(t);if(e){t=e.pathname+e.search}if(!/^\/$/.test(t)){t=t.replace(/\/+$/,"")}return t}catch(e){return t}}function t(e){return Tr(re().body,function(){return eval(e)})}function F(t){var e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function V(){Q.logger=function(e,t,r){if(console){console.log(t,e,r)}}}function j(){Q.logger=null}function C(e,t){if(t){return e.querySelector(t)}else{return C(re(),e)}}function f(e,t){if(t){return e.querySelectorAll(t)}else{return f(re(),e)}}function _(e,t){e=g(e);if(t){setTimeout(function(){_(e);e=null},t)}else{e.parentElement.removeChild(e)}}function z(e,t,r){e=g(e);if(r){setTimeout(function(){z(e,t);e=null},r)}else{e.classList&&e.classList.add(t)}}function n(e,t,r){e=g(e);if(r){setTimeout(function(){n(e,t);e=null},r)}else{if(e.classList){e.classList.remove(t);if(e.classList.length===0){e.removeAttribute("class")}}}}function $(e,t){e=g(e);e.classList.toggle(t)}function W(e,t){e=g(e);oe(e.parentElement.children,function(e){n(e,t)});z(e,t)}function v(e,t){e=g(e);if(e.closest){return e.closest(t)}else{do{if(e==null||h(e,t)){return e}}while(e=e&&u(e));return null}}function s(e,t){return e.substring(0,t.length)===t}function G(e,t){return e.substring(e.length-t.length)===t}function J(e){var t=e.trim();if(s(t,"<")&&G(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function Z(e,t){if(t.indexOf("closest ")===0){return[v(e,J(t.substr(8)))]}else if(t.indexOf("find ")===0){return[C(e,J(t.substr(5)))]}else if(t==="next"){return[e.nextElementSibling]}else if(t.indexOf("next ")===0){return[K(e,J(t.substr(5)))]}else if(t==="previous"){return[e.previousElementSibling]}else if(t.indexOf("previous ")===0){return[Y(e,J(t.substr(9)))]}else if(t==="document"){return[document]}else if(t==="window"){return[window]}else if(t==="body"){return[document.body]}else{return re().querySelectorAll(J(t))}}var K=function(e,t){var r=re().querySelectorAll(t);for(var n=0;n=0;n--){var i=r[n];if(i.compareDocumentPosition(e)===Node.DOCUMENT_POSITION_FOLLOWING){return i}}};function ue(e,t){if(t){return Z(e,t)[0]}else{return Z(re().body,e)[0]}}function g(e){if(I(e,"String")){return C(e)}else{return e}}function ve(e,t,r){if(k(t)){return{target:re().body,event:e,listener:t}}else{return{target:g(e),event:t,listener:r}}}function de(t,r,n){jr(function(){var e=ve(t,r,n);e.target.addEventListener(e.event,e.listener)});var e=k(r);return e?r:n}function ge(t,r,n){jr(function(){var e=ve(t,r,n);e.target.removeEventListener(e.event,e.listener)});return k(r)?r:n}var me=re().createElement("output");function pe(e,t){var r=ne(e,t);if(r){if(r==="this"){return[xe(e,t)]}else{var n=Z(e,r);if(n.length===0){b('The selector "'+r+'" on '+t+" returned no matches!");return[me]}else{return n}}}}function xe(e,t){return c(e,function(e){return te(e,t)!=null})}function ye(e){var t=ne(e,"hx-target");if(t){if(t==="this"){return xe(e,"hx-target")}else{return ue(e,t)}}else{var r=ae(e);if(r.boosted){return re().body}else{return e}}}function be(e){var t=Q.config.attributesToSettle;for(var r=0;r0){o=e.substr(0,e.indexOf(":"));t=e.substr(e.indexOf(":")+1,e.length)}else{o=e}var r=re().querySelectorAll(t);if(r){oe(r,function(e){var t;var r=i.cloneNode(true);t=re().createDocumentFragment();t.appendChild(r);if(!Se(o,e)){t=r}var n={shouldSwap:true,target:e,fragment:t};if(!ce(e,"htmx:oobBeforeSwap",n))return;e=n.target;if(n["shouldSwap"]){Fe(o,e,e,t,a)}oe(a.elts,function(e){ce(e,"htmx:oobAfterSwap",n)})});i.parentNode.removeChild(i)}else{i.parentNode.removeChild(i);fe(re().body,"htmx:oobErrorNoTarget",{content:i})}return e}function Ce(e,t,r){var n=ne(e,"hx-select-oob");if(n){var i=n.split(",");for(var a=0;a0){var r=t.replace("'","\\'");var n=e.tagName.replace(":","\\:");var i=o.querySelector(n+"[id='"+r+"']");if(i&&i!==o){var a=e.cloneNode();we(e,i);s.tasks.push(function(){we(e,a)})}}})}function Oe(e){return function(){n(e,Q.config.addedClass);zt(e);Nt(e);qe(e);ce(e,"htmx:load")}}function qe(e){var t="[autofocus]";var r=h(e,t)?e:e.querySelector(t);if(r!=null){r.focus()}}function m(e,t,r,n){Te(e,r,n);while(r.childNodes.length>0){var i=r.firstChild;z(i,Q.config.addedClass);e.insertBefore(i,t);if(i.nodeType!==Node.TEXT_NODE&&i.nodeType!==Node.COMMENT_NODE){n.tasks.push(Oe(i))}}}function He(e,t){var r=0;while(r-1){var t=e.replace(H,"");var r=t.match(q);if(r){return r[2]}}}function je(e,t,r,n,i,a){i.title=Ve(n);var o=l(n);if(o){Ce(r,o,i);o=Be(r,o,a);Re(o);return Fe(e,r,t,o,i)}}function _e(e,t,r){var n=e.getResponseHeader(t);if(n.indexOf("{")===0){var i=E(n);for(var a in i){if(i.hasOwnProperty(a)){var o=i[a];if(!P(o)){o={value:o}}ce(r,a,o)}}}else{var s=n.split(",");for(var l=0;l0){var o=t[0];if(o==="]"){n--;if(n===0){if(a===null){i=i+"true"}t.shift();i+=")})";try{var s=Tr(e,function(){return Function(i)()},function(){return true});s.source=i;return s}catch(e){fe(re().body,"htmx:syntax:error",{error:e,source:i});return null}}}else if(o==="["){n++}if(Qe(o,a,r)){i+="(("+r+"."+o+") ? ("+r+"."+o+") : (window."+o+"))"}else{i=i+o}a=t.shift()}}}function y(e,t){var r="";while(e.length>0&&!t.test(e[0])){r+=e.shift()}return r}function tt(e){var t;if(e.length>0&&Ze.test(e[0])){e.shift();t=y(e,Ke).trim();e.shift()}else{t=y(e,x)}return t}var rt="input, textarea, select";function nt(e,t,r){var n=[];var i=Ye(t);do{y(i,Je);var a=i.length;var o=y(i,/[,\[\s]/);if(o!==""){if(o==="every"){var s={trigger:"every"};y(i,Je);s.pollInterval=d(y(i,/[,\[\s]/));y(i,Je);var l=et(e,i,"event");if(l){s.eventFilter=l}n.push(s)}else if(o.indexOf("sse:")===0){n.push({trigger:"sse",sseEvent:o.substr(4)})}else{var u={trigger:o};var l=et(e,i,"event");if(l){u.eventFilter=l}while(i.length>0&&i[0]!==","){y(i,Je);var f=i.shift();if(f==="changed"){u.changed=true}else if(f==="once"){u.once=true}else if(f==="consume"){u.consume=true}else if(f==="delay"&&i[0]===":"){i.shift();u.delay=d(y(i,x))}else if(f==="from"&&i[0]===":"){i.shift();if(Ze.test(i[0])){var c=tt(i)}else{var c=y(i,x);if(c==="closest"||c==="find"||c==="next"||c==="previous"){i.shift();var h=tt(i);if(h.length>0){c+=" "+h}}}u.from=c}else if(f==="target"&&i[0]===":"){i.shift();u.target=tt(i)}else if(f==="throttle"&&i[0]===":"){i.shift();u.throttle=d(y(i,x))}else if(f==="queue"&&i[0]===":"){i.shift();u.queue=y(i,x)}else if(f==="root"&&i[0]===":"){i.shift();u[f]=tt(i)}else if(f==="threshold"&&i[0]===":"){i.shift();u[f]=y(i,x)}else{fe(e,"htmx:syntax:error",{token:i.shift()})}}n.push(u)}}if(i.length===a){fe(e,"htmx:syntax:error",{token:i.shift()})}y(i,Je)}while(i[0]===","&&i.shift());if(r){r[t]=n}return n}function it(e){var t=te(e,"hx-trigger");var r=[];if(t){var n=Q.config.triggerSpecsCache;r=n&&n[t]||nt(e,t,n)}if(r.length>0){return r}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,rt)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function at(e){ae(e).cancelled=true}function ot(e,t,r){var n=ae(e);n.timeout=setTimeout(function(){if(se(e)&&n.cancelled!==true){if(!ct(r,e,Wt("hx:poll:trigger",{triggerSpec:r,target:e}))){t(e)}ot(e,t,r)}},r.pollInterval)}function st(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function lt(t,r,e){if(t.tagName==="A"&&st(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"){r.boosted=true;var n,i;if(t.tagName==="A"){n="get";i=ee(t,"href")}else{var a=ee(t,"method");n=a?a.toLowerCase():"get";if(n==="get"){}i=ee(t,"action")}e.forEach(function(e){ht(t,function(e,t){if(v(e,Q.config.disableSelector)){p(e);return}he(n,i,e,t)},r,e,true)})}}function ut(e,t){if(e.type==="submit"||e.type==="click"){if(t.tagName==="FORM"){return true}if(h(t,'input[type="submit"], button')&&v(t,"form")!==null){return true}if(t.tagName==="A"&&t.href&&(t.getAttribute("href")==="#"||t.getAttribute("href").indexOf("#")!==0)){return true}}return false}function ft(e,t){return ae(e).boosted&&e.tagName==="A"&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function ct(e,t,r){var n=e.eventFilter;if(n){try{return n.call(t,r)!==true}catch(e){fe(re().body,"htmx:eventFilter:error",{error:e,source:n.source});return true}}return false}function ht(a,o,e,s,l){var u=ae(a);var t;if(s.from){t=Z(a,s.from)}else{t=[a]}if(s.changed){t.forEach(function(e){var t=ae(e);t.lastValue=e.value})}oe(t,function(n){var i=function(e){if(!se(a)){n.removeEventListener(s.trigger,i);return}if(ft(a,e)){return}if(l||ut(e,a)){e.preventDefault()}if(ct(s,a,e)){return}var t=ae(e);t.triggerSpec=s;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(a)<0){t.handledFor.push(a);if(s.consume){e.stopPropagation()}if(s.target&&e.target){if(!h(e.target,s.target)){return}}if(s.once){if(u.triggeredOnce){return}else{u.triggeredOnce=true}}if(s.changed){var r=ae(n);if(r.lastValue===n.value){return}r.lastValue=n.value}if(u.delayed){clearTimeout(u.delayed)}if(u.throttle){return}if(s.throttle>0){if(!u.throttle){o(a,e);u.throttle=setTimeout(function(){u.throttle=null},s.throttle)}}else if(s.delay>0){u.delayed=setTimeout(function(){o(a,e)},s.delay)}else{ce(a,"htmx:trigger");o(a,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:s.trigger,listener:i,on:n});n.addEventListener(s.trigger,i)})}var vt=false;var dt=null;function gt(){if(!dt){dt=function(){vt=true};window.addEventListener("scroll",dt);setInterval(function(){if(vt){vt=false;oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(e){mt(e)})}},200)}}function mt(t){if(!o(t,"data-hx-revealed")&&X(t)){t.setAttribute("data-hx-revealed","true");var e=ae(t);if(e.initHash){ce(t,"revealed")}else{t.addEventListener("htmx:afterProcessNode",function(e){ce(t,"revealed")},{once:true})}}}function pt(e,t,r){var n=D(r);for(var i=0;i=0){var t=wt(n);setTimeout(function(){xt(s,r,n+1)},t)}};t.onopen=function(e){n=0};ae(s).webSocket=t;t.addEventListener("message",function(e){if(yt(s)){return}var t=e.data;R(s,function(e){t=e.transformResponse(t,null,s)});var r=T(s);var n=l(t);var i=M(n.children);for(var a=0;a0){ce(u,"htmx:validation:halted",i);return}t.send(JSON.stringify(l));if(ut(e,u)){e.preventDefault()}})}else{fe(u,"htmx:noWebSocketSourceError")}}function wt(e){var t=Q.config.wsReconnectDelay;if(typeof t==="function"){return t(e)}if(t==="full-jitter"){var r=Math.min(e,6);var n=1e3*Math.pow(2,r);return n*Math.random()}b('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function St(e,t,r){var n=D(r);for(var i=0;i0){setTimeout(i,n)}else{i()}}function Ht(t,i,e){var a=false;oe(w,function(r){if(o(t,"hx-"+r)){var n=te(t,"hx-"+r);a=true;i.path=n;i.verb=r;e.forEach(function(e){Lt(t,e,i,function(e,t){if(v(e,Q.config.disableSelector)){p(e);return}he(r,n,e,t)})})}});return a}function Lt(n,e,t,r){if(e.sseEvent){Rt(n,r,e.sseEvent)}else if(e.trigger==="revealed"){gt();ht(n,r,t,e);mt(n)}else if(e.trigger==="intersect"){var i={};if(e.root){i.root=ue(n,e.root)}if(e.threshold){i.threshold=parseFloat(e.threshold)}var a=new IntersectionObserver(function(e){for(var t=0;t0){t.polling=true;ot(n,r,e)}else{ht(n,r,t,e)}}function At(e){if(Q.config.allowScriptTags&&(e.type==="text/javascript"||e.type==="module"||e.type==="")){var t=re().createElement("script");oe(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}var r=e.parentElement;try{r.insertBefore(t,e)}catch(e){b(e)}finally{if(e.parentElement){e.parentElement.removeChild(e)}}}}function Nt(e){if(h(e,"script")){At(e)}oe(f(e,"script"),function(e){At(e)})}function It(e){var t=e.attributes;for(var r=0;r0){var o=n.shift();var s=o.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);if(a===0&&s){o.split(":");i=s[1].slice(0,-1);r[i]=s[2]}else{r[i]+=o}a+=Bt(o)}for(var l in r){Ft(e,l,r[l])}}}function jt(e){Ae(e);for(var t=0;tQ.config.historyCacheSize){i.shift()}while(i.length>0){try{localStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(re().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Yt(e){if(!U()){return null}e=B(e);var t=E(localStorage.getItem("htmx-history-cache"))||[];for(var r=0;r=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",o);var e=l(this.response);e=e.querySelector("[hx-history-elt],[data-hx-history-elt]")||e;var t=Zt();var r=T(t);var n=Ve(this.response);if(n){var i=C("title");if(i){i.innerHTML=n}else{window.document.title=n}}Ue(t,e,r);nr(r.tasks);Jt=a;ce(re().body,"htmx:historyRestore",{path:a,cacheMiss:true,serverResponse:this.response})}else{fe(re().body,"htmx:historyCacheMissLoadError",o)}};e.send()}function ar(e){er();e=e||location.pathname+location.search;var t=Yt(e);if(t){var r=l(t.content);var n=Zt();var i=T(n);Ue(n,r,i);nr(i.tasks);document.title=t.title;setTimeout(function(){window.scrollTo(0,t.scroll)},0);Jt=e;ce(re().body,"htmx:historyRestore",{path:e,item:t})}else{if(Q.config.refreshOnHistoryMiss){window.location.reload(true)}else{ir(e)}}}function or(e){var t=pe(e,"hx-indicator");if(t==null){t=[e]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.classList["add"].call(e.classList,Q.config.requestClass)});return t}function sr(e){var t=pe(e,"hx-disabled-elt");if(t==null){t=[]}oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)+1;e.setAttribute("disabled","")});return t}function lr(e,t){oe(e,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.classList["remove"].call(e.classList,Q.config.requestClass)}});oe(t,function(e){var t=ae(e);t.requestCount=(t.requestCount||0)-1;if(t.requestCount===0){e.removeAttribute("disabled")}})}function ur(e,t){for(var r=0;r=0}function wr(e,t){var r=t?t:ne(e,"hx-swap");var n={swapStyle:ae(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(e).boosted&&!br(e)){n["show"]="top"}if(r){var i=D(r);if(i.length>0){for(var a=0;a0?l.join(":"):null;n["scroll"]=u;n["scrollTarget"]=f}else if(o.indexOf("show:")===0){var c=o.substr(5);var l=c.split(":");var h=l.pop();var f=l.length>0?l.join(":"):null;n["show"]=h;n["showTarget"]=f}else if(o.indexOf("focus-scroll:")===0){var v=o.substr("focus-scroll:".length);n["focusScroll"]=v=="true"}else if(a==0){n["swapStyle"]=o}else{b("Unknown modifier in hx-swap: "+o)}}}}return n}function Sr(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function Er(t,r,n){var i=null;R(r,function(e){if(i==null){i=e.encodeParameters(t,n,r)}});if(i!=null){return i}else{if(Sr(r)){return pr(n)}else{return mr(n)}}}function T(e){return{tasks:[],elts:[e]}}function Cr(e,t){var r=e[0];var n=e[e.length-1];if(t.scroll){var i=null;if(t.scrollTarget){i=ue(r,t.scrollTarget)}if(t.scroll==="top"&&(r||i)){i=i||r;i.scrollTop=0}if(t.scroll==="bottom"&&(n||i)){i=i||n;i.scrollTop=i.scrollHeight}}if(t.show){var i=null;if(t.showTarget){var a=t.showTarget;if(t.showTarget==="window"){a="body"}i=ue(r,a)}if(t.show==="top"&&(r||i)){i=i||r;i.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(n||i)){i=i||n;i.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Rr(e,t,r,n){if(n==null){n={}}if(e==null){return n}var i=te(e,t);if(i){var a=i.trim();var o=r;if(a==="unset"){return null}if(a.indexOf("javascript:")===0){a=a.substr(11);o=true}else if(a.indexOf("js:")===0){a=a.substr(3);o=true}if(a.indexOf("{")!==0){a="{"+a+"}"}var s;if(o){s=Tr(e,function(){return Function("return ("+a+")")()},{})}else{s=E(a)}for(var l in s){if(s.hasOwnProperty(l)){if(n[l]==null){n[l]=s[l]}}}}return Rr(u(e),t,r,n)}function Tr(e,t,r){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return r}}function Or(e,t){return Rr(e,"hx-vars",true,t)}function qr(e,t){return Rr(e,"hx-vals",false,t)}function Hr(e){return le(Or(e),qr(e))}function Lr(t,r,n){if(n!==null){try{t.setRequestHeader(r,n)}catch(e){t.setRequestHeader(r,encodeURIComponent(n));t.setRequestHeader(r+"-URI-AutoEncoded","true")}}}function Ar(t){if(t.responseURL&&typeof URL!=="undefined"){try{var e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(re().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function O(e,t){return t.test(e.getAllResponseHeaders())}function Nr(e,t,r){e=e.toLowerCase();if(r){if(r instanceof Element||I(r,"String")){return he(e,t,null,null,{targetOverride:g(r),returnPromise:true})}else{return he(e,t,g(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:g(r.target),swapOverride:r.swap,select:r.select,returnPromise:true})}}else{return he(e,t,null,null,{returnPromise:true})}}function Ir(e){var t=[];while(e){t.push(e);e=e.parentElement}return t}function kr(e,t,r){var n;var i;if(typeof URL==="function"){i=new URL(t,document.location.href);var a=document.location.origin;n=a===i.origin}else{i=t;n=s(t,document.location.origin)}if(Q.config.selfRequestsOnly){if(!n){return false}}return ce(e,"htmx:validateUrl",le({url:i,sameHost:n},r))}function he(t,r,n,i,a,e){var o=null;var s=null;a=a!=null?a:{};if(a.returnPromise&&typeof Promise!=="undefined"){var l=new Promise(function(e,t){o=e;s=t})}if(n==null){n=re().body}var M=a.handler||Mr;var X=a.select||null;if(!se(n)){ie(o);return l}var u=a.targetOverride||ye(n);if(u==null||u==me){fe(n,"htmx:targetError",{target:te(n,"hx-target")});ie(s);return l}var f=ae(n);var c=f.lastButtonClicked;if(c){var h=ee(c,"formaction");if(h!=null){r=h}var v=ee(c,"formmethod");if(v!=null){if(v.toLowerCase()!=="dialog"){t=v}}}var d=ne(n,"hx-confirm");if(e===undefined){var D=function(e){return he(t,r,n,i,a,!!e)};var U={target:u,elt:n,path:r,verb:t,triggeringEvent:i,etc:a,issueRequest:D,question:d};if(ce(n,"htmx:confirm",U)===false){ie(o);return l}}var g=n;var m=ne(n,"hx-sync");var p=null;var x=false;if(m){var B=m.split(":");var F=B[0].trim();if(F==="this"){g=xe(n,"hx-sync")}else{g=ue(n,F)}m=(B[1]||"drop").trim();f=ae(g);if(m==="drop"&&f.xhr&&f.abortable!==true){ie(o);return l}else if(m==="abort"){if(f.xhr){ie(o);return l}else{x=true}}else if(m==="replace"){ce(g,"htmx:abort")}else if(m.indexOf("queue")===0){var V=m.split(" ");p=(V[1]||"last").trim()}}if(f.xhr){if(f.abortable){ce(g,"htmx:abort")}else{if(p==null){if(i){var y=ae(i);if(y&&y.triggerSpec&&y.triggerSpec.queue){p=y.triggerSpec.queue}}if(p==null){p="last"}}if(f.queuedRequests==null){f.queuedRequests=[]}if(p==="first"&&f.queuedRequests.length===0){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(p==="all"){f.queuedRequests.push(function(){he(t,r,n,i,a)})}else if(p==="last"){f.queuedRequests=[];f.queuedRequests.push(function(){he(t,r,n,i,a)})}ie(o);return l}}var b=new XMLHttpRequest;f.xhr=b;f.abortable=x;var w=function(){f.xhr=null;f.abortable=false;if(f.queuedRequests!=null&&f.queuedRequests.length>0){var e=f.queuedRequests.shift();e()}};var j=ne(n,"hx-prompt");if(j){var S=prompt(j);if(S===null||!ce(n,"htmx:prompt",{prompt:S,target:u})){ie(o);w();return l}}if(d&&!e){if(!confirm(d)){ie(o);w();return l}}var E=xr(n,u,S);if(t!=="get"&&!Sr(n)){E["Content-Type"]="application/x-www-form-urlencoded"}if(a.headers){E=le(E,a.headers)}var _=dr(n,t);var C=_.errors;var R=_.values;if(a.values){R=le(R,a.values)}var z=Hr(n);var $=le(R,z);var T=yr($,n);if(Q.config.getCacheBusterParam&&t==="get"){T["org.htmx.cache-buster"]=ee(u,"id")||"true"}if(r==null||r===""){r=re().location.href}var O=Rr(n,"hx-request");var W=ae(n).boosted;var q=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;var H={boosted:W,useUrlParams:q,parameters:T,unfilteredParameters:$,headers:E,target:u,verb:t,errors:C,withCredentials:a.credentials||O.credentials||Q.config.withCredentials,timeout:a.timeout||O.timeout||Q.config.timeout,path:r,triggeringEvent:i};if(!ce(n,"htmx:configRequest",H)){ie(o);w();return l}r=H.path;t=H.verb;E=H.headers;T=H.parameters;C=H.errors;q=H.useUrlParams;if(C&&C.length>0){ce(n,"htmx:validation:halted",H);ie(o);w();return l}var G=r.split("#");var J=G[0];var L=G[1];var A=r;if(q){A=J;var Z=Object.keys(T).length!==0;if(Z){if(A.indexOf("?")<0){A+="?"}else{A+="&"}A+=mr(T);if(L){A+="#"+L}}}if(!kr(n,A,H)){fe(n,"htmx:invalidPath",H);ie(s);return l}b.open(t.toUpperCase(),A,true);b.overrideMimeType("text/html");b.withCredentials=H.withCredentials;b.timeout=H.timeout;if(O.noHeaders){}else{for(var N in E){if(E.hasOwnProperty(N)){var K=E[N];Lr(b,N,K)}}}var I={xhr:b,target:u,requestConfig:H,etc:a,boosted:W,select:X,pathInfo:{requestPath:r,finalRequestPath:A,anchor:L}};b.onload=function(){try{var e=Ir(n);I.pathInfo.responsePath=Ar(b);M(n,I);lr(k,P);ce(n,"htmx:afterRequest",I);ce(n,"htmx:afterOnLoad",I);if(!se(n)){var t=null;while(e.length>0&&t==null){var r=e.shift();if(se(r)){t=r}}if(t){ce(t,"htmx:afterRequest",I);ce(t,"htmx:afterOnLoad",I)}}ie(o);w()}catch(e){fe(n,"htmx:onLoadError",le({error:e},I));throw e}};b.onerror=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendError",I);ie(s);w()};b.onabort=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:sendAbort",I);ie(s);w()};b.ontimeout=function(){lr(k,P);fe(n,"htmx:afterRequest",I);fe(n,"htmx:timeout",I);ie(s);w()};if(!ce(n,"htmx:beforeRequest",I)){ie(o);w();return l}var k=or(n);var P=sr(n);oe(["loadstart","loadend","progress","abort"],function(t){oe([b,b.upload],function(e){e.addEventListener(t,function(e){ce(n,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ce(n,"htmx:beforeSend",I);var Y=q?null:Er(b,n,T);b.send(Y);return l}function Pr(e,t){var r=t.xhr;var n=null;var i=null;if(O(r,/HX-Push:/i)){n=r.getResponseHeader("HX-Push");i="push"}else if(O(r,/HX-Push-Url:/i)){n=r.getResponseHeader("HX-Push-Url");i="push"}else if(O(r,/HX-Replace-Url:/i)){n=r.getResponseHeader("HX-Replace-Url");i="replace"}if(n){if(n==="false"){return{}}else{return{type:i,path:n}}}var a=t.pathInfo.finalRequestPath;var o=t.pathInfo.responsePath;var s=ne(e,"hx-push-url");var l=ne(e,"hx-replace-url");var u=ae(e).boosted;var f=null;var c=null;if(s){f="push";c=s}else if(l){f="replace";c=l}else if(u){f="push";c=o||a}if(c){if(c==="false"){return{}}if(c==="true"){c=o||a}if(t.pathInfo.anchor&&c.indexOf("#")===-1){c=c+"#"+t.pathInfo.anchor}return{type:f,path:c}}else{return{}}}function Mr(l,u){var f=u.xhr;var c=u.target;var e=u.etc;var t=u.requestConfig;var h=u.select;if(!ce(l,"htmx:beforeOnLoad",u))return;if(O(f,/HX-Trigger:/i)){_e(f,"HX-Trigger",l)}if(O(f,/HX-Location:/i)){er();var r=f.getResponseHeader("HX-Location");var v;if(r.indexOf("{")===0){v=E(r);r=v["path"];delete v["path"]}Nr("GET",r,v).then(function(){tr(r)});return}var n=O(f,/HX-Refresh:/i)&&"true"===f.getResponseHeader("HX-Refresh");if(O(f,/HX-Redirect:/i)){location.href=f.getResponseHeader("HX-Redirect");n&&location.reload();return}if(n){location.reload();return}if(O(f,/HX-Retarget:/i)){if(f.getResponseHeader("HX-Retarget")==="this"){u.target=l}else{u.target=ue(l,f.getResponseHeader("HX-Retarget"))}}var d=Pr(l,u);var i=f.status>=200&&f.status<400&&f.status!==204;var g=f.response;var a=f.status>=400;var m=Q.config.ignoreTitle;var o=le({shouldSwap:i,serverResponse:g,isError:a,ignoreTitle:m},u);if(!ce(c,"htmx:beforeSwap",o))return;c=o.target;g=o.serverResponse;a=o.isError;m=o.ignoreTitle;u.target=c;u.failed=a;u.successful=!a;if(o.shouldSwap){if(f.status===286){at(l)}R(l,function(e){g=e.transformResponse(g,f,l)});if(d.type){er()}var s=e.swapOverride;if(O(f,/HX-Reswap:/i)){s=f.getResponseHeader("HX-Reswap")}var v=wr(l,s);if(v.hasOwnProperty("ignoreTitle")){m=v.ignoreTitle}c.classList.add(Q.config.swappingClass);var p=null;var x=null;var y=function(){try{var e=document.activeElement;var t={};try{t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null}}catch(e){}var r;if(h){r=h}if(O(f,/HX-Reselect:/i)){r=f.getResponseHeader("HX-Reselect")}if(d.type){ce(re().body,"htmx:beforeHistoryUpdate",le({history:d},u));if(d.type==="push"){tr(d.path);ce(re().body,"htmx:pushedIntoHistory",{path:d.path})}else{rr(d.path);ce(re().body,"htmx:replacedInHistory",{path:d.path})}}var n=T(c);je(v.swapStyle,c,l,g,n,r);if(t.elt&&!se(t.elt)&&ee(t.elt,"id")){var i=document.getElementById(ee(t.elt,"id"));var a={preventScroll:v.focusScroll!==undefined?!v.focusScroll:!Q.config.defaultFocusScroll};if(i){if(t.start&&i.setSelectionRange){try{i.setSelectionRange(t.start,t.end)}catch(e){}}i.focus(a)}}c.classList.remove(Q.config.swappingClass);oe(n.elts,function(e){if(e.classList){e.classList.add(Q.config.settlingClass)}ce(e,"htmx:afterSwap",u)});if(O(f,/HX-Trigger-After-Swap:/i)){var o=l;if(!se(l)){o=re().body}_e(f,"HX-Trigger-After-Swap",o)}var s=function(){oe(n.tasks,function(e){e.call()});oe(n.elts,function(e){if(e.classList){e.classList.remove(Q.config.settlingClass)}ce(e,"htmx:afterSettle",u)});if(u.pathInfo.anchor){var e=re().getElementById(u.pathInfo.anchor);if(e){e.scrollIntoView({block:"start",behavior:"auto"})}}if(n.title&&!m){var t=C("title");if(t){t.innerHTML=n.title}else{window.document.title=n.title}}Cr(n.elts,v);if(O(f,/HX-Trigger-After-Settle:/i)){var r=l;if(!se(l)){r=re().body}_e(f,"HX-Trigger-After-Settle",r)}ie(p)};if(v.settleDelay>0){setTimeout(s,v.settleDelay)}else{s()}}catch(e){fe(l,"htmx:swapError",u);ie(x);throw e}};var b=Q.config.globalViewTransitions;if(v.hasOwnProperty("transition")){b=v.transition}if(b&&ce(l,"htmx:beforeTransition",u)&&typeof Promise!=="undefined"&&document.startViewTransition){var w=new Promise(function(e,t){p=e;x=t});var S=y;y=function(){document.startViewTransition(function(){S();return w})}}if(v.swapDelay>0){setTimeout(y,v.swapDelay)}else{y()}}if(a){fe(l,"htmx:responseError",le({error:"Response Status Error Code "+f.status+" from "+u.pathInfo.requestPath},u))}}var Xr={};function Dr(){return{init:function(e){return null},onEvent:function(e,t){return true},transformResponse:function(e,t,r){return e},isInlineSwap:function(e){return false},handleSwap:function(e,t,r,n){return false},encodeParameters:function(e,t,r){return null}}}function Ur(e,t){if(t.init){t.init(r)}Xr[e]=le(Dr(),t)}function Br(e){delete Xr[e]}function Fr(e,r,n){if(e==undefined){return r}if(r==undefined){r=[]}if(n==undefined){n=[]}var t=te(e,"hx-ext");if(t){oe(t.split(","),function(e){e=e.replace(/ /g,"");if(e.slice(0,7)=="ignore:"){n.push(e.slice(7));return}if(n.indexOf(e)<0){var t=Xr[e];if(t&&r.indexOf(t)<0){r.push(t)}}})}return Fr(u(e),r,n)}var Vr=false;re().addEventListener("DOMContentLoaded",function(){Vr=true});function jr(e){if(Vr||re().readyState==="complete"){e()}else{re().addEventListener("DOMContentLoaded",e)}}function _r(){if(Q.config.includeIndicatorStyles!==false){re().head.insertAdjacentHTML("beforeend","")}}function zr(){var e=re().querySelector('meta[name="htmx-config"]');if(e){return E(e.content)}else{return null}}function $r(){var e=zr();if(e){Q.config=le(Q.config,e)}}jr(function(){$r();_r();var e=re().body;zt(e);var t=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){var t=e.target;var r=ae(t);if(r&&r.xhr){r.xhr.abort()}});const r=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){ar();oe(t,function(e){ce(e,"htmx:restored",{document:re(),triggerEvent:ce})})}else{if(r){r(e)}}};setTimeout(function(){ce(e,"htmx:load",{});e=null},0)});return Q}()}); \ No newline at end of file diff --git a/www/static/three-dots.svg b/www/static/three-dots.svg new file mode 100644 index 0000000..47092c2 --- /dev/null +++ b/www/static/three-dots.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/www/templates/ajax/closed_folder_listing.html b/www/templates/ajax/closed_folder_listing.html new file mode 100644 index 0000000..399894f --- /dev/null +++ b/www/templates/ajax/closed_folder_listing.html @@ -0,0 +1,13 @@ + +
  • + + {{ fa[post.post_metadata.icon] | raw }} + + + + {{ post.post_metadata.title }} + +
  • \ No newline at end of file diff --git a/www/templates/ajax/folder_listing.html b/www/templates/ajax/folder_listing.html new file mode 100644 index 0000000..b90fb59 --- /dev/null +++ b/www/templates/ajax/folder_listing.html @@ -0,0 +1,4 @@ + +{% for post in subposts %} +{{ include('ajax/folder_listing_entry.html') }} +{% endfor %} \ No newline at end of file diff --git a/www/templates/ajax/folder_listing_entry.html b/www/templates/ajax/folder_listing_entry.html new file mode 100644 index 0000000..e47bf9b --- /dev/null +++ b/www/templates/ajax/folder_listing_entry.html @@ -0,0 +1,26 @@ + + +
  • + {% set folder_key = random() %} + + + + + {{ post.post_metadata.title }} + + + +
      +
    • + + Loading... + +
    • +
    +
  • \ No newline at end of file diff --git a/www/templates/ajax/open_folder_listing.html b/www/templates/ajax/open_folder_listing.html new file mode 100644 index 0000000..9bd6414 --- /dev/null +++ b/www/templates/ajax/open_folder_listing.html @@ -0,0 +1,20 @@ + + +
  • + + {{ fa[post.post_metadata.icon] | raw }} + + + + {{ post.post_metadata.title }} + + +
      + {% for post in subposts %} + {{ include('ajax/closed_folder_listing.html') }} + {% endfor %} +
    +
  • \ No newline at end of file diff --git a/www/templates/fragments/filepath_bar.html b/www/templates/fragments/filepath_bar.html index 31b5265..314e019 100644 --- a/www/templates/fragments/filepath_bar.html +++ b/www/templates/fragments/filepath_bar.html @@ -17,17 +17,53 @@ {% endif %} {% endfor %} + - -
  • - raw - api -
  • -
  • +
  • + + style="padding-left: 0.3rem;" href="/feed/rss{{post.post_path}}"> {{ fa['rss']|raw }}
  • - \ No newline at end of file + + + + diff --git a/www/templates/pathed_content.html b/www/templates/pathed_content.html index af5b566..90a0350 100644 --- a/www/templates/pathed_content.html +++ b/www/templates/pathed_content.html @@ -17,7 +17,7 @@ {{ include('fragments/filepath_bar.html') }} -
    +
    {%block content_article %} {%endblock%} diff --git a/www/templates/post_types/blog_list.html b/www/templates/post_types/blog_list.html index 9fe2add..1a104fd 100644 --- a/www/templates/post_types/blog_list.html +++ b/www/templates/post_types/blog_list.html @@ -8,34 +8,6 @@ {%endblock%} {%block content_article%} - {% if subposts|length > 0 %} - -

    Art Subfolders:

    - - - - - - - {% for subpost in subposts %} - - - - - - - {% endfor %} -
    NameTitleModified
    - {{ fa[subpost.post_metadata.icon] | raw }} - - {{subpost.post_basename}} - - {{ subpost.post_metadata.title }} - - {{ subpost.post_update_time }} -
    - {% endif %} - {{ content_html|raw }} {% if blog_posts|length > 0 %} diff --git a/www/templates/post_types/directory.html b/www/templates/post_types/directory.html index ffacda0..e0babeb 100644 --- a/www/templates/post_types/directory.html +++ b/www/templates/post_types/directory.html @@ -38,6 +38,6 @@ This shouldn't happen, sorry! {% endif %} - {{ post.post_content|markdown_to_html }} + {{ content_html|raw }} {%endblock%} diff --git a/www/templates/root.html b/www/templates/root.html index 01a87d6..0919f0e 100644 --- a/www/templates/root.html +++ b/www/templates/root.html @@ -2,14 +2,24 @@ {{og.site_name}} - {{og.title}} + + + + + + + + + + {% block feed_links %} {% endblock %} @@ -38,11 +48,9 @@ {% endblock %} - - + + {%if age_gate %}
    @@ -59,10 +67,14 @@ {% endif %}
    - - full picture + + full picture + + + startBanner(); +

    {% block big_title %}{{og.site_name}}{%endblock%}

    {% block second_title %}{% endblock %} From 0f2761cd61c9e5f6f96d7d51b413cfabfd5ba969 Mon Sep 17 00:00:00 2001 From: David Bailey Date: Mon, 12 Feb 2024 23:20:43 +0100 Subject: [PATCH 04/46] feat: :sparkles: preliminary work on revised Post system --- www/mysql_adapter.php | 16 +++++++++++++++- www/post.php | 44 ++++++++++++++++++++++++++++++++++++++----- www/post_adapter.php | 2 ++ www/router.php | 4 ++-- 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/www/mysql_adapter.php b/www/mysql_adapter.php index 23904a7..7a98c09 100644 --- a/www/mysql_adapter.php +++ b/www/mysql_adapter.php @@ -386,7 +386,8 @@ class MySQLAdapter { $post_data ??= ['found' => false]; $post_data['post_path'] = $post_path; - + $post_data['parent_path'] = dirname($post_path); + $post_data = $this->_normalize_post_data($post_data); if(!$post_data['found']) { @@ -403,6 +404,19 @@ class MySQLAdapter { return $post_data; } + function get_markdown_for_id($id) { + $qry = "SELECT post_content + FROM posts + WHERE post_id = ? AND host = ? + "; + + $post_data = $this->_exec($qry, "is", $id, + $this->SITE_CONFIG['HTTP_HOST'] + )->fetch_assoc(); + + return $post_data['post_content']; + } + function get_subposts_by_path($path) { global $sql; diff --git a/www/post.php b/www/post.php index 94c0da2..872b75f 100644 --- a/www/post.php +++ b/www/post.php @@ -43,6 +43,8 @@ class PostData { $icon_mapping = [ '' => 'question', 'text/markdown' => 'markdown', + 'blog' => 'markdown', + 'blog_list' => 'rectangle-list', 'directory' => 'folder', 'gallery' => 'images', 'image' => 'image' @@ -52,17 +54,18 @@ class PostData { } function __construct($post_handler, $sql_row) { - $this->post_handler = $post_handler; + $this->handler = $post_handler; $this->content_html = null; + $this->content_markdown = null; $sql_meta = null; + if(!isset($sql_row) or !$sql_row['found']) { $sql_row = $this->_generate_404($sql_row); $sql_meta = $sql_row['post_metadata']; } - $sql_meta = $sql_row['post_metadata']; if(is_string($sql_meta)) { $sql_meta = json_decode($sql_meta, true); @@ -72,7 +75,9 @@ class PostData { $this->sql_row = $sql_row; $this->sql_meta = $sql_meta ?? []; - $sql_settings = json_decode($sql_row['post_settings_cache'], true) ?? []; + + $sql_settings = json_decode($sql_row['post_settings_cache'], true) + ?? $this->handler->get_settings_for_path($sql_row['post_path']); $data = [ 'id' => $sql_row['post_id'], @@ -133,7 +138,7 @@ class PostData { return $this->get_html(); } if($name == 'markdown') { - return $this->sql_row['post_content']; + return $this->get_markdown(); } if($name == 'json') { return $this->to_json(); @@ -148,12 +153,18 @@ class PostData { return $this->content_html; } + public function get_markdown() { + $this->content_markdown ??= + $this->handler->get_markdown_for_id($this->data['id']); + + return $this->content_markdown; + } public function to_json($with_markdown = false, $with_html = false) { $out_data = $this->data; if($with_markdown) { - $out_data['markdown'] = $this->sql_row['post_content']; + $out_data['markdown'] = $this->get_markdown(); } if($with_html) { $out_data['html'] = $this->get_html(); @@ -161,6 +172,29 @@ class PostData { return json_encode($out_data); } + + + public function get_parent_post() { + $parent_path = dirname($this->data['path']); + if($parent_path == '') + return null; + + $this->parent_post ??= new PostData($this->handler, + $this->handler->get_post_by_path($parent_path)); + + return $this->parent_post; + } + + public function get_child_posts() { + if(isset($this->child_posts)) + return $this->child_posts; + + $child_data = $this->handler->get_subposts_by_path($this->data['path']); + + $this->child_posts = array_map(function($data) { + return new PostData($this->handler, $data); + }, $child_data); + } } ?> \ No newline at end of file diff --git a/www/post_adapter.php b/www/post_adapter.php index 314f0d7..05927e4 100644 --- a/www/post_adapter.php +++ b/www/post_adapter.php @@ -36,8 +36,10 @@ class PostHandler extends MySQLAdapter { $icon_mapping = [ '' => 'question', 'text/markdown' => 'markdown', + 'blog' => 'markdown', 'directory' => 'folder', 'gallery' => 'images', + 'blog_list' => 'rectangle-list', 'image' => 'image' ]; diff --git a/www/router.php b/www/router.php index 68bdbd4..8e0d1e7 100644 --- a/www/router.php +++ b/www/router.php @@ -225,7 +225,7 @@ function try_render_post($SURI) { $search_result = $adapter->perform_post_search($search_query); $search_result = array_map(function($key) { - $post = new PostData(null, $key); + $post = new PostData($adapter, $key); return $post->data; }, $search_result['results']); @@ -285,7 +285,7 @@ function generate_website($SURI) { header('Content-Type: application/json'); - $post = new PostData(null, $adapter->get_post_by_path($match[1])); + $post = new PostData($adapter, $adapter->get_post_by_path($match[1])); echo $post->to_json(with_markdown: true, with_html: true); } elseif(preg_match('/^\/api\/subposts(.*)$/', $SURI, $match)) { From 76ca7b9c32457f42b83aded7901899239373007b Mon Sep 17 00:00:00 2001 From: David Bailey Date: Thu, 15 Aug 2024 22:53:55 +0200 Subject: [PATCH 05/46] everything(everything): simply saving work --- .gitignore | 2 + Rakefile | 0 docker_dev/Dockerfile | 2 +- docker_dev/compose.yaml | 7 +- docker_dev/mysql_schema.sql | 53 ++- www/.htaccess | 13 +- www/composer.lock | 273 +++++++++----- www/post.php | 200 ---------- www/post_adapter.php | 221 ----------- www/src/db_handler/analytics_interface.php | 17 + www/src/db_handler/db_interface.php | 86 +++++ www/src/db_handler/mysql_handler.php | 348 ++++++++++++++++++ www/src/db_handler/post.php | 205 +++++++++++ www/src/db_handler/post_handler.php | 57 +++ www/src/dbtest.php | 187 ++++++++++ www/{ => src}/dergdown.php | 0 www/{ => src}/fontawesome.php | 0 .../old_mysql_adapter.php} | 0 www/{router.php => src/old_router.php} | 29 +- www/src/router.php | 19 + www/src/serve_post.php | 46 +++ www/src/setup_db.php | 43 +++ www/src/setup_site_config.php | 19 + www/src/setup_twig.php | 24 ++ www/templates/root.html | 40 +- 25 files changed, 1330 insertions(+), 561 deletions(-) create mode 100644 Rakefile delete mode 100644 www/post.php delete mode 100644 www/post_adapter.php create mode 100644 www/src/db_handler/analytics_interface.php create mode 100644 www/src/db_handler/db_interface.php create mode 100644 www/src/db_handler/mysql_handler.php create mode 100644 www/src/db_handler/post.php create mode 100644 www/src/db_handler/post_handler.php create mode 100644 www/src/dbtest.php rename www/{ => src}/dergdown.php (100%) rename www/{ => src}/fontawesome.php (100%) rename www/{mysql_adapter.php => src/old_mysql_adapter.php} (100%) rename www/{router.php => src/old_router.php} (91%) create mode 100644 www/src/router.php create mode 100644 www/src/serve_post.php create mode 100644 www/src/setup_db.php create mode 100644 www/src/setup_site_config.php create mode 100644 www/src/setup_twig.php diff --git a/.gitignore b/.gitignore index 0f521b3..e731746 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ + /vendor/ +.docker_vols sftp.json \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..e69de29 diff --git a/docker_dev/Dockerfile b/docker_dev/Dockerfile index 6440854..2060d63 100644 --- a/docker_dev/Dockerfile +++ b/docker_dev/Dockerfile @@ -2,7 +2,7 @@ FROM composer WORKDIR /app COPY www/composer.* . -RUN composer install +COPY www/vendor/* vendor/ FROM php:apache WORKDIR /var/www/html diff --git a/docker_dev/compose.yaml b/docker_dev/compose.yaml index 1b9493d..5a25529 100644 --- a/docker_dev/compose.yaml +++ b/docker_dev/compose.yaml @@ -24,7 +24,7 @@ services: - ../.git - mysql_schema.sql volumes: - - website_datavolume:/var/www/html/raw + - ../.docker_vols/web:/var/www/html/raw mysql: build: @@ -42,7 +42,4 @@ services: - path: mysql_schema.sql action: rebuild volumes: - - sqlvolume:/var/lib/mysql -volumes: - sqlvolume: {} - website_datavolume: {} + - ../.docker_vols/sql:/var/lib/mysql diff --git a/docker_dev/mysql_schema.sql b/docker_dev/mysql_schema.sql index baf0a58..97645e6 100644 --- a/docker_dev/mysql_schema.sql +++ b/docker_dev/mysql_schema.sql @@ -3,35 +3,59 @@ CREATE DATABASE dragon_fire; USE dragon_fire; +-- DROP TABLE posts; +-- DROP TABLE path_access_counts; +-- DROP TABLE path_errcodes; +-- DROP TABLE feed_cache; + CREATE TABLE posts ( post_id INTEGER AUTO_INCREMENT, host VARCHAR(64) NOT NULL, + post_path VARCHAR(255) NOT NULL, post_path_depth INTEGER NOT NULL DEFAULT 0, - post_create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - post_update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + post_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + post_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - post_access_count INTEGER DEFAULT 0, + post_view_count INTEGER DEFAULT 0, - post_metadata JSON NOT NULL, + post_title VARCHAR(1024), + post_tags VARCHAR(1024), + post_brief TEXT(2048), + + post_metadata JSON DEFAULT NULL, post_settings_cache JSON DEFAULT NULL, - post_content MEDIUMTEXT, - PRIMARY KEY(post_id), CONSTRAINT unique_post UNIQUE (host, post_path), INDEX(host, post_path), - INDEX(post_path_depth, post_path), - INDEX(post_create_time), - INDEX(post_update_time) + INDEX(host, post_path_depth, post_path), + + INDEX(host, post_created_at), + INDEX(host, post_updated_at), + + FULLTEXT(post_tags) +); + +CREATE TABLE post_markdown ( + post_id INTEGER, + + post_markdown TEXT, + + PRIMARY KEY(post_id), + FOREIGN KEY(post_id) REFERENCES posts(post_id) + ON DELETE CASCADE, + + FULLTEXT(post_markdown) ); CREATE TABLE path_access_counts ( access_time DATETIME NOT NULL, host VARCHAR(64) NOT NULL, + post_path VARCHAR(255), agent VARCHAR(255), referrer VARCHAR(255), @@ -42,6 +66,17 @@ CREATE TABLE path_access_counts ( PRIMARY KEY(access_time, host, post_path, agent, referrer) ); +CREATE TABLE path_errcodes ( + access_timestamp DATETIME NOT NULL, + + host VARCHAR(64) NOT NULL, + + post_path VARCHAR(255), + agent VARCHAR(255), + referrer VARCHAR(255), + error VARCHAR(1024), +); + CREATE TABLE feed_cache ( host VARCHAR(64) NOT NULL, search_path VARCHAR(255), diff --git a/www/.htaccess b/www/.htaccess index eb86f63..d17abcd 100644 --- a/www/.htaccess +++ b/www/.htaccess @@ -14,21 +14,20 @@ RewriteRule ^.*\.(flv|gif|ico|jpg|jpeg|mp4|mpeg|png|svg|swf|webp)$ raw/%{HTTP_HO RewriteRule ^/?raw/(.*)$ raw/%{HTTP_HOST}/$1 [L,END] -RewriteEngine On -RewriteCond %{HTTPS} !on -RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,END] +# RewriteCond %{HTTPS} !on +# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,END] -RewriteCond %{REQUEST_URI} !^/?(static|raw|robots\.txt).* -RewriteRule (.*) router.php +RewriteCond %{REQUEST_URI} !^/?(src/dbtest\.php|static|raw|robots\.txt).* +RewriteRule (.*) src/router.php Allow from all Options +Indexes - Header set Cache-Control "max-age=315360, public" + Header set Cache-Control "max-age=60, public" - Header set Cache-Control "max-age=315360, public" + Header set Cache-Control "max-age=60, public" \ No newline at end of file diff --git a/www/composer.lock b/www/composer.lock index c157e3a..d41c77f 100644 --- a/www/composer.lock +++ b/www/composer.lock @@ -8,16 +8,16 @@ "packages": [ { "name": "dflydev/dot-access-data", - "version": "v3.0.2", + "version": "v3.0.3", "source": { "type": "git", "url": "https://github.com/dflydev/dflydev-dot-access-data.git", - "reference": "f41715465d65213d644d3141a6a93081be5d3549" + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", - "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", "shasum": "" }, "require": { @@ -77,9 +77,9 @@ ], "support": { "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", - "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" }, - "time": "2022-10-27T11:44:00+00:00" + "time": "2024-07-08T12:26:09+00:00" }, { "name": "erusev/parsedown", @@ -519,16 +519,16 @@ }, { "name": "league/commonmark", - "version": "2.4.1", + "version": "2.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" + "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", - "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ac815920de0eff6de947eac0a6a94e5ed0fb147c", + "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c", "shasum": "" }, "require": { @@ -541,8 +541,8 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", - "commonmark/commonmark.js": "0.30.0", + "commonmark/cmark": "0.31.0", + "commonmark/commonmark.js": "0.31.0", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", @@ -551,10 +551,10 @@ "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "symfony/finder": "^5.3 | ^6.0 || ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -564,7 +564,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "2.6-dev" } }, "autoload": { @@ -621,7 +621,7 @@ "type": "tidelift" } ], - "time": "2023-08-30T16:55:00+00:00" + "time": "2024-07-24T12:52:09+00:00" }, { "name": "league/config", @@ -707,31 +707,31 @@ }, { "name": "nette/schema", - "version": "v1.2.5", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", + "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": "7.1 - 8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.3" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", + "nette/tester": "^2.4", "phpstan/phpstan-nette": "^1.0", - "tracy/tracy": "^2.7" + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -763,26 +763,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.5" + "source": "https://github.com/nette/schema/tree/v1.3.0" }, - "time": "2023-10-05T20:37:59+00:00" + "time": "2023-12-11T11:54:22+00:00" }, { "name": "nette/utils", - "version": "v4.0.3", + "version": "v4.0.5", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015" + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015", + "url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", + "reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96", "shasum": "" }, "require": { - "php": ">=8.0 <8.4" + "php": "8.0 - 8.4" }, "conflict": { "nette/finder": "<3", @@ -849,9 +849,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.3" + "source": "https://github.com/nette/utils/tree/v4.0.5" }, - "time": "2023-10-29T21:02:13+00:00" + "time": "2024-08-07T15:39:19+00:00" }, { "name": "psr/event-dispatcher", @@ -983,16 +983,16 @@ }, { "name": "spatie/yaml-front-matter", - "version": "2.0.8", + "version": "2.0.9", "source": { "type": "git", "url": "https://github.com/spatie/yaml-front-matter.git", - "reference": "f2f1f749a405fafc9d6337067c92c062d51a581c" + "reference": "cbe67e1cdd0a29a96d74ccab9400fe663e078392" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/yaml-front-matter/zipball/f2f1f749a405fafc9d6337067c92c062d51a581c", - "reference": "f2f1f749a405fafc9d6337067c92c062d51a581c", + "url": "https://api.github.com/repos/spatie/yaml-front-matter/zipball/cbe67e1cdd0a29a96d74ccab9400fe663e078392", + "reference": "cbe67e1cdd0a29a96d74ccab9400fe663e078392", "shasum": "" }, "require": { @@ -1029,7 +1029,7 @@ "yaml" ], "support": { - "source": "https://github.com/spatie/yaml-front-matter/tree/2.0.8" + "source": "https://github.com/spatie/yaml-front-matter/tree/2.0.9" }, "funding": [ { @@ -1041,20 +1041,20 @@ "type": "github" } ], - "time": "2023-12-04T10:02:52+00:00" + "time": "2024-06-13T10:20:51+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -1063,7 +1063,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1092,7 +1092,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -1108,20 +1108,20 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { @@ -1135,9 +1135,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1174,7 +1171,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -1190,20 +1187,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -1217,9 +1214,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1257,7 +1251,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -1273,20 +1267,20 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", "shasum": "" }, "require": { @@ -1294,9 +1288,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -1340,7 +1331,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" }, "funding": [ { @@ -1356,20 +1347,96 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { - "name": "symfony/yaml", - "version": "v7.0.0", + "name": "symfony/polyfill-php81", + "version": "v1.30.0", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "0055b230c408428b9b5cde7c55659555be5c0278" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/0055b230c408428b9b5cde7c55659555be5c0278", - "reference": "0055b230c408428b9b5cde7c55659555be5c0278", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "fa34c77015aa6720469db7003567b9f772492bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2", + "reference": "fa34c77015aa6720469db7003567b9f772492bf2", "shasum": "" }, "require": { @@ -1411,7 +1478,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.0.0" + "source": "https://github.com/symfony/yaml/tree/v7.1.1" }, "funding": [ { @@ -1427,24 +1494,25 @@ "type": "tidelift" } ], - "time": "2023-11-07T10:26:03+00:00" + "time": "2024-05-31T14:57:53+00:00" }, { "name": "twig/markdown-extra", - "version": "v3.8.0", + "version": "v3.11.0", "source": { "type": "git", "url": "https://github.com/twigphp/markdown-extra.git", - "reference": "b6e4954ab60030233df5d293886b5404558daac8" + "reference": "504557d60d80478260ebd2221a2b3332a480865d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/b6e4954ab60030233df5d293886b5404558daac8", - "reference": "b6e4954ab60030233df5d293886b5404558daac8", + "url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/504557d60d80478260ebd2221a2b3332a480865d", + "reference": "504557d60d80478260ebd2221a2b3332a480865d", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", "twig/twig": "^3.0" }, "require-dev": { @@ -1456,6 +1524,9 @@ }, "type": "library", "autoload": { + "files": [ + "Resources/functions.php" + ], "psr-4": { "Twig\\Extra\\Markdown\\": "" }, @@ -1483,7 +1554,7 @@ "twig" ], "support": { - "source": "https://github.com/twigphp/markdown-extra/tree/v3.8.0" + "source": "https://github.com/twigphp/markdown-extra/tree/v3.11.0" }, "funding": [ { @@ -1495,34 +1566,42 @@ "type": "tidelift" } ], - "time": "2023-11-21T14:02:01+00:00" + "time": "2024-08-07T17:34:09+00:00" }, { "name": "twig/twig", - "version": "v3.8.0", + "version": "v3.11.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" + "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", - "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", + "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php80": "^1.22" + "symfony/polyfill-php80": "^1.22", + "symfony/polyfill-php81": "^1.29" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" }, "type": "library", "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], "psr-4": { "Twig\\": "src/" } @@ -1555,7 +1634,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.8.0" + "source": "https://github.com/twigphp/Twig/tree/v3.11.0" }, "funding": [ { @@ -1567,7 +1646,7 @@ "type": "tidelift" } ], - "time": "2023-11-21T18:54:41+00:00" + "time": "2024-08-08T16:15:16+00:00" } ], "packages-dev": [], diff --git a/www/post.php b/www/post.php deleted file mode 100644 index 872b75f..0000000 --- a/www/post.php +++ /dev/null @@ -1,200 +0,0 @@ - - -1, - 'post_path' => '/404.md', - 'post_title' => '404 Page', - 'post_metadata' => [ - 'type' => '404' - ] - ]; - } - - public static function _deduce_type($path) { - $ext = pathinfo($path, PATHINFO_EXTENSION); - - if(preg_match("/\.(\w+)\.md$/", $path, $ext_match)) { - $ext = $ext_match[1]; - } - - $ext_mapping = [ - '' => 'directory', - 'md' => 'text/markdown', - 'png' => 'image', - 'jpg' => 'image', - 'jpeg' => 'image' - ]; - - return $ext_mapping[$ext] ?? '?'; - } - - public static function _deduce_icon($type) { - $icon_mapping = [ - '' => 'question', - 'text/markdown' => 'markdown', - 'blog' => 'markdown', - 'blog_list' => 'rectangle-list', - 'directory' => 'folder', - 'gallery' => 'images', - 'image' => 'image' - ]; - - return $icon_mapping[$type] ?? 'unknown'; - } - - function __construct($post_handler, $sql_row) { - $this->handler = $post_handler; - - $this->content_html = null; - $this->content_markdown = null; - - $sql_meta = null; - - if(!isset($sql_row) or !$sql_row['found']) { - $sql_row = $this->_generate_404($sql_row); - $sql_meta = $sql_row['post_metadata']; - } - - $sql_meta = $sql_row['post_metadata']; - if(is_string($sql_meta)) { - $sql_meta = json_decode($sql_meta, true); - } - - unset($sql_meta['settings']); - - $this->sql_row = $sql_row; - $this->sql_meta = $sql_meta ?? []; - - $sql_settings = json_decode($sql_row['post_settings_cache'], true) - ?? $this->handler->get_settings_for_path($sql_row['post_path']); - - $data = [ - 'id' => $sql_row['post_id'], - 'path' => $sql_row['post_path'], - 'url' => 'https://' . $sql_row['host'] . $sql_row['post_path'], - 'created_at' => $sql_row['post_create_time'] ?? '', - 'updated_at' => $sql_row['post_update_time'] ?? '', - 'view_count' => $sql_row['post_access_count'] ?? 0 - ]; - - $data['title'] = $sql_meta['title'] ?? $sql_row['title']; - unset($sql_meta['title']); - - $data['tags'] = $sql_meta['tags'] ?? []; - unset($sql_meta['tags']); - - $data['type'] = $sql_meta['type'] - ?? self::_deduce_type($sql_row['post_path']); - unset($sql_meta['type']); - - $data['icon'] = $sql_meta['icon'] - ?? self::_deduce_icon($data['type']); - unset($sql_meta['icon']); - - if(isset($sql_meta['media_url'])) { - $data['media_url'] = $sql_meta['media_url']; - $data['thumb_url'] = $sql_meta['thumb_url'] ?? $data['media_url']; - - unset($sql_meta['media_url']); - unset($sql_meta['thumb_url']); - } - - $data['banners'] = $sql_meta['banners'] - ?? $sql_settings['banners'] - ?? self::$default_banners; - - unset($sql_meta['banners']); - unset($sql_settings['banners']); - - $data['preview_image'] = $sql_meta['preview_image'] ?? $data['banners'][0]['src'] ?? null; - unset($sql_meta['preview_image']); - - $data['brief'] = $sql_meta['brief'] - ?? $data['title']; - unset($sql_meta['brief']); - - $data['excerpt'] = $sql_meta['excerpt'] - ?? substr($sql_row['post_content'], 0, 256); - - $data['metadata'] = $sql_meta; - $data['settings'] = $sql_settings; - - $this->data = $data; - } - - public function __get($name) { - if($name == 'html') { - return $this->get_html(); - } - if($name == 'markdown') { - return $this->get_markdown(); - } - if($name == 'json') { - return $this->to_json(); - } - - return $this->data[$name]; - } - - public function get_html() { - $fn = self::$markdown_engine; - $this->content_html ??= $fn($this); - - return $this->content_html; - } - public function get_markdown() { - $this->content_markdown ??= - $this->handler->get_markdown_for_id($this->data['id']); - - return $this->content_markdown; - } - - public function to_json($with_markdown = false, $with_html = false) { - $out_data = $this->data; - - if($with_markdown) { - $out_data['markdown'] = $this->get_markdown(); - } - if($with_html) { - $out_data['html'] = $this->get_html(); - } - - return json_encode($out_data); - } - - - public function get_parent_post() { - $parent_path = dirname($this->data['path']); - if($parent_path == '') - return null; - - $this->parent_post ??= new PostData($this->handler, - $this->handler->get_post_by_path($parent_path)); - - return $this->parent_post; - } - - public function get_child_posts() { - if(isset($this->child_posts)) - return $this->child_posts; - - $child_data = $this->handler->get_subposts_by_path($this->data['path']); - - $this->child_posts = array_map(function($data) { - return new PostData($this->handler, $data); - }, $child_data); - } -} - -?> \ No newline at end of file diff --git a/www/post_adapter.php b/www/post_adapter.php deleted file mode 100644 index 05927e4..0000000 --- a/www/post_adapter.php +++ /dev/null @@ -1,221 +0,0 @@ -data_directory = 'raw/' . $this->SITE_CONFIG['HTTP_HOST']; - } - - function deduce_post_type($post_path) { - $ext = pathinfo($post_path, PATHINFO_EXTENSION); - - if(preg_match("/\.(\w+)\.md$/", $post_path, $ext_match)) { - $ext = $ext_match[1]; - } - - $ext_mapping = [ - '' => 'directory', - 'md' => 'text/markdown', - 'png' => 'image', - 'jpg' => 'image', - 'jpeg' => 'image' - ]; - - return $ext_mapping[$ext] ?? '?'; - } - - function fill_in_post_meta($post_path, $meta) { - $icon_mapping = [ - '' => 'question', - 'text/markdown' => 'markdown', - 'blog' => 'markdown', - 'directory' => 'folder', - 'gallery' => 'images', - 'blog_list' => 'rectangle-list', - 'image' => 'image' - ]; - - $meta["title"] ??= basename($post_path); - - if($meta["title"] == "") { - $meta["title"] = "root"; - } - - if(!isset($meta['media_file']) and preg_match("/\.(\w+)\.md$/", $post_path)) { - $meta['media_file'] = "https://" . $this->SITE_CONFIG['HTTP_HOST'] . chop($post_path, ".md"); - } - - $meta['tags'] ??= []; - $meta['type'] ??= $this->deduce_post_type($post_path); - - $meta['icon'] ??= $icon_mapping[$meta['type']] ?? 'question'; - - return $meta; - } - - function _normalize_post_data($post_data) { - $post_data = parent::_normalize_post_data($post_data); - - if(!$post_data['found']) { - return $post_data; - } - - $post_data["post_basename"] = basename($post_data["post_path"]); - - $post_meta = $post_data['post_metadata']; - - $post_data['post_metadata'] = $this->fill_in_post_meta( - $post_data['post_path'], - $post_meta); - - $post_data["post_file_dir"] = '/raw' . $post_data["post_path"]; - - return $post_data; - } - - function make_post_directory($directory) { - $data_directory = $this->data_directory . $directory; - - is_dir($data_directory) || mkdir($data_directory, 0777, true); - - parent::make_post_directory($directory); - } - - function update_or_create_post(...$args) { - $this->_exec("TRUNCATE feed_cache"); - parent::update_or_create_post(...$args); - } - - function save_file($post_path, $file_path) { - move_uploaded_file($file_path, $this->data_directory . $post_path); - } - - function save_markdown_post($post_path, $post_data) { - if(basename($post_path) == "README.md") { - $post_path = dirname($post_path); - } - - $frontmatter_post = YamlFrontMatter::parse($post_data); - $post_path = $this->_sanitize_path($post_path); - - $post_content = $frontmatter_post->body(); - - $post_metadata = $frontmatter_post->matter(); - - $post_metadata = $this->fill_in_post_meta( - $post_path, - $post_metadata); - - $post_metadata['tags'][]= 'type:' . $post_metadata['type']; - - $this->update_or_create_post($post_path, $post_metadata, $post_content); - } - - function handle_upload($post_path, $file_path) { - $post_path = $this->_sanitize_path($post_path); - $ext = pathinfo($post_path, PATHINFO_EXTENSION); - - switch($ext) { - case "md": - $this->save_markdown_post($post_path, file_get_contents($file_path)); - - $this->make_post_directory(dirname($post_path)); - move_uploaded_file($file_path, $this->data_directory . $post_path); - break; - default: - $this->save_file($post_path, $file_path); - } - } - - function try_get_cached_feed($path, $export_opt) { - $post_cache = $this->_exec("SELECT feed_content, feed_created_on - FROM feed_cache - WHERE host=? AND search_path=? AND export_type=?", - "sss", $this->SITE_CONFIG['HTTP_HOST'], $path, $export_opt)->fetch_assoc(); - - if(!isset($post_cache)) { - return null; - } - - return ['feed' => $post_cache['feed_content'], 'feed_ts' => $post_cache['feed_created_on']]; - } - - function construct_feed($path) { - $path = $this->_sanitize_path($path); - - $feed = @new Feed; - - $feed->setTitle("DergFeed"); - $feed->setLink($this->SITE_CONFIG['uri_prefix'] . $path); - $feed->setFeedLink($this->SITE_CONFIG['uri_prefix'] . "/feeds/atom" . $path, "atom"); - - $feed->setDateModified(time()); - - $feed->setDescription("DergenFeed for all your " . $path . " needs"); - - $feed_posts = $this->_exec("SELECT - post_path, - post_create_time, post_update_time, - post_content, - post_metadata - FROM posts - WHERE (host = ?) AND ((post_path = ?) OR (post_path LIKE ?)) - ORDER BY post_create_time DESC LIMIT 200", - "sss", $this->SITE_CONFIG['HTTP_HOST'], $path, $path . '/%'); - - while($row = $feed_posts->fetch_array(MYSQLI_ASSOC)) { - $row = $this->_normalize_post_data($row); - $pmeta = $row['post_metadata']; - - if($pmeta['type'] == 'directory') { - continue; - } - - $entry = $feed->createEntry(); - - $entry->setTitle($row['post_path'] . '> ' . $pmeta['title']); - $entry->setLink($this->SITE_CONFIG['uri_prefix'] . $row['post_path']); - $entry->setDateModified(strtotime($row['post_update_time'])); - $entry->setDateCreated(strtotime($row['post_create_time'])); - - $entry->setDescription($pmeta['brief'] ?? $pmeta['title']); - - $feed->addEntry($entry); - } - - return $feed; - } - - function get_laminas_feed($path, $export_opt) { - $path = $this->_sanitize_path($path); - - $feed_cache = $this->try_get_cached_feed($path, $export_opt); - - if(isset($feed_cache)) { - return $feed_cache; - } - - $feed = $this->construct_feed($path); - - $this->_exec("INSERT INTO feed_cache - (host, search_path, export_type, feed_content) - VALUES - (?, ?, 'atom', ?), - (?, ?, 'rss', ?)", - "ssssss", - $this->SITE_CONFIG['HTTP_HOST'], $path, $feed->export('atom'), - $this->SITE_CONFIG['HTTP_HOST'], $path, $feed->export('rss')); - - return $this->try_get_cached_feed($path, $export_opt); - } -} - -?> \ No newline at end of file diff --git a/www/src/db_handler/analytics_interface.php b/www/src/db_handler/analytics_interface.php new file mode 100644 index 0000000..fbe3400 --- /dev/null +++ b/www/src/db_handler/analytics_interface.php @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/www/src/db_handler/db_interface.php b/www/src/db_handler/db_interface.php new file mode 100644 index 0000000..ea72459 --- /dev/null +++ b/www/src/db_handler/db_interface.php @@ -0,0 +1,86 @@ + +escape_tag($matches[2]) . $matches[3]; +} + +interface PostdataInterface { + /* Postdata format: + * + * The Postdata array is a simple intermediate data format + * for the Post content and metadata. + * It is slightly abstracted from the SQL format itself but will + * only reformat keys, *not* do any alteration of the data itself. + * + * Any supported fields will be integrated into the database. + * Other fields will be saved in a JSON structure, and will + * be restored afterward. + * + * The following fields are mandatory for *writing* + * - path: String, must be sanitized to consist of just alphanumeric + * characters, `_-./` + * used to identify the post itself + * + * The following fields may be returned by the database: + * - id + * - created_at + * - updated_at + * - view_count + * + * The following fields may be supported by the database: + * - markdown: String, markdown of the post. May be + * stored separately and won't be returned by default! + * - type: String, defining the type of the post + * - title: String, self-explanatory + * - tags: Array of strings + * - settings: Hash, recursively merged settings (calculated by DB!) + * + * The following fields are *recommended*, but nothing more: + * - icon: String, optionally defining + */ + + public function stub_postdata($path); + public function stub_postdata_tree($path); + + public function set_postdata($data); + public function set_post_markdown($id, $markdown); + + public function get_postdata($path); + // Returns a key-value pair of child paths => child data + public function get_post_children($path, + $limit = 50, $depth_start = 1, $depth_end = 1, + $order_by = 'path'); + + public function get_post_markdown($id); +} + +?> \ No newline at end of file diff --git a/www/src/db_handler/mysql_handler.php b/www/src/db_handler/mysql_handler.php new file mode 100644 index 0000000..def19df --- /dev/null +++ b/www/src/db_handler/mysql_handler.php @@ -0,0 +1,348 @@ + +sql_connection = $sql_connection; + $this->hostname = $hostname; + + $this->debugging = false; + } + + private function _dbg($message) { + if($this->debugging) { + echo $message; + } + } + + private function _exec($qery, $argtypes = '', ...$args) { + $stmt = $this->sql_connection->prepare($qery); + + if($argtypes != ""){ + $stmt->bind_param($argtypes, ...$args); + } + $stmt->execute(); + + return $stmt->get_result(); + } + + private function clear_post_settings_cache($post_path) { + $post_path = sanitize_post_path($post_path); + + $this->_exec(" + UPDATE posts + SET post_settings_cache=NULL + WHERE host = ? AND post_path LIKE ?; + ", "ss", $this->hostname, $post_path . "%"); + } + + public function stub_postdata($path) { + $post_path = sanitize_post_path($path); + $path_depth = substr_count($post_path, "/"); + + + $qry = " + INSERT INTO posts + (host, post_path, post_path_depth) + VALUES + ( ?, ?, ?) AS new + ON DUPLICATE KEY UPDATE post_path=new.post_path;"; + + $this->_exec($qry, "ssi", + $this->hostname, + $post_path, + $path_depth); + } + + public function stub_postdata_tree($path) { + $post_path = sanitize_post_path($path); + + while(true) { + if($post_path == '/') { + $post_path = ''; + } + + try { + $this->stub_postdata($post_path); + } + catch(Exception $e) { + } + + $post_path = dirname($post_path); + if(strlen($post_path) == 0) { + break; + } + } + } + + public function set_postdata($data) { + $data['path'] = sanitize_post_path($data['path']); + $post_path = $data['path']; + unset($data['path']); + + $this->stub_postdata_tree($post_path); + + $data['title'] ??= basename($post_path); + + $post_tags = $data['tags'] ?? []; + array_push($post_tags, + 'path:' . $post_path + ); + + $sql_args = [ + $this->hostname, + $post_path, + substr_count($post_path, "/"), + $data['title'], + taglist_to_sql_string($post_tags), + $data['brief'] ?? null + ]; + + unset($data['title']); + unset($data['brief']); + + $post_markdown = $data['markdown'] ?? null; + unset($data['markdown']); + unset($data['html']); + + array_push($sql_args, json_encode($data)); + + $qry = + "INSERT INTO posts + (host, + post_path, post_path_depth, + post_title, post_tags, post_brief, + post_metadata, post_settings_cache) + VALUES + ( ?, ?, ?, ?, ?, ?, ?, null) AS new + ON DUPLICATE KEY + UPDATE post_title=new.post_title, + post_tags=new.post_tags, + post_brief=new.post_brief, + post_metadata=new.post_metadata, + post_updated_at=CURRENT_TIMESTAMP; + "; + + $this->_exec($qry, "ssissss", ...$sql_args); + + if(isset($post_markdown)) { + $this->set_post_markdown($this->sql_connection->insert_id, $post_markdown); + } + + $this->clear_post_settings_cache($post_path); + } + + public function set_post_markdown($id, $markdown) { + $qry = + "INSERT INTO post_markdown ( post_id, post_markdown ) + VALUES (?, ?) AS new + ON DUPLICATE KEY UPDATE post_markdown=new.post_markdown; + "; + + $this->_exec($qry, "is", $id, $markdown); + } + + private function get_post_settings($post_path) { + $post_path = sanitize_post_path($post_path); + + $this->_dbg("-> gps: getting path " . $post_path . "\n"); + + $post_settings = $this->_exec(" + SELECT post_settings_cache + FROM posts + WHERE post_path = ? AND host = ? + ", "ss", $post_path, $this->hostname)->fetch_assoc(); + + if(!isset($post_settings)) { + $this->_dbg("-> gps: Returning because of no result\n"); + return []; + } + if(isset($post_settings['post_settings_cache'])) { + $result = json_decode($post_settings['post_settings_cache'], true); + if($this->debugging) { + echo "-> gps: Returning because of cached result:\n"; + echo "--> " . json_encode($result) . "\n"; + } + return $result; + } + + $parent_settings = []; + if($post_path != "") { + $parent_settings = $this->get_post_settings(dirname($post_path)); + } + + $post_settings = []; + $post_metadata = $this->_exec(" + SELECT post_path, post_metadata + FROM posts + WHERE post_path = ? AND host = ? + ", "ss", $post_path, $this->hostname)->fetch_assoc(); + + if(isset($post_metadata['post_metadata'])) { + $post_metadata = json_decode($post_metadata['post_metadata'], true); + + if(isset($post_metadata['settings'])) { + $post_settings = $post_metadata['settings']; + } + } + + $post_settings = array_merge($parent_settings, $post_settings); + + $this->_dbg("-> gps: Merged post settings are " . json_encode($post_settings) . ", saving...\n"); + + $this->_exec(" + UPDATE posts SET post_settings_cache=? WHERE post_path=? AND host=? + ", "sss", + json_encode($post_settings), $post_path, $this->hostname); + + return $post_settings; + } + + private function process_postdata($data) { + if(!isset($data)) { + return null; + } + + if(!isset($data['post_path'])) { + echo "ERROR, trying to get a post data package without path!"; + die(); + } + + $outdata = []; + foreach($this::SQL_READ_COLUMNS as $key) { + if(isset($data['post_' . $key])) { + $outdata[$key] = $data['post_' . $key]; + } + } + + $post_metadata = json_decode($data['post_metadata'] ?? '{}', true); + + $post_settings = []; + + if(isset($data['post_settings_cache'])) { + $post_settings = json_decode($data['post_settings_cache'], true); + } + else { + $post_settings = $this->get_post_settings($data['post_path']); + } + + $outdata = array_merge($post_settings, $post_metadata, $outdata); + + return $outdata; + } + + public function get_postdata($path) { + $path = sanitize_post_path($path); + + $qry = " + SELECT * + FROM posts + WHERE post_path = ? AND host = ?; + "; + + $data = $this->_exec($qry, "ss", $path, $this->hostname)->fetch_assoc(); + + return $this->process_postdata($data); + } + + public function get_post_children($path, + $limit = 50, $depth_start = 1, $depth_end = 1, + $order_by = 'path') { + + $path = sanitize_post_path($path); + + $path_depth = substr_count($path, "/"); + + $allowed_ordering = [ + 'path' => true, + 'path DESC' => true, + 'created_at' => true, + 'created_at DESC' => true, + 'modified_at' => true, + 'modified_at DESC' => true + ]; + + if(!isset($allowed_ordering[$order_by])) { + throw new Exception('Children ordering not allowed'); + } + $order_by = 'post_' . $order_by; + + if($this->debugging) { + echo "-> GPC: Getting children for path " . $path; + } + + $qry = " + SELECT * + FROM posts + WHERE post_path_depth BETWEEN ? AND ? + AND post_path LIKE ? + ORDER BY " . $order_by . + " LIMIT ?"; + + $data = $this->_exec($qry, "iisi", + $path_depth + $depth_start, $path_depth + $depth_end, + $path.'/%', $limit + )->fetch_all(MYSQLI_ASSOC); + $outdata = []; + + foreach($data AS $post_element) { + $outdata[$post_element['post_path']] = + $this->process_postdata($post_element); + } + + return $outdata; + } + + public function get_post_markdown($id) { + $qry = + "SELECT post_markdown + FROM post_markdown + WHERE post_id = ? + "; + + $data = $this->_exec($qry, "i", $id)->fetch_assoc(); + + if(!isset($data)) { + return ""; + } + + return $data['post_markdown']; + } +} + +?> \ No newline at end of file diff --git a/www/src/db_handler/post.php b/www/src/db_handler/post.php new file mode 100644 index 0000000..95e150b --- /dev/null +++ b/www/src/db_handler/post.php @@ -0,0 +1,205 @@ + + -1, + 'path' => '/404.md', + 'title' => '404 Page', + 'metadata' => [ + 'type' => '404' + ] + ]; + + return $post_data; + } + + public static function _deduce_type($path) { + $ext = pathinfo($path, PATHINFO_EXTENSION); + + if(preg_match("/\.(\w+)\.md$/", $path, $ext_match)) { + $ext = $ext_match[1]; + } + + $ext_mapping = [ + '' => 'directory', + 'md' => 'text/markdown', + 'png' => 'image', + 'jpg' => 'image', + 'jpeg' => 'image' + ]; + + return $ext_mapping[$ext] ?? '?'; + } + + public static function _deduce_icon($type) { + $icon_mapping = [ + '' => 'question', + 'text/markdown' => 'markdown', + 'blog' => 'markdown', + 'blog_list' => 'rectangle-list', + 'directory' => 'folder', + 'gallery' => 'images', + 'image' => 'image' + ]; + + return $icon_mapping[$type] ?? 'unknown'; + } + + function __construct($post_handler, $post_data) { + $this->handler = $post_handler; + + $this->content_html = null; + $this->content_markdown = null; + + $this->site_defaults = null; + + if(!isset($post_data) or !isset($post_data['id'])) { + $post_data = $this->_generate_404($post_data); + } + + $data = $post_data; + + $post_data['host'] ??= 'localhost:8081'; + + $data['url'] ??= 'https://' . $post_data['host'] . $post_data['path']; + + $data['title'] ??= basename($data['path']); + + $data['tags'] ??= []; + + $data['type'] ??= self::_deduce_type($post_data['path']); + + $data['icon'] ??= self::_deduce_icon($data['type']); + + if(isset($sql_meta['media_url'])) { + $data['thumb_url'] ??= $data['media_url']; + } + + $data['preview_image'] ??= $data['banners'][0]['src'] ?? null; + + $data['brief'] ??= $data['title']; + + $this->data = $data; + } + + public function __get($name) { + if($name == 'html') { + return $this->get_html(); + } + if($name == 'markdown') { + return $this->get_markdown(); + } + if($name == 'json') { + return $this->to_json(); + } + if($name == 'child_posts') { + return $this->get_child_posts(); + } + + if(isset($this->data[$name])) { + return $this->data[$name]; + } + + if(is_null($this->site_defaults)) { + throw new RuntimeException("Post site defaults have not been set properly!"); + } + + return $this->site_defaults[$name] ?? null; + } + + public function offsetGet($offset) : mixed { + return $this->__get($offset) ?? null; + } + public function offsetExists($offset) : bool { + if(isset($this->data[$offset])) { + return true; + } + if(isset($this->site_defaults[$offset])) { + return true; + } + + return !is_null($this->offsetGet($offset)); + } + public function offsetSet($offset, $value) : void { + throw RuntimeError("Setting of post data is not allowed!"); + } + public function offsetUnset($offset) : void { + throw RuntimeError("Unsetting of post data is not allowed!"); + } + + public function get_html() { + $this->content_html ??= $this->handler->render_post($this); + + return $this->content_html; + } + public function get_markdown() { + $this->content_markdown ??= + $this->handler->get_markdown_for($this); + + return $this->content_markdown; + } + + public function get_child_posts(...$search_args) { + var_dump($search_args); + + + if(count($search_args) == 0) { + $this->child_posts ??= + $this->handler->get_children_for($this); + + return $this->child_posts; + } + else { + return $this->handler->get_children_for($this, ...$search_args); + } + } + + public function to_array($options = []) { + $out_data = $this->data; + + if(isset($options['markdown'])) { + $out_data['markdown'] = $this->get_markdown(); + } + if(isset($options['html'])) { + $out_data['html'] = $this->get_html(); + } + if(isset($options['children'])) { + die(); + } + + return $out_data; + } + public function to_json($options = []) { + return json_encode($this->to_array($options)); + } + + + public function get_parent_post() { + $parent_path = dirname($this->data['path']); + if($parent_path == '') + return null; + + $this->parent_post ??= new PostData($this->handler, + $this->handler->get_post_by_path($parent_path)); + + return $this->parent_post; + } +} + +?> \ No newline at end of file diff --git a/www/src/db_handler/post_handler.php b/www/src/db_handler/post_handler.php new file mode 100644 index 0000000..33f249c --- /dev/null +++ b/www/src/db_handler/post_handler.php @@ -0,0 +1,57 @@ +db = $db_adapter; + $this->posts = []; + + $this->markdown_engine = null; + } + + public function get_post($key) { + $key = sanitize_post_path($key); + + if(isset($this->posts[$key])) { + return $this->posts[$key]; + } + + $post_data = $this->db->get_postdata($key); + $post = null; + if(isset($post_data)) { + $post = new Post($this, $post_data); + } + + $this->posts[$key] = $post; + + return $post; + } + + public function get_markdown_for($post) { + return $this->db->get_post_markdown($post->id); + } + + public function render_post($post) { + return $this->markdown_engine($post); + } + + public function get_children_for($post, ...$search_opts) { + $child_list = $this->db->get_post_children($post->path, ...$search_opts); + + $out_list = []; + foreach($child_list as $child_data) { + array_push($out_list, new Post($this, $child_data)); + } + + return $out_list; + } +} + +?> \ No newline at end of file diff --git a/www/src/dbtest.php b/www/src/dbtest.php new file mode 100644 index 0000000..86983d6 --- /dev/null +++ b/www/src/dbtest.php @@ -0,0 +1,187 @@ +'; + echo 'Error number: ' . mysqli_connect_errno() . '
    '; + echo 'Error message: ' . mysqli_connect_error() . '
    '; + die(); +} + +$db_connection->execute_query("DELETE FROM posts;"); + +$sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST); +$adapter = new PostHandler($sql_adapter); + +$sql_adapter->debugging = true; + +function test_accounce($title) { + echo "\n\n=========================================== + _______ ______ _____ _______ + |__ __| ____|/ ____|__ __| + | | | |__ | (___ | | (_) + | | | __| \___ \ | | + | | | |____ ____) | | | _ + |_| |______|_____/ |_| (_) + +"; + echo "==== " . $title . "\n"; + echo "===========================================\n"; +} + +function adapter_fetch($post_path) { + global $db_connection; + global $sql_adapter; + + echo "-> Fetching path " . $post_path . "\n"; + + echo json_encode($db_connection->execute_query("SELECT * FROM posts WHERE post_path=?", [ + $post_path + ])->fetch_assoc(), JSON_PRETTY_PRINT); + + echo "\n-> Adapter output:\n"; + + echo json_encode($sql_adapter->get_postdata($post_path), JSON_PRETTY_PRINT) . "\n"; +} + +echo "Starting test...\n"; + +echo "Trying just a stub...\n"; +$sql_adapter->stub_postdata_tree('/testing/stubtest/1/2/3.md'); +echo "Stubbed~\n\n"; + +echo "Getting the stub post...\n"; + +echo json_encode($sql_adapter->get_postdata('/testing'), JSON_PRETTY_PRINT); + +echo "\n\n"; + +test_accounce("Basic postdata setting"); + +$sql_adapter->set_postdata([ + 'path' => '/testing/settest/test.md', + 'title' => 'One heck of a test!', + 'type' => 'text/markdown', + 'tags' => [ + 'test', + 'type:text' + ], + 'overridetest' => 'metadata' +]); + +echo "\nDone!"; + +adapter_fetch('/testing/settest/test.md'); + +echo "Done!\n\n"; + +test_accounce("Setting post markdown..."); +$sql_adapter->set_postdata([ + 'path' => '/testing/markdowntest', + 'markdown' => 'Inline markdown test should work...' +]); +$post = $sql_adapter->get_postdata('/testing/markdowntest'); +var_dump($sql_adapter->get_post_markdown($post['id'])); + +$sql_adapter->set_post_markdown($post['id'], + 'This is one hell of a cute test!' +); +var_dump($sql_adapter->get_post_markdown($post['id'])); +unset($post); + +test_accounce("Settings inheritance test..."); +echo "Setting on a parent file...\n"; + +$sql_adapter->set_postdata([ + 'path' => '/testing/settest', + 'settings' => [ + 'nom' => true, + 'type' => 'frame', + 'overridetest' => 'settings' + ] +]); + +echo "\nAnd checking if that held!\n"; + +adapter_fetch('/testing/settest'); +adapter_fetch('/testing/settest/test.md'); + +test_accounce("Testing getting child posts"); + +echo json_encode($sql_adapter->get_post_children('/testing'), JSON_PRETTY_PRINT); + +echo "\n\n------------------------------------------------------\n"; + +echo "TEST PHASE: Adapter testing"; +echo "\n------------------------------------------------------\n\n"; + +$post = $adapter->get_post('/testing/markdowntest'); +echo "Post path is " . $post->path . "\n"; +echo "Post markdown is " . $post->markdown . "\n"; +echo $post->to_json(); + +echo "\n\n"; + +echo $post->to_json([ + 'markdown' => true +]); + +test_accounce("Fetching child posts"); +echo "Root children:\n". json_encode(array_map(function($data) { + return $data->to_array(); +}, $adapter->get_post('/')->child_posts), JSON_PRETTY_PRINT); +echo "\n\n"; + +echo "Root children, extended:\n" . json_encode(array_map(function($data) { + return $data->to_array(); +}, $adapter->get_post('/')->get_child_posts(depth_end: 3)), JSON_PRETTY_PRINT); +echo "\n\n"; +?> \ No newline at end of file diff --git a/www/dergdown.php b/www/src/dergdown.php similarity index 100% rename from www/dergdown.php rename to www/src/dergdown.php diff --git a/www/fontawesome.php b/www/src/fontawesome.php similarity index 100% rename from www/fontawesome.php rename to www/src/fontawesome.php diff --git a/www/mysql_adapter.php b/www/src/old_mysql_adapter.php similarity index 100% rename from www/mysql_adapter.php rename to www/src/old_mysql_adapter.php diff --git a/www/router.php b/www/src/old_router.php similarity index 91% rename from www/router.php rename to www/src/old_router.php index 8e0d1e7..e15593d 100644 --- a/www/router.php +++ b/www/src/old_router.php @@ -4,8 +4,8 @@ $data_time_start = microtime(true); require_once 'vendor/autoload.php'; -require_once 'post_adapter.php'; - +require_once 'db_handler/mysql_handler.php'; +require_once 'db_handler/post_handler.php'; require_once 'post.php'; require_once 'fontawesome.php'; @@ -24,7 +24,30 @@ $SITE_CONFIG = Yaml::parseFile('secrets/' . $SERVER_HOST . '.config.yml'); $SITE_CONFIG['uri_prefix'] = $SERVER_PREFIX; $SITE_CONFIG['HTTP_HOST'] = $SERVER_HOST; -$adapter = new PostHandler($SITE_CONFIG); +$db_params = $SITE_CONFIG['db']; +$db_connection = null; +try { + if(false !== getenv('MYSQL_HOST')) { + $db_connection = mysqli_connect(getenv('MYSQL_HOST'), + getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'), + getenv('MYSQL_DATABASE'), + getenv('MYSQL_PORT')); + } + else { + $db_connection = mysqli_connect($db_params['host'], + $db_params['user'], $db_params['password'], + $db_params['database'], + $db_params['port']); + } +} catch (\Throwable $th) { + echo 'Connection failed
    '; + echo 'Error number: ' . mysqli_connect_errno() . '
    '; + echo 'Error message: ' . mysqli_connect_error() . '
    '; + die(); +} + +$sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST); +$adapter = new PostHandler($sql_adapter); $loader = new \Twig\Loader\FilesystemLoader(['./templates', './user_content']); diff --git a/www/src/router.php b/www/src/router.php new file mode 100644 index 0000000..6041798 --- /dev/null +++ b/www/src/router.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/www/src/serve_post.php b/www/src/serve_post.php new file mode 100644 index 0000000..29fabbb --- /dev/null +++ b/www/src/serve_post.php @@ -0,0 +1,46 @@ +get_post($PARSED_URL['path']); +if(isset($post)) { + $post->site_defaults = $SITE_CONFIG['site_defaults']; +} + +function render_root_template($template, $args = []) { + global $twig; + global $FONT_AWESOME_ARRAY; + global $SITE_CONFIG; + + $args['fontawesome'] = $FONT_AWESOME_ARRAY; + $args['page'] ??= $SITE_CONFIG['site_defaults']; + + $page = $args['page']; + + $args['opengraph'] = [ + "site_name" => $page['site_name'] ?? 'UNSET SITE NAME', + "title" => $page['title'] ?? 'UNSET TITLE', + "url" => $page['url'] ?? 'UNSET URL', + "description" => $page['description'] ?? 'UNSET DESCRIPTION' + ]; + + $args['banners'] = json_encode($page['banners'] ?? []); + + $args['age_gate'] = (!isset($_COOKIE['AgeConfirmed'])) + && isset($SITE_CONFIG['age_gate']); + + echo $twig->render($template, $args); +} + +render_root_template('root.html', [ + 'page' => $post +]); +die(); + +if(!isset($post)) { + render_404(); + die(); +} + + +?> \ No newline at end of file diff --git a/www/src/setup_db.php b/www/src/setup_db.php new file mode 100644 index 0000000..97195aa --- /dev/null +++ b/www/src/setup_db.php @@ -0,0 +1,43 @@ +'; + echo 'Error number: ' . mysqli_connect_errno() . '
    '; + echo 'Error message: ' . mysqli_connect_error() . '
    '; + die(); +} + +$sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST); +$adapter = new PostHandler($sql_adapter); + +require_once 'dergdown.php'; + +function dergdown_to_html($text) { + $Parsedown = new Dergdown(); + + return $Parsedown->text($text); +} +function post_to_html($post) { + return dergdown_to_html($post->markdown); +} +$adapter->markdown_engine = "post_to_html"; + +?> \ No newline at end of file diff --git a/www/src/setup_site_config.php b/www/src/setup_site_config.php new file mode 100644 index 0000000..683cb91 --- /dev/null +++ b/www/src/setup_site_config.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/www/src/setup_twig.php b/www/src/setup_twig.php new file mode 100644 index 0000000..e674428 --- /dev/null +++ b/www/src/setup_twig.php @@ -0,0 +1,24 @@ + true, + 'cache' => 'twig_cache' +]); +$twig->addExtension(new Twig\Extra\Markdown\MarkdownExtension()); + +use Twig\Extra\Markdown\DefaultMarkdown; +use Twig\Extra\Markdown\MarkdownRuntime; +use Twig\RuntimeLoader\RuntimeLoaderInterface; + +$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface { + public function load($class) { + if (MarkdownRuntime::class === $class) { + return new MarkdownRuntime(new DefaultMarkdown()); + } + } +}); + + +?> \ No newline at end of file diff --git a/www/templates/root.html b/www/templates/root.html index 0919f0e..c36e6ff 100644 --- a/www/templates/root.html +++ b/www/templates/root.html @@ -1,7 +1,7 @@ - {{og.site_name}} - {{og.title}} + {{opengraph.site_name}} - {{opengraph.title}} @@ -21,26 +21,26 @@ {% block feed_links %} - + {% endblock %} {% block extra_head %}{% endblock %} {% block opengraph_tags %} - + - + - - + + - + - - + + - - + + @@ -71,25 +71,29 @@ full picture -

    {% block big_title %}{{og.site_name}}{%endblock%}

    +

    {% block big_title %}{{opengraph.site_name}}{%endblock%}

    {% block second_title %}{% endblock %}
    -
  • About
  • -
  • Blog
  • -
  • Projects
  • -
  • Artworks
  • + {% for link in page.navbar_links %} +
  • {{link.text}}
  • + {% endfor %}
    - {% block main_content %}

    Soon there shall be content!

    {% endblock %} + {% block main_content %} +

    This here should have been replaced by content. +

    + + If you can see this, complain to your nearest dragon. + {% endblock %}
    From 0dcf36052e8ad60e2fc8e0037aeb16e5c06a6a3f Mon Sep 17 00:00:00 2001 From: David Bailey Date: Sat, 24 Aug 2024 13:09:28 +0200 Subject: [PATCH 06/46] ... unsure --- www/src/db_handler/post.php | 36 +++++++++++-------- www/src/db_handler/post_handler.php | 10 ++++-- www/src/dbtest.php | 8 +++-- www/src/router.php | 16 +++++++-- www/src/serve/ajax.php | 26 ++++++++++++++ www/src/{serve_post.php => serve/post.php} | 22 ++++++------ www/src/setup_db.php | 2 ++ www/static/dergstyle.css | 13 +++++-- www/templates/ajax/closed_folder_listing.html | 13 ------- .../ajax/compact_filelist/entry.html | 27 ++++++++++++++ .../ajax/compact_filelist/listing.html | 4 +++ www/templates/ajax/folder_listing.html | 4 --- www/templates/ajax/folder_listing_entry.html | 26 -------------- www/templates/ajax/open_folder_listing.html | 20 ----------- www/templates/fragments/filepath_bar.html | 20 +++++------ www/templates/pathed_content.html | 6 ++-- www/templates/post_types/markdown.html | 6 +++- www/templates/root.html | 5 ++- 18 files changed, 152 insertions(+), 112 deletions(-) create mode 100644 www/src/serve/ajax.php rename www/src/{serve_post.php => serve/post.php} (52%) delete mode 100644 www/templates/ajax/closed_folder_listing.html create mode 100644 www/templates/ajax/compact_filelist/entry.html create mode 100644 www/templates/ajax/compact_filelist/listing.html delete mode 100644 www/templates/ajax/folder_listing.html delete mode 100644 www/templates/ajax/folder_listing_entry.html delete mode 100644 www/templates/ajax/open_folder_listing.html diff --git a/www/src/db_handler/post.php b/www/src/db_handler/post.php index 95e150b..4e9494e 100644 --- a/www/src/db_handler/post.php +++ b/www/src/db_handler/post.php @@ -7,7 +7,7 @@ class Post implements ArrayAccess { private $content_html; private $content_markdown; - public $site_defaults; + private $site_defaults; public $data; @@ -15,6 +15,7 @@ class Post implements ArrayAccess { public $raw_data; private $_child_posts; + private $_parent_post; public static function _generate_404($post_data) { $post_data ??= [ @@ -61,13 +62,13 @@ class Post implements ArrayAccess { return $icon_mapping[$type] ?? 'unknown'; } - function __construct($post_handler, $post_data) { + function __construct($post_handler, $post_data, $site_defaults) { $this->handler = $post_handler; $this->content_html = null; $this->content_markdown = null; - $this->site_defaults = null; + $this->site_defaults = $site_defaults; if(!isset($post_data) or !isset($post_data['id'])) { $post_data = $this->_generate_404($post_data); @@ -75,9 +76,17 @@ class Post implements ArrayAccess { $data = $post_data; + if($data['path'] == '') { + $data['path'] = '/'; + $data['title'] ??= 'root'; + $data['basename'] ??= 'root'; + } + $post_data['host'] ??= 'localhost:8081'; - $data['url'] ??= 'https://' . $post_data['host'] . $post_data['path']; + $data['url'] ??= 'http://' . $post_data['host'] . $post_data['path']; + + $data['basename'] ??= basename($data['path']); $data['title'] ??= basename($data['path']); @@ -111,6 +120,9 @@ class Post implements ArrayAccess { if($name == 'child_posts') { return $this->get_child_posts(); } + if($name == 'parent') { + return $this->get_parent_post(); + } if(isset($this->data[$name])) { return $this->data[$name]; @@ -137,10 +149,10 @@ class Post implements ArrayAccess { return !is_null($this->offsetGet($offset)); } public function offsetSet($offset, $value) : void { - throw RuntimeError("Setting of post data is not allowed!"); + $this->data[$offset] = $value; } public function offsetUnset($offset) : void { - throw RuntimeError("Unsetting of post data is not allowed!"); + unset($this->data[$offset]); } public function get_html() { @@ -156,14 +168,11 @@ class Post implements ArrayAccess { } public function get_child_posts(...$search_args) { - var_dump($search_args); - - if(count($search_args) == 0) { - $this->child_posts ??= + $this->_child_posts ??= $this->handler->get_children_for($this); - return $this->child_posts; + return $this->_child_posts; } else { return $this->handler->get_children_for($this, ...$search_args); @@ -195,10 +204,9 @@ class Post implements ArrayAccess { if($parent_path == '') return null; - $this->parent_post ??= new PostData($this->handler, - $this->handler->get_post_by_path($parent_path)); + $this->_parent_post ??= $this->handler->get_post($parent_path); - return $this->parent_post; + return $this->_parent_post; } } diff --git a/www/src/db_handler/post_handler.php b/www/src/db_handler/post_handler.php index 33f249c..06fd3fc 100644 --- a/www/src/db_handler/post_handler.php +++ b/www/src/db_handler/post_handler.php @@ -9,10 +9,14 @@ class PostHandler { public $markdown_engine; + public $site_defaults; + function __construct($db_adapter) { $this->db = $db_adapter; $this->posts = []; + $this->site_defaults = null; + $this->markdown_engine = null; } @@ -26,7 +30,7 @@ class PostHandler { $post_data = $this->db->get_postdata($key); $post = null; if(isset($post_data)) { - $post = new Post($this, $post_data); + $post = new Post($this, $post_data, $this->site_defaults); } $this->posts[$key] = $post; @@ -39,7 +43,7 @@ class PostHandler { } public function render_post($post) { - return $this->markdown_engine($post); + return ($this->markdown_engine)($post); } public function get_children_for($post, ...$search_opts) { @@ -47,7 +51,7 @@ class PostHandler { $out_list = []; foreach($child_list as $child_data) { - array_push($out_list, new Post($this, $child_data)); + array_push($out_list, new Post($this, $child_data, $this->site_defaults)); } return $out_list; diff --git a/www/src/dbtest.php b/www/src/dbtest.php index 86983d6..ffbc378 100644 --- a/www/src/dbtest.php +++ b/www/src/dbtest.php @@ -126,13 +126,17 @@ echo "Done!\n\n"; test_accounce("Setting post markdown..."); $sql_adapter->set_postdata([ 'path' => '/testing/markdowntest', - 'markdown' => 'Inline markdown test should work...' + 'markdown' => 'Inline markdown test should work...', + 'title' => "A Markdown Test" ]); $post = $sql_adapter->get_postdata('/testing/markdowntest'); var_dump($sql_adapter->get_post_markdown($post['id'])); $sql_adapter->set_post_markdown($post['id'], - 'This is one hell of a cute test!' + ' + This is one hell of a cute test! +> Just checking in... + ' ); var_dump($sql_adapter->get_post_markdown($post['id'])); unset($post); diff --git a/www/src/router.php b/www/src/router.php index 6041798..c2f3dc7 100644 --- a/www/src/router.php +++ b/www/src/router.php @@ -12,8 +12,20 @@ require_once 'dergdown.php'; require_once 'setup_twig.php'; -$PARSED_URL = parse_url($_SERVER['REQUEST_URI']); +$REQUEST_URI = parse_url($_SERVER['REQUEST_URI']); +$REQUEST_PATH = $REQUEST_URI['path']; + +parse_str($REQUEST_URI['query'] ?? '', $REQUEST_QUERY); + +if(preg_match('/^\/api/', $REQUEST_PATH)) { + require_once 'serve/api.php'; +} +elseif(preg_match('/^\/ajax/', $REQUEST_PATH)) { + require_once 'serve/ajax.php'; +} +else { + require_once 'serve/post.php'; +} -require_once 'serve_post.php'; ?> \ No newline at end of file diff --git a/www/src/serve/ajax.php b/www/src/serve/ajax.php new file mode 100644 index 0000000..679afe0 --- /dev/null +++ b/www/src/serve/ajax.php @@ -0,0 +1,26 @@ +get_post($REQUEST_QUERY['page']); +} + + +$ajax_args['fa'] = $FONT_AWESOME_ARRAY; +$ajax_args['page'] ??= $SITE_CONFIG['site_defaults']; + +echo $twig->render('/ajax/' . $AJAX_REQUEST_TEMPLATE, $ajax_args); + +?> \ No newline at end of file diff --git a/www/src/serve_post.php b/www/src/serve/post.php similarity index 52% rename from www/src/serve_post.php rename to www/src/serve/post.php index 29fabbb..6430550 100644 --- a/www/src/serve_post.php +++ b/www/src/serve/post.php @@ -2,26 +2,24 @@ require_once 'fontawesome.php'; -$post = $adapter->get_post($PARSED_URL['path']); -if(isset($post)) { - $post->site_defaults = $SITE_CONFIG['site_defaults']; -} +$post = $adapter->get_post($REQUEST_PATH); function render_root_template($template, $args = []) { global $twig; global $FONT_AWESOME_ARRAY; global $SITE_CONFIG; - $args['fontawesome'] = $FONT_AWESOME_ARRAY; + $args['fa'] = $FONT_AWESOME_ARRAY; $args['page'] ??= $SITE_CONFIG['site_defaults']; $page = $args['page']; + $page['base'] ??= $page['url']; $args['opengraph'] = [ - "site_name" => $page['site_name'] ?? 'UNSET SITE NAME', - "title" => $page['title'] ?? 'UNSET TITLE', - "url" => $page['url'] ?? 'UNSET URL', - "description" => $page['description'] ?? 'UNSET DESCRIPTION' + "site_name" => $page['site_name'] ?? 'Nameless Site', + "title" => $page['title'] ?? 'Titleless', + "url" => $page['url'] ?? $page['path'] ?? 'No URL set', + "description" => $page['description'] ?? 'No description set' ]; $args['banners'] = json_encode($page['banners'] ?? []); @@ -32,7 +30,11 @@ function render_root_template($template, $args = []) { echo $twig->render($template, $args); } -render_root_template('root.html', [ +function render_pathed_content_template($template, $args = []) { + render_root_template($template, $args); +} + +render_pathed_content_template('post_types/markdown.html', [ 'page' => $post ]); die(); diff --git a/www/src/setup_db.php b/www/src/setup_db.php index 97195aa..70b3d88 100644 --- a/www/src/setup_db.php +++ b/www/src/setup_db.php @@ -40,4 +40,6 @@ function post_to_html($post) { } $adapter->markdown_engine = "post_to_html"; +$adapter->site_defaults = $SITE_CONFIG['site_defaults']; + ?> \ No newline at end of file diff --git a/www/static/dergstyle.css b/www/static/dergstyle.css index 2a4c1f9..6ccc0f6 100644 --- a/www/static/dergstyle.css +++ b/www/static/dergstyle.css @@ -311,12 +311,21 @@ body.htmx-request::before { .folder-listing input { display: none; } -.folder-listing input + ul { +.folder-listing input ~ ul { display: none; } -.folder-listing input:checked + ul { +.folder-listing input:checked ~ ul { display: block; } +.folder-listing label > :nth-child(2) { + display: none; +} +.folder-listing input:checked ~ label > :nth-child(1) { + display: none; +} +.folder-listing input:checked ~ label > :nth-child(2) { + display: inline-block; +} #navbar-expand-label { cursor: pointer; diff --git a/www/templates/ajax/closed_folder_listing.html b/www/templates/ajax/closed_folder_listing.html deleted file mode 100644 index 399894f..0000000 --- a/www/templates/ajax/closed_folder_listing.html +++ /dev/null @@ -1,13 +0,0 @@ - -
  • - - {{ fa[post.post_metadata.icon] | raw }} - - - - {{ post.post_metadata.title }} - -
  • \ No newline at end of file diff --git a/www/templates/ajax/compact_filelist/entry.html b/www/templates/ajax/compact_filelist/entry.html new file mode 100644 index 0000000..6c63345 --- /dev/null +++ b/www/templates/ajax/compact_filelist/entry.html @@ -0,0 +1,27 @@ + + +
  • + {% set folder_key = random() %} + + + + + + {{ post.basename }} - {{ post.title }} + + +
      +
    • + + Loading... + +
    • +
    +
  • \ No newline at end of file diff --git a/www/templates/ajax/compact_filelist/listing.html b/www/templates/ajax/compact_filelist/listing.html new file mode 100644 index 0000000..ccdc7c9 --- /dev/null +++ b/www/templates/ajax/compact_filelist/listing.html @@ -0,0 +1,4 @@ + +{% for post in page.child_posts %} +{{ include('ajax/compact_filelist/entry.html') }} +{% endfor %} \ No newline at end of file diff --git a/www/templates/ajax/folder_listing.html b/www/templates/ajax/folder_listing.html deleted file mode 100644 index b90fb59..0000000 --- a/www/templates/ajax/folder_listing.html +++ /dev/null @@ -1,4 +0,0 @@ - -{% for post in subposts %} -{{ include('ajax/folder_listing_entry.html') }} -{% endfor %} \ No newline at end of file diff --git a/www/templates/ajax/folder_listing_entry.html b/www/templates/ajax/folder_listing_entry.html deleted file mode 100644 index e47bf9b..0000000 --- a/www/templates/ajax/folder_listing_entry.html +++ /dev/null @@ -1,26 +0,0 @@ - - -
  • - {% set folder_key = random() %} - - - - - {{ post.post_metadata.title }} - - - -
      -
    • - - Loading... - -
    • -
    -
  • \ No newline at end of file diff --git a/www/templates/ajax/open_folder_listing.html b/www/templates/ajax/open_folder_listing.html deleted file mode 100644 index 9bd6414..0000000 --- a/www/templates/ajax/open_folder_listing.html +++ /dev/null @@ -1,20 +0,0 @@ - - -
  • - - {{ fa[post.post_metadata.icon] | raw }} - - - - {{ post.post_metadata.title }} - - -
      - {% for post in subposts %} - {{ include('ajax/closed_folder_listing.html') }} - {% endfor %} -
    -
  • \ No newline at end of file diff --git a/www/templates/fragments/filepath_bar.html b/www/templates/fragments/filepath_bar.html index 314e019..f4a43a2 100644 --- a/www/templates/fragments/filepath_bar.html +++ b/www/templates/fragments/filepath_bar.html @@ -1,11 +1,11 @@
  • - {{ post.post_metadata.title }} + {{ page.basename }}
  • - {% set split_post = post.post_path |split('/') %} + {% set split_post = page.path |split('/') %} {% for i in range(0, split_post|length - 1) %}
  • {% if i != 0 %} @@ -22,7 +22,7 @@
  • + style="padding-left: 0.3rem;" href="/feed/rss{{page.path}}"> {{ fa['rss']|raw }}
  • @@ -32,7 +32,7 @@
    diff --git a/www/templates/pathed_content.html b/www/templates/pathed_content.html index 90a0350..c8a3ffa 100644 --- a/www/templates/pathed_content.html +++ b/www/templates/pathed_content.html @@ -6,11 +6,11 @@ {{ parent() }} - + {% endblock %} {% block second_title %} -

    {{ post.post_metadata.title }}

    +

    {{ page.title }}

    {% endblock %} {%block main_content%} @@ -22,7 +22,7 @@ {%endblock%} - This article was created on {{ post.post_create_time }}, last edited {{ post.post_update_time }}, and was viewed {{ post.post_access_count }} times~ + This page was created on {{ page.created_at }}, last edited {{ page.updated_at }}, and was viewed {{ page.view_count }} times~
    diff --git a/www/templates/post_types/markdown.html b/www/templates/post_types/markdown.html index b9d147c..f00142c 100644 --- a/www/templates/post_types/markdown.html +++ b/www/templates/post_types/markdown.html @@ -9,5 +9,9 @@ {%endblock %} {%block content_article%} - {{ content_html|raw }} + {% if page.title %} +

    {{ page.title }}

    + {% endif %} + + {{ page.html|raw }} {% endblock %} \ No newline at end of file diff --git a/www/templates/root.html b/www/templates/root.html index c36e6ff..08c3de4 100644 --- a/www/templates/root.html +++ b/www/templates/root.html @@ -12,7 +12,6 @@ - @@ -20,6 +19,10 @@ + {% if page.base %} + + {% endif %} + {% block feed_links %} {% endblock %} From c607d572212d914ae94122eb48dff72d515f66bd Mon Sep 17 00:00:00 2001 From: David Bailey Date: Sat, 24 Aug 2024 15:47:23 +0200 Subject: [PATCH 07/46] feat(auth,api): begin work on API input --- www/src/db_handler/analytics_interface.php | 1 - www/src/db_handler/db_interface.php | 1 - www/src/db_handler/mysql_handler.php | 1 - www/src/db_handler/post.php | 12 +++++++++--- www/src/dergdown.php | 2 -- www/src/router.php | 8 +++++--- www/src/serve/api.php | 13 +++++++++++++ www/src/{setup_db.php => setup/db.php} | 0 www/src/setup/permissions.php | 17 +++++++++++++++++ .../site_config.php} | 0 www/src/{setup_twig.php => setup/twig.php} | 0 11 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 www/src/serve/api.php rename www/src/{setup_db.php => setup/db.php} (100%) create mode 100644 www/src/setup/permissions.php rename www/src/{setup_site_config.php => setup/site_config.php} (100%) rename www/src/{setup_twig.php => setup/twig.php} (100%) diff --git a/www/src/db_handler/analytics_interface.php b/www/src/db_handler/analytics_interface.php index fbe3400..de629c9 100644 --- a/www/src/db_handler/analytics_interface.php +++ b/www/src/db_handler/analytics_interface.php @@ -1,4 +1,3 @@ - data; + $out_data = array_merge($this->site_defaults, $this->data); if(isset($options['markdown'])) { $out_data['markdown'] = $this->get_markdown(); @@ -189,7 +188,14 @@ class Post implements ArrayAccess { $out_data['html'] = $this->get_html(); } if(isset($options['children'])) { - die(); + $children = $this->get_child_posts(); + $child_arrays = []; + + foreach($children AS $child) { + array_push($child_arrays, $child->to_array()); + } + + $out_data['children'] = $child_arrays; } return $out_data; diff --git a/www/src/dergdown.php b/www/src/dergdown.php index 8380be7..0405127 100644 --- a/www/src/dergdown.php +++ b/www/src/dergdown.php @@ -1,5 +1,3 @@ - - get_post($match[1]); + + echo $post->to_json($REQUEST_QUERY); + +} + +?> \ No newline at end of file diff --git a/www/src/setup_db.php b/www/src/setup/db.php similarity index 100% rename from www/src/setup_db.php rename to www/src/setup/db.php diff --git a/www/src/setup/permissions.php b/www/src/setup/permissions.php new file mode 100644 index 0000000..272d986 --- /dev/null +++ b/www/src/setup/permissions.php @@ -0,0 +1,17 @@ + true, + "upload" => false +]; + +$ACCESS_KEY = $REQUEST_QUERY['ACCESS_KEY'] ?? $_COOKIE['ACCESS_KEY'] ?? ''; + +if($ACCESS_KEY == $SITE_CONFIG['ACCESS_KEY']) { + $ACCESS_PERMISSIONS = [ + "read" => true, + "upload" => true + ]; +} + +?> \ No newline at end of file diff --git a/www/src/setup_site_config.php b/www/src/setup/site_config.php similarity index 100% rename from www/src/setup_site_config.php rename to www/src/setup/site_config.php diff --git a/www/src/setup_twig.php b/www/src/setup/twig.php similarity index 100% rename from www/src/setup_twig.php rename to www/src/setup/twig.php From c9808f90f812d70a3eca1dc555c2bcdae354683c Mon Sep 17 00:00:00 2001 From: David Bailey Date: Thu, 29 Aug 2024 20:40:36 +0200 Subject: [PATCH 08/46] feat: reworked the template structure --- www/src/db_handler/post.php | 17 +--- www/src/dbtest.php | 14 +++- www/src/dergdown.php | 79 ++++++++++++++++++- www/src/serve/ajax.php | 4 +- www/src/setup/db.php | 19 ++++- www/src/setup/derg_insert.php | 43 ++++++++++ www/static/banner.js | 6 +- .../ajax/compact_filelist/listing.html | 4 - .../{article_blop.html => blog/card.html} | 3 +- www/templates/fragments/blog/listing.html | 0 .../fragments/decoration/blog_header.html | 10 +++ .../navbar.html} | 2 +- .../directory/compact}/entry.html | 2 +- .../fragments/directory/compact/listing.html | 4 + www/templates/fragments/directory/inline.html | 39 +++++++++ www/templates/gallery/gallery_entry.html | 28 ------- www/templates/gallery/gallery_overview.html | 18 ----- .../blog_list.html | 0 .../directory.html | 0 .../gallery.html | 0 .../{post_types => old_post_types}/image.html | 0 .../markdown.html | 2 +- .../{post_types => old_post_types}/rrror.html | 0 www/templates/pathed_content.html | 2 +- 24 files changed, 216 insertions(+), 80 deletions(-) create mode 100644 www/src/setup/derg_insert.php delete mode 100644 www/templates/ajax/compact_filelist/listing.html rename www/templates/fragments/{article_blop.html => blog/card.html} (93%) create mode 100644 www/templates/fragments/blog/listing.html create mode 100644 www/templates/fragments/decoration/blog_header.html rename www/templates/fragments/{filepath_bar.html => decoration/navbar.html} (96%) rename www/templates/{ajax/compact_filelist => fragments/directory/compact}/entry.html (87%) create mode 100644 www/templates/fragments/directory/compact/listing.html create mode 100644 www/templates/fragments/directory/inline.html delete mode 100644 www/templates/gallery/gallery_entry.html delete mode 100644 www/templates/gallery/gallery_overview.html rename www/templates/{post_types => old_post_types}/blog_list.html (100%) rename www/templates/{post_types => old_post_types}/directory.html (100%) rename www/templates/{post_types => old_post_types}/gallery.html (100%) rename www/templates/{post_types => old_post_types}/image.html (100%) rename www/templates/{post_types => old_post_types}/markdown.html (79%) rename www/templates/{post_types => old_post_types}/rrror.html (100%) diff --git a/www/src/db_handler/post.php b/www/src/db_handler/post.php index 5e339e4..a61504f 100644 --- a/www/src/db_handler/post.php +++ b/www/src/db_handler/post.php @@ -6,8 +6,6 @@ class Post implements ArrayAccess { private $content_html; private $content_markdown; - private $site_defaults; - public $data; public $html_data; @@ -66,14 +64,12 @@ class Post implements ArrayAccess { $this->content_html = null; $this->content_markdown = null; - - $this->site_defaults = $site_defaults; if(!isset($post_data) or !isset($post_data['id'])) { $post_data = $this->_generate_404($post_data); } - $data = $post_data; + $data = array_merge($site_defaults, $post_data); if($data['path'] == '') { $data['path'] = '/'; @@ -127,11 +123,7 @@ class Post implements ArrayAccess { return $this->data[$name]; } - if(is_null($this->site_defaults)) { - throw new RuntimeException("Post site defaults have not been set properly!"); - } - - return $this->site_defaults[$name] ?? null; + return null; } public function offsetGet($offset) : mixed { @@ -141,9 +133,6 @@ class Post implements ArrayAccess { if(isset($this->data[$offset])) { return true; } - if(isset($this->site_defaults[$offset])) { - return true; - } return !is_null($this->offsetGet($offset)); } @@ -179,7 +168,7 @@ class Post implements ArrayAccess { } public function to_array($options = []) { - $out_data = array_merge($this->site_defaults, $this->data); + $out_data = $this->data; if(isset($options['markdown'])) { $out_data['markdown'] = $this->get_markdown(); diff --git a/www/src/dbtest.php b/www/src/dbtest.php index ffbc378..01c795a 100644 --- a/www/src/dbtest.php +++ b/www/src/dbtest.php @@ -61,6 +61,8 @@ $db_connection->execute_query("DELETE FROM posts;"); $sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST); $adapter = new PostHandler($sql_adapter); +$adapter->site_defaults = []; + $sql_adapter->debugging = true; function test_accounce($title) { @@ -127,15 +129,23 @@ test_accounce("Setting post markdown..."); $sql_adapter->set_postdata([ 'path' => '/testing/markdowntest', 'markdown' => 'Inline markdown test should work...', - 'title' => "A Markdown Test" + 'title' => "A Markdown Test", + 'brief' => "The dragons explore markdown, sort of properly... Maybe.", + 'tags' => ['one', 'two', 'three', 'sexee'] ]); $post = $sql_adapter->get_postdata('/testing/markdowntest'); var_dump($sql_adapter->get_post_markdown($post['id'])); $sql_adapter->set_post_markdown($post['id'], ' - This is one hell of a cute test! + +This is one hell of a cute test! + > Just checking in... + +{{ +template: fragments/blog/card.html +}} ' ); var_dump($sql_adapter->get_post_markdown($post['id'])); diff --git a/www/src/dergdown.php b/www/src/dergdown.php index 0405127..1f8dbcd 100644 --- a/www/src/dergdown.php +++ b/www/src/dergdown.php @@ -1,22 +1,97 @@ highlighter = new Highlighter(); + $this->highlighter = null; + $this->BlockTypes['{'] []= 'DergInsert'; + + $this->dergInsertRenderer = null; + } + + public function setDergRenderer($dergRenderer) { + $this->dergInsertRenderer = $dergRenderer; + } + + protected function blockDergInsert($Line, $currentBlock) { + if (preg_match('/^{{\s?(.*)$/', $Line['body'], $match)) { + return array( + 'text' => $match[1] ?? '' + ); + } + } + + protected function blockDergInsertContinue($Line, $Block) { + if(isset($Block['complete'])) { + return; + } + + if(preg_match('/(.*)}}/', $Line['body'], $match)) { + $Block['text'] .= "\n" . $match[1]; + $Block['complete'] = true; + return $Block; + } + + $Block['text'] .= "\n" . $Line['body']; + + return $Block; + } + + protected function blockDergInsertComplete($Block) { + try { + $parsed_data = Yaml::parse($Block['text']); + } + catch (Exception $ex) { + return array( + 'markup' => ' +
    +

    Error in a dergen template!

    + YAML could not be parsed properly:
    + + ' . $ex->getMessage() . '
    ' + ); + } + + try { + if(!isset($this->dergInsertRenderer)) { + throw new Exception("No Dergen Renderer was set!"); + } + + $render_output = $this->dergInsertRenderer->dergRender($parsed_data); + } catch (Exception $ex) { + return array( + 'markup' => ' +
    +

    Error in a dergen template!

    + Rendering engine threw an error:
    + + ' . $ex->getMessage() . '
    ' + ); + } + + return array( + 'markup' => $render_output + ); } protected function blockFencedCodeComplete($block) - { + { if (! isset($block['element']['text']['attributes'])) { return $block; } + if(!isset($this->highlighter)) { + $this->highlighter = new Highlighter(); + } + $code = $block['element']['text']['text']; $languageClass = $block['element']['text']['attributes']['class']; $language = explode('-', $languageClass); diff --git a/www/src/serve/ajax.php b/www/src/serve/ajax.php index 679afe0..1746732 100644 --- a/www/src/serve/ajax.php +++ b/www/src/serve/ajax.php @@ -21,6 +21,8 @@ if(isset($REQUEST_QUERY['page'])) { $ajax_args['fa'] = $FONT_AWESOME_ARRAY; $ajax_args['page'] ??= $SITE_CONFIG['site_defaults']; -echo $twig->render('/ajax/' . $AJAX_REQUEST_TEMPLATE, $ajax_args); +$ajax_args['post'] ??= $ajax_args['page']; + +echo $twig->render($AJAX_REQUEST_TEMPLATE, $ajax_args); ?> \ No newline at end of file diff --git a/www/src/setup/db.php b/www/src/setup/db.php index 70b3d88..30a7357 100644 --- a/www/src/setup/db.php +++ b/www/src/setup/db.php @@ -29,14 +29,27 @@ $sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST); $adapter = new PostHandler($sql_adapter); require_once 'dergdown.php'; +require_once 'setup/derg_insert.php'; -function dergdown_to_html($text) { +function dergdown_to_html($post) { + $DergInsert = new DergInsertRenderer($post); $Parsedown = new Dergdown(); + $Parsedown->setDergRenderer($DergInsert); - return $Parsedown->text($text); + $markdown = $post->markdown; + + if($markdown == '') { + $markdown = ' +{{ +template: fragments/directory/inline.html +}} +'; + } + + return $Parsedown->text($markdown); } function post_to_html($post) { - return dergdown_to_html($post->markdown); + return dergdown_to_html($post); } $adapter->markdown_engine = "post_to_html"; diff --git a/www/src/setup/derg_insert.php b/www/src/setup/derg_insert.php new file mode 100644 index 0000000..8051a3e --- /dev/null +++ b/www/src/setup/derg_insert.php @@ -0,0 +1,43 @@ +twig = $twig; + $this->post = $post; + $this->postAdapter = $adapter; + } + + public function dergRender($renderConfig) { + global $FONT_AWESOME_ARRAY; + + if(!isset($renderConfig['template'])) { + throw new Exception("No template type given!"); + } + + $template = $renderConfig['template']; + + $args = [ + 'post' => $this->post, + 'page' => $this->post, + 'fa' => $FONT_AWESOME_ARRAY + ]; + + if(isset($renderConfig['post'])) { + $args['post'] = $this->postAdapter->get_post($renderConfig['post']); + } + if(isset($renderConfig['page'])) { + $args['page'] = $this->postAdapter->get_post($renderConfig['page']); + } + + return $this->twig->render($template, $args); + } +} + +?> \ No newline at end of file diff --git a/www/static/banner.js b/www/static/banner.js index 3da66de..b3101df 100644 --- a/www/static/banner.js +++ b/www/static/banner.js @@ -1,6 +1,8 @@ const BANNER_TIME = 600 * 1000.0 -const BANNER_ANIMATION = "opacity 0.8s linear, transform 0.1s linear" +// const BANNER_ANIMATION = "opacity 0.8s linear, transform 0.1s linear" +const BANNER_ANIMATION = "opacity 0.8s linear" + class BannerHandler { constructor(banner_container, banner_image, banner_link) { @@ -32,7 +34,7 @@ class BannerHandler { console.log("Starting tick") - this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 100); + this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 1000); } stopUpdateTick() { if(this.bannerUpdateTimer === null) { diff --git a/www/templates/ajax/compact_filelist/listing.html b/www/templates/ajax/compact_filelist/listing.html deleted file mode 100644 index ccdc7c9..0000000 --- a/www/templates/ajax/compact_filelist/listing.html +++ /dev/null @@ -1,4 +0,0 @@ - -{% for post in page.child_posts %} -{{ include('ajax/compact_filelist/entry.html') }} -{% endfor %} \ No newline at end of file diff --git a/www/templates/fragments/article_blop.html b/www/templates/fragments/blog/card.html similarity index 93% rename from www/templates/fragments/article_blop.html rename to www/templates/fragments/blog/card.html index 0000271..f084f0a 100644 --- a/www/templates/fragments/article_blop.html +++ b/www/templates/fragments/blog/card.html @@ -1,5 +1,4 @@ -
    @@ -16,7 +15,7 @@ - {{post.excerpt}} + {{ post.brief }}
    diff --git a/www/templates/fragments/blog/listing.html b/www/templates/fragments/blog/listing.html new file mode 100644 index 0000000..e69de29 diff --git a/www/templates/fragments/decoration/blog_header.html b/www/templates/fragments/decoration/blog_header.html new file mode 100644 index 0000000..7dd4d48 --- /dev/null +++ b/www/templates/fragments/decoration/blog_header.html @@ -0,0 +1,10 @@ + +
    +

    + {{ post.title }} +

    + + {% if post.authors %} + Written by {{ post.authors }} + {% endif %} +
    \ No newline at end of file diff --git a/www/templates/fragments/filepath_bar.html b/www/templates/fragments/decoration/navbar.html similarity index 96% rename from www/templates/fragments/filepath_bar.html rename to www/templates/fragments/decoration/navbar.html index f4a43a2..854a30c 100644 --- a/www/templates/fragments/filepath_bar.html +++ b/www/templates/fragments/decoration/navbar.html @@ -61,7 +61,7 @@
    .. - {{ include('ajax/compact_filelist/listing.html') }} + {{ include('fragments/directory/compact/listing.html') }} diff --git a/www/templates/ajax/compact_filelist/entry.html b/www/templates/fragments/directory/compact/entry.html similarity index 87% rename from www/templates/ajax/compact_filelist/entry.html rename to www/templates/fragments/directory/compact/entry.html index 6c63345..be4ca6d 100644 --- a/www/templates/ajax/compact_filelist/entry.html +++ b/www/templates/fragments/directory/compact/entry.html @@ -6,7 +6,7 @@