Compare commits

...

4 commits

Author SHA1 Message Date
0f2761cd61 feat: preliminary work on revised Post system
Some checks reported warnings
/ phplint (push) Has been cancelled
2024-02-12 23:20:43 +01:00
b8e449006b style: add HTMX 2024-02-12 23:07:58 +01:00
a2c6842b71 feat: add new Post wrapper and Blog overview page 2024-01-25 15:40:32 +01:00
b420a6eafa feat: simplify README handling 2024-01-22 17:05:11 +01:00
23 changed files with 700 additions and 54 deletions

View file

@ -0,0 +1,11 @@
{
"folders": [
{
"path": "."
},
{
"path": "../dragon_fire_content"
}
],
"settings": {}
}

View file

@ -1,11 +1,16 @@
<?php
$FONT_AWESOME_ARRAY=[
'markdown' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="20" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M593.8 59.1H46.2C20.7 59.1 0 79.8 0 105.2v301.5c0 25.5 20.7 46.2 46.2 46.2h547.7c25.5 0 46.2-20.7 46.1-46.1V105.2c0-25.4-20.7-46.1-46.2-46.1zM338.5 360.6H277v-120l-61.5 76.9-61.5-76.9v120H92.3V151.4h61.5l61.5 76.9 61.5-76.9h61.5v209.2zm135.3 3.1L381.5 256H443V151.4h61.5V256H566z"/></svg>',
'image' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',
'images' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M160 32c-35.3 0-64 28.7-64 64V320c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H160zM396 138.7l96 144c4.9 7.4 5.4 16.8 1.2 24.6S480.9 320 472 320H328 280 200c-9.2 0-17.6-5.3-21.6-13.6s-2.9-18.2 2.9-25.4l64-80c4.6-5.7 11.4-9 18.7-9s14.2 3.3 18.7 9l17.3 21.6 56-84C360.5 132 368 128 376 128s15.5 4 20 10.7zM192 128a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM48 120c0-13.3-10.7-24-24-24S0 106.7 0 120V344c0 75.1 60.9 136 136 136H456c13.3 0 24-10.7 24-24s-10.7-24-24-24H136c-48.6 0-88-39.4-88-88V120z"/></svg>',
'folder' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z"/></svg>',
'rss' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>'
'bars' => '<svg xmlns="http://www.w3.org/2000/svg" class="fa-icn" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"/></svg>',
'markdown' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="20" class="fa-icn" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M593.8 59.1H46.2C20.7 59.1 0 79.8 0 105.2v301.5c0 25.5 20.7 46.2 46.2 46.2h547.7c25.5 0 46.2-20.7 46.1-46.1V105.2c0-25.4-20.7-46.1-46.2-46.1zM338.5 360.6H277v-120l-61.5 76.9-61.5-76.9v120H92.3V151.4h61.5l61.5 76.9 61.5-76.9h61.5v209.2zm135.3 3.1L381.5 256H443V151.4h61.5V256H566z"/></svg>',
'image' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" class="fa-icn" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',
'images' => '<svg xmlns="http://www.w3.org/2000/svg" class="fa-icn" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M160 32c-35.3 0-64 28.7-64 64V320c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H160zM396 138.7l96 144c4.9 7.4 5.4 16.8 1.2 24.6S480.9 320 472 320H328 280 200c-9.2 0-17.6-5.3-21.6-13.6s-2.9-18.2 2.9-25.4l64-80c4.6-5.7 11.4-9 18.7-9s14.2 3.3 18.7 9l17.3 21.6 56-84C360.5 132 368 128 376 128s15.5 4 20 10.7zM192 128a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM48 120c0-13.3-10.7-24-24-24S0 106.7 0 120V344c0 75.1 60.9 136 136 136H456c13.3 0 24-10.7 24-24s-10.7-24-24-24H136c-48.6 0-88-39.4-88-88V120z"/></svg>',
'turn-up' => '<svg xmlns="http://www.w3.org/2000/svg" class="fa-icn" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M350 177.5c3.8-8.8 2-19-4.6-26l-136-144C204.9 2.7 198.6 0 192 0s-12.9 2.7-17.4 7.5l-136 144c-6.6 7-8.4 17.2-4.6 26s12.5 14.5 22 14.5h88l0 192c0 17.7-14.3 32-32 32H32c-17.7 0-32 14.3-32 32v32c0 17.7 14.3 32 32 32l80 0c70.7 0 128-57.3 128-128l0-192h88c9.6 0 18.2-5.7 22-14.5z"/></svg>',
'rectangle-list' => '<svg xmlns="http://www.w3.org/2000/svg" class="fa-icn" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H512c8.8 0 16-7.2 16-16V96c0-8.8-7.2-16-16-16H64zM0 96C0 60.7 28.7 32 64 32H512c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zm96 64a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm104 0c0-13.3 10.7-24 24-24H448c13.3 0 24 10.7 24 24s-10.7 24-24 24H224c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24H448c13.3 0 24 10.7 24 24s-10.7 24-24 24H224c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24H448c13.3 0 24 10.7 24 24s-10.7 24-24 24H224c-13.3 0-24-10.7-24-24zm-72-64a32 32 0 1 1 0-64 32 32 0 1 1 0 64zM96 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>',
'folder-tree' => '<svg xmlns="http://www.w3.org/2000/svg" class="fa-icn" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 32C64 14.3 49.7 0 32 0S0 14.3 0 32v96V384c0 35.3 28.7 64 64 64H256V384H64V160H256V96H64V32zM288 192c0 17.7 14.3 32 32 32H544c17.7 0 32-14.3 32-32V64c0-17.7-14.3-32-32-32H445.3c-8.5 0-16.6-3.4-22.6-9.4L409.4 9.4c-6-6-14.1-9.4-22.6-9.4H320c-17.7 0-32 14.3-32 32V192zm0 288c0 17.7 14.3 32 32 32H544c17.7 0 32-14.3 32-32V352c0-17.7-14.3-32-32-32H445.3c-8.5 0-16.6-3.4-22.6-9.4l-13.3-13.3c-6-6-14.1-9.4-22.6-9.4H320c-17.7 0-32 14.3-32 32V480z"/></svg>',
'folder' => '<svg xmlns="http://www.w3.org/2000/svg" class="fa-icn" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z"/></svg>',
'folder-open' => '<svg xmlns="http://www.w3.org/2000/svg" class="fa-icn" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M88.7 223.8L0 375.8V96C0 60.7 28.7 32 64 32H181.5c17 0 33.3 6.7 45.3 18.7l26.5 26.5c12 12 28.3 18.7 45.3 18.7H416c35.3 0 64 28.7 64 64v32H144c-22.8 0-43.8 12.1-55.3 31.8zm27.6 16.1C122.1 230 132.6 224 144 224H544c11.5 0 22 6.1 27.7 16.1s5.7 22.2-.1 32.1l-112 192C453.9 474 443.4 480 432 480H32c-11.5 0-22-6.1-27.7-16.1s-5.7-22.2 .1-32.1l112-192z"/></svg>',
'rss' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" class="fa-icn" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>'
];
?>

