Compare commits

..

No commits in common. "main" and "dev/ci-test" have entirely different histories.

70 changed files with 552 additions and 3223 deletions

2
.gitignore vendored
View file

@ -1,3 +1 @@
/vendor/
sftp.json

6
.htaccess Normal file
View file

@ -0,0 +1,6 @@
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} !^/?static/.*
RewriteRule (.*) router.php

7
composer.json Normal file
View file

@ -0,0 +1,7 @@
{
"require": {
"twig/twig": "^3.0",
"twig/markdown-extra": "^3.6",
"league/commonmark": "^2.4"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
FROM composer
WORKDIR /app
COPY www/composer.* .
COPY composer.* .
RUN composer install
FROM php:apache
@ -12,9 +12,6 @@ RUN chmod -R a+r ./vendor
RUN a2enmod rewrite
RUN a2enmod headers
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
RUN mkdir raw
COPY www/ .
RUN chmod -R a+rw $(ls -I vendor)
COPY . .
RUN chmod -R a+r $(ls -I vendor)

View file

@ -1,4 +0,0 @@
FROM mysql:8.0-debian
WORKDIR /docker-entrypoint-initdb.d
COPY mysql_schema.sql ./

View file

@ -5,44 +5,22 @@ services:
dockerfile: docker_dev/Dockerfile
ports:
- 8081:80
environment:
MYSQL_USER: root
MYSQL_PASSWORD: example
MYSQL_DATABASE: dragon_fire
MYSQL_HOST: mysql
MYSQL_PORT: 3306
develop:
watch:
- path: ./
action: rebuild
- path: ../www/composer.*
action: rebuild
- path: ../www/
- path: ../.
action: sync
target: /var/www/html
ignore:
- ../.git
- mysql_schema.sql
volumes:
- website_datavolume:/var/www/html/raw
target: /usr/local/apache2/htdocs/
mysql:
build:
dockerfile: MysqlDockerfile
image: mysql:8.0-debian
# NOTE: use of "mysql_native_password" is not recommended: https://dev.mysql.com/doc/refman/8.0/en/upgrading-from-previous-series.html#upgrade-caching-sha2-password
# (this is just an example, not intended to be a production configuration)
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
MYSQL_ROOT_PASSWORD: example
ports:
- 3306:3306
develop:
watch:
- path: mysql_schema.sql
action: rebuild
volumes:
- sqlvolume:/var/lib/mysql
volumes:
sqlvolume: {}
website_datavolume: {}

View file

@ -1,148 +0,0 @@
CREATE DATABASE dragon_fire;
USE dragon_fire;
CREATE TABLE posts (
post_id INTEGER AUTO_INCREMENT,
host VARCHAR(64) NOT NULL,
post_path VARCHAR(255) NOT NULL,
post_path_depth INTEGER NOT NULL DEFAULT 0,
post_create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_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 UNIQUE (host, post_path),
INDEX(host, 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,
host VARCHAR(64) 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, host, post_path, agent, referrer)
);
CREATE TABLE feed_cache (
host VARCHAR(64) NOT NULL,
search_path VARCHAR(255),
export_type VARCHAR(255),
feed_created_on DATETIME DEFAULT CURRENT_TIMESTAMP,
feed_content MEDIUMTEXT,
PRIMARY KEY(host, search_path, export_type)
);
INSERT INTO posts (post_path, post_path_depth, post_metadata, post_content)
VALUES (
'/about',
0,
'
{
"tags": ["test", "test2", "hellorld"],
"brief": "This is a simple test indeed",
"type": "text/markdown",
"title": "About the dergen"
}
',
'
# About the dergs indeed
This is just a simple test. Might be nice, though!
'
), (
'/about/neira',
1,
'
{
"tags": ["test", "test2", "hellorld", "neira"],
"brief": "This is a soft grab of Neira",
"type": "text/markdown",
"title": "About her"
}
',
'
# Nothing here yet!
Sorry for this. She is working hard :>
'
), (
'/about/xasin',
1,
'
{
"tags": ["test", "test2", "hellorld", "xasin"],
"brief": "This is a soft grab of Xasin",
"type": "text/markdown",
"title": "About her"
}
',
'
# Nothing here yet!
Sorry for this. He is working hard :>
'
), (
'/about/mesh',
1,
'
{
"tags": ["test", "test2", "hellorld", "mesh"],
"brief": "This is a soft grab of Mesh",
"type": "text/markdown",
"title": "About her"
}
',
'
# Nothing here yet!
Sorry for this. Shi is working hard :>
'
), (
'/about/alviere',
1,
'
{
"tags": ["test", "test2", "hellorld", "mesh"],
"brief": "SHE GRABS",
"type": "text/markdown",
"title": "SHE GRABS"
}
',
'
# Nothing here yet!
Sorry for this. She GRABS A LOT
----
## And now, for the lorem:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eleifend mi in nulla posuere sollicitudin aliquam ultrices sagittis orci. Risus commodo viverra maecenas accumsan lacus vel facilisis. Sed viverra tellus in hac habitasse. Nulla malesuada pellentesque elit eget gravida cum. Posuere sollicitudin aliquam ultrices sagittis orci a. Libero nunc consequat interdum varius sit amet. Bibendum arcu vitae elementum curabitur vitae nunc sed velit. Amet mauris commodo quis imperdiet massa tincidunt nunc pulvinar. Sed adipiscing diam donec adipiscing. Laoreet id donec ultrices tincidunt arcu non sodales. Id semper risus in hendrerit gravida rutrum quisque non. Ut venenatis tellus in metus vulputate eu.
Risus sed vulputate odio ut enim blandit volutpat. Placerat in egestas erat imperdiet. Non curabitur gravida arcu ac tortor dignissim convallis aenean. Neque aliquam vestibulum morbi blandit cursus risus at. Elementum integer enim neque volutpat ac tincidunt vitae semper. Eu ultrices vitae auctor eu augue ut. In mollis nunc sed id semper risus in hendrerit gravida. Lectus arcu bibendum at varius vel pharetra vel turpis nunc. In pellentesque massa placerat duis. Non quam lacus suspendisse faucibus. Vitae aliquet nec ullamcorper sit amet risus nullam. Accumsan lacus vel facilisis volutpat est velit egestas dui.
Risus feugiat in ante metus dictum at tempor commodo. Duis ut diam quam nulla. Nunc aliquet bibendum enim facilisis gravida neque convallis. Tincidunt augue interdum velit euismod in pellentesque. Praesent semper feugiat nibh sed pulvinar proin gravida hendrerit lectus. Non odio euismod lacinia at quis risus sed vulputate odio. Nunc sed blandit libero volutpat sed cras ornare arcu. Adipiscing enim eu turpis egestas pretium aenean pharetra magna. Ut tristique et egestas quis ipsum suspendisse. Blandit cursus risus at ultrices mi tempus imperdiet nulla malesuada.
'
);

43
router.php Normal file
View file

@ -0,0 +1,43 @@
<?php
require_once 'vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader(['./templates', './user_content']);
$twig = new \Twig\Environment($loader,['debug' => true]);
$twig->addExtension(new Twig\Extra\Markdown\MarkdownExtension());
use Twig\Extra\Markdown\DefaultMarkdown;
use Twig\Extra\Markdown\MarkdownRuntime;
use Twig\RuntimeLoader\RuntimeLoaderInterface;
$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
public function load($class) {
if (MarkdownRuntime::class === $class) {
return new MarkdownRuntime(new DefaultMarkdown());
}
}
});
if($_SERVER['REQUEST_URI'] == '/') {
echo $twig->render('root.html');
} elseif(preg_match('/^\/about(.html)?$/', $_SERVER['REQUEST_URI'])) {
echo $twig->render('about.html');
} elseif(preg_match('/^\/gallery\/([^\?]+)/', $_SERVER['REQUEST_URI'])) {
echo $twig->render('/gallery/gallery_entry.html', [
'image_url' => '/static/banner/0.png',
'image_title' => 'Test!',
'image_desc' => 'A soft piece made by a dear friend',
'artist_name' => 'Doggonaut',
'artist_src_link' => 'https://twitter.com/doggonaut'
]);
} else {
echo $twig->render('rrror.html',[
"error_code" => '404 Hoard not found!',
"error_description" => "Well, we searched
far and wide for `" . $_SERVER['REQUEST_URI'] . "` but
somehow it must have gotten lost... Sorry!"
]);
}
?>

