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_path_depth INTEGER NOT NULL DEFAULT 0,
|
||||||
|
|
||||||
post_create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
post_create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
post_update_time DATETIME NOT NULL
|
post_update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
DEFAULT CURRENT_TIMESTAMP
|
|
||||||
ON UPDATE CURRENT_TIMESTAMP,
|
post_access_count INTEGER DEFAULT 0,
|
||||||
|
|
||||||
post_metadata JSON NOT NULL,
|
post_metadata JSON NOT NULL,
|
||||||
|
post_settings_cache JSON DEFAULT NULL,
|
||||||
|
|
||||||
post_content MEDIUMTEXT,
|
post_content MEDIUMTEXT,
|
||||||
|
|
||||||
PRIMARY KEY(post_id),
|
PRIMARY KEY(post_id),
|
||||||
CONSTRAINT unique_post_path UNIQUE (post_path),
|
CONSTRAINT unique_post_path UNIQUE (post_path),
|
||||||
|
|
||||||
INDEX(post_path, post_update_time),
|
INDEX(post_path),
|
||||||
INDEX(post_path, post_create_time),
|
INDEX(post_path_depth, 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)
|
INSERT INTO posts (post_path, post_path_depth, post_metadata, post_content)
|
||||||
VALUES (
|
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
|
title: A little image test idea
|
||||||
|
directory:
|
||||||
|
settings:
|
||||||
|
colourscheme: spicy
|
||||||
|
banners:
|
||||||
|
- src: /about/neira/Neira_Queen.png
|
||||||
---
|
---
|
||||||
|
|
||||||
# README concept
|
# README concept
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
|
||||||
AddType text/plain .md
|
AddType text/plain .md
|
||||||
|
AddType text/plain .atom
|
||||||
|
AddType text/plain .rss
|
||||||
|
|
||||||
php_value upload_max_filesize 40M
|
php_value upload_max_filesize 40M
|
||||||
php_value post_max_size 42M
|
php_value post_max_size 42M
|
||||||
|
@ -7,7 +9,7 @@ php_value post_max_size 42M
|
||||||
RewriteEngine On
|
RewriteEngine On
|
||||||
RewriteBase /
|
RewriteBase /
|
||||||
|
|
||||||
RewriteCond %{REQUEST_URI} !^/?(static|raw)/.*
|
RewriteCond %{REQUEST_URI} !^/?(static|raw|robots\.txt).*
|
||||||
RewriteRule (.*) router.php
|
RewriteRule (.*) router.php
|
||||||
|
|
||||||
Allow from all
|
Allow from all
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"twig/twig": "^3.0",
|
"twig/twig": "^3.0",
|
||||||
"twig/markdown-extra": "^3.6",
|
"twig/markdown-extra": "^3.6",
|
||||||
"league/commonmark": "^2.4",
|
"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,19 +29,36 @@ 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 = $this->raw->prepare($qery);
|
||||||
|
if($argtypes != ""){
|
||||||
$stmt->bind_param($argtypes, ...$args);
|
$stmt->bind_param($argtypes, ...$args);
|
||||||
|
}
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
return $stmt->get_result();
|
return $stmt->get_result();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _normalize_post_data($post_data) {
|
function _normalize_post_data($post_data) {
|
||||||
if($post_data == null) {
|
$post_data ??= ['found' => null];
|
||||||
return [
|
|
||||||
"found" => false
|
if(isset($post_data['found']) && $post_data['found'] == false) {
|
||||||
];
|
return $post_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$post_data["found"] = true;
|
$post_data["found"] = true;
|
||||||
|
@ -53,7 +70,7 @@ class MySQLAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
function bump_post($post_path, $post_metadata = [], $create_dirs = true) {
|
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, "/");
|
$path_depth = substr_count($post_path, "/");
|
||||||
|
|
||||||
if($create_dirs) {
|
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) {
|
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, "/");
|
$path_depth = substr_count($post_path, "/");
|
||||||
|
|
||||||
$this->make_post_directory(dirname($post_path));
|
$this->make_post_directory(dirname($post_path));
|
||||||
|
|
||||||
|
$this->reset_post_settings_cache($post_path);
|
||||||
|
|
||||||
$qry = "
|
$qry = "
|
||||||
INSERT INTO posts
|
INSERT INTO posts
|
||||||
(post_path, post_path_depth, post_metadata, post_content)
|
(post_path, post_path_depth, post_metadata, post_content)
|
||||||
VALUES
|
VALUES
|
||||||
( ?, ?, ?, ?) AS new
|
( ?, ?, ?, ?) 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",
|
$this->_exec($qry, "siss",
|
||||||
$post_path,
|
$post_path,
|
||||||
|
@ -108,17 +234,78 @@ class MySQLAdapter {
|
||||||
$post_content);
|
$post_content);
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_post_by_path($post_path, $with_subposts = true) {
|
function get_settings_for_path($post_path) {
|
||||||
$qry = "SELECT * FROM posts WHERE 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();
|
$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);
|
$post_data = $this->_normalize_post_data($post_data);
|
||||||
|
|
||||||
|
if(!$post_data['found']) {
|
||||||
|
return $post_data;
|
||||||
|
}
|
||||||
|
|
||||||
if($with_subposts) {
|
if($with_subposts) {
|
||||||
$post_data['subposts'] = $this->get_subposts_by_path($post_path);
|
$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;
|
return $post_data;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +313,7 @@ class MySQLAdapter {
|
||||||
function get_subposts_by_path($path) {
|
function get_subposts_by_path($path) {
|
||||||
global $sql;
|
global $sql;
|
||||||
|
|
||||||
$path = chop($path, '/');
|
$path = $this->_sanitize_path($path);
|
||||||
|
|
||||||
$path_depth = substr_count($path, "/");
|
$path_depth = substr_count($path, "/");
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
require_once 'mysql_adapter.php';
|
require_once 'mysql_adapter.php';
|
||||||
|
|
||||||
use Spatie\YamlFrontMatter\YamlFrontMatter;
|
use Spatie\YamlFrontMatter\YamlFrontMatter;
|
||||||
|
use Laminas\Feed\Writer\Feed;
|
||||||
|
|
||||||
class PostHandler extends MySQLAdapter {
|
class PostHandler extends MySQLAdapter {
|
||||||
public $data_directory;
|
public $data_directory;
|
||||||
|
@ -16,11 +17,18 @@ class PostHandler extends MySQLAdapter {
|
||||||
function _normalize_post_data($post_data) {
|
function _normalize_post_data($post_data) {
|
||||||
$post_data = parent::_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_data["post_basename"] = basename($post_data["post_path"]);
|
||||||
|
|
||||||
$post_meta = $post_data['post_metadata'];
|
$post_meta = $post_data['post_metadata'];
|
||||||
|
|
||||||
$post_meta["title"] ??= basename($post_data["post_path"]);
|
$post_meta["title"] ??= basename($post_data["post_path"]);
|
||||||
|
if($post_meta["title"] == "") {
|
||||||
|
$post_meta["title"] = "root";
|
||||||
|
}
|
||||||
|
|
||||||
if(!isset($post_meta['type'])) {
|
if(!isset($post_meta['type'])) {
|
||||||
$type = null;
|
$type = null;
|
||||||
|
@ -31,17 +39,33 @@ class PostHandler extends MySQLAdapter {
|
||||||
'' => 'directory',
|
'' => 'directory',
|
||||||
'md' => 'text/markdown',
|
'md' => 'text/markdown',
|
||||||
'png' => 'image',
|
'png' => 'image',
|
||||||
|
'jpg' => 'image',
|
||||||
|
'jpeg' => 'image'
|
||||||
];
|
];
|
||||||
|
|
||||||
if(isset($ext_mapping[$ext])) {
|
if(isset($ext_mapping[$ext])) {
|
||||||
$post_meta['type'] = $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_metadata'] = $post_meta;
|
||||||
|
|
||||||
|
$post_data["post_file_dir"] = '/' . $this->data_directory . $post_data["post_path"];
|
||||||
|
|
||||||
return $post_data;
|
return $post_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +77,11 @@ class PostHandler extends MySQLAdapter {
|
||||||
parent::make_post_directory($directory);
|
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) {
|
function save_file($post_path, $file_path) {
|
||||||
$this->bump_post($post_path);
|
$this->bump_post($post_path);
|
||||||
move_uploaded_file($file_path, $this->data_directory . $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) {
|
function save_markdown_post($post_path, $post_data) {
|
||||||
$frontmatter_post = YamlFrontMatter::parse($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_content = $frontmatter_post->body();
|
||||||
$post_metadata = $frontmatter_post->matter();
|
$post_metadata = $frontmatter_post->matter();
|
||||||
|
|
||||||
if(basename($post_path) == "README.md") {
|
if(basename($post_path) == "README.md") {
|
||||||
$readme_metadata = [];
|
$readme_metadata = [];
|
||||||
if(isset($post_metadata['directory_data'])) {
|
if(isset($post_metadata['settings'])) {
|
||||||
$readme_metadata = $post_metadata['directory_data'];
|
$readme_metadata['settings'] = $post_metadata['settings'];
|
||||||
|
}
|
||||||
|
if(isset($post_metadata['directory'])) {
|
||||||
|
$readme_metadata = $post_metadata['directory'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->update_or_create_post(dirname($post_path),
|
$this->update_or_create_post(dirname($post_path),
|
||||||
|
@ -94,6 +126,86 @@ class PostHandler extends MySQLAdapter {
|
||||||
$this->save_file($post_path, $file_path);
|
$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: /
|
196
www/router.php
196
www/router.php
|
@ -1,20 +1,14 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
require_once 'vendor/autoload.php';
|
$data_time_start = microtime(true);
|
||||||
require_once 'post_adapter.php';
|
|
||||||
|
|
||||||
// $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();
|
$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']);
|
$loader = new \Twig\Loader\FilesystemLoader(['./templates', './user_content']);
|
||||||
|
|
||||||
$twig = new \Twig\Environment($loader,['debug' => true]);
|
$twig = new \Twig\Environment($loader,['debug' => true]);
|
||||||
|
@ -24,6 +18,42 @@ use Twig\Extra\Markdown\DefaultMarkdown;
|
||||||
use Twig\Extra\Markdown\MarkdownRuntime;
|
use Twig\Extra\Markdown\MarkdownRuntime;
|
||||||
use Twig\RuntimeLoader\RuntimeLoaderInterface;
|
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 {
|
$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
|
||||||
public function load($class) {
|
public function load($class) {
|
||||||
if (MarkdownRuntime::class === $class) {
|
if (MarkdownRuntime::class === $class) {
|
||||||
|
@ -32,11 +62,93 @@ $twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$SURI = $_SERVER['REQUEST_URI'];
|
function render_twig($template, $args = []) {
|
||||||
|
global $twig;
|
||||||
|
global $FONT_AWESOME_ARRAY;
|
||||||
|
|
||||||
if($SURI == '/') {
|
$args['fa'] = $FONT_AWESOME_ARRAY;
|
||||||
echo $twig->render('root.html');
|
|
||||||
} elseif(preg_match('/^\/api\/admin/', $SURI)) {
|
$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'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$args['og']['image'] ??= 'https://lucidragons.de' . $args['banner'][0]["src"];
|
||||||
|
|
||||||
|
$args['banner'] = json_encode($args['banner']);
|
||||||
|
|
||||||
|
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');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$user_api_key = '';
|
$user_api_key = '';
|
||||||
|
@ -54,7 +166,7 @@ if($SURI == '/') {
|
||||||
"authorized" => false
|
"authorized" => false
|
||||||
]);
|
]);
|
||||||
|
|
||||||
die();
|
log_and_die('/api/401');
|
||||||
}
|
}
|
||||||
|
|
||||||
if($SURI = '/api/admin/upload') {
|
if($SURI = '/api/admin/upload') {
|
||||||
|
@ -63,7 +175,13 @@ if($SURI == '/') {
|
||||||
echo json_encode(["ok" => true]);
|
echo json_encode(["ok" => true]);
|
||||||
}
|
}
|
||||||
} elseif(preg_match('/^\/api/', $SURI)) {
|
} elseif(preg_match('/^\/api/', $SURI)) {
|
||||||
if(preg_match('/^\/api\/posts(.*)$/', $SURI, $match)) {
|
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');
|
header('Content-Type: application/json');
|
||||||
echo json_encode($adapter->get_post_by_path($match[1]));
|
echo json_encode($adapter->get_post_by_path($match[1]));
|
||||||
|
@ -76,42 +194,24 @@ if($SURI == '/') {
|
||||||
|
|
||||||
echo $twig->render('upload.html');
|
echo $twig->render('upload.html');
|
||||||
}
|
}
|
||||||
} elseif($_SERVER['HTTP_SEC_FETCH_DEST'] == 'image') {
|
} 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);
|
header('Location: /raw' . $SURI);
|
||||||
exit(0);
|
exit(0);
|
||||||
} elseif(true) {
|
} elseif(true) {
|
||||||
$post = $adapter->get_post_by_path($SURI);
|
try_render_post($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', [
|
generate_website($_SERVER['REQUEST_URI']);
|
||||||
"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 {
|
log_and_die($_SERVER['REQUEST_URI']);
|
||||||
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!"
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
|
@ -1,69 +1,160 @@
|
||||||
|
|
||||||
const banner_show_time = 600 * 1000.0
|
const BANNER_TIME = 600 * 1000.0
|
||||||
const banner_animated_style = "opacity 0.8s linear, transform 0.1s linear"
|
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() {
|
this.bannerUpdateTimer = null
|
||||||
return (new Date()).getTime() / banner_show_time
|
this.currentPhase = 0
|
||||||
|
|
||||||
|
this.currentBannerData = null
|
||||||
|
try {
|
||||||
|
this.currentBannerData = JSON.parse(localStorage.getItem('main_banner_img'))
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
this.currentBannerData ||= {}
|
||||||
|
|
||||||
|
this.bannerDOM.onload=() => { this.onBannerLoaded() }
|
||||||
|
this.bannerDOM.onerror=() => {
|
||||||
|
this.fadeOut();
|
||||||
|
setTimeout(() => this.loadNextBanner(), 1000);
|
||||||
}
|
}
|
||||||
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
|
|
||||||
|
|
||||||
const banner_top = (1-(getBannerTime()%1)) * banner_top_min
|
|
||||||
banner.style.transform = "translateY(" + banner_top + 'px' + ")"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let banner_update_src = banner_current_src
|
startUpdateTick() {
|
||||||
function update_banner(banner, banner_container) {
|
if(this.bannerUpdateTimer !== null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
image_select = getBannerSrc()
|
console.log("Starting tick")
|
||||||
|
|
||||||
update_banner_top(banner, banner_container)
|
this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 100);
|
||||||
|
}
|
||||||
|
stopUpdateTick() {
|
||||||
|
if(this.bannerUpdateTimer === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if(image_select != banner_update_src) {
|
console.log("Stopping tick!")
|
||||||
banner.style.opacity = 0
|
|
||||||
|
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(() => {
|
setTimeout(() => {
|
||||||
banner.src = image_select
|
this.animateOn()
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
banner_update_src = image_select
|
this.startUpdateTick()
|
||||||
localStorage.setItem('main_banner_img', image_select)
|
}, 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const banner_container = document.getElementById("main_header")
|
animateOn() {
|
||||||
const banner = document.getElementById("main_banner_img")
|
this.bannerDOM.style.transition = BANNER_ANIMATION
|
||||||
|
|
||||||
|
|
||||||
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
|
start() {
|
||||||
})
|
this.fadeIn()
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
this.loadBanner()
|
||||||
banner.src = getBannerSrc()
|
}
|
||||||
document.getElementById("main_banner_img_link").href = "/gallery/test"
|
}
|
||||||
})
|
|
||||||
|
|
||||||
setInterval(() => update_banner(banner, banner_container), 100)
|
var bannerHandler = new BannerHandler(
|
||||||
addEventListener("resize", () => update_banner(banner, banner_container));
|
document.getElementById("main_header"),
|
||||||
|
document.getElementById("main_banner_img"),
|
||||||
|
document.getElementById("main_banner_img_link"))
|
||||||
|
|
||||||
|
bannerHandler.start()
|
||||||
|
|
||||||
|
// addEventListener("resize", () => update_banner(banner, banner_container));
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 2.4 MiB |
|
@ -6,6 +6,11 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--text_1);
|
||||||
|
padding-top: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
--bg_1: #0e0a2a;
|
--bg_1: #0e0a2a;
|
||||||
--bg_2: #2c2943;
|
--bg_2: #2c2943;
|
||||||
|
@ -26,6 +31,13 @@ body {
|
||||||
padding-bottom: 4rem;
|
padding-bottom: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.hsmol_hide {
|
||||||
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:link {
|
:link {
|
||||||
color: var(--highlight_1);
|
color: var(--highlight_1);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
@ -153,6 +165,11 @@ a:hover {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
#post_file_path a {
|
#post_file_path a {
|
||||||
color: var(--text_1);
|
color: var(--text_1);
|
||||||
|
@ -186,6 +203,17 @@ a:hover {
|
||||||
margin-left: auto;
|
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 {
|
#main_footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
|
|
@ -34,10 +34,3 @@ table.directory .entry_update_time {
|
||||||
display: block;
|
display: block;
|
||||||
width: 12rem;
|
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('/') %}
|
{% set split_post = post.post_path |split('/') %}
|
||||||
{% for i in range(0, split_post|length - 1) %}
|
{% for i in range(0, split_post|length - 1) %}
|
||||||
<li>
|
<li>
|
||||||
{% if i != 0 %}
|
|
||||||
>
|
|
||||||
{% endif %}
|
|
||||||
{% if i != 0 %}
|
{% if i != 0 %}
|
||||||
<a href="{{split_post|slice(0,i+1)|join('/')}}">
|
<a href="{{split_post|slice(0,i+1)|join('/')}}">
|
||||||
{{ split_post[i] }}
|
> {{ split_post[i] }}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/">root</a>
|
<a href="/">root</a>
|
||||||
|
@ -22,9 +19,15 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
<li style="margin-left: auto;">
|
<li class="hsmol_hide" style="margin-left: auto;">
|
||||||
<a href="/raw{{post.post_path}}">raw</a>
|
<a href="/raw{{post.post_path}}">raw</a>
|
||||||
<a href="/api/posts{{post.post_path}}">api</a>
|
<a href="/api/posts{{post.post_path}}">api</a>
|
||||||
</li>
|
</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>
|
</menu>
|
||||||
</div>
|
</div>
|
|
@ -2,6 +2,11 @@
|
||||||
|
|
||||||
{% extends "root.html" %}
|
{% 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 %}
|
{% block second_title %}
|
||||||
<h2> {{ post.post_metadata.title }} </h2>
|
<h2> {{ post.post_metadata.title }} </h2>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -13,6 +18,10 @@
|
||||||
<article>
|
<article>
|
||||||
{%block content_article %}
|
{%block content_article %}
|
||||||
{%endblock%}
|
{%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>
|
</article>
|
||||||
|
|
||||||
{%endblock%}
|
{%endblock%}
|
||||||
|
|
|
@ -14,12 +14,12 @@
|
||||||
<th></th>
|
<th></th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th class="entry_update_time">Modified</th>
|
<th class="entry_update_time hsmol_hide">Modified</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for subpost in subposts %}
|
{% for subpost in subposts %}
|
||||||
<tr class="entry">
|
<tr class="entry">
|
||||||
<td>
|
<td>
|
||||||
ICN
|
{{ fa[subpost.post_metadata.icon] | raw }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={{subpost.post_path}}>{{subpost.post_basename}}</a>
|
<a href={{subpost.post_path}}>{{subpost.post_basename}}</a>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<td class="entry_title">
|
<td class="entry_title">
|
||||||
{{ subpost.post_metadata.title }}
|
{{ subpost.post_metadata.title }}
|
||||||
</td>
|
</td>
|
||||||
<td class="entry_update_time">
|
<td class="entry_update_time hsmol_hide">
|
||||||
{{ subpost.post_update_time }}
|
{{ subpost.post_update_time }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -6,6 +6,14 @@
|
||||||
<link rel="stylesheet" href="/static/imagestyle.css">
|
<link rel="stylesheet" href="/static/imagestyle.css">
|
||||||
{%endblock%}
|
{%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%}
|
{%block content_article%}
|
||||||
<figure>
|
<figure>
|
||||||
<a target="_blank" href="{{post['post_file_dir']}}">
|
<a target="_blank" href="{{post['post_file_dir']}}">
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
{% extends "pathed_content.html" %}
|
{% extends "pathed_content.html" %}
|
||||||
|
|
||||||
|
{% block opengraph_tags %}
|
||||||
|
{{ parent() }}
|
||||||
|
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
{%endblock %}
|
||||||
|
|
||||||
{%block content_article%}
|
{%block content_article%}
|
||||||
{{ post['post_content']|markdown_to_html }}
|
{{ post['post_content']|markdown_to_html }}
|
||||||
{% endblock %}
|
{% 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>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Lucidragons' Fire</title>
|
<title>The Dergsite - {{og.title}}</title>
|
||||||
<link rel="stylesheet" href="/static/dergstyle.css">
|
<link rel="stylesheet" href="/static/dergstyle.css">
|
||||||
<link rel="icon" type="image/x-icon" href="/static/icon.jpeg">
|
<link rel="icon" type="image/x-icon" href="/static/icon.jpeg">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<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 %}
|
{% block extra_head %}{% endblock %}
|
||||||
|
|
||||||
<script src="/static/banner.js" defer></script>
|
{% 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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header id="main_header">
|
<header id="main_header">
|
||||||
<img id="main_banner_img"></img>
|
<img id="main_banner_img"></img>
|
||||||
<a id="main_banner_img_link" href="/gallery"> full picture</a>
|
<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>
|
<h1 id="big_title">{% block big_title %}The dergsite{%endblock%}</h1>
|
||||||
{% block second_title %}{% endblock %}
|
{% block second_title %}{% endblock %}
|
||||||
<div id="title_separator"></div>
|
<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