View file

@ -268,7 +268,7 @@ class MySQLAdapter {
];
$qry = "
SELECT post_path, post_metadata
SELECT *
FROM posts
WHERE MATCH(post_tags) AGAINST (? IN BOOLEAN MODE)
";
@ -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;

200
www/post.php Normal file
View file

@ -0,0 +1,200 @@
<?php
class PostData {
public static $default_banners = [];
public static $markdown_engine = null;
public $data;
public $html_data;
public $raw_data;
public static function _generate_404($sql_row) {
$sql_row ??= [
'post_id' => -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);
}
}
?>

View file

@ -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'
];
@ -97,6 +99,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,29 +116,18 @@ 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);
}
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:

View file

@ -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'];
@ -126,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;
@ -160,6 +186,7 @@ function try_render_post($SURI) {
break;
case 'blog':
case 'text/markdown':
echo render_twig('post_types/markdown.html', [
"post" => $post
@ -185,6 +212,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($adapter, $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 +284,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($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)) {
@ -247,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');
@ -255,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);
}
}

View file

@ -1,2 +1,3 @@
*.json
*.yml
api_admin_key

View file

@ -3,9 +3,9 @@ Allow from all
Options +Indexes
<filesMatch ".(flv|gif|ico|jpg|jpeg|mp4|mpeg|png|svg|swf|webp)$">
Header set Cache-Control "max-age=315360, public"
Header set Cache-Control "max-age=60, public"
</filesMatch>
<filesMatch ".(js)$">
Header set Cache-Control "max-age=315360, public"
Header set Cache-Control "max-age=60, public"
</filesMatch>

View file

@ -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);
}

View file

@ -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()
}

View file