View file

@ -1,15 +0,0 @@
CURL_URL=${DERG_UPLOAD_URL:-https://lucidragons.de/api/admin/upload}
CURL_KEY=${DERG_UPLOAD_KEY:-SoftDragonKeys}
for FILE_PATH in "$@"
do
if [ -f "${FILE_PATH}" ]; then
POST_PATH="/${FILE_PATH#"./"}"
echo "Uploading ${FILE_PATH} to ${POST_PATH}"
curl -i -X POST -H "Content-Type: multipart/form-data" \
-F "api_key=${CURL_KEY}" -F "post_path=${POST_PATH}" -F "post_data=@${FILE_PATH}" "${CURL_URL}"
fi
done

View file

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

69
static/banner.js Normal file
View file

@ -0,0 +1,69 @@
const banner_show_time = 600 * 1000.0
const banner_animated_style = "opacity 0.8s linear, transform 0.1s linear"
var banner_current_src = localStorage.getItem('main_banner_img')
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
const banner_top = (1-(getBannerTime()%1)) * banner_top_min
banner.style.transform = "translateY(" + banner_top + 'px' + ")"
}
let banner_update_src = banner_current_src
function update_banner(banner, banner_container) {
image_select = getBannerSrc()
update_banner_top(banner, banner_container)
if(image_select != banner_update_src) {
banner.style.opacity = 0
setTimeout(() => {
banner.src = image_select
}, 1000)
banner_update_src = image_select
localStorage.setItem('main_banner_img', image_select)
document.getElementById("main_banner_img_link").href = "/gallery/test"
}
}
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
})
document.addEventListener("DOMContentLoaded", function () {
banner.src = getBannerSrc()
document.getElementById("main_banner_img_link").href = "/gallery/test"
})
setInterval(() => update_banner(banner, banner_container), 100)
addEventListener("resize", () => update_banner(banner, banner_container));

View file

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 527 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After

134
static/dergstyle.css Normal file
View file

@ -0,0 +1,134 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
color: #B0B0B0;
background: #201c2a;
margin: 0px;
position: relative;
min-height: 100vh;
padding-bottom: 4em;
}
:link {
color: cyan;
font-style: italic;
text-decoration: none;
transition: color 0.2s;
}
a:visited {
color: cyan;
}
a:hover {
color: lightblue;
}
#main_header {
overflow: hidden;
position: relative;
padding-bottom: 0.7em;
}
#main_banner_img {
position: absolute;
left: 0px;
right: 0px;
width: 100vw;
z-index: -1;
opacity: 0;
top: 0px;
}
#main_banner_img_link {
position: absolute;
right: 2vw;
bottom: 0.5em;
font-size: 0.8em;
}
#nav_bar {
display: flex;
flex-direction: row;
justify-content: center;
list-style-type: none;
margin-top: 1em;
padding: 0px;
}
#nav_bar li {
padding: 0em 0.3em 0em 0.3em;
}
#big_title {
text-align: center;
font-size: 2.5em;
margin-bottom: 0.2em;
}
#main_header h2 {
text-align: center;
font-size: 2em;
margin-bottom: 0.2em;
}
#title_separator {
height: 1.5px;
background-color: #ddd;
opacity: 0.5;
margin-left: 2em;
margin-right: 2em;
}
#main_content_wrapper {
padding: 3vmin 1em 1em 1em;
width: auto;
margin-left: max(0.75em, min(max(20vmin, 50vw - 30rem), 50vw - 25rem));
margin-right: max(0.75em, min(max(20vmin, 50vw - 30rem), 50vw - 25rem));
min-height: 100%;
background: #3e355479;
}
#main_content_wrapper article {
background: #3e3554;
color: #c6c3c3;
border-radius: 0.3em;
box-shadow: 3px 7px 7px 0px #00000040;
padding: 0.75em;
}
#main_content_wrapper article h1 {
text-align: left;
padding-left: 3vmin;
margin-bottom: 0.2em;
border-bottom: solid 1px darkgrey;
}
#main_footer {
display: flex;
height: 2.5em;
text-align: center;
background-color: #3a3a3a;
margin: 0px;
position: absolute;
bottom: 0px;
width: 100%;
}
#main_footer span {
align-self: flex-end;
width: 100%;
}

View file

@ -1,12 +1,18 @@
#main_content_wrapper figcaption h1 {
article {
margin-top: 0.8em;
margin-left: 10%;
margin-right: 10%;
}
figcaption {
text-align: center;
border-bottom: solid 3px darkgrey;
font-size: 1.8rem;
font-size: 2em;
margin-bottom: 0.3rem;
margin-bottom: 0.3em;
}
#gallery_image {

View file

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before After
Before After

13
static/rrrorstyle.css Normal file
View file

@ -0,0 +1,13 @@
#rrr_header {
font-size: 2em;
border-bottom: 1px solid grey;
padding-bottom: 0.2em;
margin-bottom: 0.3em;
}
#rrr_code {
font-size: 1.5em;
}

13
templates/about.html Normal file
View file

@ -0,0 +1,13 @@
{% extends "root.html" %}
{% block second_title %}
<h2> Abouts </h2>
{% endblock %}
{%block main_content%}
<article>
{{ include('about.md')|markdown_to_html }}
</article>
{%endblock%}

31
templates/index.html Normal file
View file

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<title>Lucidragons' Fire</title>
<link rel="stylesheet" href="/dergstyle.css">
</head>
<body>
<h1 id="big_title">The dergsite</h1>
<div id="title_separator"></div>
<ul>
<li><a href="/about"> About </a></li>
<li><a href="/blog"> Blog </a></li>
<li><a href="/projects"> Projects </a></li>
<li><a href="/artwork"> Artworks </a></li>
</ul>
<div id="main_content_wrapper">
{% block content %}<h3>Soon there shall be content!</h3>{% endblock %}
</div>
<div id="sidebar-wrapper">
{%block sidebar %}
<ul>
<li> Test 1 </li>
</ul>
{% endblock %}
</div>
</body>
</html>

18
templates/root.dergplate Normal file
View file

@ -0,0 +1,18 @@
<html>
<head>
<title>PHP Test</title>
</head>
<body>
<?php
echo '<p>Hello World</p>';
require 'src/templater.php';
$test = new TemplateFillout('test.dergplate');
$test->render();
?>
</body>
</html>

