Compare commits
32 commits
d47c4e2072
...
f4824f8ca8
Author | SHA1 | Date | |
---|---|---|---|
f4824f8ca8 | |||
5d1d4cdc32 | |||
454161e98a | |||
1b8bff401b | |||
30019b4c47 | |||
d584a2c373 | |||
d44b854673 | |||
83b849fda8 | |||
98e062611e | |||
91224e634a | |||
62089b9ee2 | |||
87a14e0624 | |||
596cc0e1a3 | |||
c6297fd81b | |||
b089bfc551 | |||
70a2c45509 | |||
e777ea5675 | |||
2add216157 | |||
a4fe4c4489 | |||
35ff4951ad | |||
edeecb8cf9 | |||
226d19e62c | |||
6cb60a6652 | |||
94b65aec8c | |||
eb87a78625 | |||
b552562f31 | |||
d2dc57a36a | |||
40a270059f | |||
2ee294b012 | |||
37357da4b0 | |||
b8ae2c5617 | |||
f2f8a235c8 |
28 changed files with 829 additions and 219 deletions
|
@ -10,22 +10,46 @@ CREATE TABLE posts (
|
|||
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
|
||||
ON UPDATE CURRENT_TIMESTAMP,
|
||||
post_update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
post_access_count INTEGER DEFAULT 0,
|
||||
|
||||
post_metadata JSON NOT NULL,
|
||||
post_settings_cache JSON DEFAULT NULL,
|
||||
|
||||
post_content MEDIUMTEXT,
|
||||
|
||||
PRIMARY KEY(post_id),
|
||||
CONSTRAINT unique_post_path UNIQUE (post_path),
|
||||
|
||||
INDEX(post_path, post_update_time),
|
||||
INDEX(post_path, post_create_time),
|
||||
INDEX(post_path_depth, post_path)
|
||||
INDEX(post_path),
|
||||
INDEX(post_path_depth, post_path),
|
||||
INDEX(post_create_time),
|
||||
INDEX(post_update_time)
|
||||
);
|
||||
|
||||
CREATE TABLE path_access_counts (
|
||||
access_time DATETIME NOT NULL,
|
||||
post_path VARCHAR(255),
|
||||
agent VARCHAR(255),
|
||||
referrer VARCHAR(255),
|
||||
|
||||
path_access_count INTEGER DEFAULT 0,
|
||||
path_processing_time DOUBLE PRECISION DEFAULT 0,
|
||||
|
||||
PRIMARY KEY(access_time, post_path, agent, referrer)
|
||||
);
|
||||
|
||||
CREATE TABLE feed_cache (
|
||||
search_path VARCHAR(255),
|
||||
export_type VARCHAR(255),
|
||||
|
||||
feed_created_on DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
feed_content MEDIUMTEXT,
|
||||
|
||||
PRIMARY KEY(search_path, export_type)
|
||||
);
|
||||
|
||||
INSERT INTO posts (post_path, post_path_depth, post_metadata, post_content)
|
||||
VALUES (
|
||||
|
|
8
test_entries/README.md
Normal file
8
test_entries/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
settings:
|
||||
colourscheme: fun
|
||||
post_style: generic
|
||||
banners:
|
||||
- src: /banner/0.png
|
||||
- src: /banner/1.png
|
||||
---
|
BIN
test_entries/about/neira/Neira_Queen.png
Normal file
BIN
test_entries/about/neira/Neira_Queen.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 796 KiB |
10
test_entries/about/neira/README.md
Normal file
10
test_entries/about/neira/README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
directory:
|
||||
settings:
|
||||
banners:
|
||||
- src: /about/neira/Neira_Queen.png
|
||||
from: 0.5
|
||||
to: 0.95
|
||||
---
|
||||
|
||||
# She is soft~<3
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Oh, the pain
|
||||
---
|
||||
|
||||
# AAA
|
Before Width: | Height: | Size: 527 KiB After Width: | Height: | Size: 527 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
|
@ -1,5 +1,10 @@
|
|||
---
|
||||
title: A little image test idea
|
||||
directory:
|
||||
settings:
|
||||
colourscheme: spicy
|
||||
banners:
|
||||
- src: /about/neira/Neira_Queen.png
|
||||
---
|
||||
|
||||
# README concept
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
|
||||
AddType text/plain .md
|
||||
AddType text/plain .atom
|
||||
AddType text/plain .rss
|
||||
|
||||
php_value upload_max_filesize 40M
|
||||
php_value post_max_size 42M
|
||||
|
@ -7,7 +9,7 @@ php_value post_max_size 42M
|
|||
RewriteEngine On
|
||||
RewriteBase /
|
||||
|
||||
RewriteCond %{REQUEST_URI} !^/?(static|raw)/.*
|
||||
RewriteCond %{REQUEST_URI} !^/?(static|raw|robots\.txt).*
|
||||
RewriteRule (.*) router.php
|
||||
|
||||
Allow from all
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"twig/twig": "^3.0",
|
||||
"twig/markdown-extra": "^3.6",
|
||||
"league/commonmark": "^2.4",
|
||||
"spatie/yaml-front-matter": "^2.0"
|
||||
"spatie/yaml-front-matter": "^2.0",
|
||||
"laminas/laminas-feed": "^2.6"
|
||||
}
|
||||
}
|
||||
|
|
10
www/fontawesome.php
Normal file
10
www/fontawesome.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?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>',
|
||||
'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>'
|
||||
];
|
||||
|
||||
?>
|
|
@ -29,21 +29,38 @@ class MySQLAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
function _exec($qery, $argtypes, ...$args) {
|
||||
function _sanitize_path($post_path) {
|
||||
$post_path = chop($post_path, '/');
|
||||
|
||||
if($post_path == "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if(!preg_match('/^(?:\/[\w-]+)+(?:\.[\w-]+)*$/', $post_path)) {
|
||||
echo "Post path match against " . $post_path . " failed!";
|
||||
die();
|
||||
}
|
||||
|
||||
return $post_path;
|
||||
}
|
||||
|
||||
function _exec($qery, $argtypes = '', ...$args) {
|
||||
$stmt = $this->raw->prepare($qery);
|
||||
$stmt->bind_param($argtypes, ...$args);
|
||||
if($argtypes != ""){
|
||||
$stmt->bind_param($argtypes, ...$args);
|
||||
}
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->get_result();
|
||||
}
|
||||
|
||||
function _normalize_post_data($post_data) {
|
||||
if($post_data == null) {
|
||||
return [
|
||||
"found" => false
|
||||
];
|
||||
}
|
||||
$post_data ??= ['found' => null];
|
||||
|
||||
if(isset($post_data['found']) && $post_data['found'] == false) {
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
$post_data["found"] = true;
|
||||
|
||||
$post_data['post_metadata'] = json_decode($post_data["post_metadata"], true) ?? [];
|
||||
|
@ -53,7 +70,7 @@ class MySQLAdapter {
|
|||
}
|
||||
|
||||
function bump_post($post_path, $post_metadata = [], $create_dirs = true) {
|
||||
$post_path = chop($post_path, '/');
|
||||
$post_path = $this->_sanitize_path($post_path);
|
||||
$path_depth = substr_count($post_path, "/");
|
||||
|
||||
if($create_dirs) {
|
||||
|
@ -88,18 +105,127 @@ class MySQLAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
function log_post_access($post_path, $agent, $referrer, $time) {
|
||||
$post_path = $this->_sanitize_path($post_path);
|
||||
|
||||
$qry = "INSERT INTO path_access_counts
|
||||
(access_time,
|
||||
post_path, agent, referrer,
|
||||
path_access_count,
|
||||
path_processing_time)
|
||||
VALUES ( from_unixtime(floor(unix_timestamp(CURRENT_TIMESTAMP) / 300)*300),
|
||||
?, ?, ?, 1, ?
|
||||
) AS new
|
||||
ON DUPLICATE KEY
|
||||
UPDATE path_access_count=path_access_counts.path_access_count+1,
|
||||
path_processing_time=path_access_counts.path_processing_time+new.path_processing_time;
|
||||
";
|
||||
|
||||
$this->_exec($qry, "sssd", $post_path, $agent, $referrer, $time);
|
||||
|
||||
if(preg_match('/^user/', $agent)) {
|
||||
$this->_exec("UPDATE posts SET post_access_count=post_access_count+1 WHERE post_path=?", "s", $post_path);
|
||||
}
|
||||
}
|
||||
|
||||
function get_post_access_counters() {
|
||||
$qry = "
|
||||
SELECT post_path, agent, path_access_count, path_processing_time
|
||||
FROM path_access_counts
|
||||
WHERE path_last_access_time > ( CURRENT_TIMESTAMP - INTERVAL 10 MINUTE );
|
||||
";
|
||||
|
||||
$data = $this->_exec($qry, "")->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$out_data = [];
|
||||
|
||||
foreach($data AS $post_data) {
|
||||
$path = $post_data['post_path'];
|
||||
|
||||
$agent_data = ($out_data[$path] ?? []);
|
||||
|
||||
$agent_data[$post_data['agent']] = [
|
||||
'count' => $post_data['path_access_count'],
|
||||
'time' => round($post_data['path_processing_time'], 6)
|
||||
];
|
||||
|
||||
$out_data[$path] = $agent_data;
|
||||
}
|
||||
|
||||
return $out_data;
|
||||
}
|
||||
|
||||
function get_post_access_counters_line() {
|
||||
$qry = "
|
||||
SELECT access_time, post_path, agent, referrer, path_access_count, path_processing_time
|
||||
FROM path_access_counts
|
||||
WHERE access_time < ( CURRENT_TIMESTAMP - INTERVAL 6 MINUTE )
|
||||
ORDER BY access_time DESC;
|
||||
";
|
||||
|
||||
$this->raw->begin_transaction();
|
||||
$top_access_time = null;
|
||||
|
||||
try {
|
||||
$data = $this->_exec($qry, "")->fetch_all(MYSQLI_ASSOC);
|
||||
|
||||
$data_prefix="access_metrics,host=" . $_SERVER['SERVER_NAME'];
|
||||
|
||||
$out_data = "";
|
||||
|
||||
foreach($data AS $post_data) {
|
||||
$top_access_time ??= $post_data['access_time'];
|
||||
|
||||
$path = $post_data['post_path'];
|
||||
if($path == '') {
|
||||
$path = '/';
|
||||
}
|
||||
$out_data .= $data_prefix . ",agent=".$post_data['agent'].",path=".$path.",referrer=".$post_data['referrer'];
|
||||
|
||||
$out_data .= " access_sum=" . $post_data['path_access_count'] . ",time_sum=" . $post_data['path_processing_time'];
|
||||
$out_data .= " " . strtotime($post_data['access_time']) . "000000000\n";
|
||||
}
|
||||
|
||||
|
||||
$this->_exec("DELETE FROM path_access_counts WHERE access_time <= ?", "s", $top_access_time);
|
||||
|
||||
$this->raw->commit();
|
||||
return $out_data;
|
||||
|
||||
} catch (\Throwable $th) {
|
||||
$this->raw->rollback();
|
||||
|
||||
throw $th;
|
||||
}
|
||||
}
|
||||
|
||||
function reset_post_settings_cache($post_path) {
|
||||
$post_path = $this->_sanitize_path($post_path);
|
||||
|
||||
$this->_exec("
|
||||
UPDATE posts
|
||||
SET post_settings_cache=NULL
|
||||
WHERE post_path LIKE ?;
|
||||
", "s", $post_path . "%");
|
||||
}
|
||||
|
||||
function update_or_create_post($post_path, $post_metadata, $post_content) {
|
||||
$post_path = chop($post_path, '/');
|
||||
$post_path = $this->_sanitize_path($post_path);
|
||||
$path_depth = substr_count($post_path, "/");
|
||||
|
||||
$this->make_post_directory(dirname($post_path));
|
||||
|
||||
$this->reset_post_settings_cache($post_path);
|
||||
|
||||
$qry = "
|
||||
INSERT INTO posts
|
||||
(post_path, post_path_depth, post_metadata, post_content)
|
||||
VALUES
|
||||
( ?, ?, ?, ?) AS new
|
||||
ON DUPLICATE KEY UPDATE post_metadata=new.post_metadata, post_content=new.post_content;";
|
||||
ON DUPLICATE KEY
|
||||
UPDATE post_metadata=new.post_metadata,
|
||||
post_content=new.post_content,
|
||||
post_update_time=CURRENT_TIMESTAMP;";
|
||||
|
||||
$this->_exec($qry, "siss",
|
||||
$post_path,
|
||||
|
@ -108,17 +234,78 @@ class MySQLAdapter {
|
|||
$post_content);
|
||||
}
|
||||
|
||||
function get_post_by_path($post_path, $with_subposts = true) {
|
||||
$qry = "SELECT * FROM posts WHERE post_path = ?";
|
||||
function get_settings_for_path($post_path) {
|
||||
$post_path = $this->_sanitize_path($post_path);
|
||||
|
||||
$post_path = chop($post_path, '/');
|
||||
$post_settings = $this->_exec("
|
||||
SELECT post_path, post_settings_cache
|
||||
FROM posts
|
||||
WHERE post_path = ?
|
||||
", "s", $post_path)->fetch_assoc();
|
||||
|
||||
if(!isset($post_settings)) {
|
||||
return [];
|
||||
}
|
||||
if(isset($post_settings['post_settings_cache'])) {
|
||||
return json_decode($post_settings['post_settings_cache'], true);
|
||||
}
|
||||
|
||||
$parent_settings = [];
|
||||
if($post_path != "") {
|
||||
$parent_settings = $this->get_settings_for_path(dirname($post_path));
|
||||
}
|
||||
|
||||
$post_settings = [];
|
||||
$post_metadata = $this->_exec("
|
||||
SELECT post_path, post_metadata
|
||||
FROM posts
|
||||
WHERE post_path = ?
|
||||
", "s", $post_path)->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->_exec("UPDATE posts SET post_settings_cache=? WHERE post_path=?", "ss",
|
||||
json_encode($post_settings), $post_path);
|
||||
|
||||
return $post_settings;
|
||||
}
|
||||
|
||||
function get_post_by_path($post_path,
|
||||
$with_subposts = false, $with_settings = true) {
|
||||
|
||||
$qry = "SELECT *
|
||||
FROM posts WHERE post_path = ?
|
||||
";
|
||||
|
||||
$post_path = $this->_sanitize_path($post_path);
|
||||
|
||||
$post_data = $this->_exec($qry, "s", $post_path)->fetch_assoc();
|
||||
|
||||
if(!isset($post_data)) {
|
||||
$post_data = ['found' => false];
|
||||
}
|
||||
$post_data['post_path'] = $post_path;
|
||||
|
||||
$post_data = $this->_normalize_post_data($post_data);
|
||||
|
||||
if(!$post_data['found']) {
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
if($with_subposts) {
|
||||
$post_data['subposts'] = $this->get_subposts_by_path($post_path);
|
||||
}
|
||||
if($with_settings) {
|
||||
$post_data['settings'] = $this->get_settings_for_path($post_path);
|
||||
}
|
||||
|
||||
return $post_data;
|
||||
}
|
||||
|
@ -126,7 +313,7 @@ class MySQLAdapter {
|
|||
function get_subposts_by_path($path) {
|
||||
global $sql;
|
||||
|
||||
$path = chop($path, '/');
|
||||
$path = $this->_sanitize_path($path);
|
||||
|
||||
$path_depth = substr_count($path, "/");
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
require_once 'mysql_adapter.php';
|
||||
|
||||
use Spatie\YamlFrontMatter\YamlFrontMatter;
|
||||
use Laminas\Feed\Writer\Feed;
|
||||
|
||||
class PostHandler extends MySQLAdapter {
|
||||
public $data_directory;
|
||||
|
@ -16,12 +17,19 @@ class PostHandler extends MySQLAdapter {
|
|||
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_meta["title"] ??= basename($post_data["post_path"]);
|
||||
|
||||
if($post_meta["title"] == "") {
|
||||
$post_meta["title"] = "root";
|
||||
}
|
||||
|
||||
if(!isset($post_meta['type'])) {
|
||||
$type = null;
|
||||
|
||||
|
@ -31,17 +39,33 @@ class PostHandler extends MySQLAdapter {
|
|||
'' => 'directory',
|
||||
'md' => 'text/markdown',
|
||||
'png' => 'image',
|
||||
'jpg' => 'image',
|
||||
'jpeg' => 'image'
|
||||
];
|
||||
|
||||
if(isset($ext_mapping[$ext])) {
|
||||
$post_meta['type'] = $ext_mapping[$ext];
|
||||
}
|
||||
else {
|
||||
$post_meta['type'] = '?';
|
||||
}
|
||||
}
|
||||
|
||||
$post_data["post_file_dir"] = '/' . $this->data_directory . $post_data["post_path"];
|
||||
if(!isset($post_meta['icon'])) {
|
||||
$icon_mapping = [
|
||||
'' => 'question',
|
||||
'text/markdown' => 'markdown',
|
||||
'directory' => 'folder',
|
||||
'image' => 'image'
|
||||
];
|
||||
|
||||
$post_meta['icon'] = $icon_mapping[$post_meta['type']] ?? 'question';
|
||||
}
|
||||
|
||||
$post_data['post_metadata'] = $post_meta;
|
||||
|
||||
$post_data["post_file_dir"] = '/' . $this->data_directory . $post_data["post_path"];
|
||||
|
||||
return $post_data;
|
||||
}
|
||||
|
||||
|
@ -53,6 +77,11 @@ class PostHandler extends MySQLAdapter {
|
|||
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) {
|
||||
$this->bump_post($post_path);
|
||||
move_uploaded_file($file_path, $this->data_directory . $post_path);
|
||||
|
@ -60,15 +89,18 @@ class PostHandler extends MySQLAdapter {
|
|||
|
||||
function save_markdown_post($post_path, $post_data) {
|
||||
$frontmatter_post = YamlFrontMatter::parse($post_data);
|
||||
$post_path = chop($post_path, '/');
|
||||
$post_path = $this->_sanitize_path($post_path);
|
||||
|
||||
$post_content = $frontmatter_post->body();
|
||||
$post_metadata = $frontmatter_post->matter();
|
||||
|
||||
if(basename($post_path) == "README.md") {
|
||||
$readme_metadata = [];
|
||||
if(isset($post_metadata['directory_data'])) {
|
||||
$readme_metadata = $post_metadata['directory_data'];
|
||||
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),
|
||||
|
@ -94,6 +126,86 @@ class PostHandler extends MySQLAdapter {
|
|||
$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 search_path=? AND export_type=?", "ss", $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("https://lucidragons.de" . $path);
|
||||
$feed->setFeedLink("https://lucidragons.de/feeds/atom" . $path, "atom");
|
||||
|
||||
$feed->setDateModified(time());
|
||||
|
||||
$feed->setDescription("DergenFeed for all your " . $path . " needs <3");
|
||||
|
||||
$feed_posts = $this->_exec("SELECT
|
||||
post_path,
|
||||
post_create_time, post_update_time,
|
||||
post_content,
|
||||
post_metadata
|
||||
FROM posts
|
||||
WHERE (post_path = ?) OR (post_path LIKE ?)
|
||||
ORDER BY post_create_time DESC LIMIT 200",
|
||||
"ss", $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('https://lucidragons.de' . $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
|
||||
(search_path, export_type, feed_content)
|
||||
VALUES
|
||||
(?, 'atom', ?),
|
||||
(?, 'rss', ?)",
|
||||
"ssss", $path, $feed->export('atom'),
|
||||
$path, $feed->export('rss'));
|
||||
|
||||
return $this->try_get_cached_feed($path, $export_opt);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
2
www/robots.txt
Normal file
2
www/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Allow: /
|
272
www/router.php
272
www/router.php
|
@ -1,20 +1,14 @@
|
|||
<?php
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
require_once 'post_adapter.php';
|
||||
$data_time_start = microtime(true);
|
||||
|
||||
// $sql = mysqli_connect('mysql', 'root', 'example', 'dragon_fire');
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
require_once 'post_adapter.php';
|
||||
require_once 'fontawesome.php';
|
||||
|
||||
$adapter = new PostHandler();
|
||||
|
||||
//if (!$sql)
|
||||
// {
|
||||
// echo 'Connection failed<br>';
|
||||
// echo 'Error number: ' . mysqli_connect_errno() . '<br>';
|
||||
// echo 'Error message: ' . mysqli_connect_error() . '<br>';
|
||||
// die();
|
||||
// }
|
||||
|
||||
$loader = new \Twig\Loader\FilesystemLoader(['./templates', './user_content']);
|
||||
|
||||
$twig = new \Twig\Environment($loader,['debug' => true]);
|
||||
|
@ -24,6 +18,42 @@ use Twig\Extra\Markdown\DefaultMarkdown;
|
|||
use Twig\Extra\Markdown\MarkdownRuntime;
|
||||
use Twig\RuntimeLoader\RuntimeLoaderInterface;
|
||||
|
||||
function deduce_user_agent() {
|
||||
$real_agent=$_SERVER['HTTP_USER_AGENT'];
|
||||
|
||||
if(preg_match('/(Googlebot|\w*Google\w*)/', $real_agent, $match)) {
|
||||
return "bot/google/" . $match[1];
|
||||
}
|
||||
elseif(preg_match('/(Mozilla|Chrome|Chromium)/', $real_agent, $match)) {
|
||||
return "user/" . $match[1];
|
||||
}
|
||||
else {
|
||||
return "unidentified";
|
||||
}
|
||||
}
|
||||
|
||||
function log_and_die($path, $die_code = 0, $referrer = null) {
|
||||
global $data_time_start;
|
||||
global $adapter;
|
||||
|
||||
$data_time_end = microtime(true);
|
||||
|
||||
if(!isset($referrer)) {
|
||||
$referrer = 'magic';
|
||||
|
||||
if(isset($_SERVER['HTTP_REFERER'])) {
|
||||
$referrer = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
|
||||
}
|
||||
}
|
||||
|
||||
$adapter->log_post_access($path,
|
||||
deduce_user_agent(),
|
||||
$referrer,
|
||||
$data_time_end - $data_time_start);
|
||||
|
||||
die($die_code);
|
||||
}
|
||||
|
||||
$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
|
||||
public function load($class) {
|
||||
if (MarkdownRuntime::class === $class) {
|
||||
|
@ -32,86 +62,156 @@ $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
|
|||
}
|
||||
});
|
||||
|
||||
$SURI = $_SERVER['REQUEST_URI'];
|
||||
function render_twig($template, $args = []) {
|
||||
global $twig;
|
||||
global $FONT_AWESOME_ARRAY;
|
||||
|
||||
if($SURI == '/') {
|
||||
echo $twig->render('root.html');
|
||||
} elseif(preg_match('/^\/api\/admin/', $SURI)) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$user_api_key = '';
|
||||
if(isset($_GET['api_key'])) {
|
||||
$user_api_key = $_GET['api_key'];
|
||||
}
|
||||
if(isset($_POST['api_key'])) {
|
||||
$user_api_key = $_POST['api_key'];
|
||||
$args['fa'] = $FONT_AWESOME_ARRAY;
|
||||
|
||||
$post = $args['post'] ?? [];
|
||||
$settings = $post['settings'] ?? [];
|
||||
$meta = $post['post_metadata'] ?? [];
|
||||
|
||||
$args['banner'] ??= $settings['banners'] ?? [
|
||||
["src"=> "/static/banner/0.png"],
|
||||
["src" => "/static/banner/1.png"]
|
||||
];
|
||||
|
||||
$args['og'] = array_merge([
|
||||
"title" => $meta['title'] ?? "Dergennibble",
|
||||
"url" => $_SERVER['REQUEST_URI'],
|
||||
"description" => $meta['description']
|
||||
?? $settings['description']
|
||||
?? "The softest spot to find dragons on"
|
||||
], $args['og'] ?? []);
|
||||
|
||||
if(($meta['type'] ?? '') == 'image') {
|
||||
$args['og']['image'] ??= "https://lucidragons.de" . $post['post_file_dir'];
|
||||
}
|
||||
|
||||
if($user_api_key != file_get_contents('secrets/api_admin_key')) {
|
||||
http_response_code(401);
|
||||
$args['og']['image'] ??= 'https://lucidragons.de' . $args['banner'][0]["src"];
|
||||
|
||||
echo json_encode([
|
||||
"authorized" => false
|
||||
]);
|
||||
$args['banner'] = json_encode($args['banner']);
|
||||
|
||||
die();
|
||||
}
|
||||
|
||||
if($SURI = '/api/admin/upload') {
|
||||
$adapter->handle_upload($_POST['post_path'], $_FILES['post_data']['tmp_name']);
|
||||
|
||||
echo json_encode(["ok" => true]);
|
||||
}
|
||||
} elseif(preg_match('/^\/api/', $SURI)) {
|
||||
if(preg_match('/^\/api\/posts(.*)$/', $SURI, $match)) {
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($adapter->get_post_by_path($match[1]));
|
||||
|
||||
} elseif(preg_match('/^\/api\/subposts(.*)$/', $SURI, $match)) {
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(get_subposts($match[1]));
|
||||
} elseif($SURI == '/api/upload') {
|
||||
|
||||
echo $twig->render('upload.html');
|
||||
}
|
||||
} elseif($_SERVER['HTTP_SEC_FETCH_DEST'] == 'image') {
|
||||
header('Location: /raw' . $SURI);
|
||||
exit(0);
|
||||
} elseif(true) {
|
||||
$post = $adapter->get_post_by_path($SURI);
|
||||
|
||||
if($post['post_metadata']['type'] == 'directory') {
|
||||
if(preg_match('/^(.*[^\/])((?:#.*)?)$/', $SURI, $match)) {
|
||||
header('Location: ' . $match[1] . '/' . $match[2]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo $twig->render('post_types/directory.html', [
|
||||
"post" => $post,
|
||||
"subposts" => $post['subposts']
|
||||
]);
|
||||
}
|
||||
elseif($post['post_metadata']['type'] == 'text/markdown') {
|
||||
echo $twig->render('post_types/markdown.html', [
|
||||
"post" => $post,
|
||||
"subposts" => $post['subposts']
|
||||
]);
|
||||
}
|
||||
elseif($post['post_metadata']['type'] == 'image') {
|
||||
echo $twig->render('post_types/image.html', [
|
||||
"post" => $post
|
||||
]);
|
||||
}
|
||||
|
||||
} else {
|
||||
echo $twig->render('rrror.html',[
|
||||
"error_code" => '404 Hoard not found!',
|
||||
"error_description" => "Well, we searched
|
||||
far and wide for `" . $SURI . "` but
|
||||
somehow it must have gotten lost... Sorry!"
|
||||
]);
|
||||
echo $twig->render($template, $args);
|
||||
}
|
||||
|
||||
function try_render_post($SURI) {
|
||||
global $adapter;
|
||||
|
||||
$post = $adapter->get_post_by_path($SURI);
|
||||
|
||||
if(!$post['found']) {
|
||||
echo render_twig('post_types/rrror.html',[
|
||||
"error_code" => '404 Hoard not found!',
|
||||
"error_description" => "Well, we searched
|
||||
far and wide for `" . $SURI . "` but
|
||||
somehow it must have gotten lost... Sorry!",
|
||||
"post" => array_merge($post, [
|
||||
"post_metadata" => ["title" => "404 ???"]
|
||||
])
|
||||
]);
|
||||
|
||||
log_and_die('/404', referrer: ($_SERVER['HTTP_REFERER'] ?? 'magic'));
|
||||
}
|
||||
|
||||
switch($post['post_metadata']['type']) {
|
||||
case 'directory':
|
||||
if(preg_match('/^(.*[^\/])((?:#.*)?)$/', $SURI, $match)) {
|
||||
header('Location: ' . $match[1] . '/' . $match[2]);
|
||||
|
||||
die();
|
||||
}
|
||||
|
||||
echo render_twig('post_types/directory.html', [
|
||||
"post" => $post,
|
||||
"subposts" => $adapter->get_subposts_by_path($SURI)
|
||||
]);
|
||||
|
||||
break;
|
||||
|
||||
case 'text/markdown':
|
||||
echo render_twig('post_types/markdown.html', [
|
||||
"post" => $post
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'image':
|
||||
echo render_twig('post_types/image.html', [
|
||||
"post" => $post,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function generate_website($SURI) {
|
||||
global $adapter;
|
||||
global $FONT_AWESOME_ARRAY;
|
||||
|
||||
if(preg_match('/^\/api\/admin/', $SURI)) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$user_api_key = '';
|
||||
if(isset($_GET['api_key'])) {
|
||||
$user_api_key = $_GET['api_key'];
|
||||
}
|
||||
if(isset($_POST['api_key'])) {
|
||||
$user_api_key = $_POST['api_key'];
|
||||
}
|
||||
|
||||
if($user_api_key != file_get_contents('secrets/api_admin_key')) {
|
||||
http_response_code(401);
|
||||
|
||||
echo json_encode([
|
||||
"authorized" => false
|
||||
]);
|
||||
|
||||
log_and_die('/api/401');
|
||||
}
|
||||
|
||||
if($SURI = '/api/admin/upload') {
|
||||
$adapter->handle_upload($_POST['post_path'], $_FILES['post_data']['tmp_name']);
|
||||
|
||||
echo json_encode(["ok" => true]);
|
||||
}
|
||||
} elseif(preg_match('/^\/api/', $SURI)) {
|
||||
if($SURI == '/api/post_counters') {
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($adapter->get_post_access_counters());
|
||||
} elseif($SURI == '/api/metrics') {
|
||||
header('Content-Type: application/line');
|
||||
echo $adapter->get_post_access_counters_line();
|
||||
} elseif(preg_match('/^\/api\/posts(.*)$/', $SURI, $match)) {
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($adapter->get_post_by_path($match[1]));
|
||||
|
||||
} elseif(preg_match('/^\/api\/subposts(.*)$/', $SURI, $match)) {
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode(get_subposts($match[1]));
|
||||
} elseif($SURI == '/api/upload') {
|
||||
|
||||
echo $twig->render('upload.html');
|
||||
}
|
||||
} elseif(preg_match('/^\/feed(?:\/(rss|atom)(.*))?$/', $SURI, $match)) {
|
||||
$feed = $adapter->get_laminas_feed($match[2] ?? '/', $match[1] ?? 'rss');
|
||||
|
||||
header('Content-Type: application/xml');
|
||||
header('Cache-Control: max-age=1800');
|
||||
header('Etag: W/"' . $SURI . '/' . strtotime($feed['feed_ts']) . '"');
|
||||
|
||||
echo $feed['feed'];
|
||||
} elseif(preg_match('/^\s*image/', $_SERVER['HTTP_ACCEPT'])) {
|
||||
header('Location: /raw' . $SURI);
|
||||
exit(0);
|
||||
} elseif(true) {
|
||||
try_render_post($SURI);
|
||||
}
|
||||
}
|
||||
|
||||
generate_website($_SERVER['REQUEST_URI']);
|
||||
|
||||
log_and_die($_SERVER['REQUEST_URI']);
|
||||
|
||||
?>
|
||||
|
|
|
@ -1,69 +1,160 @@
|
|||
|
||||
const banner_show_time = 600 * 1000.0
|
||||
const banner_animated_style = "opacity 0.8s linear, transform 0.1s linear"
|
||||
const BANNER_TIME = 600 * 1000.0
|
||||
const BANNER_ANIMATION = "opacity 0.8s linear, transform 0.1s linear"
|
||||
|
||||
var banner_current_src = localStorage.getItem('main_banner_img')
|
||||
class BannerHandler {
|
||||
constructor(banner_container, banner_image, banner_link) {
|
||||
this.bannerContainerDOM = banner_container
|
||||
this.bannerDOM = banner_image
|
||||
this.bannerLinkDOM = banner_link
|
||||
|
||||
function getBannerTime() {
|
||||
return (new Date()).getTime() / banner_show_time
|
||||
}
|
||||
function getBannerSrc() {
|
||||
return "/static/banner/" + Math.floor(getBannerTime() + 1000/banner_show_time) % 2 + ".png"
|
||||
}
|
||||
function update_banner_top(banner, banner_container) {
|
||||
const banner_top_max = 0
|
||||
const banner_top_min = -banner.clientHeight + banner_container.clientHeight
|
||||
this.bannerUpdateTimer = null
|
||||
this.currentPhase = 0
|
||||
|
||||
const banner_top = (1-(getBannerTime()%1)) * banner_top_min
|
||||
banner.style.transform = "translateY(" + banner_top + 'px' + ")"
|
||||
}
|
||||
this.currentBannerData = null
|
||||
try {
|
||||
this.currentBannerData = JSON.parse(localStorage.getItem('main_banner_img'))
|
||||
} catch(e) {}
|
||||
|
||||
let banner_update_src = banner_current_src
|
||||
function update_banner(banner, banner_container) {
|
||||
this.currentBannerData ||= {}
|
||||
|
||||
image_select = getBannerSrc()
|
||||
this.bannerDOM.onload=() => { this.onBannerLoaded() }
|
||||
this.bannerDOM.onerror=() => {
|
||||
this.fadeOut();
|
||||
setTimeout(() => this.loadNextBanner(), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
update_banner_top(banner, banner_container)
|
||||
startUpdateTick() {
|
||||
if(this.bannerUpdateTimer !== null) {
|
||||
return
|
||||
}
|
||||
|
||||
if(image_select != banner_update_src) {
|
||||
banner.style.opacity = 0
|
||||
console.log("Starting tick")
|
||||
|
||||
this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 100);
|
||||
}
|
||||
stopUpdateTick() {
|
||||
if(this.bannerUpdateTimer === null) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Stopping tick!")
|
||||
|
||||
clearInterval(this.bannerUpdateTimer);
|
||||
this.bannerUpdateTimer = null
|
||||
}
|
||||
|
||||
getPhase() {
|
||||
return (new Date()).getTime() / BANNER_TIME;
|
||||
}
|
||||
|
||||
getTargetBanner() {
|
||||
if(window.dergBannerOptions == null) {
|
||||
return {}
|
||||
}
|
||||
|
||||
var banner_index = Math.floor(this.getPhase()) % window.dergBannerOptions.length
|
||||
var banner_choice = window.dergBannerOptions[banner_index]
|
||||
|
||||
return banner_choice
|
||||
}
|
||||
|
||||
updateTranslation() {
|
||||
const bannerTranslateMax = -this.bannerDOM.clientHeight + this.bannerContainerDOM.clientHeight
|
||||
|
||||
const bannerPercentageFrom = this.currentBannerData.from || 0;
|
||||
const bannerPercentageTo = this.currentBannerData.to || 1;
|
||||
|
||||
const bannerPercentage = (bannerPercentageFrom + (bannerPercentageTo - bannerPercentageFrom) * this.currentPhase)
|
||||
|
||||
const banner_top = (1-bannerPercentage) * bannerTranslateMax
|
||||
this.bannerDOM.style.transform = "translateY(" + banner_top + 'px' + ")"
|
||||
}
|
||||
|
||||
fadeOut() {
|
||||
this.bannerDOM.style.opacity = 0;
|
||||
}
|
||||
fadeIn() {
|
||||
this.bannerDOM.style.opacity = 0.3;
|
||||
}
|
||||
|
||||
loadNextBanner() {
|
||||
this.currentBannerData = this.getTargetBanner()
|
||||
this.currentBannerData.bannerTime = new Date()
|
||||
|
||||
this.loadBanner()
|
||||
}
|
||||
|
||||
loadBanner() {
|
||||
console.log("Target banner:");
|
||||
console.log(this.currentBannerData);
|
||||
|
||||
localStorage.setItem("main_banner_img", JSON.stringify(this.currentBannerData))
|
||||
|
||||
this.currentPhase = 0
|
||||
|
||||
if((this.currentBannerData === null)
|
||||
|| (this.currentBannerData.src === undefined)) {
|
||||
|
||||
this.onBannerLoaded()
|
||||
return
|
||||
}
|
||||
|
||||
this.bannerDOM.src = this.currentBannerData.src
|
||||
this.bannerLinkDOM.href = this.currentBannerData.href || this.currentBannerData.src
|
||||
}
|
||||
onBannerLoaded() {
|
||||
console.log("Loaded?");
|
||||
|
||||
this.currentPhase = this.getPhase() % 1
|
||||
|
||||
this.updateTranslation()
|
||||
this.fadeIn()
|
||||
|
||||
setTimeout(() => {
|
||||
banner.src = image_select
|
||||
}, 1000)
|
||||
this.animateOn()
|
||||
|
||||
banner_update_src = image_select
|
||||
localStorage.setItem('main_banner_img', image_select)
|
||||
this.startUpdateTick()
|
||||
}, 10)
|
||||
}
|
||||
|
||||
document.getElementById("main_banner_img_link").href = "/gallery/test"
|
||||
updateTick() {
|
||||
console.log("tick")
|
||||
|
||||
const nextPhase = this.getPhase() % 1;
|
||||
|
||||
if((nextPhase > this.currentPhase)
|
||||
&& (this.currentBannerData.src == this.getTargetBanner().src)) {
|
||||
|
||||
this.currentPhase = nextPhase;
|
||||
this.updateTranslation();
|
||||
} else {
|
||||
this.fadeOut()
|
||||
setTimeout(() => {
|
||||
this.loadNextBanner()
|
||||
}, 1000);
|
||||
|
||||
this.stopUpdateTick()
|
||||
}
|
||||
}
|
||||
|
||||
animateOn() {
|
||||
this.bannerDOM.style.transition = BANNER_ANIMATION
|
||||
}
|
||||
|
||||
start() {
|
||||
this.fadeIn()
|
||||
|
||||
this.loadBanner()
|
||||
}
|
||||
}
|
||||
|
||||
const banner_container = document.getElementById("main_header")
|
||||
const banner = document.getElementById("main_banner_img")
|
||||
|
||||
|
||||
banner.addEventListener('load', () => {
|
||||
update_banner_top(banner, banner_container)
|
||||
|
||||
const next_banner_src = getBannerSrc()
|
||||
|
||||
if(banner_current_src != next_banner_src) {
|
||||
banner.style.transition = banner_animated_style
|
||||
setTimeout(() => banner.style.opacity = 0.3, 1000)
|
||||
}
|
||||
else {
|
||||
banner.style.opacity = 0.3
|
||||
setTimeout(() => banner.style.transition = banner_animated_style, 0)
|
||||
}
|
||||
|
||||
banner_current_src = next_banner_src
|
||||
})
|
||||
var bannerHandler = new BannerHandler(
|
||||
document.getElementById("main_header"),
|
||||
document.getElementById("main_banner_img"),
|
||||
document.getElementById("main_banner_img_link"))
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
banner.src = getBannerSrc()
|
||||
document.getElementById("main_banner_img_link").href = "/gallery/test"
|
||||
})
|
||||
bannerHandler.start()
|
||||
|
||||
setInterval(() => update_banner(banner, banner_container), 100)
|
||||
addEventListener("resize", () => update_banner(banner, banner_container));
|
||||
// addEventListener("resize", () => update_banner(banner, banner_container));
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.4 MiB |
|
@ -6,6 +6,11 @@
|
|||
padding: 0;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: var(--text_1);
|
||||
padding-top: 0.1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
--bg_1: #0e0a2a;
|
||||
--bg_2: #2c2943;
|
||||
|
@ -26,6 +31,13 @@ body {
|
|||
padding-bottom: 4rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
.hsmol_hide {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
}
|
||||
|
||||
:link {
|
||||
color: var(--highlight_1);
|
||||
font-style: italic;
|
||||
|
@ -153,6 +165,11 @@ a:hover {
|
|||
display: flex;
|
||||
flex-direction: row;
|
||||
list-style-type: none;
|
||||
|
||||
overflow-x: scroll;
|
||||
overflow-y: hidden;
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
#post_file_path a {
|
||||
color: var(--text_1);
|
||||
|
@ -186,6 +203,17 @@ a:hover {
|
|||
margin-left: auto;
|
||||
}
|
||||
|
||||
#content_footer {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
|
||||
margin-top: 0.5em;
|
||||
|
||||
font-size: 0.8em;
|
||||
border-top: solid 1px darkgrey;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
#main_footer {
|
||||
display: flex;
|
||||
|
||||
|
|
|
@ -33,11 +33,4 @@ table.directory tr.entry .entry_title {
|
|||
table.directory .entry_update_time {
|
||||
display: block;
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
table.directory .entry_update_time {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -8,12 +8,9 @@
|
|||
{% set split_post = post.post_path |split('/') %}
|
||||
{% for i in range(0, split_post|length - 1) %}
|
||||
<li>
|
||||
{% if i != 0 %}
|
||||
>
|
||||
{% endif %}
|
||||
{% if i != 0 %}
|
||||
<a href="{{split_post|slice(0,i+1)|join('/')}}">
|
||||
{{ split_post[i] }}
|
||||
> {{ split_post[i] }}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="/">root</a>
|
||||
|
@ -22,9 +19,15 @@
|
|||
{% endfor %}
|
||||
|
||||
|
||||
<li style="margin-left: auto;">
|
||||
<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">
|
||||
<a rel="alternate" type="application/rss+xml" target="_blank"
|
||||
style="padding-left: 0.3rem;" href="/feed/rss{{post.post_path}}">
|
||||
{{ fa['rss']|raw }}
|
||||
</a>
|
||||
</li>
|
||||
</menu>
|
||||
</div>
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
{% extends "root.html" %}
|
||||
|
||||
{% block feed_links %}
|
||||
<link rel="alternate" type="application/rss+xml" title="DergSite Global Feed" href="https://lucidragons.de/feed">
|
||||
<link rel="alternate" type="application/atom+xml" title="DergSite Feed for {{post.post_path}}" href="https://lucidragons.de/feed/atom{{post.post_path}}">
|
||||
{% endblock %}
|
||||
|
||||
{% block second_title %}
|
||||
<h2> {{ post.post_metadata.title }} </h2>
|
||||
{% endblock %}
|
||||
|
@ -13,6 +18,10 @@
|
|||
<article>
|
||||
{%block content_article %}
|
||||
{%endblock%}
|
||||
|
||||
<span id="content_footer">
|
||||
This article was created on {{ post.post_create_time }}, last edited {{ post.post_update_time }}, and was viewed {{ post.post_access_count }} times~
|
||||
</span>
|
||||
</article>
|
||||
|
||||
{%endblock%}
|
||||
|
|
|
@ -14,12 +14,12 @@
|
|||
<th></th>
|
||||
<th>Name</th>
|
||||
<th>Title</th>
|
||||
<th class="entry_update_time">Modified</th>
|
||||
<th class="entry_update_time hsmol_hide">Modified</th>
|
||||
</tr>
|
||||
{% for subpost in subposts %}
|
||||
<tr class="entry">
|
||||
<td>
|
||||
ICN
|
||||
{{ fa[subpost.post_metadata.icon] | raw }}
|
||||
</td>
|
||||
<td>
|
||||
<a href={{subpost.post_path}}>{{subpost.post_basename}}</a>
|
||||
|
@ -27,7 +27,7 @@
|
|||
<td class="entry_title">
|
||||
{{ subpost.post_metadata.title }}
|
||||
</td>
|
||||
<td class="entry_update_time">
|
||||
<td class="entry_update_time hsmol_hide">
|
||||
{{ subpost.post_update_time }}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -6,6 +6,14 @@
|
|||
<link rel="stylesheet" href="/static/imagestyle.css">
|
||||
{%endblock%}
|
||||
|
||||
{% block opengraph_tags %}
|
||||
{{ parent() }}
|
||||
|
||||
<meta property="og:type" content="image" />
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="robots" content="max-image-preview:large">
|
||||
{%endblock %}
|
||||
|
||||
{%block content_article%}
|
||||
<figure>
|
||||
<a target="_blank" href="{{post['post_file_dir']}}">
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
|
||||
{% extends "pathed_content.html" %}
|
||||
|
||||
{% block opengraph_tags %}
|
||||
{{ parent() }}
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
{%endblock %}
|
||||
|
||||
{%block content_article%}
|
||||
{{ post['post_content']|markdown_to_html }}
|
||||
{% endblock %}
|
19
www/templates/post_types/rrror.html
Normal file
19
www/templates/post_types/rrror.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
|
||||
{% extends "pathed_content.html" %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="/static/rrrorstyle.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block second_title %}
|
||||
<h2> (Broken) </h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_article %}
|
||||
<h1 id="rrr_header"> The Dergs are confused:</h2>
|
||||
<h2 id="rrr_code"> {{ error_code }}</h1>
|
||||
|
||||
<div>
|
||||
{{ error_description|markdown_to_html }}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,21 +1,44 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Lucidragons' Fire</title>
|
||||
<title>The Dergsite - {{og.title}}</title>
|
||||
<link rel="stylesheet" href="/static/dergstyle.css">
|
||||
<link rel="icon" type="image/x-icon" href="/static/icon.jpeg">
|
||||
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
{% block feed_links %}
|
||||
<link rel="alternate" type="application/rss+xml" title="DergSite Global Feed" href="https://lucidragons.de/feed">
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_head %}{% endblock %}
|
||||
|
||||
<script src="/static/banner.js" defer></script>
|
||||
</head>
|
||||
{% block opengraph_tags %}
|
||||
<meta property="og:site_name" content="The Dergsite">
|
||||
|
||||
<meta property="og:url" content="{{og.url}}" />
|
||||
|
||||
<meta property="og:title" content="{{ og.title|e }}" />
|
||||
<meta property="twitter:title" content="{{ og.title|e }}" />
|
||||
|
||||
<meta property="og:description" content="{{ og.description|e }}" />
|
||||
<meta property="twitter:description" content="{{ og.description|e }}" />
|
||||
|
||||
<meta property="og:image" content="{{og.image}}" />
|
||||
<meta name="twitter:image" content="{{og.image}}" />
|
||||
{% endblock %}
|
||||
|
||||
<script type="text/javascript">
|
||||
window.dergBannerOptions = JSON.parse('{{banner|raw}}');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<header id="main_header">
|
||||
<img id="main_banner_img"></img>
|
||||
<a id="main_banner_img_link" href="/gallery"> full picture</a>
|
||||
|
||||
<script src="/static/banner.js"></script>
|
||||
|
||||
<h1 id="big_title">{% block big_title %}The dergsite{%endblock%}</h1>
|
||||
{% block second_title %}{% endblock %}
|
||||
<div id="title_separator"></div>
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
|
||||
{% extends "root.html" %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="/static/rrrorstyle.css">
|
||||
{% endblock %}
|
||||
|
||||
{% block second_title %}
|
||||
<h2> (Broken) </h2>
|
||||
{% endblock %}
|
||||
|
||||
{% block main_content %}
|
||||
<article>
|
||||
<h1 id="rrr_header"> The Dergs are confused:</h2>
|
||||
<h2 id="rrr_code"> {{ error_code }}</h1>
|
||||
|
||||
<div>
|
||||
{{ error_description|markdown_to_html }}
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
|
@ -1,15 +0,0 @@
|
|||
# About the dergens
|
||||
|
||||
### Who we are
|
||||
Just little things, but it does feel good.
|
||||
|
||||
|
||||
No spacing, huh...
|
||||
|
||||
### What we like to do
|
||||
|
||||
nom Nom Nom test?
|
||||
Though I don't understand why this is not quite functional...
|
||||
|
||||
|
||||
Markdown truly is a delight to work with!
|
Loading…
Add table
Add a link
Reference in a new issue