@ -6,9 +6,11 @@
padding: 0;
}
svg {
.fa-icn {
fill: var(--text_1);
padding-top: 0.1rem;
height: 1em;
vertical-align: -0.125em;
}
body {
@ -16,6 +18,7 @@ body {
--bg_2: #2c2943;
--bg_3: #3f4148;
--highlight_0: #ee9015b1;
--highlight_1: #ee9015;
--highlight_2: #edd29e;
@ -174,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;
@ -185,6 +217,7 @@ a:hover {
z-index: 5;
}
#post_file_titles {
display: flex;
flex-direction: row;
@ -194,10 +227,11 @@ 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;
font-size: 1.5rem;
background: var(--highlight_1);
}
@ -208,6 +242,8 @@ a:hover {
padding-left: 0.5rem;
background: var(--highlight_1);
font-size: 1.1rem;
display: flex;
flex-direction: row;
@ -223,7 +259,77 @@ a:hover {
padding-right: 0.2rem;
}
#main_content_wrapper article {
.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);
border-radius: 0rem 0rem 0.8rem 0.8rem;
@ -232,7 +338,7 @@ a:hover {
padding: 0.75rem;
}
#main_content_wrapper article img {
article img {
display: block;
max-width: 100%;

1
www/static/htmx.min.js vendored Normal file

File diff suppressed because one or more lines are too long

15
www/static/three-dots.svg Normal file
View file

@ -0,0 +1,15 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="120" height="30" viewBox="0 0 120 30" xmlns="http://www.w3.org/2000/svg" fill="#fff">
<circle cx="15" cy="15" r="15">
<animate attributeName="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcMode="linear" repeatCount="indefinite"/>
<animate attributeName="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="60" cy="15" r="9" fill-opacity="0.3">
<animate attributeName="r" from="9" to="9" begin="0s" dur="0.8s" values="9;15;9" calcMode="linear" repeatCount="indefinite"/>
<animate attributeName="fill-opacity" from="0.5" to="0.5" begin="0s" dur="0.8s" values=".5;1;.5" calcMode="linear" repeatCount="indefinite"/>
</circle>
<circle cx="105" cy="15" r="15">
<animate attributeName="r" from="15" to="15" begin="0s" dur="0.8s" values="15;9;15" calcMode="linear" repeatCount="indefinite"/>
<animate attributeName="fill-opacity" from="1" to="1" begin="0s" dur="0.8s" values="1;.5;1" calcMode="linear" repeatCount="indefinite"/>
</circle>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,13 @@
<li class="folder-listing">
<span hx-boost="false"
hx-get="/ajax/open_folder_listing{{ post.post_path }}"
hx-target="closest li"
hx-swap="outerHTML">
{{ fa[post.post_metadata.icon] | raw }}
</span>
<a href={{post.post_path}}>
{{ post.post_metadata.title }}
</a>
</li>

View file

@ -0,0 +1,4 @@
{% for post in subposts %}
{{ include('ajax/folder_listing_entry.html') }}
{% endfor %}

View file

@ -0,0 +1,26 @@
<li class="folder-listing">
{% set folder_key = random() %}
<label for="folder-open-{{folder_key}}"
hx-trigger="click once queue:all"
hx-get="/ajax/folder_listing{{post.post_path}}"
hx-target="next ul">
{{ fa[post.post_metadata.icon] | raw }}
</label>
<a href={{post.post_path}}>
{{ post.post_metadata.title }}
</a>
<input type="checkbox" id="folder-open-{{folder_key}}">
<ul class="folder-listing">
<li>
<span>
Loading...
</span>
</li>
</ul>
</li>

View file

@ -0,0 +1,20 @@
<li class="folder-listing">
<span hx-boost="false"
hx-get="/ajax/closed_folder_listing{{ post.post_path }}"
hx-target="closest li"
hx-swap="outerHTML">
{{ fa[post.post_metadata.icon] | raw }}
</span>
<a href={{post.post_path}}>
{{ post.post_metadata.title }}
</a>
<ul class="folder-listing">
{% for post in subposts %}
{{ include('ajax/closed_folder_listing.html') }}
{% endfor %}
</ul>
</li>

View file

@ -0,0 +1,23 @@
<a href="{{post.url}}">
<div class="article_blop">
<div class="article_blop_bg" style="background-image:url({{post.preview_image}});"></div>
<div class="article_blop_content">
<h1 class="article_blop_title">
{{post.title}}
</h1>
<ul class="article_blop_tags">
{% for tag in post.tags %}
<li> {{ tag }} </li>
{% endfor %}
</ul>
<span class="article_blop_excerpt">
{{post.excerpt}}
</span>
</div>
</div>
</a>

View file

@ -17,17 +17,53 @@
{% endif %}
</li>
{% endfor %}
<li class="hsmol_hide" style="margin-left: auto;">
<a href="/raw{{post.post_path}}">raw</a>
<a href="/api/posts{{post.post_path}}">api</a>
</li>
<li class="hsmol_hide">
<li style="margin-left: auto;">
<label for="navbar-expander" id="navbar-expand-label"> {{ fa['bars'] | raw }} </label>
<a rel="alternate" type="application/rss+xml" target="_blank"
style="padding-left: 0.3rem;" href="/feed/rss{{post.post_path}}">
style="padding-left: 0.3rem;" href="/feed/rss{{post.post_path}}">
{{ fa['rss']|raw }}
</a>
</li>
</menu>
</div>
<input id="navbar-expander" class="navbar-expand" type="checkbox" hx-preserve>
<div class="navbar-expand">
Full path:
<ul class="navbar-full-path">
{% set split_post = post.post_path |split('/') %}
{% for i in range(0, split_post|length - 1) %}
<li>
{% if i != 0 %}
<a href="{{split_post|slice(0,i+1)|join('/')}}">
/ {{ split_post[i] }}
</a>
{% else %}
<a href="/">root</a>
{% endif %}
</li>
{% endfor %}
</ul>
Dev links:
<a hx-boost="false" href="/raw{{post.post_path}}">raw</a>
<a hx-boost="false" href="/api/posts{{post.post_path}}">api</a>
<h4>Folder browser: </h4>
<ul class="navbar-folder-list">
<li class="folder-listing">
<span>
{{ fa['turn-up'] | raw}}
</span>
<a href={{post.parent_path}}>..</a>
</li>
{% for post in subposts %}
{{ include('ajax/folder_listing_entry.html') }}
{% endfor %}
</ul>
</div>
</div>

View file

@ -17,7 +17,7 @@
{{ include('fragments/filepath_bar.html') }}
<article>
<article id="content_article">
{%block content_article %}
{%endblock%}

View file

@ -0,0 +1,20 @@
{% extends "pathed_content.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/directorystyle.css">
<link rel="stylesheet" href="/static/article_blop.css">
{%endblock%}
{%block content_article%}
{{ content_html|raw }}
{% if blog_posts|length > 0 %}
{% for post in blog_posts %}
{% include('fragments/article_blop.html') %}
{% endfor %}
{%else%}
<h4>How sad. There are no blog posts yet... What a real shame :c </h4>
{% endif %}
{%endblock%}

View file

@ -38,6 +38,6 @@
<span>This shouldn't happen, sorry!</span>
{% endif %}
{{ post.post_content|markdown_to_html }}
{{ content_html|raw }}
{%endblock%}

View file

@ -2,14 +2,24 @@
<html>
<head>
<title>{{og.site_name}} - {{og.title}}</title>
<link rel="stylesheet" href="/static/dergstyle.css">
<link rel="stylesheet" href="/static/modest.css">
<link rel="stylesheet" href="/static/solarized-dark.css">
<link rel="stylesheet" href="/static/directorystyle.css">
<link rel="stylesheet" href="/static/article_blop.css">
<link rel="stylesheet" href="/static/gallerystyle.css">
<link rel="icon" type="image/x-icon" href="/static/icon.jpeg">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script src="/static/htmx.min.js"></script>
<script id="main_banner_script" src="/static/banner.js"></script>
{% block feed_links %}
<link rel="alternate" type="application/rss+xml" title="{{og.site_name}} Global Feed" href="{{site_config.uri_prefix}}/feed">
{% endblock %}
@ -38,11 +48,9 @@
<meta property="al:android:app_name" content="Medium" />
{% endblock %}
<script type="text/javascript">
window.dergBannerOptions = JSON.parse('{{banner|raw}}');
</script>
</head>
<body>
<body id="main_body" hx-sync="a:replace img:drop" hx-boost="true" hx-indicator="#main_body">
{%if age_gate %}
<div id="age_gate_block">
@ -59,10 +67,14 @@
{% endif %}
<header id="main_header">
<img id="main_banner_img"></img>
<a id="main_banner_img_link" href="/gallery"> full picture</a>
<img id="main_banner_img" hx-preserve></img>
<a id="main_banner_img_link" href="/gallery" hx-preserve> full picture</a>
<script type="text/javascript">
window.dergBannerOptions = JSON.parse('{{banner|raw}}');
<script src="/static/banner.js"></script>
startBanner();
</script>
<h1 id="big_title">{% block big_title %}{{og.site_name}}{%endblock%}</h1>
{% block second_title %}{% endblock %}