42
templates/root.html Normal file
View file

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>Lucidragons' Fire</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 extra_head %}{% endblock %}
<script src="/static/banner.js" defer></script>
</head>
<body>
<header id="main_header">
<img id="main_banner_img"></img>
<a id="main_banner_img_link" href="/gallery"> full picture</a>
<h1 id="big_title">{% block big_title %}The dergsite{%endblock%}</h1>
{% block second_title %}{% endblock %}
<div id="title_separator"></div>
<menu id="nav_bar">
<li><a href="/about"> About </a></li>
<li><a href="/blog"> Blog </a></li>
<li><a href="/projects"> Projects </a></li>
<li><a href="/artwork"> Artworks </a></li>
</menu>
</header>
<main id="main_content_wrapper">
{% block main_content %}<h3>Soon there shall be content!</h3>{% endblock %}
</main>
<footer id="main_footer">
{% block main_footer %}
<span> test? </span>
{% endblock %}
</footer>
</body>
</html>

21
templates/rrror.html Normal file
View file

@ -0,0 +1,21 @@
{% 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 %}

View file

@ -1,8 +0,0 @@
---
settings:
colourscheme: fun
post_style: generic
banners:
- src: /banner/0.png
- src: /banner/1.png
---

View file

@ -1,11 +0,0 @@
---
tags: [what]
directory_data:
type: text/markdown
---
# The dergens
The
yes

View file

@ -1,8 +0,0 @@
---
---
# About her
This is some test content, to simulate a little markdown page for... Well, our website.
I wouldn't worry too much about it. Just know it's here :)

View file

@ -1,8 +0,0 @@
---
title: A cuddly image <3
author: Shaky // Doggonaut
---
# Cuddly dragons
A dear picture made by a dear friend. Shaky - we hope you are OK.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 796 KiB

View file

@ -1,10 +0,0 @@
---
directory:
settings:
banners:
- src: /about/neira/Neira_Queen.png
from: 0.5
to: 0.95
---
# She is soft~<3

View file

@ -1,5 +0,0 @@
---
title: Oh, the pain
---
# AAA

View file

@ -1,9 +0,0 @@
---
tags:
- neira
- comfy
- testing
title: Subtest, it's nice :>
---
## Subtests, because it's nice

View file

@ -1,12 +0,0 @@
---
title: A little image test idea
---
# README concept
This file is just to show the README concept - it's its own file but will be
rendered under a directory listing :)
There's also a test for an image! Let's hope that works:
![A very cute image :>](1.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View file

@ -1,8 +0,0 @@
---
title: A cuddly image <3
author: Shaky // Doggonaut
---
# Cuddly dragons
A dear picture made by a dear friend. Shaky - we hope you are OK.

View file

@ -1,17 +0,0 @@
---
title: A little image test idea
directory:
settings:
colourscheme: spicy
banners:
- src: /about/neira/Neira_Queen.png
---
# README concept
This file is just to show the README concept - it's its own file but will be
rendered under a directory listing :)
There's also a test for an image! Let's hope that works:
![A very cute image :>](1.png)

15
user_content/about.md Normal file
View file

@ -0,0 +1,15 @@
# 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!

1
www/.gitignore vendored
View file

@ -1 +0,0 @@
vendor/

View file

@ -1,34 +1 @@
AddType text/plain .md
AddType text/plain .atom
AddType text/plain .rss
# php_value upload_max_filesize 40M
# php_value post_max_size 42M
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_URI} !^/(raw|static)/
RewriteRule ^.*\.(flv|gif|ico|jpg|jpeg|mp4|mpeg|png|svg|swf|webp)$ raw/%{HTTP_HOST}%{REQUEST_URI} [L,END]
RewriteRule ^/?raw/(.*)$ raw/%{HTTP_HOST}/$1 [L,END]
RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,END]
RewriteCond %{REQUEST_URI} !^/?(static|raw|robots\.txt).*
RewriteRule (.*) router.php
Allow from all
Options +Indexes
<filesMatch ".(flv|gif|ico|jpg|jpeg|mp4|mpeg|png|svg|swf|webp)$">
Header set Cache-Control "max-age=315360, public"
</filesMatch>
<filesMatch ".(js)$">
Header set Cache-Control "max-age=315360, public"
</filesMatch>

View file

@ -1,12 +0,0 @@
{
"require": {
"twig/twig": "^3.0",
"twig/markdown-extra": "^3.6",
"league/commonmark": "^2.4",
"spatie/yaml-front-matter": "^2.0",
"laminas/laminas-feed": "^2.6",
"erusev/parsedown": "^1.7",
"erusev/parsedown-extra": "^0.8.1",
"scrivo/highlight.php": "v9.18.1.10"
}
}

View file

@ -1,39 +0,0 @@
<?php
use Highlight\Highlighter;
class Dergdown extends ParsedownExtra
{
protected $highlighter;
public function __construct()
{
$this->highlighter = new Highlighter();
}
protected function blockFencedCodeComplete($block)
{
if (! isset($block['element']['text']['attributes'])) {
return $block;
}
$code = $block['element']['text']['text'];
$languageClass = $block['element']['text']['attributes']['class'];
$language = explode('-', $languageClass);
try {
$highlighted = $this->highlighter->highlight($language[1], $code);
$block['element']['text']['attributes']['class'] = vsprintf('%s hljs %s', [
$languageClass,
$highlighted->language,
]);
$block['element']['text']['rawHtml'] = $highlighted->value;
unset($block['element']['text']['text']);
} catch (DomainException $e) {
}
return $block;
}
}

42
www/dergstyle.css Normal file
View file

@ -0,0 +1,42 @@
body {
color: #B0B0B0;
background: #302A3F;
}
:link {
color: cyan;
font-style: italic;
text-decoration: none;
transition: color 0.2s;
}
:link:hover {
color: lightblue;
}
ul {
display: flex;
flex-direction: row;
justify-content: center;
list-style-type: none;
margin-top: 1em;
padding: 0px;
}
ul li {
padding: 0em 0.3em 0em 0.3em;
}
#big_title {
text-align: center;
font-size: 2.5em;
margin-bottom: 0.2em;
}
#title_separator {
height: 1.5px;
background-color: #ddd;
opacity: 0.5;
margin-left: 2em;
margin-right: 2em;
}

View file

@ -1,11 +0,0 @@
<?php
$FONT_AWESOME_ARRAY=[
'markdown' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="20" viewBox="0 0 640 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M593.8 59.1H46.2C20.7 59.1 0 79.8 0 105.2v301.5c0 25.5 20.7 46.2 46.2 46.2h547.7c25.5 0 46.2-20.7 46.1-46.1V105.2c0-25.4-20.7-46.1-46.2-46.1zM338.5 360.6H277v-120l-61.5 76.9-61.5-76.9v120H92.3V151.4h61.5l61.5 76.9 61.5-76.9h61.5v209.2zm135.3 3.1L381.5 256H443V151.4h61.5V256H566z"/></svg>',
'image' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M0 96C0 60.7 28.7 32 64 32H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6h96 32H424c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>',
'images' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M160 32c-35.3 0-64 28.7-64 64V320c0 35.3 28.7 64 64 64H512c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H160zM396 138.7l96 144c4.9 7.4 5.4 16.8 1.2 24.6S480.9 320 472 320H328 280 200c-9.2 0-17.6-5.3-21.6-13.6s-2.9-18.2 2.9-25.4l64-80c4.6-5.7 11.4-9 18.7-9s14.2 3.3 18.7 9l17.3 21.6 56-84C360.5 132 368 128 376 128s15.5 4 20 10.7zM192 128a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM48 120c0-13.3-10.7-24-24-24S0 106.7 0 120V344c0 75.1 60.9 136 136 136H456c13.3 0 24-10.7 24-24s-10.7-24-24-24H136c-48.6 0-88-39.4-88-88V120z"/></svg>',
'folder' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M64 480H448c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H288c-10.1 0-19.6-4.7-25.6-12.8L243.2 57.6C231.1 41.5 212.1 32 192 32H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64z"/></svg>',
'rss' => '<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM96 136c0-13.3 10.7-24 24-24c137 0 248 111 248 248c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-110.5-89.5-200-200-200c-13.3 0-24-10.7-24-24zm0 96c0-13.3 10.7-24 24-24c83.9 0 152 68.1 152 152c0 13.3-10.7 24-24 24s-24-10.7-24-24c0-57.4-46.6-104-104-104c-13.3 0-24-10.7-24-24zm0 120a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>'
];
?>

