Compare commits

..

No commits in common. "dev/post_data_rework" and "main" have entirely different histories.

72 changed files with 972 additions and 4637 deletions

2
.gitignore vendored
View file

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

View file

View file

@ -2,9 +2,9 @@ FROM composer
WORKDIR /app
COPY www/composer.* .
COPY www/vendor/* vendor/
RUN composer install
FROM php:8.3-apache
FROM php:apache
WORKDIR /var/www/html
COPY --from=0 /app/ ./
@ -15,7 +15,6 @@ RUN a2enmod headers
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
RUN mkdir raw
RUN chmod a+wr raw
COPY www/ .
RUN chmod -R a+rw $(ls -I vendor)
RUN chmod -R a+rw $(ls -I vendor)

View file

@ -24,7 +24,7 @@ services:
- ../.git
- mysql_schema.sql
volumes:
- ../.docker_vols/web:/var/www/html/raw
- website_datavolume:/var/www/html/raw
mysql:
build:
@ -42,4 +42,7 @@ services:
- path: mysql_schema.sql
action: rebuild
volumes:
- ../.docker_vols/sql:/var/lib/mysql
- sqlvolume:/var/lib/mysql
volumes:
sqlvolume: {}
website_datavolume: {}

View file

@ -3,106 +3,146 @@ CREATE DATABASE dragon_fire;
USE dragon_fire;
-- DROP TABLE posts;
-- DROP TABLE path_access_counts;
-- DROP TABLE path_errcodes;
-- DROP TABLE feed_cache;
CREATE TABLE dev_posts (
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_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
post_view_count INTEGER DEFAULT 0,
post_access_count INTEGER DEFAULT 0,
post_title VARCHAR(1024),
post_tags VARCHAR(1024),
post_brief TEXT(2048),
post_metadata JSON DEFAULT NULL,
post_metadata JSON NOT NULL,
post_settings_cache JSON DEFAULT NULL,
post_counters JSON DEFAULT NULL,
post_content MEDIUMTEXT,
PRIMARY KEY(post_id),
CONSTRAINT unique_post UNIQUE (post_path),
CONSTRAINT unique_post UNIQUE (host, post_path),
INDEX(post_path),
INDEX(host, post_path),
INDEX(post_path_depth, post_path),
INDEX(post_created_at),
INDEX(post_updated_at),
FULLTEXT(post_path),
FULLTEXT(post_tags),
FULLTEXT(post_title),
FULLTEXT(post_brief)
INDEX(post_create_time),
INDEX(post_update_time)
);
CREATE TABLE dev_post_markdown (
post_id INTEGER,
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,
post_markdown TEXT,
PRIMARY KEY(post_id),
FOREIGN KEY(post_id) REFERENCES dev_posts(post_id)
ON DELETE CASCADE,
FULLTEXT(post_markdown)
PRIMARY KEY(access_time, host, post_path, agent, referrer)
);
CREATE TABLE dev_yaps (
yap_id INTEGER AUTO_INCREMENT,
PRIMARY KEY(yap_id),
CREATE TABLE feed_cache (
host VARCHAR(64) NOT NULL,
search_path VARCHAR(255),
export_type VARCHAR(255),
post_path VARCHAR(255) NOT NULL,
yap_category VARCHAR(32) NOT NULL,
yap_tag VARCHAR(40) NOT NULL,
feed_created_on DATETIME DEFAULT CURRENT_TIMESTAMP,
-- Uniqueness detection based on associated path, category and tag
yap_hash CHAR(32) AS (MD5(CONCAT(post_path, yap_category, yap_tag))),
CONSTRAINT YAPS_UNIQUE UNIQUE(yap_hash),
feed_content MEDIUMTEXT,
yap_created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
yap_metadata JSON DEFAULT NULL,
yap_text TEXT,
-- Make it possible to look up changes from e.g. a commit hash inexpensively
INDEX(yap_tag),
-- Make it possible to look up specific post feeds efficiently (e.g. changelog)
INDEX(post_path, yap_category, yap_created_at),
-- Make it possible to globally look up specific feeds efficiently
INDEX(yap_category, yap_created_at),
-- And just in general, make searching feeds in a timeframe efficient
INDEX(yap_created_at),
FULLTEXT(yap_text)
PRIMARY KEY(host, search_path, export_type)
);
CREATE TABLE analytics_summations (
time_bucket DATETIME NOT NULL,
metric VARCHAR(16) NOT NULL,
tags JSON NOT NULL,
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
metric_value DOUBLE PRECISION DEFAULT 0,
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!
tags_md5 CHAR(32) AS (MD5(tags)),
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!
INDEX(time_bucket, metric),
CONSTRAINT unique_analytic UNIQUE(time_bucket, metric, tags_md5)
);
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!
CREATE TABLE analytics_events (
event_time DATETIME NOT NULL,
metric VARCHAR(64) NOT NULL DEFAULT 'error_msg',
tags JSON NOT NULL,
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!
event_text TEXT,
Sorry for this. She GRABS A LOT
INDEX(event_time)
----
## 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.
'
);

View file

@ -1,19 +0,0 @@
{
"folders": [
{
"path": "."
},
{
"path": "../dragon_fire_content"
}
],
"settings": {
"conventionalCommits.scopes": [
"search",
"templates",
"css",
"database",
"analytics"
]
}
}

View file

@ -3,8 +3,8 @@ AddType text/plain .md
AddType text/plain .atom
AddType text/plain .rss
php_value upload_max_filesize 40M
php_value post_max_size 42M
# php_value upload_max_filesize 40M
# php_value post_max_size 42M
RewriteEngine On
RewriteBase /
@ -14,24 +14,21 @@ RewriteRule ^.*\.(flv|gif|ico|jpg|jpeg|mp4|mpeg|png|svg|swf|webp)$ raw/%{HTTP_HO
RewriteRule ^/?raw/(.*)$ raw/%{HTTP_HOST}/$1 [L,END]
# RewriteCond %{HTTPS} !on
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,END]
RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,END]
RewriteCond %{REQUEST_URI} !^/api/
RewriteCond %{REQUEST_URI} /\w+$
RewriteRule (.*) $1/ [R=301,L,END]
RewriteCond %{REQUEST_URI} !^/?(src/dbtest\.php|static|raw|robots\.txt).*
RewriteRule (.*) src/router.php
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=60, public"
Header set Cache-Control "max-age=315360, public"
</filesMatch>
<filesMatch ".(js)$">
Header set Cache-Control "max-age=60, public"
</filesMatch>
Header set Cache-Control "max-age=315360, public"
</filesMatch>

331
www/composer.lock generated
View file

@ -8,16 +8,16 @@
"packages": [
{
"name": "dflydev/dot-access-data",
"version": "v3.0.3",
"version": "v3.0.2",
"source": {
"type": "git",
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f"
"reference": "f41715465d65213d644d3141a6a93081be5d3549"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f",
"reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f",
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549",
"reference": "f41715465d65213d644d3141a6a93081be5d3549",
"shasum": ""
},
"require": {
@ -77,9 +77,9 @@
],
"support": {
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3"
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2"
},
"time": "2024-07-08T12:26:09+00:00"
"time": "2022-10-27T11:44:00+00:00"
},
{
"name": "erusev/parsedown",
@ -184,33 +184,33 @@
},
{
"name": "laminas/laminas-escaper",
"version": "2.15.0",
"version": "2.13.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "c612b0488ae486284c39885efca494c180f16351"
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/c612b0488ae486284c39885efca494c180f16351",
"reference": "c612b0488ae486284c39885efca494c180f16351",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/af459883f4018d0f8a0c69c7a209daef3bf973ba",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
"php": "~8.1.0 || ~8.2.0 || ~8.3.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.27.11",
"laminas/laminas-coding-standard": "~3.0.1",
"infection/infection": "^0.27.0",
"laminas/laminas-coding-standard": "~2.5.0",
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.6.22",
"psalm/plugin-phpunit": "^0.19.0",
"vimeo/psalm": "^5.26.1"
"phpunit/phpunit": "^9.6.7",
"psalm/plugin-phpunit": "^0.18.4",
"vimeo/psalm": "^5.9"
},
"type": "library",
"autoload": {
@ -242,7 +242,7 @@
"type": "community_bridge"
}
],
"time": "2024-12-17T19:39:54+00:00"
"time": "2023-10-10T08:35:13+00:00"
},
{
"name": "laminas/laminas-feed",
@ -519,16 +519,16 @@
},
{
"name": "league/commonmark",
"version": "2.6.1",
"version": "2.4.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad"
"reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad",
"reference": "d990688c91cedfb69753ffc2512727ec646df2ad",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5",
"reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5",
"shasum": ""
},
"require": {
@ -541,8 +541,8 @@
},
"require-dev": {
"cebe/markdown": "^1.0",
"commonmark/cmark": "0.31.1",
"commonmark/commonmark.js": "0.31.1",
"commonmark/cmark": "0.30.0",
"commonmark/commonmark.js": "0.30.0",
"composer/package-versions-deprecated": "^1.8",
"embed/embed": "^4.4",
"erusev/parsedown": "^1.0",
@ -551,11 +551,10 @@
"michelf/php-markdown": "^1.4 || ^2.0",
"nyholm/psr7": "^1.5",
"phpstan/phpstan": "^1.8.2",
"phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
"phpunit/phpunit": "^9.5.21",
"scrutinizer/ocular": "^1.8.1",
"symfony/finder": "^5.3 | ^6.0 | ^7.0",
"symfony/process": "^5.4 | ^6.0 | ^7.0",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0",
"symfony/finder": "^5.3 | ^6.0",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
"unleashedtech/php-coding-standard": "^3.1.1",
"vimeo/psalm": "^4.24.0 || ^5.0.0"
},
@ -565,7 +564,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.7-dev"
"dev-main": "2.5-dev"
}
},
"autoload": {
@ -622,7 +621,7 @@
"type": "tidelift"
}
],
"time": "2024-12-29T14:10:59+00:00"
"time": "2023-08-30T16:55:00+00:00"
},
{
"name": "league/config",
@ -708,31 +707,31 @@
},
{
"name": "nette/schema",
"version": "v1.3.2",
"version": "v1.2.5",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "da801d52f0354f70a638673c4a0f04e16529431d"
"reference": "0462f0166e823aad657c9224d0f849ecac1ba10a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
"reference": "da801d52f0354f70a638673c4a0f04e16529431d",
"url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a",
"reference": "0462f0166e823aad657c9224d0f849ecac1ba10a",
"shasum": ""
},
"require": {
"nette/utils": "^4.0",
"php": "8.1 - 8.4"
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
"php": "7.1 - 8.3"
},
"require-dev": {
"nette/tester": "^2.5.2",
"nette/tester": "^2.3 || ^2.4",
"phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.8"
"tracy/tracy": "^2.7"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
"dev-master": "1.2-dev"
}
},
"autoload": {
@ -764,26 +763,26 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.3.2"
"source": "https://github.com/nette/schema/tree/v1.2.5"
},
"time": "2024-10-06T23:10:23+00:00"
"time": "2023-10-05T20:37:59+00:00"
},
{
"name": "nette/utils",
"version": "v4.0.5",
"version": "v4.0.3",
"source": {
"type": "git",
"url": "https://github.com/nette/utils.git",
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96"
"reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/utils/zipball/736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"reference": "736c567e257dbe0fcf6ce81b4d6dbe05c6899f96",
"url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015",
"reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015",
"shasum": ""
},
"require": {
"php": "8.0 - 8.4"
"php": ">=8.0 <8.4"
},
"conflict": {
"nette/finder": "<3",
@ -850,9 +849,9 @@
],
"support": {
"issues": "https://github.com/nette/utils/issues",
"source": "https://github.com/nette/utils/tree/v4.0.5"
"source": "https://github.com/nette/utils/tree/v4.0.3"
},
"time": "2024-08-07T15:39:19+00:00"
"time": "2023-10-29T21:02:13+00:00"
},
{
"name": "psr/event-dispatcher",
@ -984,21 +983,21 @@
},
{
"name": "spatie/yaml-front-matter",
"version": "2.1.0",
"version": "2.0.8",
"source": {
"type": "git",
"url": "https://github.com/spatie/yaml-front-matter.git",
"reference": "5d0009289dd19a23e5f6cbb72c959a9fc1881e32"
"reference": "f2f1f749a405fafc9d6337067c92c062d51a581c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/yaml-front-matter/zipball/5d0009289dd19a23e5f6cbb72c959a9fc1881e32",
"reference": "5d0009289dd19a23e5f6cbb72c959a9fc1881e32",
"url": "https://api.github.com/repos/spatie/yaml-front-matter/zipball/f2f1f749a405fafc9d6337067c92c062d51a581c",
"reference": "f2f1f749a405fafc9d6337067c92c062d51a581c",
"shasum": ""
},
"require": {
"php": "^8.0",
"symfony/yaml": "^6.0|^7.0"
"php": "^7.0|^8.0",
"symfony/yaml": "^3.0|^4.0|^5.0|^6.0|^7.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
@ -1030,7 +1029,7 @@
"yaml"
],
"support": {
"source": "https://github.com/spatie/yaml-front-matter/tree/2.1.0"
"source": "https://github.com/spatie/yaml-front-matter/tree/2.0.8"
},
"funding": [
{
@ -1042,20 +1041,20 @@
"type": "github"
}
],
"time": "2024-12-02T08:40:45+00:00"
"time": "2023-12-04T10:02:52+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.1",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"shasum": ""
},
"require": {
@ -1063,12 +1062,12 @@
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "3.5-dev"
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@ -1093,7 +1092,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
},
"funding": [
{
@ -1109,24 +1108,24 @@
"type": "tidelift"
}
],
"time": "2024-09-25T14:20:29+00:00"
"time": "2023-05-23T14:45:45+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.31.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb",
"shasum": ""
},
"require": {
"php": ">=7.2"
"php": ">=7.1"
},
"provide": {
"ext-ctype": "*"
@ -1136,9 +1135,12 @@
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -1172,7 +1174,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0"
},
"funding": [
{
@ -1188,24 +1190,24 @@
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.31.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
"reference": "42292d99c55abe617799667f454222c54c60e229"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229",
"reference": "42292d99c55abe617799667f454222c54c60e229",
"shasum": ""
},
"require": {
"php": ">=7.2"
"php": ">=7.1"
},
"provide": {
"ext-mbstring": "*"
@ -1215,9 +1217,12 @@
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -1252,7 +1257,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0"
},
"funding": [
{
@ -1268,30 +1273,33 @@
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
"time": "2023-07-28T09:04:16+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.31.0",
"version": "v1.28.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
"reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5",
"shasum": ""
},
"require": {
"php": ">=7.2"
"php": ">=7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.28-dev"
},
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@ -1332,7 +1340,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0"
},
"funding": [
{
@ -1348,101 +1356,24 @@
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/yaml",
"version": "v7.2.3",
"version": "v7.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "ac238f173df0c9c1120f862d0f599e17535a87ec"
"reference": "0055b230c408428b9b5cde7c55659555be5c0278"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/ac238f173df0c9c1120f862d0f599e17535a87ec",
"reference": "ac238f173df0c9c1120f862d0f599e17535a87ec",
"url": "https://api.github.com/repos/symfony/yaml/zipball/0055b230c408428b9b5cde7c55659555be5c0278",
"reference": "0055b230c408428b9b5cde7c55659555be5c0278",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
@ -1480,7 +1411,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v7.2.3"
"source": "https://github.com/symfony/yaml/tree/v7.0.0"
},
"funding": [
{
@ -1496,29 +1427,28 @@
"type": "tidelift"
}
],
"time": "2025-01-07T12:55:42+00:00"
"time": "2023-11-07T10:26:03+00:00"
},
{
"name": "twig/markdown-extra",
"version": "v3.19.0",
"version": "v3.8.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/markdown-extra.git",
"reference": "6c464fc3e016ada9f17be4511daf2576ba4085c5"
"reference": "b6e4954ab60030233df5d293886b5404558daac8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/6c464fc3e016ada9f17be4511daf2576ba4085c5",
"reference": "6c464fc3e016ada9f17be4511daf2576ba4085c5",
"url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/b6e4954ab60030233df5d293886b5404558daac8",
"reference": "b6e4954ab60030233df5d293886b5404558daac8",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"twig/twig": "^3.13|^4.0"
"php": ">=7.2.5",
"twig/twig": "^3.0"
},
"require-dev": {
"erusev/parsedown": "dev-master as 1.x-dev",
"erusev/parsedown": "^1.7",
"league/commonmark": "^1.0|^2.0",
"league/html-to-markdown": "^4.8|^5.0",
"michelf/php-markdown": "^1.8|^2.0",
@ -1526,9 +1456,6 @@
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Twig\\Extra\\Markdown\\": ""
},
@ -1556,7 +1483,7 @@
"twig"
],
"support": {
"source": "https://github.com/twigphp/markdown-extra/tree/v3.19.0"
"source": "https://github.com/twigphp/markdown-extra/tree/v3.8.0"
},
"funding": [
{
@ -1568,42 +1495,34 @@
"type": "tidelift"
}
],
"time": "2025-01-19T15:54:05+00:00"
"time": "2023-11-21T14:02:01+00:00"
},
{
"name": "twig/twig",
"version": "v3.19.0",
"version": "v3.8.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e"
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/d4f8c2b86374f08efc859323dbcd95c590f7124e",
"reference": "d4f8c2b86374f08efc859323dbcd95c590f7124e",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"shasum": ""
},
"require": {
"php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php81": "^1.29"
"symfony/polyfill-php80": "^1.22"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
"symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0"
},
"type": "library",
"autoload": {
"files": [
"src/Resources/core.php",
"src/Resources/debug.php",
"src/Resources/escaper.php",
"src/Resources/string_loader.php"
],
"psr-4": {
"Twig\\": "src/"
}
@ -1636,7 +1555,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.19.0"
"source": "https://github.com/twigphp/Twig/tree/v3.8.0"
},
"funding": [
{
@ -1648,7 +1567,7 @@
"type": "tidelift"
}
],
"time": "2025-01-29T07:06:14+00:00"
"time": "2023-11-21T18:54:41+00:00"
}
],
"packages-dev": [],

39
www/dergdown.php Normal file
View file

@ -0,0 +1,39 @@
<?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;
}
}

11
www/fontawesome.php Normal file
View file

@ -0,0 +1,11 @@
<?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>'
];
?>

View file

@ -268,7 +268,7 @@ class MySQLAdapter {
];
$qry = "
SELECT *
SELECT post_path, post_metadata
FROM posts
WHERE MATCH(post_tags) AGAINST (? IN BOOLEAN MODE)
";
@ -386,8 +386,7 @@ class MySQLAdapter {
$post_data ??= ['found' => false];
$post_data['post_path'] = $post_path;
$post_data['parent_path'] = dirname($post_path);
$post_data = $this->_normalize_post_data($post_data);
if(!$post_data['found']) {
@ -404,19 +403,6 @@ class MySQLAdapter {
return $post_data;
}
function get_markdown_for_id($id) {
$qry = "SELECT post_content
FROM posts
WHERE post_id = ? AND host = ?
";
$post_data = $this->_exec($qry, "is", $id,
$this->SITE_CONFIG['HTTP_HOST']
)->fetch_assoc();
return $post_data['post_content'];
}
function get_subposts_by_path($path) {
global $sql;

226
www/post_adapter.php Normal file
View file

@ -0,0 +1,226 @@
<?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

@ -4,10 +4,7 @@ $data_time_start = microtime(true);
require_once 'vendor/autoload.php';
require_once 'db_handler/mysql_handler.php';
require_once 'db_handler/post_handler.php';
require_once 'post.php';
require_once 'post_adapter.php';
require_once 'fontawesome.php';
require_once 'dergdown.php';
@ -24,30 +21,7 @@ $SITE_CONFIG = Yaml::parseFile('secrets/' . $SERVER_HOST . '.config.yml');
$SITE_CONFIG['uri_prefix'] = $SERVER_PREFIX;
$SITE_CONFIG['HTTP_HOST'] = $SERVER_HOST;
$db_params = $SITE_CONFIG['db'];
$db_connection = null;
try {
if(false !== getenv('MYSQL_HOST')) {
$db_connection = mysqli_connect(getenv('MYSQL_HOST'),
getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'),
getenv('MYSQL_DATABASE'),
getenv('MYSQL_PORT'));
}
else {
$db_connection = mysqli_connect($db_params['host'],
$db_params['user'], $db_params['password'],
$db_params['database'],
$db_params['port']);
}
} catch (\Throwable $th) {
echo 'Connection failed<br>';
echo 'Error number: ' . mysqli_connect_errno() . '<br>';
echo 'Error message: ' . mysqli_connect_error() . '<br>';
die();
}
$sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST);
$adapter = new PostHandler($sql_adapter);
$adapter = new PostHandler($SITE_CONFIG);
$loader = new \Twig\Loader\FilesystemLoader(['./templates', './user_content']);
@ -66,10 +40,6 @@ function dergdown_to_html($text) {
return $Parsedown->text($text);
}
function post_to_html($post) {
return dergdown_to_html($post->markdown);
}
PostData::$markdown_engine = "post_to_html";
function deduce_user_agent() {
$real_agent=$_SERVER['HTTP_USER_AGENT'];
@ -156,25 +126,6 @@ function render_twig($template, $args = []) {
echo $twig->render($template, $args);
}
function try_render_ajax($SURI) {
global $adapter;
$match = null;
preg_match('/^\/ajax\/([^\/]+)(.*)$/', $SURI, $match);
if(!isset($match)) {
die();
}
$post = $adapter->get_post_by_path($match[2]);
$subposts = $adapter->get_subposts_by_path($match[2]);
echo render_twig('ajax/' . $match[1] . '.html', [
"post" => $post,
"subposts" => $subposts
]);
}
function try_render_post($SURI) {
global $adapter;
@ -209,7 +160,6 @@ function try_render_post($SURI) {
break;
case 'blog':
case 'text/markdown':
echo render_twig('post_types/markdown.html', [
"post" => $post
@ -235,30 +185,6 @@ function try_render_post($SURI) {
]);
break;
case 'blog_list':
if(preg_match('/^(.*[^\/])((?:#.*)?)$/', $SURI, $match)) {
header('Location: ' . $match[1] . '/' . $match[2]);
die();
}
$search_query = $post['post_metadata']['search_tags'] ??
('+type:blog +path:' . $post['post_path'] . '/*');
$search_result = $adapter->perform_post_search($search_query);
$search_result = array_map(function($key) {
$post = new PostData($adapter, $key);
return $post->data;
}, $search_result['results']);
echo render_twig('post_types/blog_list.html', [
"post" => $post,
"subposts" => $adapter->get_subposts_by_path($SURI),
"blog_posts" => $search_result
]);
break;
case 'image':
echo render_twig('post_types/image.html', [
"post" => $post,
@ -307,9 +233,7 @@ function generate_website($SURI) {
} elseif(preg_match('/^\/api\/posts(.*)$/', $SURI, $match)) {
header('Content-Type: application/json');
$post = new PostData($adapter, $adapter->get_post_by_path($match[1]));
echo $post->to_json(with_markdown: true, with_html: true);
echo json_encode($adapter->get_post_by_path($match[1]));
} elseif(preg_match('/^\/api\/subposts(.*)$/', $SURI, $match)) {
@ -323,8 +247,6 @@ function generate_website($SURI) {
header('Content-Type: application/json');
echo json_encode($adapter->perform_post_search($_GET['search_query']));
}
} elseif(preg_match('/^\/ajax\//', $SURI)) {
try_render_ajax($SURI);
} elseif(preg_match('/^\/feed(?:\/(rss|atom)(.*))?$/', $SURI, $match)) {
$feed = $adapter->get_laminas_feed($match[2] ?? '/', $match[1] ?? 'rss');
@ -333,7 +255,7 @@ function generate_website($SURI) {
header('Etag: W/"' . $SURI . '/' . strtotime($feed['feed_ts']) . '"');
echo $feed['feed'];
} else {
} elseif(true) {
try_render_post($SURI);
}
}

View file

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

View file

@ -1,20 +0,0 @@
<?php
interface AnalyticsInterface {
public function get_current_timestamp();
public function increment_counter($tags, $counter, $value = 1, $timestamp = null);
public function log_path_access(
$path,
$agent,
$referrer, $runtime, $status = 200);
public function log_path_errcode(
$path,
$code, $message);
public function pop_analytics($delete = true);
}
?>

View file

@ -1,109 +0,0 @@
<?php
function sanitize_post_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 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];
}
interface PostdataInterface {
/* Postdata format:
*
* The Postdata array is a simple intermediate data format
* for the Post content and metadata.
* It is slightly abstracted from the SQL format itself but will
* only reformat keys, *not* do any alteration of the data itself.
*
* Any supported fields will be integrated into the database.
* Other fields will be saved in a JSON structure, and will
* be restored afterward.
*
* The following fields are mandatory for *writing*
* - path: String, must be sanitized to consist of just alphanumeric
* characters, `_-./`
* used to identify the post itself
*
* The following fields may be returned by the database:
* - id
* - created_at
* - updated_at
* - view_count
*
* The following fields may be supported by the database:
* - markdown: String, markdown of the post. May be
* stored separately and won't be returned by default!
* - type: String, defining the type of the post
* - title: String, self-explanatory
* - tags: Array of strings
* - settings: Hash, recursively merged settings (calculated by DB!)
*
* The following fields are *recommended*, but nothing more:
* - icon: String, optionally defining
*/
public function stub_postdata($path);
public function stub_postdata_tree($path);
public function set_postdata($data);
public function set_post_markdown($id, $markdown);
public function increment_post_counter($path, $counter = 'views', $value = 1);
public function get_postdata($path);
// Returns a key-value pair of child paths => child data
public function get_post_children($path,
$limit = 50, $depth_start = 1, $depth_end = 1,
$order_by = 'path');
public function get_post_markdown($id);
// Returns an array of PostData information
// based on various search parameters.
//
// search_options can either be:
// - An Array
// - Or a String
//
// In case of it being an Array, it may include
// the keys:
// - "query" (which will be processed similar
// to how $search_options will be processed),
// - "text", which is searched for in text fields
// (title, brief, fulltext),
// - "tags", which is matched IN BINARY MODE against
// the post tags
// - "path", which is used as filter
// - "order_by": determines which column to order by. NULL
// will order by FULLTEXT match scores
// - "limit" and "offset", self-explanatory
public function search_posts($search_options);
}
?>