11
www/index.php Normal file
View file

@ -0,0 +1,11 @@
<?php
require_once '../vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader('../templates');
$twig = new \Twig\Environment($loader);
echo $twig->render('index.html',[
"a_variable" => "is very spicy. In a good way."
]);
?>

View file

@ -1,431 +0,0 @@
<?php
class MySQLAdapter {
public $raw;
function __construct($SITE_CONFIG) {
$this->SITE_CONFIG = $SITE_CONFIG;
$db_params = $SITE_CONFIG['db'];
try {
if(false !== getenv('MYSQL_HOST')) {
$this->raw = mysqli_connect(getenv('MYSQL_HOST'),
getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'),
getenv('MYSQL_DATABASE'),
getenv('MYSQL_PORT'));
}
else {
$this->raw = mysqli_connect($db_params['host'],
$db_params['user'], $db_params['password'],
$db_params['database'],
$db_params['port']);
}
} catch (\Throwable $th) {
echo 'Connection failed<br>';
echo 'Error number: ' . mysqli_connect_errno() . '<br>';
echo 'Error message: ' . mysqli_connect_error() . '<br>';
die();
}
}
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);
if($argtypes != ""){
$stmt->bind_param($argtypes, ...$args);
}
$stmt->execute();
return $stmt->get_result();
}
function _normalize_post_data($post_data) {
$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) ?? [];
$post_data["post_content"] ??= '';
return $post_data;
}
function _normalize_post_array($post_data) {
$post_data ??= [];
return array_map(function($post) {
return $this->_normalize_post_data($post);
}, $post_data);
}
function bump_post($post_path, $post_metadata = [], $create_dirs = true) {
$post_path = $this->_sanitize_path($post_path);
$path_depth = substr_count($post_path, "/");
if($create_dirs) {
$this->make_post_directory(dirname($post_path));
}
$qry = "
INSERT INTO posts
(host, post_path, post_path_depth, post_metadata, post_content)
VALUES
( ?, ?, ?, ?, ?) AS new
ON DUPLICATE KEY UPDATE post_path=new.post_path;";
$this->_exec($qry, "ssiss",
$this->SITE_CONFIG['HTTP_HOST'],
$post_path,
$path_depth,
json_encode($post_metadata),
'');
}
function make_post_directory($directory) {
$json_metadata = ["type" => 'directory'];
while(strlen($directory) > 1) {
try {
$this->bump_post($directory, $json_metadata, false);
}
catch(Exception $e) {
}
$directory = dirname($directory);
}
}
function log_post_access($post_path, $agent, $referrer, $time) {
$post_path = $this->_sanitize_path($post_path);
$qry = "INSERT INTO path_access_counts
(access_time,
host, 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, "ssssd", $this->SITE_CONFIG['HTTP_HOST'], $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=? AND host=?", "ss",
$post_path, $this->SITE_CONFIG['HTTP_HOST']);
}
}
function get_post_access_counters() {
$qry = "
SELECT host, 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 host, 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";
$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 . ",host=" . $post_data['host'] . ",agent=".$post_data['agent'];
$out_data .= ",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 host = ? AND post_path LIKE ?;
", "ss", $this->SITE_CONFIG['HTTP_HOST'], $post_path . "%");
}
function escape_tag($tag) {
return preg_replace_callback('/[\WZ]/', function($match) {
return "Z" . ord($match[0]);
}, strtolower($tag));
}
function escape_search_tag($tag) {
preg_match("/^([\+\-]?)(.*?)(\*?)$/", $tag, $matches);
if(!isset($matches[1])) {
echo "Problem with tag!";
var_dump($tag);
}
return $matches[1] . $this->escape_tag($matches[2]) . $matches[3];
}
function update_post_search_data($post_path, $post_tags) {
$post_tags []= "path:" . $post_path;
$post_tags []= "host:" . $this->SITE_CONFIG['HTTP_HOST'];
$post_tags = array_unique($post_tags);
$post_tags = array_map(function($val) {
return $this->escape_tag($val);
}, $post_tags);
asort($post_tags);
$post_tags = join(' ', $post_tags);
$qry = "
INSERT INTO posts
( host, post_path, post_tags )
VALUES
( ?, ?, ? ) AS new
ON DUPLICATE KEY
UPDATE post_tags=new.post_tags;
";
$this->_exec($qry, "sss",
$this->SITE_CONFIG['HTTP_HOST'], $post_path, $post_tags);
}
function perform_post_search($taglist, $order = null, $limit = 20, $page = 0) {
$allowed_ordering = [
"post_create_time"
];
$qry = "
SELECT post_path, post_metadata
FROM posts
WHERE MATCH(post_tags) AGAINST (? IN BOOLEAN MODE)
";
if(!is_array($taglist)) {
$taglist = explode(' ', $taglist);
}
$taglist []= '+host:' . $this->SITE_CONFIG['HTTP_HOST'];
$taglist = array_unique($taglist);
$taglist = array_map(function($key) {
return $this->escape_search_tag($key);
}, $taglist);
$taglist = implode(' ', $taglist);
if(isset($order) and in_array($order, $allowed_ordering)) {
$qry = $qry . " ORDER BY " . $order;
}
$qry = $qry . " LIMIT ? OFFSET ?";
$search_results = $this->_exec($qry, "sii", $taglist, $limit, $limit * $page)->fetch_all(MYSQLI_ASSOC);
$search_results = [
"query_string" => $taglist,
"results" => $this->_normalize_post_array($search_results)
];
return $search_results;
}
function update_or_create_post($post_path, $post_metadata, $post_content) {
$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
(host, 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,
post_update_time=CURRENT_TIMESTAMP;";
$this->_exec($qry, "ssiss",
$this->SITE_CONFIG['HTTP_HOST'],
$post_path,
$path_depth,
json_encode($post_metadata),
$post_content);
$this->update_post_search_data($post_path, $post_metadata['tags'] ?? []);
}
function get_settings_for_path($post_path) {
$post_path = $this->_sanitize_path($post_path);
$post_settings = $this->_exec("
SELECT post_path, post_settings_cache
FROM posts
WHERE post_path = ? AND host = ?
", "ss", $post_path, $this->SITE_CONFIG['HTTP_HOST'])->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 = ? AND host = ?
", "ss", $post_path, $this->SITE_CONFIG['HTTP_HOST'])->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 = ? AND host = ?
";
$post_path = $this->_sanitize_path($post_path);
$post_data = $this->_exec($qry, "ss", $post_path, $this->SITE_CONFIG['HTTP_HOST'])->fetch_assoc();
$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;
}
function get_subposts_by_path($path) {
global $sql;
$path = $this->_sanitize_path($path);
$path_depth = substr_count($path, "/");
$qry = "SELECT post_path, post_metadata, post_update_time
FROM posts
WHERE
host = ?
AND (post_path LIKE CONCAT(?,'/%'))
AND post_path_depth = ?
ORDER BY post_path ASC
LIMIT 50";
$post_data = $this->_exec($qry, "ssi", $this->SITE_CONFIG['HTTP_HOST'],
$path, $path_depth+1)->fetch_all(MYSQLI_ASSOC);
$post_data = $this->_normalize_post_array($post_data);
return $post_data;
}
}
?>

View file

@ -1,226 +0,0 @@
<?php
require_once 'mysql_adapter.php';
use Spatie\YamlFrontMatter\YamlFrontMatter;
use Laminas\Feed\Writer\Feed;
class PostHandler extends MySQLAdapter {
public $data_directory;
function __construct($SITE_CONFIG) {
parent::__construct($SITE_CONFIG);
$this->data_directory = 'raw/' . $this->SITE_CONFIG['HTTP_HOST'];
}
function deduce_post_type($post_path) {
$ext = pathinfo($post_path, PATHINFO_EXTENSION);
if(preg_match("/\.(\w+)\.md$/", $post_path, $ext_match)) {
$ext = $ext_match[1];
}
$ext_mapping = [
'' => 'directory',
'md' => 'text/markdown',
'png' => 'image',
'jpg' => 'image',
'jpeg' => 'image'
];
return $ext_mapping[$ext] ?? '?';
}
function fill_in_post_meta($post_path, $meta) {
$icon_mapping = [
'' => 'question',
'text/markdown' => 'markdown',
'directory' => 'folder',
'gallery' => 'images',
'image' => 'image'
];
$meta["title"] ??= basename($post_path);
if($meta["title"] == "") {
$meta["title"] = "root";
}
if(!isset($meta['media_file']) and preg_match("/\.(\w+)\.md$/", $post_path)) {
$meta['media_file'] = "https://" . $this->SITE_CONFIG['HTTP_HOST'] . chop($post_path, ".md");
}
$meta['tags'] ??= [];
$meta['type'] ??= $this->deduce_post_type($post_path);
$meta['icon'] ??= $icon_mapping[$meta['type']] ?? 'question';
return $meta;
}
function _normalize_post_data($post_data) {
$post_data = parent::_normalize_post_data($post_data);
if(!$post_data['found']) {
return $post_data;
}
$post_data["post_basename"] = basename($post_data["post_path"]);
$post_meta = $post_data['post_metadata'];
$post_data['post_metadata'] = $this->fill_in_post_meta(
$post_data['post_path'],
$post_meta);
$post_data["post_file_dir"] = '/raw' . $post_data["post_path"];
return $post_data;
}
function make_post_directory($directory) {
$data_directory = $this->data_directory . $directory;
is_dir($data_directory) || mkdir($data_directory, 0777, true);
parent::make_post_directory($directory);
}
function update_or_create_post(...$args) {
$this->_exec("TRUNCATE feed_cache");
parent::update_or_create_post(...$args);
}
function save_file($post_path, $file_path) {
move_uploaded_file($file_path, $this->data_directory . $post_path);
}
function save_markdown_post($post_path, $post_data) {
$frontmatter_post = YamlFrontMatter::parse($post_data);
$post_path = $this->_sanitize_path($post_path);
$post_content = $frontmatter_post->body();
$post_metadata = $frontmatter_post->matter();
$post_metadata = $this->fill_in_post_meta(
$post_path,
$post_metadata);
$post_metadata['tags'][]= 'type:' . $post_metadata['type'];
if(basename($post_path) == "README.md") {
$readme_metadata = [];
if(isset($post_metadata['settings'])) {
$readme_metadata['settings'] = $post_metadata['settings'];
}
if(isset($post_metadata['directory'])) {
$readme_metadata = $post_metadata['directory'];
}
$this->update_or_create_post(dirname($post_path),
$readme_metadata, $post_content);
}
$this->update_or_create_post($post_path, $post_metadata, $post_content);
}
function handle_upload($post_path, $file_path) {
$ext = pathinfo($post_path, PATHINFO_EXTENSION);
switch($ext) {
case "md":
$this->save_markdown_post($post_path, file_get_contents($file_path));
move_uploaded_file($file_path, $this->data_directory . $post_path);
break;
default:
$this->save_file($post_path, $file_path);
}
}
function try_get_cached_feed($path, $export_opt) {
$post_cache = $this->_exec("SELECT feed_content, feed_created_on
FROM feed_cache
WHERE host=? AND search_path=? AND export_type=?",
"sss", $this->SITE_CONFIG['HTTP_HOST'], $path, $export_opt)->fetch_assoc();
if(!isset($post_cache)) {
return null;
}
return ['feed' => $post_cache['feed_content'], 'feed_ts' => $post_cache['feed_created_on']];
}
function construct_feed($path) {
$path = $this->_sanitize_path($path);
$feed = @new Feed;
$feed->setTitle("DergFeed");
$feed->setLink($this->SITE_CONFIG['uri_prefix'] . $path);
$feed->setFeedLink($this->SITE_CONFIG['uri_prefix'] . "/feeds/atom" . $path, "atom");
$feed->setDateModified(time());
$feed->setDescription("DergenFeed for all your " . $path . " needs");
$feed_posts = $this->_exec("SELECT
post_path,
post_create_time, post_update_time,
post_content,
post_metadata
FROM posts
WHERE (host = ?) AND ((post_path = ?) OR (post_path LIKE ?))
ORDER BY post_create_time DESC LIMIT 200",
"sss", $this->SITE_CONFIG['HTTP_HOST'], $path, $path . '/%');
while($row = $feed_posts->fetch_array(MYSQLI_ASSOC)) {
$row = $this->_normalize_post_data($row);
$pmeta = $row['post_metadata'];
if($pmeta['type'] == 'directory') {
continue;
}
$entry = $feed->createEntry();
$entry->setTitle($row['post_path'] . '> ' . $pmeta['title']);
$entry->setLink($this->SITE_CONFIG['uri_prefix'] . $row['post_path']);
$entry->setDateModified(strtotime($row['post_update_time']));
$entry->setDateCreated(strtotime($row['post_create_time']));
$entry->setDescription($pmeta['brief'] ?? $pmeta['title']);
$feed->addEntry($entry);
}
return $feed;
}
function get_laminas_feed($path, $export_opt) {
$path = $this->_sanitize_path($path);
$feed_cache = $this->try_get_cached_feed($path, $export_opt);
if(isset($feed_cache)) {
return $feed_cache;
}
$feed = $this->construct_feed($path);
$this->_exec("INSERT INTO feed_cache
(host, search_path, export_type, feed_content)
VALUES
(?, ?, 'atom', ?),
(?, ?, 'rss', ?)",
"ssssss",
$this->SITE_CONFIG['HTTP_HOST'], $path, $feed->export('atom'),
$this->SITE_CONFIG['HTTP_HOST'], $path, $feed->export('rss'));
return $this->try_get_cached_feed($path, $export_opt);
}
}
?>

View file

@ -1,2 +0,0 @@
User-agent: *
Allow: /

View file

@ -1,268 +0,0 @@
<?php
$data_time_start = microtime(true);
require_once 'vendor/autoload.php';
require_once 'post_adapter.php';
require_once 'fontawesome.php';
require_once 'dergdown.php';
use Symfony\Component\Yaml\Yaml;
$SERVER_HOST = $_SERVER['HTTP_HOST'];
if(!preg_match('/^[\w\.]+$/', $SERVER_HOST)) {
die();
}
$SERVER_PREFIX = "https://" . $SERVER_HOST;
$SITE_CONFIG = Yaml::parseFile('secrets/' . $SERVER_HOST . '.config.yml');
$SITE_CONFIG['uri_prefix'] = $SERVER_PREFIX;
$SITE_CONFIG['HTTP_HOST'] = $SERVER_HOST;
$adapter = new PostHandler($SITE_CONFIG);
$loader = new \Twig\Loader\FilesystemLoader(['./templates', './user_content']);
$twig = new \Twig\Environment($loader,[
'debug' => true,
'cache' => 'twig_cache'
]);
$twig->addExtension(new Twig\Extra\Markdown\MarkdownExtension());
use Twig\Extra\Markdown\DefaultMarkdown;
use Twig\Extra\Markdown\MarkdownRuntime;
use Twig\RuntimeLoader\RuntimeLoaderInterface;
function dergdown_to_html($text) {
$Parsedown = new Dergdown();
return $Parsedown->text($text);
}
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) {
return new MarkdownRuntime(new DefaultMarkdown());
}
}
});
function render_twig($template, $args = []) {
global $twig;
global $FONT_AWESOME_ARRAY;
global $SITE_CONFIG;
$args['fa'] = $FONT_AWESOME_ARRAY;
$post = $args['post'] ?? [];
$settings = $post['settings'] ?? [];
$meta = $post['post_metadata'] ?? [];
$args['banner'] ??= $settings['banners'] ?? $SITE_CONFIG['banners'];
$args['og'] = array_merge([
"site_name" => $SITE_CONFIG['opengraph']['site_name'],
"title" => $meta['title'] ?? $SITE_CONFIG['opengraph']['site_name'],
"url" => $_SERVER['REQUEST_URI'],
"type" => "article",
"description" => $meta['description']
?? $settings['description']
?? $SITE_CONFIG['opengraph']['description']
], $args['og'] ?? []);
if(($meta['type'] ?? '') == 'image') {
$args['og']['image'] ??= $meta['media_file'];
$args['og']['type'] = "image";
}
$args['og']['image'] ??= $args['banner'][0]["src"];
$args['banner'] = json_encode($args['banner']);
$args['site_config'] = $SITE_CONFIG;
$args['age_gate'] = (!isset($_COOKIE['AgeConfirmed'])) && isset($SITE_CONFIG['age_gate']);
$args['content_html'] ??= dergdown_to_html($post['post_content'] ?? '');
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 'gallery':
if(preg_match('/^(.*[^\/])((?:#.*)?)$/', $SURI, $match)) {
header('Location: ' . $match[1] . '/' . $match[2]);
die();
}
$search_query = $post['post_metadata']['search_tags'] ??
('+type:image +path:' . $post['post_path'] . '/*');
$search_result = $adapter->perform_post_search($search_query);
echo render_twig('post_types/gallery.html', [
"post" => $post,
"subposts" => $adapter->get_subposts_by_path($SURI),
"gallery_images" => $search_result['results']
]);
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($SURI == '/api/search') {
header('Content-Type: application/json');
echo json_encode($adapter->perform_post_search($_GET['search_query']));
}
} 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(true) {
try_render_post($SURI);
}
}
$URL_PATH = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
generate_website($URL_PATH);
log_and_die($URL_PATH);
?>

View file

@ -1,2 +0,0 @@
*.json
api_admin_key

View file

@ -1,2 +0,0 @@
Deny from all

View file

@ -1,12 +0,0 @@
function confirmAge() {
let ageGate = document.getElementById("age_gate_block");
ageGate.style.opacity = 0;
setTimeout(() => {
ageGate.parentElement.removeChild(ageGate);
}, 1000);
document.cookie += "AgeConfirmed=true; path=/; max-age=31536000"
}

View file

@ -1,160 +0,0 @@
const BANNER_TIME = 600 * 1000.0
const BANNER_ANIMATION = "opacity 0.8s linear, transform 0.1s linear"
class BannerHandler {
constructor(banner_container, banner_image, banner_link) {
this.bannerContainerDOM = banner_container
this.bannerDOM = banner_image
this.bannerLinkDOM = banner_link
this.bannerUpdateTimer = null
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);
}
}
startUpdateTick() {
if(this.bannerUpdateTimer !== null) {
return
}
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 = this.currentBannerData.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(() => {
this.animateOn()
this.startUpdateTick()
}, 10)
}
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()
}
}
var bannerHandler = new BannerHandler(
document.getElementById("main_header"),
document.getElementById("main_banner_img"),
document.getElementById("main_banner_img_link"))
bannerHandler.start()
// addEventListener("resize", () => update_banner(banner, banner_container));