View file

@ -1,335 +0,0 @@
<?php
require_once 'analytics_interface.php';
class MySQLAnalyticsHandler
implements AnalyticsInterface {
private $sql_connection;
private $hostname;
function __construct($sql_connection, $hostname) {
$this->sql_connection = $sql_connection;
$this->hostname = $hostname;
}
private function _exec($qery, $argtypes = '', ...$args) {
$stmt = $this->sql_connection->prepare($qery);
if($argtypes != ""){
$stmt->bind_param($argtypes, ...$args);
}
$stmt->execute();
return $stmt->get_result();
}
public function get_current_timestamp() {
return (int)($this->_exec(
"SELECT unix_timestamp(NOW()) AS ctime"
)->fetch_assoc()['ctime']);
}
public function increment_counter($tags, $counter, $value = 1, $timestamp = null) {
$timestamp ??= $this->get_current_timestamp();
$qry =
"INSERT INTO analytics_summations
(
time_bucket,
metric,
tags,
metric_value
)
VALUES
(
from_unixtime(floor(? / 300) * 300),
?,
?,
?
) AS new
ON DUPLICATE KEY
UPDATE metric_value=analytics_summations.metric_value + new.metric_value;
";
$this->_exec($qry,
"dssd",
$timestamp, $counter, json_encode($tags), $value);
}
public function insert_event($event_tags, $event_text) {
$qry =
"INSERT INTO analytics_events (
event_time, tags, event_text
)
VALUES (NOW(), ?, ?)";
$this->_exec($qry, "ss",
json_encode($event_tags), $event_text);
}
public function log_path_access(
$path,
$agent,
$referrer,
$time, $status = 200) {
if(strlen($path) == 0) {
$path = '/';
}
$this->increment_counter([
'host' => $this->hostname,
'path' => $path,
'agent' => $agent,
'referrer' => $referrer,
'status' => $status
], 'access_sum');
$this->increment_counter([
'host' => $this->hostname,
'path' => $path
], 'runtime', $time);
}
public function log_path_errcode(
$path, $code, $message) {
$this->insert_event([
'host' => $this->hostname,
'path' => $path,
'code' => $code
], $message);
}
public function generate_lp_line($table, $tags, $values, $timestamp) {
$out_str = $table;
$line_tags = [];
foreach($tags AS $tag_key => $tag_value) {
if(!preg_match('/^[\w_]+$/', $tag_key)) {
throw new Exception('Invalid line tag key (' . $tag_key . ')!');
}
$tag_value = preg_replace('/([,=\s])/', '\\\\$0', $tag_value);
$line_tags []= $tag_key . '=' . $tag_value;
}
$line_values = [];
foreach($values AS $tag_key => $tag_value) {
if(!preg_match('/^[\w_]+$/', $tag_key)) {
throw new Exception('Invalid line value key (' . $tag_key . ')!');
}
if(gettype($tag_value) == 'string') {
$tag_value = preg_replace('/(["\])/', '\\\\$0', $tag_value);
$tag_value = preg_replace('/\n/', '\\\\n', $tag_value);
$tag_value = '"' . $tag_value . '"';
}
elseif (gettype($tag_value) == 'integer') {
$tag_value = $tag_value . 'i';
}
$line_values []= $tag_key . '=' . $tag_value;
}
return $table
. ',' . implode(',', $line_tags)
. ' ' . implode(',', $line_values)
. ' ' . $timestamp;
}
public function pop_analytics($delete = true) {
$this->sql_connection->begin_transaction();
try {
$barrier_time = $this->_exec("SELECT NOW() - INTERVAL 6 MINUTE AS ctime")->fetch_assoc()['ctime'];
$result = $this->_exec("
SELECT *
FROM analytics_summations
WHERE time_bucket < ?
ORDER BY metric, time_bucket DESC", "s", $barrier_time);
$data_category = "access_metrics";
$row = $result->fetch_assoc();
$out_str = '';
while(isset($row)) {
$row_tags = json_decode($row['tags']);
$row_value = $row['metric_value'];
$row_metric = $row['metric'];
$out_str .= $this->generate_lp_line($data_category, $row_tags, [
$row_metric => $row_value
], strtotime($row['time_bucket']) . "000000000") . "\n";
$row = $result->fetch_assoc();
}
$result = $this->_exec("
SELECT *
FROM analytics_events
WHERE event_time < ?
ORDER BY event_time DESC", "s", $barrier_time);
while(isset($row)) {
$row_tags = json_decode($row['tags']);
$row_value = $row['event_text'];
$row_metric = $row['metric'];
$out_str .= $this->generate_lp_line($data_category, $row_tags, [
$row_metric => $row_value
], strtotime($row['time_bucket']) . "000000000") . "\n";
$row = $result->fetch_assoc();
}
if($delete) {
$this->_exec("DELETE FROM analytics_summations WHERE time_bucket <= ?", "s", $barrier_time);
}
$this->sql_connection->commit();
return $out_str;
} catch (\Throwable $th) {
$this->sql_connection->rollback();
throw $th;
}
}
public function pop_analytics_json($delete = true) {
$this->sql_connection->begin_transaction();
try {
$barrier_time = $this->_exec("SELECT NOW() - INTERVAL 6 MINUTE AS ctime")->fetch_assoc()['ctime'];
$out_data = [];
$result = $this->_exec("
SELECT *
FROM analytics_summations
WHERE time_bucket < ?
ORDER BY metric, time_bucket DESC", "s", $barrier_time);
$row = $result->fetch_assoc();
$current_metric_collection = [];
$current_time_bucket_collection = [];
$current_metric = $row['metric'] ?? null;
$current_time_bucket = $row['time_bucket'] ?? null;
while(isset($row)) {
$current_time_bucket_collection[]= [
'tags' => json_decode($row['tags']),
'value' => floatval($row['metric_value'])
];
$row = $result->fetch_assoc();
if(!isset($row)
OR ($row['time_bucket'] != $current_time_bucket)
OR ($row['metric'] != $current_metric)) {
$current_metric_collection []= [
'time' => $current_time_bucket,
'data' => $current_time_bucket_collection
];
$current_time_bucket_collection = [];
$current_time_bucket = $row['time_bucket'] ?? null;
}
if(!isset($row) OR ($row['metric'] != $current_metric)) {
$out_data []= [
'metric' => $current_metric,
'data' => $current_metric_collection
];
$current_metric_collection = [];
$current_metric = $row['metric'] ?? null;
}
}
if($delete) {
$this->_exec("DELETE FROM analytics_summations WHERE time_bucket <= ?", "s", $barrier_time);
}
$this->sql_connection->commit();
return json_encode($out_data);
} catch (\Throwable $th) {
$this->sql_connection->rollback();
throw $th;
}
}
public function pop_analytics_old($delete = true) {
$this->sql_connection->begin_transaction();
$out_data = "";
try {
$barrier_time = $this->_exec("SELECT NOW() - INTERVAL 6 MINUTE AS ctime")->fetch_assoc()['ctime'];
$data = $this->_exec("
SELECT *
FROM analytics_access_sums
WHERE time_bucket < ?
", "s", $barrier_time)->fetch_all(MYSQLI_ASSOC);
$data_prefix="analytics_access_sums";
foreach($data AS $post_data) {
$path = $post_data['request_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['access_sum'];
$out_data .= " " . strtotime($post_data['time_bucket']) . "000000000\n";
}
$data = $this->_exec("
SELECT *
FROM analytics_processing_time_sums
WHERE time_bucket < ?
", "s", $barrier_time)->fetch_all(MYSQLI_ASSOC);
$data_prefix="analytics_processing_time_sums";
foreach($data AS $post_data) {
$path = $post_data['request_path'];
if($path == '') {
$path = '/';
}
$out_data .= $data_prefix . ",host=" . $post_data['host'];
$out_data .= ",path=".$path;
$out_data .= " time_sum=" . $post_data['time_sum'];
$out_data .= " " . strtotime($post_data['time_bucket']) . "000000000\n";
}
if($delete) {
$this->_exec("DELETE FROM analytics_access_sums WHERE time_bucket <= ?", "s", $barrier_time);
$this->_exec("DELETE FROM analytics_processing_time_sums WHERE time_bucket <= ?", "s", $barrier_time);
}
$this->sql_connection->commit();
return $out_data;
} catch (\Throwable $th) {
$this->sql_connection->rollback();
throw $th;
}
}
}
?>

View file

@ -1,529 +0,0 @@
<?php
require_once 'db_interface.php';
require_once 'mysql_taglist_handling.php';
class MySQLHandler
implements PostdataInterface {
CONST SQL_READ_COLUMNS = [
'id', 'path', 'created_at', 'updated_at',
'title', 'view_count', 'brief', 'search_score'];
CONST SQL_WRITE_COLUMNS = ['path', 'title', 'brief'];
CONST SQL_ORDER_BY_OPTIONS = [
'search_score' => 'post_search_score',
'search_score_desc' => 'post_search_score DESC',
'path' => 'post_path',
'path_desc' => 'post_path DESC',
'created_at' => 'post_created_at',
'created_at_desc' => 'post_created_at DESC'
];
private $sql_connection;
private $db_prefix;
public $hostname;
public $debugging;
function __construct($sql_connection, $hostname, $db_prefix) {
$this->sql_connection = $sql_connection;
$this->hostname = $hostname;
$this->db_prefix = $db_prefix;
$this->debugging = false;
}
private function _dbg($message) {
if($this->debugging) {
echo $message;
}
}
private function _exec($qery, $argtypes = '', ...$args) {
$stmt = $this->sql_connection->prepare($qery);
if($argtypes != ""){
$stmt->bind_param($argtypes, ...$args);
}
$stmt->execute();
return $stmt->get_result();
}
private function clear_post_settings_cache($post_path) {
$post_path = sanitize_post_path($post_path);
$this->_exec("
UPDATE {$this->db_prefix}_posts
SET post_settings_cache=NULL
WHERE post_path LIKE ?;
", "s", $post_path . "%");
}
public function stub_postdata($path) {
$post_path = sanitize_post_path($path);
$path_depth = substr_count($post_path, "/");
$qry = "
INSERT INTO {$this->db_prefix}_posts
(post_path, post_path_depth)
VALUES
( ?, ?) AS new
ON DUPLICATE KEY UPDATE post_path=new.post_path;";
$this->_exec($qry, "si",
$post_path,
$path_depth);
}
public function stub_postdata_tree($path) {
$post_path = sanitize_post_path($path);
while(true) {
if($post_path == '/') {
$post_path = '';
}
try {
$this->stub_postdata($post_path);
}
catch(Exception $e) {
}
$post_path = dirname($post_path);
if(strlen($post_path) == 0) {
break;
}
}
}
public function set_postdata($data) {
$data['path'] = sanitize_post_path($data['path']);
$post_path = $data['path'];
unset($data['path']);
$this->stub_postdata_tree($post_path);
$data['title'] ??= basename($post_path);
$post_tags = $data['tags'] ?? [];
array_push($post_tags,
'path:' . $post_path
);
if(isset($data['type'])) {
$post_tags []= 'type:' . $data['type'];
}
$sql_args = [
$post_path,
substr_count($post_path, "/"),
$data['title'],
TagList\create_db_str($post_tags),
$data['brief'] ?? null
];
unset($data['title']);
unset($data['brief']);
$post_markdown = $data['markdown'] ?? null;
unset($data['markdown']);
unset($data['html']);
array_push($sql_args, json_encode($data));
$qry =
"INSERT INTO {$this->db_prefix}_posts
(post_path, post_path_depth,
post_title, post_tags, post_brief,
post_metadata, post_settings_cache)
VALUES
( ?, ?, ?, ?, ?, ?, null) AS new
ON DUPLICATE KEY
UPDATE post_title=new.post_title,
post_tags=new.post_tags,
post_brief=new.post_brief,
post_metadata=new.post_metadata,
post_updated_at=CURRENT_TIMESTAMP;
";
$this->_exec($qry, "sissss", ...$sql_args);
if(isset($post_markdown)) {
$this->set_post_markdown($this->sql_connection->insert_id, $post_markdown);
}
$this->clear_post_settings_cache($post_path);
}
public function set_post_markdown($id, $markdown) {
$qry =
"INSERT INTO {$this->db_prefix}_post_markdown ( post_id, post_markdown )
VALUES (?, ?) AS new
ON DUPLICATE KEY UPDATE post_markdown=new.post_markdown;
";
$this->_exec($qry, "is", $id, $markdown);
}
public function increment_post_counter($path, $counter = 'views', $value = 1) {
$path = sanitize_post_path($path);
$qry =
"UPDATE {$this->db_prefix}_posts
SET post_counters = JSON_SET(COALESCE(post_counters, \"{}\"), ?, COALESCE(JSON_EXTRACT(post_counters, ?), 0) + ?)
WHERE post_path = ?";
$json_path = "$.{$counter}";
$this->_exec($qry, "ssds", $json_path, $json_path, $value, $path);
}
private function get_post_settings($post_path) {
$post_path = sanitize_post_path($post_path);
$this->_dbg("-> gps: getting path " . $post_path . "\n");
$post_settings = $this->_exec("
SELECT post_settings_cache
FROM {$this->db_prefix}_posts
WHERE post_path = ?
", "s", $post_path)->fetch_assoc();
if(!isset($post_settings)) {
$this->_dbg("-> gps: Returning because of no result\n");
return [];
}
if(isset($post_settings['post_settings_cache'])) {
$result = json_decode($post_settings['post_settings_cache'], true);
if($this->debugging) {
echo "-> gps: Returning because of cached result:\n";
echo "--> " . json_encode($result) . "\n";
}
return $result;
}
$parent_settings = [];
if($post_path != "") {
$parent_settings = $this->get_post_settings(dirname($post_path));
}
$post_settings = [];
$post_metadata = $this->_exec("
SELECT post_path, post_metadata
FROM {$this->db_prefix}_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->_dbg("-> gps: Merged post settings are " . json_encode($post_settings) . ", saving...\n");
$this->_exec("
UPDATE {$this->db_prefix}_posts SET post_settings_cache=? WHERE post_path=?
", "ss",
json_encode($post_settings), $post_path);
return $post_settings;
}
private function process_postdata($data) {
if(!isset($data)) {
return null;
}
if(!isset($data['post_path'])) {
echo "ERROR, trying to get a post data package without path!";
die();
}
$outdata = [];
foreach($this::SQL_READ_COLUMNS as $key) {
if(isset($data['post_' . $key])) {
$outdata[$key] = $data['post_' . $key];
}
}
$post_metadata = json_decode($data['post_metadata'] ?? '{}', true);
$post_settings = [];
if(isset($data['post_settings_cache'])) {
$post_settings = json_decode($data['post_settings_cache'], true);
}
else {
$post_settings = $this->get_post_settings($data['post_path']);
}
if(isset($data['post_counters'])) {
$outdata['counters'] = json_decode($data['post_counters'], true);
}
else {
$outdata['counters'] = [];
}
$outdata = array_merge($post_settings, $post_metadata, $outdata);
$outdata['host'] ??= $this->hostname;
return $outdata;
}
public function get_postdata($path) {
$path = sanitize_post_path($path);
$qry = "
SELECT *
FROM {$this->db_prefix}_posts
WHERE post_path = ?;
";
$data = $this->_exec($qry, "s", $path)->fetch_assoc();
return $this->process_postdata($data);
}
public function get_post_children($path,
$limit = 50, $depth_start = 1, $depth_end = 1,
$order_by = 'path') {
$path = sanitize_post_path($path);
$path_depth = substr_count($path, "/");
$allowed_ordering = [
'path' => true,
'path DESC' => true,
'created_at' => true,
'created_at DESC' => true,
'updated_at' => true,
'updated_at DESC' => true
];
if(!isset($allowed_ordering[$order_by])) {
throw new Exception('Children ordering not allowed');
}
$order_by = 'post_' . $order_by;
if($this->debugging) {
echo "-> GPC: Getting children for path " . $path;
}
$qry = "
SELECT *
FROM {$this->db_prefix}_posts
WHERE post_path_depth BETWEEN ? AND ?
AND post_path LIKE ?
ORDER BY " . $order_by .
" LIMIT ?";
$data = $this->_exec($qry, "iisi",
$path_depth + $depth_start, $path_depth + $depth_end,
$path.'/%', $limit
)->fetch_all(MYSQLI_ASSOC);
$outdata = [];
foreach($data AS $post_element) {
$outdata[$post_element['post_path']] =
$this->process_postdata($post_element);
}
return $outdata;
}
public function get_post_markdown($id) {
$qry =
"SELECT post_markdown
FROM {$this->db_prefix}_post_markdown
WHERE post_id = ?
";
$data = $this->_exec($qry, "i", $id)->fetch_assoc();
if(!isset($data)) {
return '';
}
return $data['post_markdown'] ?? '';
}
public function parse_search_query_string($text) {
$element_array = explode(' ', $text);
$return_text = '';
$return_tags = [];
$return_options = [];
foreach($element_array as $element) {
if(strlen($element) == 0)
continue;
if(preg_match('/^(\w+):(.+)$/', $element, $match)) {
if($match[1] == 'tags') {
$return_tags = array_merge($return_tags, explode(',', $match[2]));
} else {
$return_options[$match[1]] = $match[2];
}
} else {
$return_text .= $element . ' ';
}
}
return [
'text' => $return_text,
'tags' => $return_tags,
'options' => $return_options
];
}
public function search_posts($options) {
// Function to perform an arbitrary search across
// the database.
//
// "options" input is a Hash with the following
// possible keys:
// - query: This text will be interpreted
// as a combination of text to search as well as
// tags, order-by requirements, etc.
// - text: This text will be used as unmodified
// input to the FULLTEXT matching
// - tags: This may be either a list or a string of tags
// to use for searching
// - path: Which path to search within
// - order_by: What column (if any) to search by
// - limit: Number of results to return, at most
// - offset: Number of results to skip before returning
if(gettype($options) == 'string') {
$options = [
'query' => $options
];
}
// Arrays to construct the query selection later
$qry_selects = ['posts.*'];
$qry_select_data = [];
$qry_select_types = '';
$qry_wheres = [];
$qry_where_data = [];
$qry_where_types = '';
$options['text'] ??= '';
if(gettype($options['tags'] ?? null) == 'string') {
$options['tags'] = TagList\_str_to_raw_taglist($options['tags']);
} else {
$options['tags'] ??= [];
}
// This code will take a generic user-input string, and will process it
// to see if there are any special options to consider.
//
// These options will always be overridden by the original "options"
// array. Text and Tags will be merged. For the limit, the minimum will
// be chosen.
if(isset($options['query'])) {
$search_options = $this->parse_search_query_string($options['query']);
if(strlen($search_options['text']) > 0) {
$options['text'] ??= '';
$options['text'] .= ' ' . $search_options['text'];
}
$options['tags'] = array_merge($options['tags'], $search_options['tags']);
if(isset($search_options['limit'])) {
$options['limit'] = min($options['limit'], intval($search_options['limit']));
}
if(isset($search_options['offset'])) {
$options['offset'] = intval($options['offset']);
}
$options = array_merge($options, $search_options['options']);
}
// If we have any tags, construct a tag-matching query
if(count($options['tags']) > 0) {
$tag_search_string = TagList\create_db_search($options['tags'])['parameter_string'];
$qry_wheres []= "MATCH(post_tags) AGAINST (? IN BOOLEAN MODE)";
$qry_where_data []= $tag_search_string;
$qry_where_types .= 's';
}
// If we have any text query strings, we get to construct a rather fun, complex
// array of MATCH() AGAINST() text queries.
if(strlen($options['text']) > 0) {
$text_search_scores = [0];
$text_search_wheres = [];
foreach([['title', 15], ['brief', 6], ['markdown', 1]] as $arg) {
$text_search_scores []= "((MATCH(post_" . $arg[0] . ") AGAINST (?)) * " . $arg[1] . ')';
$qry_select_data []= $options['text'];
$qry_select_types .= 's';
$text_search_wheres []= "(MATCH(post_" . $arg[0] . ") AGAINST (?))";
$qry_where_data []= $options['text'];
$qry_where_types .= 's';
}
$qry_selects []= '(' . implode('+', $text_search_scores) . ') AS post_search_score';
$qry_wheres []= '(' . implode(' OR ', $text_search_wheres) . ')';
} else {
$qry_selects []= '0 AS post_search_score';
}
if(isset($options['path']) && strlen($options['path']) > 0) {
$qry_wheres []= "post_path LIKE ?";
$qry_where_data []= $options['path'] . '%';
$qry_where_types .= 's';
}
if(count($qry_wheres) == 0) {
return [];
}
$options['offset'] ??= 0;
$options['limit'] = min($options['limit'] ?? 100, 100);
$options['order_by'] ??= 'search_score_desc';
if(!isset($this::SQL_ORDER_BY_OPTIONS[$options['order_by']])) {
throw new Exception("Incorrect order_by option chosen!");
}
$qry_order_by = $this::SQL_ORDER_BY_OPTIONS[$options['order_by']];
$qry =
"SELECT " . implode(', ', $qry_selects) . "
FROM {$this->db_prefix}_posts AS posts
LEFT JOIN {$this->db_prefix}_post_markdown AS post_markdown
ON posts.post_id = post_markdown.post_id
WHERE " . implode(' and ', $qry_wheres) . "
ORDER BY " . $qry_order_by . "
LIMIT ? OFFSET ?";
$search_results = $this->_exec($qry, $qry_select_types . $qry_where_types . "ii",
...array_merge($qry_select_data, $qry_where_data, [$options['limit'], $options['offset']]))->fetch_all(MYSQLI_ASSOC);
$outdata = [];
foreach($search_results AS $post_element) {
$outdata []=
$this->process_postdata($post_element);
}
return $outdata;
}
}
?>

View file

@ -1,81 +0,0 @@
<?php
namespace TagList;
function escape_entry($tag) {
return preg_replace_callback('/[\WZ]/', function($match) {
return "Z" . ord($match[0]);
}, strtolower($tag));
}
function escape_search_entry($tag) {
preg_match("/^([\+\-]?)([^\*]*)(\*?)$/", $tag, $matches);
if(!isset($matches[1])) {
echo "Problem with tag!";
var_dump($tag);
}
return $matches[1] . escape_entry($matches[2]) . $matches[3];
}
function _str_to_raw_taglist($taglist) {
$split_list = explode(' ', $taglist);
$split_list = array_filter($split_list, function($entry) {
if(strlen($entry) == 0)
return false;
if(preg_match('/^\s*$/', $entry))
return false;
return true;
});
return $split_list;
}
function create_db_str($taglist) {
if(gettype($taglist) == 'string') {
$taglist = _str_to_raw_taglist($taglist);
}
$taglist = array_unique($taglist);
$taglist = array_map(function($val) {
return escape_entry($val);
}, $taglist);
asort($taglist);
$taglist = join(' ', $taglist);
return $taglist;
}
function create_db_search($taglist) {
if(gettype($taglist) == 'string') {
$taglist = _str_to_raw_taglist($taglist);
}
$search_params = [];
$search_modifiers = [];
foreach($taglist as $tag) {
if(preg_match('/^(order|limit):(.*)$/i', $tag, $match)) {
$search_modifiers[$match[1]] = $match[2];
} else {
array_push($search_params, $tag);
}
}
$search_params = array_map(function($val) {
return escape_search_entry($val);
}, $search_params);
asort($search_params);
$search_params = join(' ', $search_params);
return [
'modifiers' => $search_modifiers,
'parameter_string' => $search_params
];
}
?>

View file

@ -1,158 +0,0 @@
<?php
require_once 'db_interface.php';
require_once 'yaps_interface.php';
class MySQL_YapsHandler
implements YapsInterface {
private $debugging;
private $sql_connection;
private $hostname;
private $db_prefix;
function __construct($sql_connection, $hostname, $db_prefix) {
$this->sql_connection = $sql_connection;
$this->hostname = $hostname;
$this->db_prefix = $db_prefix;
$this->debugging = false;
}
private function _exec($qery, $argtypes = '', ...$args) {
$stmt = $this->sql_connection->prepare($qery);
if($argtypes != ""){
$stmt->bind_param($argtypes, ...$args);
}
$stmt->execute();
return $stmt->get_result();
}
public function add_yap($yap_data_ext) {
# A little copy to prevent accidentally mutating
# external data
$yap_data = $yap_data_ext;
if(!isset($yap_data['tag'])) {
throw new Exception('Yap is missing a tag!');
}
if(!isset($yap_data['post'])) {
throw new Exception('Yap is missing a post!');
}
if(!isset($yap_data['category'])) {
throw new Exception('Yap is missing a category!');
}
var_dump($yap_data);
$yap_tag = $yap_data['tag'];
unset($yap_data['tag']);
$yap_post = $yap_data['post'];
unset($yap_data['post']);
$yap_post = sanitize_post_path($yap_post);
$yap_cateogry = $yap_data['category'];
unset($yap_data['category']);
$yap_text = $yap_data['text'] ?? null;
unset($yap_data['text']);
date_default_timezone_set('UTC');
$yap_created_at = $yap_data['created_at'] ?? null;
unset($yap_data['created_at']);
$yap_data = json_encode($yap_data);
$qry = "
INSERT INTO {$this->db_prefix}_yaps
(`post_path`, `yap_category`, `yap_tag`,
`yap_created_at`, `yap_metadata`, `yap_text` )
VALUES ( ?, ?, ?,
COALESCE(?, NOW()), ?, ? ) AS new
ON DUPLICATE KEY
UPDATE
{$this->db_prefix}_yaps.yap_metadata = new.yap_metadata,
{$this->db_prefix}_yaps.yap_text = new.yap_text;
";
$this->_exec($qry,
"ssssss",
$yap_post, $yap_cateogry, $yap_tag,
$yap_created_at, $yap_data, $yap_text);
}
public function get_yaplist($args = []) {
$category = $args['category'] ?? null;
$post = $args['post'] ?? $args['path'] ?? null;
$tag = $args['tag'] ?? null;
$before = $args['before'] ?? null;
$limit = $args['limit'] ?? null;
$qry_where = [];
$qry_where_data = [];
$qry_where_types = '';
if(isset($category)) {
if($category[-1] = '%') {
$qry_where []= 'yap_category LIKE ?';
}
else {
$qry_where []= 'yap_category = ?';
}
$qry_where_data []= $category;
$qry_where_types .= 's';
}
if(isset($post)) {
if($post[-1] = '%') {
$qry_where []= 'post_path LIKE ?';
}
else {
$qry_where []= 'post_path = ?';
}
$qry_where_data []= $post;
$qry_where_types .= 's';
}
if(isset($tag)) {
$qry_where []= 'yap_tag = ?';
$qry_where_data [] = $tag;
$qry_where_types .= 's';
}
if(isset($before)) {
$qry_where []= 'yap_created_at < ?';
$qry_where_data []= $before;
$qry_where_types .= 's';
}
$limit = min(max(intval($limit ?? 30), 0), 30);
$qry_where []= 'TRUE';
$qry = "
SELECT *
FROM {$this->db_prefix}_yaps
WHERE
" . implode(' AND ', $qry_where) .
" ORDER BY yap_created_at DESC
LIMIT ?";
$yaplist = $this->_exec($qry,
$qry_where_types . "i",
...array_merge($qry_where_data, [$limit]))->fetch_all(MYSQLI_ASSOC);
return $yaplist;
}
}
?>

View file

@ -1,250 +0,0 @@
<?php
class Post implements ArrayAccess {
public $handler;
private $content_html;
private $content_markdown;
public $data;
public $html_data;
public $raw_data;
private $_child_posts;
private $_parent_post;
public static function _generate_404($post_data) {
$post_data ??= [
'id' => -1,
'path' => '/404.md',
'title' => '404 Page',
'metadata' => [
'type' => '404'
],
'markdown' => 'Whoops! The dergen could not quite find that...'
];
return $post_data;
}
public static function deduce_type($path) {
$ext = pathinfo($path, PATHINFO_EXTENSION);
if(preg_match("/\.(\w+)\.md$/", $path, $ext_match)) {
$ext = $ext_match[1];
}
$ext_mapping = [
'' => 'directory',
'md' => 'text/markdown',
'png' => 'image',
'jpg' => 'image',
'jpeg' => 'image'
];
return $ext_mapping[$ext] ?? '?';
}
public static function deduce_media_url($path) {
if(preg_match("/^(.*\.\w+)\.md$/", $path, $ext_match)) {
return $ext_match[1];
}
return null;
}
public static function deduce_icon($type) {
$icon_mapping = [
'' => 'question',
'text/markdown' => 'markdown',
'markdown' => 'markdown',
'blog' => 'markdown',
'blog_list' => 'rectangle-list',
'directory' => 'folder',
'gallery' => 'images',
'image' => 'image'
];
return $icon_mapping[$type] ?? 'unknown';
}
public static function deduce_template($type) {
$template_mapping = [
'directory' => 'directory',
'gallery' => 'gallery',
'image' => 'image'
];
return $template_mapping[$type] ?? 'vanilla';
}
function __construct($post_handler, $post_data, $site_defaults) {
$this->handler = $post_handler;
$this->content_html = null;
$this->content_markdown = null;
if(!isset($post_data) or !isset($post_data['id'])) {
$post_data = $this->_generate_404($post_data);
}
$data = array_merge($site_defaults, $post_data);
if($data['path'] == '') {
$data['path'] = '/';
$data['title'] ??= 'root';
$data['basename'] ??= 'root';
}
$data['basename'] ??= basename($data['path']);
$data['title'] ??= basename($data['path']);
$data['tags'] ??= [];
$data['type'] ??= self::deduce_type($post_data['path']);
if(!isset($data['url'])) {
$url = $site_defaults['uri_prefix'] . $post_data['path'];
// Adding a trailing slash for "non-filename" paths that
// need a trail to render properly
if(!preg_match('/\.\w+$/', $url)) {
$url .= '/';
}
$data['url'] = $url;
}
$data['icon'] ??= self::deduce_icon($data['type']);
$data['template'] ??= self::deduce_template($data['type']);
$data['media_url'] ??= self::deduce_media_url($data['path']);
$data['media_preview_url'] ??= $data['media_url'];
// TODO: Try to check for thumb image automatically here
$data['preview_image'] ??= $data['media_preview_url'] ??
$data['banners'][0]['src'] ?? null;
$data['brief'] ??= $data['title'];
if($data['type'] == 'gallery') {
$data['search'] ??= [
'path' => $data['path'],
'tags' => [
'+type:image'
]
];
}
$this->data = $data;
}
public function increment_counter($type = 'views', $value = 1) {
$this->handler->increment_post_counter($this->data['path'], $type, $value);
}
public function __get($name) {
if($name == 'html') {
return $this->get_html();
}
if($name == 'markdown') {
return $this->get_markdown();
}
if($name == 'json') {
return $this->to_json();
}
if($name == 'child_posts') {
return $this->get_child_posts();
}
if($name == 'parent') {
return $this->get_parent_post();
}
if(isset($this->data[$name])) {
return $this->data[$name];
}
return null;
}
public function offsetGet($offset) : mixed {
return $this->__get($offset) ?? null;
}
public function offsetExists($offset) : bool {
if(isset($this->data[$offset])) {
return true;
}
return !is_null($this->offsetGet($offset));
}
public function offsetSet($offset, $value) : void {
$this->data[$offset] = $value;
}
public function offsetUnset($offset) : void {
unset($this->data[$offset]);
}
public function get_html() {
$this->content_html ??= $this->handler->render_post($this);
return $this->content_html;
}
public function get_markdown() {
$this->content_markdown ??=
$this->handler->get_markdown_for($this);
return $this->content_markdown;
}
public function get_child_posts(...$search_args) {
if(count($search_args) == 0) {
$this->_child_posts ??=
$this->handler->get_children_for($this);
return $this->_child_posts;
}
else {
return $this->handler->get_children_for($this, ...$search_args);
}
}
public function to_array($options = []) {
$out_data = $this->data;
if(isset($options['markdown'])) {
$out_data['markdown'] = $this->get_markdown();
}
if(isset($options['html'])) {
$out_data['html'] = $this->get_html();
}
if(isset($options['children'])) {
$children = $this->get_child_posts();
$child_arrays = [];
foreach($children AS $child) {
array_push($child_arrays, $child->to_array());
}
$out_data['children'] = $child_arrays;
}
return $out_data;
}
public function to_json($options = []) {
return json_encode($this->to_array($options));
}
public function get_parent_post() {
$parent_path = dirname($this->data['path']);
if($parent_path == '')
return null;
$this->_parent_post ??= $this->handler->get_post($parent_path);
return $this->_parent_post;
}
}
?>

View file

@ -1,76 +0,0 @@
<?php
require_once 'db_interface.php';
require_once 'db_handler/post.php';
class PostHandler {
private $db;
private $posts;
public $markdown_engine;
public $site_defaults;
function __construct($db_adapter) {
$this->db = $db_adapter;
$this->posts = [];
$this->site_defaults = null;
$this->markdown_engine = null;
}
public function get_post($key) {
$key = sanitize_post_path($key);
if(isset($this->posts[$key])) {
return $this->posts[$key];
}
$post_data = $this->db->get_postdata($key);
$post = null;
if(isset($post_data)) {
$post = new Post($this, $post_data, $this->site_defaults);
}
$this->posts[$key] = $post;
return $post;
}
public function get_markdown_for($post) {
return $this->db->get_post_markdown($post->id);
}
public function render_post($post) {
return ($this->markdown_engine)($post);
}
public function increment_post_counter(...$opts) {
$this->db->increment_post_counter(...$opts);
}
public function get_children_for($post, ...$search_opts) {
$child_list = $this->db->get_post_children($post->path, ...$search_opts);
$out_list = [];
foreach($child_list as $child_data) {
array_push($out_list, new Post($this, $child_data, $this->site_defaults));
}
return $out_list;
}
public function search_posts($search_query) {
$search_results = $this->db->search_posts($search_query);
$out_list = [];
foreach($search_results as $search_result) {
array_push($out_list, new Post($this, $search_result, $this->site_defaults));
}
return $out_list;
}
}
?>

View file

@ -1,68 +0,0 @@
<?php
interface YapsInterface {
/* Yaps format:
*
* The yaps are even simpler versions of Postdata,
* as they will have almost no defined metadata.
*
* The purpose of yaps is to provide very lightweight
* additions to existing posts.
*
* The only properly defined fields are the following:
* - Relevant Post ID
* - yap category ("changelog", "comment", "build log", etc.)
* - yap tag/hash (will e.g. use the Git Commit Hash for changelogs)
* - yap creation time
* - yap text
* - other yap data (free-form :D)
*
* There will be little to no post-processing by the
* database, as a lot is somewhat free-form.
* No settings, no caching, etc.
*
* Instead, searching yaps will be a fair bit more
* important, as they act as a lot of the feeds!
* RSS/Atom will run over "changelog" yaps, and changelogs
* will be displayed in most posts when present.
* The system might also serve user comments, as well as
* a posting alternative to social media feeds...
* As well as a few WebMentions comments ^^'
*
* Oh yes, Yaps will always be ordered by time, descending.
* It's feeds :P
* */
/* Adds a yap, or overwrites/updates an existing one
* The yap-data MUST include the following:
* - post (string path!)
* - tag
* - category
*
* And may include:
* - text (which is added to yap_text)
* - created_at (if not set, NOW() is used)
*
* With other fields slapped into yap_metadata
*/
public function add_yap($yap_data);
/* Returns a list of yaps for the given filters.
* - Category must always be set, but may be a "LIKE" wildcard
* - post *may* be set. If set, only yaps for that given post are returned
* - $since, which, if set and a valid timestamp, will return yaps after that time
* - $limit, which should be obvious
*
* This function always returns an array of yaps, chronologically ordered.
*
* The following search args are supported:
* - category: String, matched to the Yap Category. Can have a trailing % for LIKE search
* - post: String, post path to look for. Can have a trailing % for LIKE search
* - tag: Yap tag to look for.
* - before: Datetime string. Shows yaps before the given date
* - limit: Limit the number of returned yaps
*/
public function get_yaplist($args);
}
?>

View file

@ -1,201 +0,0 @@
<?php
header("content-type: text/plain; charset=UTF-8; imeanit=yes");
header("X-Content-Type-Options: nosniff");
header('Content-Disposition: inline');
// Reporting E_NOTICE can be good too (to report uninitialized
// variables or catch variable name misspellings ...)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
$data_time_start = microtime(true);
require_once '../vendor/autoload.php';
require_once 'db_handler/mysql_handler.php';
require_once 'db_handler/post_handler.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)) {
http_response_code(500);
echo "Not a valid server host (was " . $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;
$db_params = $SITE_CONFIG['db'];
$db_connection = null;
try {
if(false !== getenv('MYSQL_HOST')) {
$db_connection = mysqli_connect(getenv('MYSQL_HOST'),
getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'),
getenv('MYSQL_DATABASE'),
getenv('MYSQL_PORT'));
}
else {
$db_connection = mysqli_connect($db_params['host'],
$db_params['user'], $db_params['password'],
$db_params['database'],
$db_params['port']);
}
} catch (\Throwable $th) {
echo 'Connection failed<br>';
echo 'Error number: ' . mysqli_connect_errno() . '<br>';
echo 'Error message: ' . mysqli_connect_error() . '<br>';
die();
}
$db_connection->execute_query("DELETE FROM posts;");
$sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST);
$adapter = new PostHandler($sql_adapter);
$adapter->site_defaults = [];
$sql_adapter->debugging = true;
function test_accounce($title) {
echo "\n\n===========================================
_______ ______ _____ _______
|__ __| ____|/ ____|__ __|
| | | |__ | (___ | | (_)
| | | __| \___ \ | |
| | | |____ ____) | | | _
|_| |______|_____/ |_| (_)
";
echo "==== " . $title . "\n";
echo "===========================================\n";
}
function adapter_fetch($post_path) {
global $db_connection;
global $sql_adapter;
echo "-> Fetching path " . $post_path . "\n";
echo json_encode($db_connection->execute_query("SELECT * FROM posts WHERE post_path=?", [
$post_path
])->fetch_assoc(), JSON_PRETTY_PRINT);
echo "\n-> Adapter output:\n";
echo json_encode($sql_adapter->get_postdata($post_path), JSON_PRETTY_PRINT) . "\n";
}
echo "Starting test...\n";
echo "Trying just a stub...\n";
$sql_adapter->stub_postdata_tree('/testing/stubtest/1/2/3.md');
echo "Stubbed~\n\n";
echo "Getting the stub post...\n";
echo json_encode($sql_adapter->get_postdata('/testing'), JSON_PRETTY_PRINT);
echo "\n\n";
test_accounce("Basic postdata setting");
$sql_adapter->set_postdata([
'path' => '/testing/settest/test.md',
'title' => 'One heck of a test!',
'type' => 'text/markdown',
'tags' => [
'test',
'type:text'
],
'overridetest' => 'metadata'
]);
echo "\nDone!";
adapter_fetch('/testing/settest/test.md');
echo "Done!\n\n";
test_accounce("Setting post markdown...");
$sql_adapter->set_postdata([
'path' => '/testing/markdowntest',
'markdown' => 'Inline markdown test should work...',
'title' => "A Markdown Test",
'brief' => "The dragons explore markdown, sort of properly... Maybe.",
'tags' => ['one', 'two', 'three', 'sexee']
]);
$post = $sql_adapter->get_postdata('/testing/markdowntest');
var_dump($sql_adapter->get_post_markdown($post['id']));
$sql_adapter->set_post_markdown($post['id'],
'
This is one hell of a cute test!
> Just checking in...
{{
template: fragments/blog/card.html
}}
'
);
var_dump($sql_adapter->get_post_markdown($post['id']));
unset($post);
test_accounce("Settings inheritance test...");
echo "Setting on a parent file...\n";
$sql_adapter->set_postdata([
'path' => '/testing/settest',
'settings' => [
'nom' => true,
'type' => 'frame',
'overridetest' => 'settings'
]
]);
echo "\nAnd checking if that held!\n";
adapter_fetch('/testing/settest');
adapter_fetch('/testing/settest/test.md');
test_accounce("Testing getting child posts");
echo json_encode($sql_adapter->get_post_children('/testing'), JSON_PRETTY_PRINT);
echo "\n\n------------------------------------------------------\n";
echo "TEST PHASE: Adapter testing";
echo "\n------------------------------------------------------\n\n";
$post = $adapter->get_post('/testing/markdowntest');
echo "Post path is " . $post->path . "\n";
echo "Post markdown is " . $post->markdown . "\n";
echo $post->to_json();
echo "\n\n";
echo $post->to_json([
'markdown' => true
]);
test_accounce("Fetching child posts");
echo "Root children:\n". json_encode(array_map(function($data) {
return $data->to_array();
}, $adapter->get_post('/')->child_posts), JSON_PRETTY_PRINT);
echo "\n\n";
echo "Root children, extended:\n" . json_encode(array_map(function($data) {
return $data->to_array();
}, $adapter->get_post('/')->get_child_posts(depth_end: 3)), JSON_PRETTY_PRINT);
echo "\n\n";
?>

View file

@ -1,113 +0,0 @@
<?php
use Symfony\Component\Yaml\Yaml;
use Highlight\Highlighter;
class Dergdown extends ParsedownExtra
{
protected $highlighter;
protected $dergInsertRenderer;
public function __construct()
{
$this->highlighter = null;
$this->BlockTypes['{'] []= 'DergInsert';
$this->dergInsertRenderer = null;
}
public function setDergRenderer($dergRenderer) {
$this->dergInsertRenderer = $dergRenderer;
}
protected function blockDergInsert($Line, $currentBlock) {
if (preg_match('/^{{\s?(.*)$/', $Line['body'], $match)) {
return array(
'text' => $match[1] ?? ''
);
}
}
protected function blockDergInsertContinue($Line, $Block) {
if(isset($Block['complete'])) {
return;
}
if(preg_match('/(.*)}}/', $Line['body'], $match)) {
$Block['text'] .= "\n" . $match[1];
$Block['complete'] = true;
return $Block;
}
$Block['text'] .= "\n" . $Line['body'];
return $Block;
}
protected function blockDergInsertComplete($Block) {
try {
$parsed_data = Yaml::parse($Block['text']);
}
catch (Exception $ex) {
return array(
'markup' => '
<div class="derg-insert-error">
<h3> Error in a dergen template! </h3>
YAML could not be parsed properly: <br>
<code>
' . $ex->getMessage() . '</code> </div>'
);
}
try {
if(!isset($this->dergInsertRenderer)) {
throw new Exception("No Dergen Renderer was set!");
}
$render_output = $this->dergInsertRenderer->dergRender($parsed_data);
} catch (Exception $ex) {
return array(
'markup' => '
<div class="derg-insert-error">
<h3> Error in a dergen template! </h3>
Rendering engine threw an error: <br>
<code>
' . $ex->getMessage() . '</code> </div>'
);
}
return array(
'markup' => $render_output,
'interrupted' => true
);
}
protected function blockFencedCodeComplete($block)
{
if (! isset($block['element']['text']['attributes'])) {
return $block;
}
if(!isset($this->highlighter)) {
$this->highlighter = new Highlighter();
}
$code = $block['element']['text']['text'];
$languageClass = $block['element']['text']['attributes']['class'];
$language = explode('-', $languageClass);
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;
}
}

View file

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

View file

@ -1,35 +0,0 @@
<?php
$data_time_start = microtime(true);
require_once '../vendor/autoload.php';
require_once 'setup/site_config.php';
require_once 'setup/db.php';
require_once 'fontawesome.php';
require_once 'dergdown.php';
require_once 'setup/twig.php';
$REQUEST_URI = parse_url($_SERVER['REQUEST_URI']);
$REQUEST_PATH = $REQUEST_URI['path'];
parse_str($REQUEST_URI['query'] ?? '', $REQUEST_QUERY);
require_once 'setup/permissions.php';
require_once 'setup/analytics.php';
if(preg_match('/^\/api/', $REQUEST_PATH)) {
require_once 'serve/api.php';
}
elseif(preg_match('/^\/ajax/', $REQUEST_PATH)) {
require_once 'serve/ajax.php';
}
else {
require_once 'serve/post.php';
}
?>

View file

@ -1,36 +0,0 @@
<?php
$match = null;
preg_match('/^\/ajax\/(.*)$/', $REQUEST_PATH, $match);
if(!isset($match)) {
die();
}
$AJAX_REQUEST_TEMPLATE = $match[1];
$ajax_args = [
];
if(isset($REQUEST_QUERY['page'])) {
$ajax_args['page'] = $adapter->get_post($REQUEST_QUERY['page']);
}
if(isset($REQUEST_QUERY['query'])) {
$REQUEST_QUERY['search'] ??= $REQUEST_QUERY['query'];
}
if(isset($REQUEST_QUERY['search'])) {
$ajax_args['search_query'] = $REQUEST_QUERY['search'];
$ajax_args['search_results'] = $adapter->search_posts($REQUEST_QUERY['search']);
}
$ajax_args['fa'] = $FONT_AWESOME_ARRAY;
$ajax_args['page'] ??= $SITE_CONFIG['site_defaults'];
$ajax_args['post'] ??= $ajax_args['page'];
echo $twig->render($AJAX_REQUEST_TEMPLATE, $ajax_args);
?>

View file

@ -1,137 +0,0 @@
<?php
header('Content-Type: application/json');
use Spatie\YamlFrontMatter\YamlFrontMatter;
preg_match('/^\/api\/([^\/]*)(.*)/', $REQUEST_PATH, $match);
$API_FUNCTION = $match[1];
switch($API_FUNCTION) {
case 'posts':
$post = $adapter->get_post($match[2]);
if(!isset($post)) {
echo json_encode([
'found' => false,
'status' => 404
]);
} else {
echo $post->to_json($REQUEST_QUERY);
}
break;
case 'db_post':
echo json_encode($sql_adapter->get_postdata($match[2]));
break;
case 'metrics':
// TODO Change this to a "can access metrics", but whatever :>
if(!access_can_upload()) {
http_response_code(401);
echo json_encode([
'status' => '401 Unauthorized'
]);
die();
}
echo $analytics_adapter->pop_analytics($delete = true);
break;
case 'db_yaps':
echo json_encode($yap_adapter->get_yaplist($_GET));
break;
case 'add_yap':
if( !isset($_POST['path'])
or !isset($_POST['yap_category'])
or !isset($_POST['yap_text'])) {
echo json_encode([
'status' => 'Missing paramters (must POST path, category and a text!)'
]);
die();
}
$yap_data = [
'post' => $_POST['path'],
'category' => $_POST['yap_category'],
'text' => $_POST['yap_text']
];
$yap_data['tag'] = MD5($_POST['path'] . microtime());
$yap_adapter->add_yap($yap_data);
echo json_encode([
'status' => '200 OK',
'yap' => $yap_data
]);
break;
case 'upload':
if(!access_can_upload()) {
http_response_code(401);
echo json_encode([
'status' => '401 Unauthorized'
]);
die();
}
if( !isset($_POST['path']) or
!isset($_FILES['file'])) {
echo json_encode([
'status' => 'Missing paramters (must POST path and file!)'
]);
die();
}
$file_path = sanitize_post_path($_POST['path']);
$physical_file_path = $SITE_CONFIG['upload']['file_path'] . $file_path;
$file_dir = dirname($physical_file_path);
if(!is_dir($file_dir)) {
mkdir($file_dir, recursive: true);
}
move_uploaded_file($_FILES['file']['tmp_name'], $physical_file_path);
$file_ext = pathinfo($file_path, PATHINFO_EXTENSION);
if($file_ext == 'md') {
$is_directory = false;
$original_file_path = $file_path;
if(basename($file_path) == 'README.md') {
$is_directory = true;
$file_path = dirname($file_path);
}
$post_matter = YamlFrontMatter::parse(file_get_contents($physical_file_path));
$post_data = $post_matter->matter();
$post_data['path'] = $file_path;
$post_data['markdown'] = $post_matter->body();
// TODO: This should be moved to an appropriately abstracted prep function
if($is_directory) {
$post_data['base'] ??= $original_file_path;
$post_data['type'] ??= 'directory';
}
$post_data['tags'] ??= [];
$sql_adapter->set_postdata($post_data);
}
break;
}
?>

View file

@ -1,134 +0,0 @@
<?php
require_once 'fontawesome.php';
function render_root_template($template, $args = []) {
global $twig;
global $FONT_AWESOME_ARRAY;
global $SITE_CONFIG;
$args['fa'] = $FONT_AWESOME_ARRAY;
$args['page'] ??= $SITE_CONFIG['site_defaults'];
$page = $args['page'];
$page['base'] ??= $page['url'] ?? null;
$args['opengraph'] = [
"site_name" => $page['site_name'] ?? 'Nameless Site',
"title" => $page['title'] ?? 'Titleless',
"url" => $page['url'] ?? $page['path'] ?? 'No URL set',
"description" => $page['brief'] ?? $page['description'] ?? 'No description set',
"image" => $page['preview_image'] ?? $page['banners'][0]
];
$args['banners'] = json_encode($page['banners'] ?? []);
$args['age_gate'] = (!isset($_COOKIE['AgeConfirmed']))
&& isset($SITE_CONFIG['age_gate']);
echo $twig->render($template, $args);
}
function render_pathed_content_template($template, $args = []) {
render_root_template($template, $args);
}
function render_post($post, $args = []) {
$template = $post['template']
?? ($post['markdown'] == '' ? 'directory' : 'vanilla');
if(isset($post['search_tags'])) {
$post['search'] = $post['search_tags'];
}
if(isset($post['search'])) {
$args['search_results'] = $post->handler->search_posts($post['search']);
}
$args['page'] = $post;
render_pathed_content_template(
'render_templates/' . $template . '.html',
$args);
}
if($REQUEST_PATH == '/upload.php') {
render_root_template('upload.html');
die();
}
if($REQUEST_PATH == '/yap.php') {
render_root_template('_dev_add_yap.html');
die();
}
if($REQUEST_PATH == '/search/') {
$search_results = [];
$display_type = 'none';
$search_query = [
'user_input' => $_GET['query'] ?? null,
'user_type' => $_GET['search_type'] ?? 'all',
'types' => [['All', 'all'], ['Images', 'image'], ['Blog Posts', 'blog']]
];
if(isset($_GET['query']) && strlen($_GET['query']) > 0) {
$search_request['query'] = $_GET['query'];
if(isset($_GET['search_type']) && $_GET['search_type'] != 'all') {
$search_request['tags'] []= 'type:' . $_GET['search_type'];
}
$search_results = $adapter->search_posts($search_request);
$type_count = [];
if(count($search_results) > 0) {
foreach($search_results as $result) {
$type_count[$result['type']] ??= 0;
$type_count[$result['type']] += 1;
}
$display_type = array_search(max($type_count), $type_count);
}
} else {
$display_type = 'no_search';
}
$search_page = $SITE_CONFIG['site_defaults'];
$search_page['path'] = '/search';
$search_page['title'] = 'Search';
$search_page['basename'] = 'search';
render_root_template('search.html', [
'search_results' => $search_results,
'page' => $search_page,
'display_type' => $display_type,
'search_query' => $search_query
]);
die();
}
$post = $adapter->get_post($REQUEST_PATH);
if(!isset($post)) {
$analytics_return_status = 404;
$error_page = $SITE_CONFIG['site_defaults'];
$error_page['path'] = '/404';
$error_page['title'] = '404 oh no';
$error_page['basename'] = '404.md';
render_root_template('derg_error.html', [
'page' => $error_page,
'error_code' => '404 Hoard not found!',
'error_description' => "Looks like we couldn't find `" . $REQUEST_PATH . "` for you. Check in later!"
]);
}
else {
$post->increment_counter();
render_post($post);
}
?>

View file

@ -1,112 +0,0 @@
<?php
$data_time_start = microtime(true);
$analytics_enable_tail = false;
$analytics_post = null;
$analytics_return_status = 200;
$analytics_known_bogus_requests = [
'/\.(git|env|aws|xmlrpc|well-known|svn)/',
'/^\/wp-/',
'/^\/ID3/',
'/^\/config/',
'/^\/web\.config/',
'/^\/storage/',
'/^\/web\/config\.php/',
'/^\/phpinfo\.php/',
'/^\/swagger\.json/',
'/^\/package\.json/',
'/^\/info\.php/',
'/^\/db\.ini/',
'/^\/administrator/'
];
$analytics_request_is_bogus = null;
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 analytics_is_bogus_request() {
global $analytics_request_is_bogus;
global $analytics_known_bogus_requests;
global $REQUEST_PATH;
if(isset($analytics_request_is_bogus)) {
return $analytics_request_is_bogus;
}
foreach($analytics_known_bogus_requests AS $bogus_check) {
if(preg_match($bogus_check, $REQUEST_PATH)) {
$analytics_request_is_bogus = true;
return true;
}
}
$analytics_request_is_bogus = false;
return false;
}
function analytics_is_user() {
return preg_match('/^user/', deduce_user_agent());
}
register_shutdown_function(function() {
$data_end_time = microtime(true);
global $data_time_start;
global $analytics_adapter;
global $REQUEST_PATH;
global $REQUEST_QUERY;
global $analytics_enable_tail;
global $analytics_return_status;
$data_time_end = microtime(true);
$http_referer = 'magic';
if(isset($_SERVER['HTTP_REFERER'])) {
$http_referer = parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST);
}
$referrer = $REQUEST_QUERY['referer'] ?? $REQUEST_QUERY['ref'] ?? $http_referer;
$compute_time = $data_time_end - $data_time_start;
if(analytics_is_bogus_request()) {
$analytics_return_status = 'bogus';
}
$analytics_adapter->log_path_access($REQUEST_PATH,
deduce_user_agent(),
$referrer,
$compute_time, $analytics_return_status);
if($analytics_enable_tail) {
echo "<!-- Total page time was: " . $compute_time . " -->";
}
if(isset($analytics_post)) {
$analytics_post->increment_counter("compute_time", $compute_time);
if(analytics_is_user()) {
$analytics_post->increment_counter("views");
}
}
});
?>

View file

@ -1,70 +0,0 @@
<?php
require_once 'db_handler/mysql_handler.php';
require_once 'db_handler/mysql_analytics_handler.php';
require_once 'db_handler/mysql_yaps_handler.php';
require_once 'db_handler/post_handler.php';
$db_params = $SITE_CONFIG['db'];
$db_connection = null;
try {
if(false !== getenv('MYSQL_HOST')) {
$db_connection = mysqli_connect(getenv('MYSQL_HOST'),
getenv('MYSQL_USER'), getenv('MYSQL_PASSWORD'),
getenv('MYSQL_DATABASE'),
getenv('MYSQL_PORT'));
}
else {
$db_connection = mysqli_connect($db_params['host'],
$db_params['user'], $db_params['password'],
$db_params['database'],
$db_params['port']);
}
} catch (\Throwable $th) {
echo 'Connection failed<br>';
echo 'Error number: ' . mysqli_connect_errno() . '<br>';
echo 'Error message: ' . mysqli_connect_error() . '<br>';
die();
}
$sql_adapter = new MySQLHandler($db_connection,
$SITE_CONFIG['site_defaults']['uri_prefix'],
$db_params['prefix']);
$analytics_adapter = new MySQLAnalyticsHandler($db_connection,
$SITE_CONFIG['site_defaults']['uri_prefix']);
$yap_adapter = new MySQL_YapsHandler($db_connection,
$SITE_CONFIG['site_defaults']['uri_prefix'],
$db_params['prefix']);
$adapter = new PostHandler($sql_adapter);
require_once 'dergdown.php';
require_once 'setup/derg_insert.php';
function dergdown_to_html($post) {
$DergInsert = new DergInsertRenderer($post);
$Parsedown = new Dergdown();
$Parsedown->setDergRenderer($DergInsert);
$markdown = $post->markdown;
if($markdown == '') {
$markdown = '
{{
template: fragments/directory/inline.html
}}
';
}
return $Parsedown->text($markdown);
}
function post_to_html($post) {
return dergdown_to_html($post);
}
$adapter->markdown_engine = "post_to_html";
$adapter->site_defaults = $SITE_CONFIG['site_defaults'];
?>

View file

@ -1,46 +0,0 @@
<?php
class DergInsertRenderer {
protected $twig;
protected $post;
protected $postAdapter;
public function __construct($post) {
global $twig;
global $adapter;
$this->twig = $twig;
$this->post = $post;
$this->postAdapter = $adapter;
}
public function dergRender($renderConfig) {
global $FONT_AWESOME_ARRAY;
if(!isset($renderConfig['template'])) {
throw new Exception("No template type given!");
}
$template = $renderConfig['template'];
$args = [
'post' => $this->post,
'page' => $this->post,
'fa' => $FONT_AWESOME_ARRAY
];
if(isset($renderConfig['post'])) {
$args['post'] = $this->postAdapter->get_post($renderConfig['post']);
}
if(isset($renderConfig['page'])) {
$args['page'] = $this->postAdapter->get_post($renderConfig['page']);
}
if(isset($renderConfig['search'])) {
$args['search_results'] = $this->postAdapter->search_posts($renderConfig['search']);
}
return $this->twig->render($template, $args);
}
}
?>

View file

@ -1,26 +0,0 @@
<?php
$ACCESS_PERMISSIONS = [
"read" => true,
"upload" => false
];
$ACCESS_KEY = $REQUEST_QUERY['ACCESS_KEY']
?? $_POST['ACCESS_KEY']
?? $_COOKIE['ACCESS_KEY']
?? '';
if($ACCESS_KEY == $SITE_CONFIG['ACCESS_KEY']) {
$ACCESS_PERMISSIONS = [
"read" => true,
"upload" => true
];
}
function access_can_upload() {
global $ACCESS_PERMISSIONS;
return $ACCESS_PERMISSIONS['upload'];
}
?>

View file

@ -1,19 +0,0 @@
<?php
use Symfony\Component\Yaml\Yaml;
$SERVER_HOST = $_SERVER['HTTP_HOST'];
if(!preg_match('/^[\w\.\:]+$/', $SERVER_HOST)) {
http_response_code(500);
echo "Not a valid server host (was " . $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;
?>

View file

@ -1,24 +0,0 @@
<?php
$loader = new \Twig\Loader\FilesystemLoader(['../templates']);
$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;
$twig->addRuntimeLoader(new class implements RuntimeLoaderInterface {
public function load($class) {
if (MarkdownRuntime::class === $class) {
return new MarkdownRuntime(new DefaultMarkdown());
}
}
});
?>

View file

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

View file

@ -1,81 +0,0 @@
.article_blop {
position: relative;
overflow: clip;
z-index: 0;
background: var(--bg_2);
margin: 2rem;
padding: 0;
border-radius: 1rem;
box-shadow: 0px 5px 5px 0px #00000040;
transition: 0.3s;
min-height: 10rem;
}
.article_blop_bg {
position: absolute;
left: 0px;
right: 0;
top: 0;
bottom: 0;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
opacity: 0.2;
z-index: -1;
}
.article_blop:hover {
box-shadow: 0px 8px 8px 0px #00000040;
}
.article_blop_content {
padding: 1rem;
padding-top: 5rem;
}
.article_blop_tags {
position: absolute;
top: 0.5rem;
right: 0.2rem;
width: 40%;
height: auto;
list-style: none;
display: flex;
flex-direction: row;
flex-flow: row wrap;
justify-content: right;
}
.article_blop_tags :first-child {
margin-left: auto;
}
.article_blop_tags li {
margin-right: 0.4rem;
margin-bottom: 0.4rem;
padding-left: 0.2rem;
padding-right: 0.2rem;
font-size: 0.8rem;
font-style: normal;
font-weight: bold;
background-color: var(--highlight_1);
border-radius: 0.3rem;
color: var(--bg_2);
}

View file

@ -1,8 +1,6 @@
const BANNER_TIME = 600 * 1000.0
const BANNER_ANIMATION = "opacity 0.8s linear, transform 1s linear"
// const BANNER_ANIMATION = "opacity 0.8s linear"
const BANNER_ANIMATION = "opacity 0.8s linear, transform 0.1s linear"
class BannerHandler {
constructor(banner_container, banner_image, banner_link) {
@ -34,7 +32,7 @@ class BannerHandler {
console.log("Starting tick")
this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 1000);
this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 100);
}
stopUpdateTick() {
if(this.bannerUpdateTimer === null) {
@ -63,8 +61,6 @@ class BannerHandler {
}
updateTranslation() {
this.bannerContainerDOM = document.getElementById("main_header")
const bannerTranslateMax = -this.bannerDOM.clientHeight + this.bannerContainerDOM.clientHeight
const bannerPercentageFrom = this.currentBannerData.from || 0;
@ -73,7 +69,7 @@ class BannerHandler {
const bannerPercentage = (bannerPercentageFrom + (bannerPercentageTo - bannerPercentageFrom) * this.currentPhase)
const banner_top = (1-bannerPercentage) * bannerTranslateMax
this.bannerDOM.style.transform = "translateZ(0.1px) translateY(" + banner_top + 'px)'
this.bannerDOM.style.transform = "translateY(" + banner_top + 'px' + ")"
}
fadeOut() {
@ -124,7 +120,7 @@ class BannerHandler {
}
updateTick() {
console.log("tick")
const nextPhase = this.getPhase() % 1;
@ -154,17 +150,11 @@ class BannerHandler {
}
}
let bannerHandler = null;
var bannerHandler = new BannerHandler(
document.getElementById("main_header"),
document.getElementById("main_banner_img"),
document.getElementById("main_banner_img_link"))
function startBanner() {
if(bannerHandler !== null) {
return;
}
bannerHandler.start()
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,13 +1,4 @@
@import "styles/age_gate.css";
@import "styles/post_navbar.css";
@import "styles/search.css";
@import "styles/toc.css";
/********************
* GENERAL SETTINGS *
********************
*/
* {
box-sizing: border-box;
@ -15,11 +6,9 @@
padding: 0;
}
.fa-icn {
svg {
fill: var(--text_1);
padding-top: 0.1rem;
height: 1em;
vertical-align: -0.125em;
}
body {
@ -27,23 +16,13 @@ body {
--bg_2: #2c2943;
--bg_3: #3f4148;
--highlight_0: #ee9015b1;
--highlight_1: #ee9015;
--highlight_1a: #ee901540;
--highlight_2: #edd29e;
--text_1: #FFFFFF;
--text_border: #A0A0A080;
--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));
width: 100vw;
color: var(--text_1);
color: var(--text_1);
background: var(--bg_1);
margin: 0px;
@ -51,11 +30,6 @@ body {
min-height: 100vh;
padding-bottom: 4rem;
overflow-y: overlay;
overflow-x: clip;
scrollbar-gutter: stable;
}
@media only screen and (max-width: 600px) {
@ -65,34 +39,6 @@ body {
}
}
@media only screen and (max-width: 1000px) {
#toc_container {
display: none !important;
visibility: hidden !important;
}
#main_content_flexbox:before {
flex: 0.5 0 0 !important;
}
}
/* width */
::-webkit-scrollbar {
width: 3px;
height: 3px;
}
/* Track */
::-webkit-scrollbar-track {
background: transparent;
}
/* Handle */
::-webkit-scrollbar-thumb {
background: var(--highlight_1);
}
:link {
color: var(--highlight_1);
font-style: italic;
@ -107,6 +53,45 @@ 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;
@ -165,157 +150,97 @@ a:hover {
margin-right: 2rem;
}
:target, html, body {
scroll-margin-top: 140px;
}
:target {
outline: 1px solid var(--highlight_1);
}
#main_content_flexbox {
width: 100vw;
display: flex;
justify-content: left;
align-items: flex-start;
padding-left: 1em;
padding-right: 1em;
&:before {
content: '';
flex: 0.2 0 0;
}
:target {
scroll-margin-top: 6rem;
}
#main_content_wrapper {
/*padding: 0rem var(--content-padding) 1rem var(--content-padding);*/
/*width: var(--content-width);*/
flex: 0 1 var(--content-width);
--content-width: min(100vw, calc(20rem + 40vw));
--content-total-margin: calc(calc(100vw - var(--content-width)) / 2);
/*margin-left: calc(var(--content-margin));
//margin-right: var(--content-margin);*/
--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);
min-width: 0;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
#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%;
height: 100%;
background-image: url('/static/three-dots.svg');
background-repeat: no-repeat;
background-position: center;
opacity: 0;
z-index: 10;
pointer-events: none;
transition: opacity 0.2s;
}
.htmx-indicator {
position: relative;
}
.htmx-indicator::before {
content: '';
font-style: italic;
padding-left: 0.5rem;
position: absolute;
background: var(--highlight_1);
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-image: url('/static/three-dots.svg');
background-repeat: no-repeat;
background-position: center;
opacity: 0;
z-index: 10;
pointer-events: none;
transition: opacity 0.2s;
display: flex;
flex-direction: row;
list-style-type: none;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
}
.htmx-request::before {
opacity: 0.5;
transition-delay: 0.5s;
#post_file_path a {
color: var(--text_1);
padding-right: 0.2rem;
}
.folder_listing {
& input {
display: none;
}
& input ~ ul {
display: none;
}
& input:checked ~ ul {
display: block;
}
label > :nth-child(2) {
display: none;
}
input:checked ~ label > :nth-child(1) {
display: none;
}
input:checked ~ label > :nth-child(2) {
display: inline-block;
}
}
label.expandable {
cursor: pointer;
}
input.expandable {
display: none;
}
input.expandable + .expandable {
display: none;
}
input.expandable:checked + .expandable {
display: block;
}
article {
#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;
& img {
display: block;
max-width: 100%;
}
max-height: 70vh;
margin: 1vmin;
margin-right: auto;
margin-left: auto;
}
#main_content_wrapper article img {
display: block;
max-width: 100%;
max-height: 70vh;
margin: 1vmin;
margin-right: auto;
margin-left: auto;
}
#content_footer {
@ -340,8 +265,9 @@ article {
position: absolute;
bottom: 0px;
width: 100%;
& span {
align-self: flex-end;
width: 100%;
}
}
#main_footer span {
align-self: flex-end;
width: 100%;
}

File diff suppressed because one or more lines are too long

View file

@ -93,114 +93,111 @@ html {
article {
line-height: 1.5;
}
& p,
.modest-p {
font-size: 1rem;
margin-bottom: 1.3rem;
}
article p,
.modest-p {
font-size: 1rem;
margin-bottom: 1.3rem;
}
& h1,
.modest-h1,
& h2,
.modest-h2,
& h3,
.modest-h3,
& h4,
.modest-h4 {
margin: 1.5em 0 .3em;
font-weight: inherit;
line-height: 1.42;
padding-left: 1.5rem;
}
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;
}
& > :first-child {
margin-top: 0.3rem !important;
}
article > :first-child {
margin-top: 0.3rem !important;
}
& h1,
.modest-h1 {
margin-top: 0;
font-size: 1.998rem;
}
article h1,
.modest-h1 {
margin-top: 0;
font-size: 1.998rem;
}
& h2,
.modest-h2 {
font-size: 1.427rem;
}
article h2,
.modest-h2 {
font-size: 1.427rem;
}
& h3,
.modest-h3 {
font-size: 1.299rem;
}
article h3,
.modest-h3 {
font-size: 1.299rem;
}
& h4,
.modest-h4 {
font-size: 1.1rem;
}
article h4,
.modest-h4 {
font-size: 1.1rem;
}
& h5,
.modest-h5 {
font-size: 1rem;
}
article h5,
.modest-h5 {
font-size: 1rem;
}
& h6,
.modest-h6 {
font-size: .88rem;
}
article h6,
.modest-h6 {
font-size: .88rem;
}
& small,
.modest-small {
font-size: .707rem;
}
article small,
.modest-small {
font-size: .707rem;
}
/* https://github.com/mrmrs/fluidity */
/* https://github.com/mrmrs/fluidity */
& h1,
& h2,
& h3 {
border-bottom: 1px solid var(--text_border);
padding-bottom: .3rem;
}
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 {
padding-left: 0.8rem;
margin-left: 0.8rem;
margin-right: 4em;
}
blockquote {
border-left: 4px solid var(--text_border);
text-align: justify;
}
blockquote {
border-left: 4px solid var(--text_border);
text-align: justify;
}
pre {
border-radius: 0.5rem;
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;
}
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;
}
@media screen and (max-width: 32rem) {
pre, blockquote {
margin-right: 1.5em;
}
}
& ul,
& ol {
padding-left: 2em;
}
& li {
margin-bottom: 1em;
}
article ul,
article ol {
padding-left: 2em;
}

View file

@ -1,42 +0,0 @@
/****************
* AGE GATE CSS *
****************/
#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;
& 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%);
}
& p {
min-height: 5em;
}
}

View file

@ -1,222 +0,0 @@
.navbar {
position: sticky;
top: 0px;
background: var(--bg_2);
box-shadow: 0px 5px 5px 0px #00000040;
z-index: 5;
& > ._titles {
display: flex;
flex-direction: row;
justify-content: left;
height: 2.3rem;
list-style-type: none;
padding: 0px;
& li {
padding: 0.2rem 0.8rem;
font-style: bold;
font-size: 1.5rem;
background: var(--highlight_1);
}
}
& ._search_field {
position: relative;
width: 1.3em;
height: auto;
margin-right: 0.2em;
overflow-x: clip;
/*transition: width 0.5s ease;*/
& > input {
color: var(--text_1);
height: 1em;
width: 100%;
font-size: 1em;
background-color: transparent;
outline: none;
border: none;
padding-left: 1.3rem;
}
& .fa-icn {
position: absolute;
left: 0.1em;
top: 0.3em;
pointer-events: none;
}
&:focus-within, &:hover, &:has(:focus) {
width: min(20em, 60vw);
min-width: min(20em, 60vw);
& ._search_list {
display: block;
opacity: 1;
}
& input {
border-bottom: 0.15em solid var(--bg_2);
}
}
& ._search_list {
display: none;
opacity: 0;
background: var(--bg_2);
transition: opacity 0.3s ease 0.5s;
transition: visibility 0s none 1s;
width: 100%
max-height: 50vh;
overflow-y: scroll;
padding: 0.5em;
z-index: 6;
& li {
border-bottom: 1px solid var(--highlight_1);
&:hover {
background: var(--highlight_1a);
}
}
}
}
& ._path {
width: 100%;
height: 1.5rem;
padding-left: 0.5rem;
padding-right: 0.6rem;
background: var(--highlight_1);
font-size: 1.1rem;
display: flex;
flex-direction: row;
list-style-type: none;
white-space: nowrap;
& > a {
color: var(--text_1);
padding-right: 0.2rem;
}
}
& ._path_list {
width: 100%;
height: 100%;
margin-right: 1em;
font-style: italic;
background: var(--highlight_1);
font-size: 1.1rem;
display: flex;
flex-direction: row;
list-style-type: none;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
& a {
color: var(--text_1);
padding-right: 0.2rem;
}
}
& > ._expand_label {
cursor: pointer;
}
& > ._details {
background-color: var(--bg_3);
border: 0.15rem solid var(--highlight_1);
border-top: none;
border-radius: 0 0 0.2rem 0.2rem;
overflow: clip;
padding-bottom: 0.2rem;
padding-left: 0.3rem;
max-height: 80vh;
overflow-y: scroll;
& > ._full_path {
width: 100%;
font-style: italic;
padding-left: 0.2rem;
display: flex;
flex-direction: row;
list-style-type: none;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
& li {
margin-left: 0.3rem;
}
}
& > ._folder_list {
list-style: none;
padding-left: 0.4rem;
margin-left: 0.4rem;
border-left: 1px solid var(--text_border);
& ul {
list-style: none;
padding-left: 0.4rem;
margin-left: 0.4rem;
border-left: 1px solid var(--text_border);
}
& a:hover {
background-color: rgba(255, 255, 255, 0.1);
}
& span, & label {
width: 1rem;
display: inline-block;
cursor: pointer;
}
}
}
}

View file

@ -1,113 +0,0 @@
.dergen_search_form {
text-align: center;
& h1 {
padding-left: 0px;
border: none;
margin-bottom: 0.1em;
}
& input[type='text'] {
color: var(--text_1);
height: 2.5rem;
min-width: min(80%, 30em);
font-size: 1.2em;
background-color: #616161;
border: none;
border-radius: 1.25rem;
padding-left: 1.5rem;
margin-bottom: 0.8em;
box-shadow: 3px 3px 3px #00000033;
}
& button {
color: var(--text_1);
background-color: #515151;
border: none;
border-radius: 0.5rem;
cursor: pointer;
width: 15em;
height: 2.5em;
margin-left: 1em;
margin-right: 1em;
margin-bottom: 1em;
box-shadow: 3px 3px 2px #00000033;
}
button, input[type='text'] {
transition: background-color 0.3s;
&:hover {
background-color: #777777;
}
}
& ._search_type {
text-align: left;
margin-top: 1em;
& input {
display: none;
}
& label {
min-width: 5em;
text-align: center;
padding-left: 0.7em;
padding-right: 0.7em;
cursor: pointer;
display: inline-block;
border-bottom: 2px solid #FFFFFF50;
}
& input:checked + label {
border-bottom: 2px solid #FFFFFF;
}
}
}
.dergen_search_result_listing {
list-style: none;
margin-left: 0em;
padding-left: 0;
& > li {
margin-bottom: 2em;
}
& ._details {
list-style: none;
padding-left: 1.3em;
& ._path_details {
color: #AAAAAA;
& span {
font-size: 0.8rem;
min-width: 5rem;
display: inline-block;
}
}
& ._title {
font-size: 1.5rem;
}
}
}

View file

@ -1,67 +0,0 @@
.table_of_contents {
position: sticky;
float: left;
top: 0px;
flex: 0.1 0 15em;
padding-right: 0.3em;
padding-bottom: 0.5em;
border-radius: 0 0em 0em 0.5em;
box-shadow: -0.2em 0.2em 0.2em black;
--toc-fg: #cecece;
--toc-bg: var(--highlight_1a);
background-color: var(--bg_2);
& a {
display: inline-block;
transition: all 0.5s !important;
line-height: 1em;
text-align: justify;
font-size: 0.9rem;
padding-left: 0.2em;
padding-right: 0.2em;
padding-bottom: 0.2em;
color: var(--toc-fg) !important;
}
& .active > a {
background-color: var(--toc-bg);
border-bottom: 1px solid var(--highlight_1);
transition: all 0.2s;
}
& ol {
padding-left: 0.5em;
list-style: none;
}
& > li {
border-radius: 0.2em;
border-bottom: 1px solid transparent;
margin-bottom: 0.2em;
transition: all 1s;
}
& .toc_collapsing {
display: none;
}
& li:is(:has(.active),.active) > ol > .toc_collapsing {
display: block;
}
}

View file

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

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,316 +0,0 @@
// Code By Webdevtrick ( https://webdevtrick.com )
// https://webdevtrick.com/dynamic-table-of-contents/
//
// Modified by dergens
let tocId = "toc";
const TOC_IDENTIFIER = "toc";
const TOC_MAIN_CONTENT_ID = "#content_article"
const TOC_TOP_PIXEL_MARGIN = 150;
class TocTracker {
known_heading_elements = [];
constructor() {
this.intersection_observer = null;
this.tracking_update_callbacks = [];
this.tracking_rebuild_callbacks = [];
}
_disconnectIntersectionObserver() {
if(this.intersection_observer) {
this.intersection_observer.disconnect();
this.intersection_observer = null;
}
}
_setupIntersectionObserver() {
let options = {
root: null,
rootMargin: `-${TOC_TOP_PIXEL_MARGIN}px 0px 0px 0px`,
threshold: [1]
};
this.intersection_observer = new IntersectionObserver(
(entries) => this._findActiveHeading(),
options
);
this.known_heading_elements.forEach((element) => {
this.intersection_observer.observe(element.dom);
});
}
_searchForHeadings() {
const main = document.querySelector(TOC_MAIN_CONTENT_ID);
if (!main) {
throw Error("A `main` tag section is required to query headings from.");
}
let headings = main.querySelectorAll("h1, h2, h3, h4, h5, h6, li strong");
headings.forEach((heading, index) => {
let heading_level = parseInt(heading.tagName.slice(-1));
let heading_collapsed = false;
let heading_name = heading.innerHTML;
if(heading.tagName == 'STRONG') {
heading_level = -1;
heading_collapsed = true;
heading = heading.closest('li');
}
this.known_heading_elements.push({
level: heading_level,
name: heading_name,
dom: heading,
collapse: heading_collapsed
});
});
}
_sortHeadings() {
this.known_heading_elements.sort((a, b) => {
return a.dom.getBoundingClientRect().y
- b.dom.getBoundingClientRect().y;
});
let lastHeadingLevel = 0;
this.known_heading_elements.forEach((element) => {
if(element.level == -1) {
let extra_depth = 0;
let current_dom = element.dom;
while(current_dom.tagName != 'ARTICLE') {
if((current_dom.tagName == 'OL') || (current_dom.tagName == 'UL'))
extra_depth += 1;
current_dom = current_dom.parentElement.closest('ol,ul,article');
}
element.level = lastHeadingLevel+extra_depth;
}
else
lastHeadingLevel = element.level;
});
}
_generateHeadingPaths() {
let current_path = [];
this.known_heading_elements.forEach(heading => {
while((current_path.length != 0) && (current_path[current_path.length-1].level >= heading.level))
current_path.pop();
current_path.push(heading);
// The zero does nothing. But according to stackoverflow, it makes it faster.
// Why? No idea.
// Just leave it in I suppose.
heading.path = current_path.slice(0);
});
}
_fillHeadingIDs() {
this.known_heading_elements.forEach(heading => {
heading.id = heading.dom.id;
if(heading.id)
return;
heading.dom.id = heading.path.map(i =>
i.name.replace(/\W+/g, '-').trim()
).join('--').toLowerCase();
heading.id = heading.dom.id;
});
}
_findActiveHeading() {
const arr = this.known_heading_elements;
if(arr.length == 0)
return;
// Start should always be higher
// End always lower
let start = 0, end = arr.length - 1;
let mid = Math.floor((start + end) / 2);
// Special case handling, if all items are above the scroll margin
if(arr[end].dom.getBoundingClientRect().y < TOC_TOP_PIXEL_MARGIN) {
start = end;
mid = end;
}
// Iterate until we find the boundary
while (mid > start) {
// Check if the mid is above our boundary ((lower Y))
if (arr[mid].dom.getBoundingClientRect().y < TOC_TOP_PIXEL_MARGIN)
start = mid;
else
end = mid;
// Find the mid index
mid = Math.floor((start + end) / 2);
}
this.tracking_update_callbacks.forEach(callback => callback(arr[start]));
return arr[start];
}
reloadHeadings() {
this.known_heading_elements = [];
this._disconnectIntersectionObserver();
this._searchForHeadings();
this._sortHeadings();
this._generateHeadingPaths();
this._fillHeadingIDs();
this.tracking_rebuild_callbacks.forEach(item => item());
this._setupIntersectionObserver();
}
onTrackingUpdate(callback) {
this.tracking_update_callbacks.push(callback);
return callback;
}
onTrackingRebuild(callback) {
this.tracking_rebuild_callbacks.push(callback);
return callback;
}
}
class TocNavBarUpdater {
constructor(toc_tracker) {
this.toc_tracker = toc_tracker;
this.navbar_dom = null;
this.added_navbar_elements = [];
this.toc_tracker.onTrackingRebuild(() =>
this.trackingRebuildCallback() );
this.toc_tracker.onTrackingUpdate((element) => this.trackingUpdateCallback(element) );
}
_removeNavbarElements() {
this.added_navbar_elements.forEach(dom => dom.remove());
this.added_navbar_elements = [];
}
trackingRebuildCallback() {
this._removeNavbarElements();
this.navbar_dom = document.querySelector('#main_navbar_path_list');
}
trackingUpdateCallback(element) {
this._removeNavbarElements();
element.path.forEach(pathItem => {
let newNode = document.createElement('li');
const pathURL = location.pathname + '#' + pathItem.id + location.search;
newNode.innerHTML = "<a hx-boost=false href=" + pathURL + "> #<sub>" + pathItem.level + '</sub>' + pathItem.name + '</a>'
this.navbar_dom.appendChild(newNode);
this.added_navbar_elements.push(newNode);
});
}
}
class TocSidemenu {
constructor(toc_tracker) {
this.toc_tracker = toc_tracker;
this.sidebar_dom = null;
this.sidebar_elements = {};
this.currently_active_entry = null;
toc_tracker.onTrackingRebuild(() => this.trackingRebuildCallback());
toc_tracker.onTrackingUpdate((element) => this.trackingUpdateCallback(element));
}
_clearSidebar() {
if(this.sidebar_dom) {
this.sidebar_dom.remove();
}
this.sidebar_elements = {};
this.currently_active_entry = null;
}
_generateSidebar() {
let toc_stack = [this.sidebar_dom];
let last_added_li = null;
this.toc_tracker.known_heading_elements.forEach(element => {
while(element.level > toc_stack.length) {
if(!last_added_li) {
last_added_li = document.createElement('li');
toc_stack[toc_stack.length-1].appendChild(last_added_li);
}
let new_ol = document.createElement('ol');
last_added_li.appendChild(new_ol);
last_added_li = false;
toc_stack.push(new_ol);
}
while(element.level < toc_stack.length)
toc_stack.pop();
let new_element = document.createElement('li');
last_added_li = new_element;
const pathURL = location.pathname + '#' + element.id + location.search;
new_element.innerHTML = "<a hx-boost=false style=\"padding-left: " + element.level * 0.8 + "em\" href=" + pathURL + ">" + element.name + "</a>";
if(element.collapse)
new_element.classList.add('toc_collapsing');
this.sidebar_elements[element.id] = new_element;
toc_stack[toc_stack.length-1].appendChild(new_element);
});
}
trackingRebuildCallback() {
this._clearSidebar();
this.sidebar_dom = document.createElement('ol');
document.querySelector('#toc').appendChild(this.sidebar_dom);
this._generateSidebar();
}
trackingUpdateCallback(entry) {
if(this.currently_active_entry) {
this.currently_active_entry.classList.remove('active');
}
Object.values(this.sidebar_elements).forEach((entry) => entry.classList.remove('contains_active'));
let active_entry = this.sidebar_elements[entry.dom.id];
active_entry.classList.add('active');
this.currently_active_entry = active_entry;
entry.path.forEach((path_piece) => this.sidebar_elements[path_piece.id].classList.add('contains_active'));
}
}
let tracker = new TocTracker();
let navbar_updater = new TocNavBarUpdater(tracker);
let sidebar_updater = new TocSidemenu(tracker);
document.addEventListener('DOMContentLoaded', (event) => tracker.reloadHeadings());

View file

@ -1,36 +0,0 @@
{% extends "root.html" %}
{% block second_title %}
<h2> YAP SOMETHIN' </h2>
{% endblock %}
{%block main_content%}
<article>
<form method="post" enctype="multipart/form-data"
action="/api/add_yap"
hx-target="#yap-result"
hx-swap="innerHTML">
<label for="ACCESS_KEY"> Password: </label>
<input type="password" id="ACCESS_KEY" name="ACCESS_KEY"/>
<label for="path"> Path: </label>
<input type="text" id="path" name="path"/>
<label for="yap_category"> Yap Category: </label>
<input type="text" id="yap_category" name="yap_category"
value="yap"/>
<textarea type="file" id="yap_text" name="yap_text">
</textarea>
<button>Submit</button>
</form>
Return data:
<code id="yap-result">
</code>
</article>
{%endblock%}

View file

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

View file

@ -1,4 +0,0 @@
{% for post in search_results %}
{{ include('fragments/blog/card.html') }}
{% endfor %}

View file

@ -1,10 +0,0 @@
<div>
<h1>
{{ post.title }}
</h1>
{% if post.authors %}
Written by {{ post.authors }}
{% endif %}
</div>

View file

@ -1,96 +0,0 @@
<div class="navbar">
<menu class="_titles">
<li>
{{ page.basename }}
</li>
</menu>
<menu class="_path" id="main_navbar_path">
<ul class="_path_list" id="main_navbar_path_list">
{% set split_post = page.path |split('/') %}
{% for i in range(0, split_post|length - 1) %}
<li>
{% if i != 0 %}
<a href="{{split_post|slice(0,i+1)|join('/')}}">
> {{ split_post[i] }}
</a>
{% else %}
<a href="/">root</a>
{% endif %}
</li>
{% endfor %}
</ul>
<form class="_search_field"
action="/search"
method="GET"
style="margin-left: auto;">
{{ fa['magnifying-glass']|raw }}
<input class="_search_input"
type="text"
id="inline_search_query" name="query"
autocomplete="off"
hx-target="#inline_search_result_list"
hx-indicator="#inline_search_result_list"
hx-sync="this:replace"
hx-trigger="keypress changed delay:0.25s, input changed delay:0.25s"
hx-get="/ajax/fragments/search/inline.html">
</input>
<ul class="_search_list htmx-indicator"
id="inline_search_result_list"
tabindex="1">
Type to search...
</ul>
</form>
<li>
<label for="navbar-expander" class="expandable"> {{ fa['bars'] | raw }} </label>
<a rel="alternate" type="application/rss+xml" target="_blank"
style="padding-left: 0.3rem;" href="/feed/rss{{page.path}}">
{{ fa['rss']|raw }}
</a>
</li>
</menu>
<input id="navbar-expander" class="expandable" type="checkbox" hx-preserve>
<div class="_details expandable">
Full path:
<ul class="_full_path">
{% set split_post = page.path |split('/') %}
{% for i in range(0, split_post|length - 1) %}
<li>
{% if i != 0 %}
<a href="{{split_post|slice(0,i+1)|join('/')}}">
/ {{ split_post[i] }}
</a>
{% else %}
<a href="/">root</a>
{% endif %}
</li>
{% endfor %}
</ul>
Dev links:
<a hx-boost="false" href="/raw{{page.path}}">raw</a>
<a hx-boost="false" href="/api/posts{{page.path}}">api</a>
<h4>Folder browser: </h4>
<ul class="_folder_list">
<li>
<span>
{{ fa['turn-up'] | raw}}
</span>
<a href={{page.parent.path}}>..</a>
</li>
{{ include('fragments/directory/compact/listing.html') }}
</ul>
</div>
</div>

View file

@ -1,27 +0,0 @@
<li class="folder_listing">
{% set folder_key = random() %}
<input type="checkbox" id="folder-open-{{folder_key}}" name="folder-open-{{folder_key}}">
<label for="folder-open-{{folder_key}}"
hx-trigger="click once queue:last, mouseenter once queue:all, intersect once queue:all"
hx-get="/ajax/fragments/directory/compact/listing.html?page={{ post.path }}"
hx-target="next ul">
{{ fa['folder'] | raw }}
{{ fa['folder-open'] | raw }}
</label>
<a href={{post.path}}>
{{ post.basename }} - {{ post.title }}
</a>
<ul class="folder_listing">
<li>
<span>
Loading...
</span>
</li>
</ul>
</li>

View file

@ -1,4 +0,0 @@
{% for post in page.child_posts %}
{{ include('fragments/directory/compact/entry.html') }}
{% endfor %}

View file

@ -1,39 +0,0 @@
<h3>Directory contents for {{page.basename}}:</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>
<tr class="entry">
<td>
{{ fa['turn-up'] | raw }}
</td>
<td class="hsmol_hide">
<a href={{page.parent.path}}> .. </a>
</td>
<td class="entry_title">
<a href={{page.parent.path}}> .. </a>
</td>
<td class="entry_update_time hsmol_hide">
</td>
</tr>
{% for post in page.child_posts %}
<tr class="entry">
<td>
{{ fa[post.icon] | raw }}
</td>
<td class="hsmol_hide">
<a href={{post.path}}>{{post.basename}}</a>
</td>
<td class="entry_title">
<a href={{post.path}}>{{ post.title }}</a>
</td>
<td class="entry_update_time hsmol_hide">
{{ post.updated_at }}
</td>
</tr>
{% endfor %}
</table>

View file

@ -0,0 +1,33 @@
<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,10 +0,0 @@
{% for post in search_results %}
<a href="{{post.url}}#:~:text={{ search_query|url_encode }}"
rel="noopener"
hx-boost="off"> {# We can't boost because text-fragments HAVE to be user-inited! #}
<li>
{{post.title}}
</li>
</a>
{% endfor %}

View file

@ -0,0 +1,28 @@
{% extends "root.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/gallerystyle.css">
{% endblock %}
{% block second_title %}
<h2> Gallery </h2>
{% endblock %}
{%block main_content%}
<figure>
<a href="{{ image_url }}" target="_blank">
<img id="gallery_image" src="{{ image_url }}"> </img>
</a>
<article>
<figcaption id="gallery_title"> {{ image_title }} </figcaption>
<span> {{ image_desc }} </span>
<br>
<a href="{{ artist_src_link }}"> Artist: {{ artist_name }} </a>
<br>
<a href="{{ image_src_link }}"> Source link </a>
</article>
</figure>
{%endblock%}

View file

@ -0,0 +1,18 @@
{% extends "root.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/gallerystyle.css">
{% endblock %}
{% block second_title %}
<h2> Gallery </h2>
{% endblock %}
{%block main_content%}
<div id="gallery_root_grid">
{% for key,value in array_path %}
<div class=""
{% endfor %}
</div>
{%endblock%}

View file

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

View file

@ -6,23 +6,23 @@
{{ parent() }}
<link rel="alternate" type="application/atom+xml" title="{{opengraph.site_name}} Feed for {{page.path}}" href="{{page.uri_prefix}}/feed/atom{{page.path}}">
<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> {{ page.title }} </h2>
<h2> {{ post.post_metadata.title }} </h2>
{% endblock %}
{%block main_content%}
{{ include('fragments/decoration/navbar.html') }}
{{ include('fragments/filepath_bar.html') }}
<article id="content_article">
<article>
{%block content_article %}
{%endblock%}
<span id="content_footer">
This page was created on {{ page.created_at }}, last edited {{ page.updated_at }}, and was viewed {{ page.counters.views }} times~
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>

View file

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

@ -0,0 +1,59 @@
{% 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,4 +1,5 @@
{% extends "pathed_content.html" %}
{% block extra_head %}
@ -13,12 +14,12 @@
{%block content_article%}
<figure>
<a target="_blank" href="{{page.media_url}}">
<img id="gallery_image" src="{{page.media_url}}"></img>
<a target="_blank" href="{{post.post_metadata.media_file}}">
<img id="gallery_image" src="{{post.post_metadata.media_file}}"></img>
</a>
<figcaption>
{{ page.html|raw }}
{{ content_html|raw }}
</figcaption>
</figure>
{%endblock%}

View file

@ -9,9 +9,5 @@
{%endblock %}
{%block content_article%}
{% if page.title and (page.title != page.basename) %}
<h1 class="content-title"> {{ page.title }} </h1>
{% endif %}
{{ page.html|raw }}
{{ content_html|raw }}
{% endblock %}

View file

@ -1,13 +0,0 @@
{% extends "pathed_content.html" %}
{% block extra_head %}
<link rel="stylesheet" href="/static/directorystyle.css">
{%endblock%}
{%block content_article%}
{{ include('fragments/directory/inline.html') }}
{{ page.html|raw }}
{%endblock%}

View file

@ -1,27 +0,0 @@
{% extends "pathed_content.html" %}
{%block content_article%}
{{ include('fragments/directory/inline.html') }}
{{ content_html|raw }}
{% if search_results|length > 0 %}
<ul class="gallery">
{% for post in search_results %}
<li class="entry">
<a href={{post.path}}>
<figure>
<img src="{{post.media_preview_url}}">
<figcaption>
{{ post.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,46 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<title>{{opengraph.site_name}} - {{opengraph.title}}</title>
<title>{{og.site_name}} - {{og.title}}</title>
<link rel="stylesheet" href="/static/dergstyle.css">
<link rel="stylesheet" href="/static/modest.css">
<link rel="stylesheet" href="/static/solarized-dark.css">
<link rel="stylesheet" href="/static/directorystyle.css">
<link rel="stylesheet" href="/static/article_blop.css">
<link rel="stylesheet" href="/static/gallerystyle.css">
<link rel="icon" type="image/x-icon" href="/static/icon.jpeg">
<meta name="viewport" content="width=device-width,initial-scale=1">
<script src="/static/htmx.min.js"></script>
<script id="main_banner_script" src="/static/banner.js"></script>
<script src="/static/toc.js"></script>
{% block feed_links %}
<link rel="alternate" type="application/rss+xml" title="{{opengraph.site_name}} Global Feed" href="{{site_config.uri_prefix}}/feed">
<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="{{opengraph.site_name}}">
<meta property="og:site_name" content="{{og.site_name}}">
<meta property="og:url" content="{{opengraph.url}}" />
<meta property="og:url" content="{{og.url}}" />
<meta property="og:title" content="{{ opengraph.title|e }}" />
<meta name="twitter:title" content="{{ opengraph.title|e }}" />
<meta property="og:title" content="{{ og.title|e }}" />
<meta name="twitter:title" content="{{ og.title|e }}" />
<meta property="og:type" content="{{opengraph.type}}" />
<meta property="og:type" content="{{og.type}}" />
<meta property="og:description" content="{{ opengraph.description|e }}" />
<meta name="twitter:description" content="{{ opengraph.description|e }}" />
<meta property="og:description" content="{{ og.description|e }}" />
<meta name="twitter:description" content="{{ og.description|e }}" />
<meta property="og:image" content="{{opengraph.image}}" />
<meta name="twitter:image" content="{{opengraph.image}}" />
<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">
@ -48,9 +38,11 @@
<meta property="al:android:app_name" content="Medium" />
{% endblock %}
<script type="text/javascript">
window.dergBannerOptions = JSON.parse('{{banner|raw}}');
</script>
</head>
<body id="main_body" hx-sync="a:replace img:drop" hx-boost="true" hx-indicator="#main_body">
<body>
{%if age_gate %}
<div id="age_gate_block">
@ -67,54 +59,32 @@
{% endif %}
<header id="main_header">
<img id="main_banner_img" hx-preserve></img>
<a id="main_banner_img_link" href="/gallery" hx-preserve> full picture</a>
<script type="text/javascript">
window.dergBannerOptions = JSON.parse('{{banners|raw}}');
<img id="main_banner_img"></img>
<a id="main_banner_img_link" href="/gallery"> full picture</a>
startBanner();
</script>
<script src="/static/banner.js"></script>
<h1 id="big_title">{% block big_title %}{{opengraph.site_name}}{%endblock%}</h1>
<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">
{% for link in page.navbar_links %}
<li><a href="{{link.href}}"> {{link.text}} </a></li>
{% endfor %}
<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>
<div id="main_content_flexbox">
<nav id="toc_container" class="table_of_contents navbar">
<div class="_titles">
</div>
<div class="_path">
toc
</div>
<div id="toc">
</div>
</nav>
<main id="main_content_wrapper">
{% block main_content %}
<h3>This here should have been replaced by content.
</h3>
If you can see this, complain to your nearest dragon.
{% endblock %}
</main>
</div>
<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>
<script> tracker.reloadHeadings(); </script>
</body>
</html>

View file

@ -1,85 +0,0 @@
{% extends "pathed_content.html" %}
{% block second_title %}
<h2> Dergen Search </h2>
{% endblock %}
{%block content_article %}
<form class="dergen_search_form" method="get" enctype="multipart/form-data" action="/search">
<h1>
Dergle
</h1>
<input type="text" id="query" autocomplete="on" name="query" placeholder="Search for..."
{% if search_query.user_type %}
value="{{search_query.user_input}}"
{% endif %}
/> <br>
<button type="submit">Search!</button>
<button>I'm feeling cuddly</button>
<div class="_search_type">
{% for type in search_query.types %}
<input type="radio" name="search_type"
id="type_{{type[1]}}" value="{{type[1]}}"
onchange="this.form.submit();"
{% if type[1] == search_query.user_type %}
checked
{% endif %}
>
<label for="type_{{type[1]}}">
{{type[0]}}
</label>
{% endfor %}
</div>
</form>
{% if display_type == 'no_search' %}
<h3>Search for something :D</h3>
<ul>
<li>Type normal words to search for post title, brief descriptions and main content</li>
<li>Use the syntax <code>tags:tag_a,tag_b,...</code> to search for specific tags</li>
<li>Use <code>path:/some/example/path</code> to limit search to a specific path </li>
<li>Use the type selector to more precisely look for images, blogs, etc.</li>
<li>Annoy the dragons to implement more metadata search tags</li>
</ul>
{% elseif display_type == 'image' %}
<ul class="gallery">
{% for post in search_results %}
<li class="entry">
<a href={{post.path}}>
<figure>
<img src="{{post.media_preview_url}}">
<figcaption>
{{ post.title }} ({{ post.search_score }})
</figcaption>
</figure>
</a>
</li>
{% endfor %}
</ul>
{%elseif display_type == 'no results' %}
<h4>How sad. There are no search results here... :c </h4>
{%else%}
<ul class="dergen_search_result_listing">
{% for post in search_results %}
<li>
<ul class="_details">
<li class="_path_details"> <span>Score: {{post.search_score|round(2)}}</span> <span>Path: {{post.path}}</span> </li>
<li class="_title">
<a href="{{post.url}}#:~:text={{ search_query.user_input|url_encode }}"
target="_blank"
rel="noopener">
{{post.title}} </a>
</li>
<li class="_brief"> {{post.brief}} </li>
</ul>
</li>
{% endfor %}
</ul>
{% endif %}
{%endblock%}

View file

@ -8,10 +8,10 @@
{%block main_content%}
<article>
<form method="post" enctype="multipart/form-data" action="/api/upload">
<input type="text" id="path" name="path"/>
<input type="password" id="ACCESS_KEY" name="ACCESS_KEY"/>
<input type="file" id="file" name="file"/>
<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>