View file

@ -1,273 +0,0 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
svg {
fill: var(--text_1);
padding-top: 0.1rem;
}
body {
--bg_1: #0e0a2a;
--bg_2: #2c2943;
--bg_3: #3f4148;
--highlight_1: #ee9015;
--highlight_2: #edd29e;
--text_1: #FFFFFF;
--text_border: #A0A0A080;
color: var(--text_1);
background: var(--bg_1);
margin: 0px;
position: relative;
min-height: 100vh;
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;
text-decoration: none;
transition: color 0.2s;
}
a:visited {
color: var(--highlight_1);
}
a:hover {
color: var(--highlight_2);
}
#age_gate_block {
position: fixed;
top: 0;
left: 0;
z-index: 1040;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(20px);
transition: opacity 0.8s linear;
}
#age_gate_block div {
background-color: var(--bg_3);
border-radius: 1em;
padding: 1em;
width: 12em;
margin:0 auto;
display: table;
position: absolute;
left: 0;
right:0;
top: 50%;
-webkit-transform:translateY(-50%);
-moz-transform:translateY(-50%);
-ms-transform:translateY(-50%);
-o-transform:translateY(-50%);
transform:translateY(-50%);
}
#age_gate_block p {
min-height: 5em;
}
#main_header {
overflow: hidden;
position: relative;
padding-bottom: 0.7rem;
}
#main_banner_img {
position: absolute;
left: 0px;
right: 0px;
width: 100vw;
z-index: -1;
opacity: 0;
top: 0px;
}
#main_banner_img_link {
position: absolute;
right: 2vw;
bottom: 0.5rem;
font-size: 0.8rem;
}
#nav_bar {
display: flex;
flex-direction: row;
justify-content: center;
list-style-type: none;
margin-top: 1rem;
padding: 0px;
}
#nav_bar li {
padding: 0rem 0.3rem 0rem 0.3rem;
}
#big_title {
text-align: center;
font-size: 2.5rem;
margin-bottom: 0.2rem;
}
#main_header h2 {
text-align: center;
font-size: 2rem;
margin-bottom: 0.2rem;
}
#title_separator {
height: 1.5px;
background-color: #ddd;
opacity: 0.5;
margin-left: 2rem;
margin-right: 2rem;
}
:target {
scroll-margin-top: 6rem;
}
#main_content_wrapper {
--content-width: min(100vw, calc(20rem + 40vw));
--content-total-margin: calc(calc(100vw - var(--content-width)) / 2);
--content-padding: max(0.5rem, min(1rem, var(--content-total-margin)));
--content-margin: max(0px, calc(var(--content-total-margin) - 1rem));
padding: 0rem var(--content-padding) 1rem var(--content-padding);
width: auto;
margin-left: var(--content-margin);
margin-right: var(--content-margin);
margin-top: 0px;
min-height: 100%;
background: var(--bg_2);
}
#post_file_bar {
position: sticky;
top: 0px;
background: var(--bg_2);
box-shadow: 0px 5px 5px 0px #00000040;
z-index: 5;
}
#post_file_titles {
display: flex;
flex-direction: row;
justify-content: left;
list-style-type: none;
padding: 0px;
}
#post_file_titles * {
padding: 0.5rem;
font-style: bold;
font-size: 1.3rem;
background: var(--highlight_1);
}
#post_file_path {
width: 100%;
font-style: italic;
padding-left: 0.5rem;
background: var(--highlight_1);
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);
padding-right: 0.2rem;
}
#main_content_wrapper article {
background: var(--bg_3);
border-radius: 0rem 0rem 0.8rem 0.8rem;
box-shadow: 3px 7px 7px 0px #00000040;
padding: 0.75rem;
}
#main_content_wrapper article img {
display: block;
max-width: 100%;
max-height: 70vh;
margin: 1vmin;
margin-right: 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 {
display: flex;
height: 2.5rem;
text-align: center;
background-color: #3a3a3a;
margin: 0px;
position: absolute;
bottom: 0px;
width: 100%;
}
#main_footer span {
align-self: flex-end;
width: 100%;
}

View file

@ -1,36 +0,0 @@
table.directory {
width: 100%;
}
table.directory caption {
text-align: left;
font-size: 1.5rem;
padding-left: 3vmin;
}
table.directory td {
padding: 0.2rem;
text-align: left;
}
table.directory th {
padding: 0.2rem;
text-align: left;
padding-bottom: 0.05rem;
}
table.directory tr.entry:hover {
background: rgba(255, 255, 255, 0.1);
}
table.directory tr.entry .entry_title {
width: 100%;
}
table.directory .entry_update_time {
display: block;
width: 12rem;
}

View file

@ -1,64 +0,0 @@
.gallery {
display: flex;
flex-direction: row;
flex-flow: row wrap;
justify-content: center;
list-style-type: none;
margin-top: 1rem;
padding: 0px;
}
.gallery li {
margin: 0.2rem;
margin-bottom: 1rem;
border: 1px solid var(--text_border);
float: left;
border-radius: 1rem;
box-shadow: 0px 5px 5px 0px #00000040;
background: var(--bg_2);
height: auto;
transition: 0.3s;
z-index: 1;
}
.gallery li:hover {
border: 1px solid var(--text_1);
transform: scale(1.02);
}
#main_content_wrapper .gallery img {
width: 100%;
height: 15rem;
margin: 0;
border-radius: 1rem 1rem 0 0;
object-fit: cover;
}
@media screen and (max-width: 48rem) {
.gallery li {
width: 45%;
}
#main_content_wrapper .gallery img {
height: 100%;
width: auto;
}
#main_content_wrapper .gallery figure {
height: auto;
}
}
.gallery figcaption {
padding: 0.1rem;
text-align: center;
}

View file

@ -1,203 +0,0 @@
/*
modest.css, licensed under MIT license, Crafted with <3 by John Otander (@4lpine).
Taken from: https://github.com/markdowncss/modest
Modified to fit Dergsite needs
*/
@media print {
*,
*:before,
*:after {
background: transparent !important;
color: #000 !important;
box-shadow: none !important;
text-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
a[href^="#"]:after,
a[href^="javascript:"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
}
pre,
code {
font-family: Menlo, Monaco, "Courier New", monospace;
}
.modest-no-decoration {
text-decoration: none;
}
html {
font-size: 14px;
}
@media screen and (min-width: 32rem) and (max-width: 48rem) {
html {
font-size: 16px;
}
}
@media screen and (min-width: 48rem) {
html {
font-size: 17px;
}
}
article {
line-height: 1.5;
}
article p,
.modest-p {
font-size: 1rem;
margin-bottom: 1.3rem;
}
article h1,
.modest-h1,
article h2,
.modest-h2,
article h3,
.modest-h3,
article h4,
.modest-h4 {
margin: 1.5em 0 .3em;
font-weight: inherit;
line-height: 1.42;
padding-left: 1.5rem;
}
article > :first-child {
margin-top: 0.3rem !important;
}
article h1,
.modest-h1 {
margin-top: 0;
font-size: 1.998rem;
}
article h2,
.modest-h2 {
font-size: 1.427rem;
}
article h3,
.modest-h3 {
font-size: 1.299rem;
}
article h4,
.modest-h4 {
font-size: 1.1rem;
}
article h5,
.modest-h5 {
font-size: 1rem;
}
article h6,
.modest-h6 {
font-size: .88rem;
}
article small,
.modest-small {
font-size: .707rem;
}
/* https://github.com/mrmrs/fluidity */
article h1,
article h2,
article h3 {
border-bottom: 1px solid var(--text_border);
padding-bottom: .3rem;
}
blockquote {
padding-left: 0.8rem;
margin-left: 0.8rem;
margin-right: 4em;
}
blockquote {
border-left: 4px solid var(--text_border);
text-align: justify;
}
pre {
border-radius: 0.5rem;
box-shadow: 2px 5px 5px 0px #00000040;
border-left: 4px solid #206475;
background-color: var(--bg_2);
margin-bottom: 1.3rem;
margin-left: 0.8rem;
margin-right: 4em;
}
pre code {
border-radius: 0.5rem;
}
@media screen and (max-width: 32rem) {
pre, blockquote {
margin-right: 1.5em;
}
}
article ul,
article ol {
padding-left: 2em;
}

View file

@ -1,13 +0,0 @@
#rrr_header {
font-size: 2rem;
border-bottom: 1px solid grey;
padding-bottom: 0.2rem;
margin-bottom: 0.3rem;
}
#rrr_code {
font-size: 1.5rem;
}

View file

@ -1,84 +0,0 @@
/*
Orginal Style from ethanschoonover.com/solarized (c) Jeremy Hull <sourdrums@gmail.com>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #002b36;
color: #839496;
}
.hljs-comment,
.hljs-quote {
color: #586e75;
}
/* Solarized Green */
.hljs-keyword,
.hljs-selector-tag,
.hljs-addition {
color: #859900;
}
/* Solarized Cyan */
.hljs-number,
.hljs-string,
.hljs-meta .hljs-meta-string,
.hljs-literal,
.hljs-doctag,
.hljs-regexp {
color: #2aa198;
}
/* Solarized Blue */
.hljs-title,
.hljs-section,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #268bd2;
}
/* Solarized Yellow */
.hljs-attribute,
.hljs-attr,
.hljs-variable,
.hljs-template-variable,
.hljs-class .hljs-title,
.hljs-type {
color: #b58900;
}
/* Solarized Orange */
.hljs-symbol,
.hljs-bullet,
.hljs-subst,
.hljs-meta,
.hljs-meta .hljs-keyword,
.hljs-selector-attr,
.hljs-selector-pseudo,
.hljs-link {
color: #cb4b16;
}
/* Solarized Red */
.hljs-built_in,
.hljs-deletion {
color: #dc322f;
}
.hljs-formula {
background: #073642;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}

View file

@ -1,33 +0,0 @@
<div id="post_file_bar">
<menu id="post_file_titles">
<li>
{{ post.post_metadata.title }}
</li>
</menu>
<menu id="post_file_path">
{% set split_post = post.post_path |split('/') %}
{% for i in range(0, split_post|length - 1) %}
<li>
{% if i != 0 %}
<a href="{{split_post|slice(0,i+1)|join('/')}}">
> {{ split_post[i] }}
</a>
{% else %}
<a href="/">root</a>
{% endif %}
</li>
{% endfor %}
<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>

View file

@ -1,29 +0,0 @@
{% extends "root.html" %}
{% block feed_links %}
{{ parent() }}
<link rel="alternate" type="application/atom+xml" title="{{og.site_name}} Feed for {{post.post_path}}" href="{{site_config.uri_prefix}}/feed/atom{{post.post_path}}">
{% endblock %}
{% block second_title %}
<h2> {{ post.post_metadata.title }} </h2>
{% endblock %}
{%block main_content%}
{{ include('fragments/filepath_bar.html') }}
<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%}

View file

@ -1,43 +0,0 @@
{% extends "pathed_content.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/directorystyle.css">
{%endblock%}
{%block content_article%}
{% if subposts|length > 0 %}
<h3>Directory contents:</h3>
<table class="directory">
<tr>
<th></th>
<th class="hsmol_hide">Name</th>
<th>Title</th>
<th class="entry_update_time hsmol_hide">Modified</th>
</tr>
{% for subpost in subposts %}
<tr class="entry">
<td>
{{ fa[subpost.post_metadata.icon] | raw }}
</td>
<td class="hsmol_hide">
<a href={{subpost.post_path}}>{{subpost.post_basename}}</a>
</td>
<td class="entry_title">
<a href={{subpost.post_path}}>{{ subpost.post_metadata.title }}</a>
</td>
<td class="entry_update_time hsmol_hide">
{{ subpost.post_update_time }}
</td>
</tr>
{% endfor %}
</table>
{%else%}
<h4>This directory appears to be empty...</h4>
<span>This shouldn't happen, sorry!</span>
{% endif %}
{{ post.post_content|markdown_to_html }}
{%endblock%}

View file

@ -1,59 +0,0 @@
{% extends "pathed_content.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/directorystyle.css">
<link rel="stylesheet" href="/static/gallerystyle.css">
{%endblock%}
{%block content_article%}
{% if subposts|length > 0 %}
<table class="directory">
<h3>Art Subfolders:</h3>
<tr>
<th></th>
<th class="hsmol_hide">Name</th>
<th>Title</th>
<th class="entry_update_time hsmol_hide">Modified</th>
</tr>
{% for subpost in subposts %}
<tr class="entry">
<td>
{{ fa[subpost.post_metadata.icon] | raw }}
</td>
<td class="hsmol_hide">
<a href={{subpost.post_path}}>{{subpost.post_basename}}</a>
</td>
<td class="entry_title">
<a href={{subpost.post_path}}>{{ subpost.post_metadata.title }}</a>
</td>
<td class="entry_update_time hsmol_hide">
{{ subpost.post_update_time }}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{{ content_html|raw }}
{% if gallery_images|length > 0 %}
<ul class="gallery">
{% for post in gallery_images %}
<li class="entry">
<a href={{post.post_path}}>
<figure>
<img src="{{post.post_metadata.media_file}}">
<figcaption>
{{ post.post_metadata.title }}
</figcaption>
</figure>
</a>
</li>
{% endfor %}
</ul>
{%else%}
<h4>How sad. There are no images yet... What a real shame :c </h4>
{% endif %}
{%endblock%}

View file

@ -1,25 +0,0 @@
{% extends "pathed_content.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/imagestyle.css">
{%endblock%}
{% block opengraph_tags %}
{{ parent() }}
<meta property="og:type" content="image" />
{%endblock %}
{%block content_article%}
<figure>
<a target="_blank" href="{{post.post_metadata.media_file}}">
<img id="gallery_image" src="{{post.post_metadata.media_file}}"></img>
</a>
<figcaption>
{{ content_html|raw }}
</figcaption>
</figure>
{%endblock%}

View file

@ -1,13 +0,0 @@
{% extends "pathed_content.html" %}
{% block opengraph_tags %}
{{ parent() }}
<meta property="og:type" content="article" />
{%endblock %}
{%block content_article%}
{{ content_html|raw }}
{% endblock %}

View file

@ -1,19 +0,0 @@
{% 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 %}

View file

@ -1,90 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>{{og.site_name}} - {{og.title}}</title>
<link rel="stylesheet" href="/static/dergstyle.css">
<link rel="stylesheet" href="/static/modest.css">
<link rel="stylesheet" href="/static/solarized-dark.css">
<link rel="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="{{og.site_name}} Global Feed" href="{{site_config.uri_prefix}}/feed">
{% endblock %}
{% block extra_head %}{% endblock %}
{% block opengraph_tags %}
<meta property="og:site_name" content="{{og.site_name}}">
<meta property="og:url" content="{{og.url}}" />
<meta property="og:title" content="{{ og.title|e }}" />
<meta name="twitter:title" content="{{ og.title|e }}" />
<meta property="og:type" content="{{og.type}}" />
<meta property="og:description" content="{{ og.description|e }}" />
<meta name="twitter:description" content="{{ og.description|e }}" />
<meta property="og:image" content="{{og.image}}" />
<meta name="twitter:image" content="{{og.image}}" />
<meta name="twitter:card" content="summary_large_image">
<meta name="robots" content="max-image-preview:large">
<meta property="al:android:app_name" content="Medium" />
{% endblock %}
<script type="text/javascript">
window.dergBannerOptions = JSON.parse('{{banner|raw}}');
</script>
</head>
<body>
{%if age_gate %}
<div id="age_gate_block">
<div>
<p>
This website may contain content meant for an 18+ audience.
</p>
<input type="button" onclick="confirmAge()" value="I understand and am over 18.">
</div>
</div>
<script src="/static/age_gate_check.js"></script>
{% endif %}
<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 %}{{og.site_name}}{%endblock%}</h1>
{% block second_title %}{% endblock %}
<div id="title_separator"></div>
<menu id="nav_bar">
<li><a href="/about"> About </a></li>
<li><a href="/blog"> Blog </a></li>
<li><a href="/projects"> Projects </a></li>
<li><a href="/artwork"> Artworks </a></li>
</menu>
</header>
<main id="main_content_wrapper">
{% block main_content %}<h3>Soon there shall be content!</h3>{% endblock %}
</main>
<footer id="main_footer">
{% block main_footer %}
<span> test? </span>
{% endblock %}
</footer>
</body>
</html>

View file

@ -1,19 +0,0 @@
{% extends "root.html" %}
{% block second_title %}
<h2> UPLOAD CONTENT </h2>
{% endblock %}
{%block main_content%}
<article>
<form method="post" enctype="multipart/form-data" action="/api/admin/upload">
<input type="text" id="post_path" name="post_path"/>
<input type="password" id="api_key" name="api_key"/>
<input type="file" id="post_data" name="post_data"/>
<button>Submit</button>
</form>
</article>
{%endblock%}