Compare commits

...

21 commits

Author SHA1 Message Date
de1f1446a3 feat: use FlexBox to properly arrange the sidebar layouts
Some checks reported warnings
/ phplint (push) Has been cancelled
2025-01-27 10:24:41 +01:00
771e9a2ec8 feat: added TOC :D 2025-01-16 23:28:37 +01:00
31150b9b12 style(css): change out CSS classes according to stylesheet update 2025-01-06 22:46:28 +01:00
d609265862 style(css): use of new CSS features (@import and nested CSS) 2025-01-06 22:45:37 +01:00
95e3fc0b00 feat: add function to get raw Database output, for debugging 2025-01-06 22:45:01 +01:00
fc0d38e118 feat: add small search hooks (to be improved later, presumably) 2025-01-06 22:44:09 +01:00
2fe4d20187 fix: adjust default gallery search to new search format 2025-01-06 22:40:15 +01:00
7c8d0191d2 feat(search): add actual searching 2025-01-06 22:39:28 +01:00
02054d418d fix: add guard against whitespace-only entries from messing up tag search 2025-01-06 22:33:44 +01:00
c877c8ce31 fix(search): add FULLTEXT indices to actually be able to search these 2025-01-06 22:33:01 +01:00
cd5b6f2942 fix: 📌 pin PHP version to that used by actual servers 2025-01-06 22:32:23 +01:00
9de4838081 feat(templates): 🎨 adjust templates to fit the new rendering data format 2024-12-09 10:53:05 +01:00
9a5b9e609e feat(search): Add search_tags//search_results post rendering data 2024-12-09 10:52:39 +01:00
bf2486caa4 feat: 🎨 clean up function naming a little 2024-12-09 10:51:02 +01:00
22c953793c feat(search): add proper DB tag searching 2024-12-09 10:50:36 +01:00
f9cfb81079 feature(posts): Added template parameter 2024-11-19 08:59:58 +01:00
aabe0c72d5 feature(post rendering): ability to add additional argument contents 2024-11-18 23:00:24 +01:00
626a4bbaf6 feature(security): add can-upload check 2024-11-18 22:48:28 +01:00
2e012b4fd5 feature(api): add more API functions such as upload 2024-11-18 22:48:13 +01:00
c9808f90f8 feat: reworked the template structure 2024-08-29 20:40:36 +02:00
c607d57221 feat(auth,api): begin work on API input 2024-08-24 15:47:23 +02:00
52 changed files with 1806 additions and 639 deletions

View file

@ -4,7 +4,7 @@ WORKDIR /app
COPY www/composer.* .
COPY www/vendor/* vendor/
FROM php:apache
FROM php:8.2-apache
WORKDIR /var/www/html
COPY --from=0 /app/ ./
@ -15,6 +15,7 @@ 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

@ -37,7 +37,10 @@ CREATE TABLE posts (
INDEX(host, post_created_at),
INDEX(host, post_updated_at),
FULLTEXT(post_tags)
FULLTEXT(post_path),
FULLTEXT(post_tags),
FULLTEXT(post_title),
FULLTEXT(post_brief)
);
CREATE TABLE post_markdown (

View file

@ -7,5 +7,11 @@
"path": "../dragon_fire_content"
}
],
"settings": {}
"settings": {
"conventionalCommits.scopes": [
"search",
"templates",
"css"
]
}
}

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 /

181
www/composer.lock generated
View file

@ -184,33 +184,33 @@
},
{
"name": "laminas/laminas-escaper",
"version": "2.13.0",
"version": "2.14.0",
"source": {
"type": "git",
"url": "https://github.com/laminas/laminas-escaper.git",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba"
"reference": "0f7cb975f4443cf22f33408925c231225cfba8cb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/af459883f4018d0f8a0c69c7a209daef3bf973ba",
"reference": "af459883f4018d0f8a0c69c7a209daef3bf973ba",
"url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/0f7cb975f4443cf22f33408925c231225cfba8cb",
"reference": "0f7cb975f4443cf22f33408925c231225cfba8cb",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-mbstring": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0"
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"conflict": {
"zendframework/zend-escaper": "*"
},
"require-dev": {
"infection/infection": "^0.27.0",
"laminas/laminas-coding-standard": "~2.5.0",
"infection/infection": "^0.27.9",
"laminas/laminas-coding-standard": "~3.0.0",
"maglnet/composer-require-checker": "^3.8.0",
"phpunit/phpunit": "^9.6.7",
"psalm/plugin-phpunit": "^0.18.4",
"vimeo/psalm": "^5.9"
"phpunit/phpunit": "^9.6.16",
"psalm/plugin-phpunit": "^0.19.0",
"vimeo/psalm": "^5.21.1"
},
"type": "library",
"autoload": {
@ -242,7 +242,7 @@
"type": "community_bridge"
}
],
"time": "2023-10-10T08:35:13+00:00"
"time": "2024-10-24T10:12:53+00:00"
},
{
"name": "laminas/laminas-feed",
@ -519,16 +519,16 @@
},
{
"name": "league/commonmark",
"version": "2.5.1",
"version": "2.5.3",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c"
"reference": "b650144166dfa7703e62a22e493b853b58d874b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ac815920de0eff6de947eac0a6a94e5ed0fb147c",
"reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0",
"reference": "b650144166dfa7703e62a22e493b853b58d874b0",
"shasum": ""
},
"require": {
@ -541,8 +541,8 @@
},
"require-dev": {
"cebe/markdown": "^1.0",
"commonmark/cmark": "0.31.0",
"commonmark/commonmark.js": "0.31.0",
"commonmark/cmark": "0.31.1",
"commonmark/commonmark.js": "0.31.1",
"composer/package-versions-deprecated": "^1.8",
"embed/embed": "^4.4",
"erusev/parsedown": "^1.0",
@ -621,7 +621,7 @@
"type": "tidelift"
}
],
"time": "2024-07-24T12:52:09+00:00"
"time": "2024-08-16T11:46:16+00:00"
},
{
"name": "league/config",
@ -707,24 +707,24 @@
},
{
"name": "nette/schema",
"version": "v1.3.0",
"version": "v1.3.2",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188"
"reference": "da801d52f0354f70a638673c4a0f04e16529431d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
"reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188",
"url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d",
"reference": "da801d52f0354f70a638673c4a0f04e16529431d",
"shasum": ""
},
"require": {
"nette/utils": "^4.0",
"php": "8.1 - 8.3"
"php": "8.1 - 8.4"
},
"require-dev": {
"nette/tester": "^2.4",
"nette/tester": "^2.5.2",
"phpstan/phpstan-nette": "^1.0",
"tracy/tracy": "^2.8"
},
@ -763,9 +763,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.3.0"
"source": "https://github.com/nette/schema/tree/v1.3.2"
},
"time": "2023-12-11T11:54:22+00:00"
"time": "2024-10-06T23:10:23+00:00"
},
{
"name": "nette/utils",
@ -983,21 +983,21 @@
},
{
"name": "spatie/yaml-front-matter",
"version": "2.0.9",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/yaml-front-matter.git",
"reference": "cbe67e1cdd0a29a96d74ccab9400fe663e078392"
"reference": "5d0009289dd19a23e5f6cbb72c959a9fc1881e32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/yaml-front-matter/zipball/cbe67e1cdd0a29a96d74ccab9400fe663e078392",
"reference": "cbe67e1cdd0a29a96d74ccab9400fe663e078392",
"url": "https://api.github.com/repos/spatie/yaml-front-matter/zipball/5d0009289dd19a23e5f6cbb72c959a9fc1881e32",
"reference": "5d0009289dd19a23e5f6cbb72c959a9fc1881e32",
"shasum": ""
},
"require": {
"php": "^7.0|^8.0",
"symfony/yaml": "^3.0|^4.0|^5.0|^6.0|^7.0"
"php": "^8.0",
"symfony/yaml": "^6.0|^7.0"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
@ -1029,7 +1029,7 @@
"yaml"
],
"support": {
"source": "https://github.com/spatie/yaml-front-matter/tree/2.0.9"
"source": "https://github.com/spatie/yaml-front-matter/tree/2.1.0"
},
"funding": [
{
@ -1041,20 +1041,20 @@
"type": "github"
}
],
"time": "2024-06-13T10:20:51+00:00"
"time": "2024-12-02T08:40:45+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.5.0",
"version": "v3.5.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6",
"shasum": ""
},
"require": {
@ -1092,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.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1"
},
"funding": [
{
@ -1108,24 +1108,24 @@
"type": "tidelift"
}
],
"time": "2024-04-18T09:32:20+00:00"
"time": "2024-09-25T14:20:29+00:00"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540"
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
"reference": "0424dff1c58f028c451efff2045f5d92410bd540",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-ctype": "*"
@ -1171,7 +1171,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
},
"funding": [
{
@ -1187,24 +1187,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"provide": {
"ext-mbstring": "*"
@ -1251,7 +1251,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@ -1267,24 +1267,24 @@
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
"reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"type": "library",
"extra": {
@ -1331,7 +1331,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
},
"funding": [
{
@ -1347,24 +1347,24 @@
"type": "tidelift"
}
],
"time": "2024-05-31T15:07:36+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.30.0",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af"
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af",
"reference": "3fb075789fb91f9ad9af537c4012d523085bd5af",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.1"
"php": ">=7.2"
},
"type": "library",
"extra": {
@ -1407,7 +1407,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0"
"source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
},
"funding": [
{
@ -1423,24 +1423,25 @@
"type": "tidelift"
}
],
"time": "2024-06-19T12:30:46+00:00"
"time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/yaml",
"version": "v7.1.1",
"version": "v7.2.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "fa34c77015aa6720469db7003567b9f772492bf2"
"reference": "099581e99f557e9f16b43c5916c26380b54abb22"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/fa34c77015aa6720469db7003567b9f772492bf2",
"reference": "fa34c77015aa6720469db7003567b9f772492bf2",
"url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22",
"reference": "099581e99f557e9f16b43c5916c26380b54abb22",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/deprecation-contracts": "^2.5|^3.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
@ -1478,7 +1479,7 @@
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v7.1.1"
"source": "https://github.com/symfony/yaml/tree/v7.2.0"
},
"funding": [
{
@ -1494,26 +1495,26 @@
"type": "tidelift"
}
],
"time": "2024-05-31T14:57:53+00:00"
"time": "2024-10-23T06:56:12+00:00"
},
{
"name": "twig/markdown-extra",
"version": "v3.11.0",
"version": "v3.16.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/markdown-extra.git",
"reference": "504557d60d80478260ebd2221a2b3332a480865d"
"reference": "25f23c02936f8c7157a8413154c06a462c9c20d3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/504557d60d80478260ebd2221a2b3332a480865d",
"reference": "504557d60d80478260ebd2221a2b3332a480865d",
"url": "https://api.github.com/repos/twigphp/markdown-extra/zipball/25f23c02936f8c7157a8413154c06a462c9c20d3",
"reference": "25f23c02936f8c7157a8413154c06a462c9c20d3",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"twig/twig": "^3.0"
"twig/twig": "^3.13|^4.0"
},
"require-dev": {
"erusev/parsedown": "^1.7",
@ -1554,7 +1555,7 @@
"twig"
],
"support": {
"source": "https://github.com/twigphp/markdown-extra/tree/v3.11.0"
"source": "https://github.com/twigphp/markdown-extra/tree/v3.16.0"
},
"funding": [
{
@ -1566,31 +1567,31 @@
"type": "tidelift"
}
],
"time": "2024-08-07T17:34:09+00:00"
"time": "2024-09-03T20:17:35+00:00"
},
{
"name": "twig/twig",
"version": "v3.11.0",
"version": "v3.16.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d"
"reference": "475ad2dc97d65d8631393e721e7e44fb544f0561"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/e80fb8ebba85c7341a97a9ebf825d7fd4b77708d",
"reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/475ad2dc97d65d8631393e721e7e44fb544f0561",
"reference": "475ad2dc97d65d8631393e721e7e44fb544f0561",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"php": ">=8.0.2",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php80": "^1.22",
"symfony/polyfill-php81": "^1.29"
},
"require-dev": {
"phpstan/phpstan": "^2.0",
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0"
},
@ -1634,7 +1635,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.11.0"
"source": "https://github.com/twigphp/Twig/tree/v3.16.0"
},
"funding": [
{
@ -1646,7 +1647,7 @@
"type": "tidelift"
}
],
"time": "2024-08-08T16:15:16+00:00"
"time": "2024-11-29T08:27:05+00:00"
}
],
"packages-dev": [],

View file

@ -1,4 +1,3 @@
<?php
interface AnalyticsInterface {

View file

@ -1,4 +1,3 @@
<?php
function sanitize_post_path($post_path) {
@ -81,6 +80,28 @@ interface PostdataInterface {
$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,33 +1,16 @@
<?php
require_once 'analytics_interface.php';
require_once 'db_interface.php';
function taglist_escape_tag($tag) {
return preg_replace_callback('/[\WZ]/', function($match) {
return "Z" . ord($match[0]);
}, strtolower($tag));
}
function taglist_to_sql_string($post_tags) {
$post_tags = array_unique($post_tags);
$post_tags = array_map(function($val) {
return taglist_escape_tag($val);
}, $post_tags);
asort($post_tags);
$post_tags = join(' ', $post_tags);
return $post_tags;
}
require_once 'mysql_taglist_handling.php';
class MySQLHandler
implements PostdataInterface {
CONST SQL_READ_COLUMNS = [
'id', 'path', 'created_at', 'updated_at',
'title', 'view_count', 'brief'];
'title', 'view_count', 'brief', 'search_score'];
CONST SQL_WRITE_COLUMNS = ['path', 'title', 'brief'];
@ -129,7 +112,7 @@ class MySQLHandler
$post_path,
substr_count($post_path, "/"),
$data['title'],
taglist_to_sql_string($post_tags),
TagList\create_db_str($post_tags),
$data['brief'] ?? null
];
@ -293,8 +276,8 @@ class MySQLHandler
'path DESC' => true,
'created_at' => true,
'created_at DESC' => true,
'modified_at' => true,
'modified_at DESC' => true
'updated_at' => true,
'updated_at DESC' => true
];
if(!isset($allowed_ordering[$order_by])) {
@ -343,6 +326,167 @@ class MySQLHandler
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'] ??= [];
}
$options['limit'] = min($options['limit'] ?? 100, 100);
// 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', 6], ['brief', 4], ['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) {
throw new Exception("No search filtering options supplied!");
}
$options['offset'] ??= 0;
$qry =
"SELECT " . implode(', ', $qry_selects) . "
FROM posts
LEFT JOIN post_markdown ON posts.post_id = post_markdown.post_id
WHERE " . implode(' and ', $qry_wheres) . "
ORDER BY post_search_score DESC
LIMIT " . $options['limit'] . "
OFFSET " . $options['offset'];
$search_results = $this->_exec($qry, $qry_select_types . $qry_where_types,
...array_merge($qry_select_data, $qry_where_data))->fetch_all(MYSQLI_ASSOC);
$outdata = [];
foreach($search_results AS $post_element) {
$outdata []=
$this->process_postdata($post_element);
}
return $outdata;
}
}
?>

View file

@ -0,0 +1,81 @@
<?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,4 +1,3 @@
<?php
class Post implements ArrayAccess {
@ -7,8 +6,6 @@ class Post implements ArrayAccess {
private $content_html;
private $content_markdown;
private $site_defaults;
public $data;
public $html_data;
@ -30,7 +27,7 @@ class Post implements ArrayAccess {
return $post_data;
}
public static function _deduce_type($path) {
public static function deduce_type($path) {
$ext = pathinfo($path, PATHINFO_EXTENSION);
if(preg_match("/\.(\w+)\.md$/", $path, $ext_match)) {
@ -48,10 +45,19 @@ class Post implements ArrayAccess {
return $ext_mapping[$ext] ?? '?';
}
public static function _deduce_icon($type) {
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',
@ -62,19 +68,27 @@ class Post implements ArrayAccess {
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;
$this->site_defaults = $site_defaults;
if(!isset($post_data) or !isset($post_data['id'])) {
$post_data = $this->_generate_404($post_data);
}
$data = $post_data;
$data = array_merge($site_defaults, $post_data);
if($data['path'] == '') {
$data['path'] = '/';
@ -87,23 +101,33 @@ class Post implements ArrayAccess {
$data['url'] ??= 'http://' . $post_data['host'] . $post_data['path'];
$data['basename'] ??= basename($data['path']);
$data['title'] ??= basename($data['path']);
$data['tags'] ??= [];
$data['type'] ??= self::_deduce_type($post_data['path']);
$data['type'] ??= self::deduce_type($post_data['path']);
$data['icon'] ??= self::_deduce_icon($data['type']);
$data['icon'] ??= self::deduce_icon($data['type']);
$data['template'] ??= self::deduce_template($data['type']);
if(isset($sql_meta['media_url'])) {
$data['thumb_url'] ??= $data['media_url'];
}
$data['media_url'] ??= self::deduce_media_url($data['path']);
$data['media_preview_url'] ??= $data['media_url'];
$data['preview_image'] ??= $data['banners'][0]['src'] ?? null;
// 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;
}
@ -128,11 +152,7 @@ class Post implements ArrayAccess {
return $this->data[$name];
}
if(is_null($this->site_defaults)) {
throw new RuntimeException("Post site defaults have not been set properly!");
}
return $this->site_defaults[$name] ?? null;
return null;
}
public function offsetGet($offset) : mixed {
@ -142,9 +162,6 @@ class Post implements ArrayAccess {
if(isset($this->data[$offset])) {
return true;
}
if(isset($this->site_defaults[$offset])) {
return true;
}
return !is_null($this->offsetGet($offset));
}
@ -189,7 +206,14 @@ class Post implements ArrayAccess {
$out_data['html'] = $this->get_html();
}
if(isset($options['children'])) {
die();
$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;

View file

@ -56,6 +56,17 @@ class PostHandler {
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

@ -61,6 +61,8 @@ $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) {
@ -127,15 +129,23 @@ test_accounce("Setting post markdown...");
$sql_adapter->set_postdata([
'path' => '/testing/markdowntest',
'markdown' => 'Inline markdown test should work...',
'title' => "A Markdown Test"
'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!
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']));

View file

@ -1,24 +1,97 @@
<?php
use Symfony\Component\Yaml\Yaml;
use Highlight\Highlighter;
class Dergdown extends ParsedownExtra
{
protected $highlighter;
protected $dergInsertRenderer;
public function __construct()
{
$this->highlighter = new Highlighter();
$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
);
}
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);

View file

@ -4,19 +4,21 @@ $data_time_start = microtime(true);
require_once '../vendor/autoload.php';
require_once 'setup_site_config.php';
require_once 'setup_db.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';
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';
if(preg_match('/^\/api/', $REQUEST_PATH)) {
require_once 'serve/api.php';
}

View file

@ -17,10 +17,15 @@ if(isset($REQUEST_QUERY['page'])) {
$ajax_args['page'] = $adapter->get_post($REQUEST_QUERY['page']);
}
if(isset($REQUEST_QUERY['search'])) {
$ajax_args['search_results'] = $post->handler->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/' . $AJAX_REQUEST_TEMPLATE, $ajax_args);
echo $twig->render($AJAX_REQUEST_TEMPLATE, $ajax_args);
?>

94
www/src/serve/api.php Normal file
View file

@ -0,0 +1,94 @@
<?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 '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(dirname($physical_file_path), 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'] ??= [];
$post_data['tags'] []= "type:" . ($post_data['type'] ?? Post::deduce_type($file_path));
$post_data['tags'] []= "path:" . $file_path;
$sql_adapter->set_postdata($post_data);
}
break;
}
?>

View file

@ -2,7 +2,6 @@
require_once 'fontawesome.php';
$post = $adapter->get_post($REQUEST_PATH);
function render_root_template($template, $args = []) {
global $twig;
@ -13,8 +12,8 @@ function render_root_template($template, $args = []) {
$args['page'] ??= $SITE_CONFIG['site_defaults'];
$page = $args['page'];
$page['base'] ??= $page['url'];
$page['base'] ??= $page['url'] ?? null;
$args['opengraph'] = [
"site_name" => $page['site_name'] ?? 'Nameless Site',
"title" => $page['title'] ?? 'Titleless',
@ -23,7 +22,7 @@ function render_root_template($template, $args = []) {
];
$args['banners'] = json_encode($page['banners'] ?? []);
$args['age_gate'] = (!isset($_COOKIE['AgeConfirmed']))
&& isset($SITE_CONFIG['age_gate']);
@ -34,9 +33,79 @@ function render_pathed_content_template($template, $args = []) {
render_root_template($template, $args);
}
render_pathed_content_template('post_types/markdown.html', [
'page' => $post
]);
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') {
render_root_template('upload.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);
render_post($post);
die();
if(!isset($post)) {

View file

@ -29,14 +29,27 @@ $sql_adapter = new MySQLHandler($db_connection, $SERVER_HOST);
$adapter = new PostHandler($sql_adapter);
require_once 'dergdown.php';
require_once 'setup/derg_insert.php';
function dergdown_to_html($text) {
function dergdown_to_html($post) {
$DergInsert = new DergInsertRenderer($post);
$Parsedown = new Dergdown();
$Parsedown->setDergRenderer($DergInsert);
return $Parsedown->text($text);
$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->markdown);
return dergdown_to_html($post);
}
$adapter->markdown_engine = "post_to_html";

View file

@ -0,0 +1,43 @@
<?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']);
}
return $this->twig->render($template, $args);
}
}
?>

View file

@ -0,0 +1,26 @@
<?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,6 +1,8 @@
const BANNER_TIME = 600 * 1000.0
const BANNER_ANIMATION = "opacity 0.8s linear, transform 0.1s linear"
const BANNER_ANIMATION = "opacity 0.8s linear, transform 1s linear"
// const BANNER_ANIMATION = "opacity 0.8s linear"
class BannerHandler {
constructor(banner_container, banner_image, banner_link) {
@ -32,7 +34,7 @@ class BannerHandler {
console.log("Starting tick")
this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 100);
this.bannerUpdateTimer = setInterval(() => { this.updateTick() }, 1000);
}
stopUpdateTick() {
if(this.bannerUpdateTimer === null) {
@ -71,7 +73,7 @@ class BannerHandler {
const bannerPercentage = (bannerPercentageFrom + (bannerPercentageTo - bannerPercentageFrom) * this.currentPhase)
const banner_top = (1-bannerPercentage) * bannerTranslateMax
this.bannerDOM.style.transform = "translateY(" + banner_top + 'px' + ")"
this.bannerDOM.style.transform = "translateZ(0.1px) translateY(" + banner_top + 'px)'
}
fadeOut() {

View file

@ -1,4 +1,13 @@
@import "styles/age_gate.css";
@import "styles/post_navbar.css";
@import "styles/search.css";
@import "styles/toc.css";
/********************
* GENERAL SETTINGS *
********************
*/
* {
box-sizing: border-box;
@ -25,6 +34,12 @@ body {
--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));
color: var(--text_1);
background: var(--bg_1);
margin: 0px;
@ -42,6 +57,17 @@ 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;
}
}
:link {
color: var(--highlight_1);
font-style: italic;
@ -56,45 +82,6 @@ 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;
@ -157,19 +144,31 @@ a:hover {
scroll-margin-top: 6rem;
}
#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;
}
}
#main_content_wrapper {
--content-width: min(100vw, calc(20rem + 40vw));
--content-total-margin: calc(calc(100vw - var(--content-width)) / 2);
/*padding: 0rem var(--content-padding) 1rem var(--content-padding);*/
/*width: var(--content-width);*/
flex: 0 1 var(--content-width);
--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-left: calc(var(--content-margin));
//margin-right: var(--content-margin);*/
margin-top: 0px;
min-height: 100%;
@ -206,137 +205,42 @@ body.htmx-request::before {
transition-delay: 0.5s;
}
#post_file_bar {
position: sticky;
.folder_listing {
& input {
display: none;
}
top: 0px;
& input ~ ul {
display: none;
}
& input:checked ~ ul {
display: block;
}
background: var(--bg_2);
box-shadow: 0px 5px 5px 0px #00000040;
z-index: 5;
label > :nth-child(2) {
display: none;
}
input:checked ~ label > :nth-child(1) {
display: none;
}
input:checked ~ label > :nth-child(2) {
display: inline-block;
}
}
#post_file_titles {
display: flex;
flex-direction: row;
justify-content: left;
list-style-type: none;
padding: 0px;
}
#post_file_titles > li {
padding: 0.2rem 0.8rem;
font-style: bold;
font-size: 1.5rem;
background: var(--highlight_1);
}
#post_file_path {
width: 100%;
font-style: italic;
padding-left: 0.5rem;
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;
}
#post_file_path a {
color: var(--text_1);
padding-right: 0.2rem;
}
.navbar-expand {
background-color: var(--bg_3);
border: 0.15rem solid var(--highlight_1);
border-top: none;
border-radius: 0 0 0.2rem 0.2rem;
overflow: clip;
padding-bottom: 0.2rem;
padding-left: 0.3rem;
max-height: 80vh;
overflow-y: scroll;
}
.navbar-full-path {
width: 100%;
font-style: italic;
padding-left: 0.2rem;
display: flex;
flex-direction: row;
list-style-type: none;
overflow-x: scroll;
overflow-y: hidden;
white-space: nowrap;
}
.navbar-full-path li {
margin-left: 0.3rem;
}
.navbar-folder-list span, .navbar-folder-list label {
width: 1rem;
display: inline-block;
label.expandable {
cursor: pointer;
}
.navbar-folder-list ul, .navbar-folder-list {
list-style: none;
padding-left: 0.4rem;
margin-left: 0.4rem;
border-left: 1px solid var(--text_border);
}
.navbar-folder-list a:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.folder-listing input {
input.expandable {
display: none;
}
.folder-listing input ~ ul {
input.expandable + .expandable {
display: none;
}
.folder-listing input:checked ~ ul {
input.expandable:checked + .expandable {
display: block;
}
.folder-listing label > :nth-child(2) {
display: none;
}
.folder-listing input:checked ~ label > :nth-child(1) {
display: none;
}
.folder-listing input:checked ~ label > :nth-child(2) {
display: inline-block;
}
#navbar-expand-label {
cursor: pointer;
}
#navbar-expander, .navbar-expand {
display: none;
}
#navbar-expander:checked + .navbar-expand {
display: block;
}
article {
background: var(--bg_3);
@ -345,17 +249,17 @@ article {
box-shadow: 3px 7px 7px 0px #00000040;
padding: 0.75rem;
}
article img {
display: block;
max-width: 100%;
max-height: 70vh;
margin: 1vmin;
margin-right: auto;
margin-left: auto;
& img {
display: block;
max-width: 100%;
max-height: 70vh;
margin: 1vmin;
margin-right: auto;
margin-left: auto;
}
}
#content_footer {
@ -380,9 +284,8 @@ article img {
position: absolute;
bottom: 0px;
width: 100%;
}
#main_footer span {
align-self: flex-end;
width: 100%;
& span {
align-self: flex-end;
width: 100%;
}
}

View file

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

View file

@ -0,0 +1,42 @@
/****************
* 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

@ -0,0 +1,122 @@
.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);
}
}
& > ._path {
width: 100%;
height: 1.5rem;
font-style: italic;
padding-left: 0.5rem;
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

@ -0,0 +1,113 @@
.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;
}
}
}

60
www/static/styles/toc.css Normal file
View file

@ -0,0 +1,60 @@
.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: #EE901544;
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 {
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;
}
}

265
www/static/toc.js Normal file
View file

@ -0,0 +1,265 @@
// 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 = 300;
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");
headings.forEach((heading, index) => {
const heading_level = parseInt(heading.tagName.slice(-1));
this.known_heading_elements.push({
level: heading_level,
name: heading.innerHTML,
dom: heading,
});
});
}
_sortHeadings() {
this.known_heading_elements.sort((a, b) => {
return a.dom.getBoundingClientRect().y
- b.dom.getBoundingClientRect().y;
});
}
_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');
}
trackingUpdateCallback(element) {
this._removeNavbarElements();
const navbar_prev_node = this.navbar_dom.children[this.navbar_dom.children.length -1];
element.path.forEach(pathItem => {
let newNode = document.createElement('li');
const pathURL = location.pathname + '#' + pathItem.id + location.search;
newNode.innerHTML = "<a href=" + pathURL + "> #<sub>" + pathItem.level + '</sub>' + pathItem.name + '</a>'
this.navbar_dom.insertBefore(newNode, navbar_prev_node);
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() {
this.toc_tracker.known_heading_elements.forEach(element => {
let new_element = document.createElement('li');
const pathURL = location.pathname + '#' + element.id + location.search;
new_element.style = "padding-left: " + element.level * 0.8 + "em";
new_element.innerHTML = "<a href=" + pathURL + ">" + element.name + "</a>";
this.sidebar_elements[element.id] = new_element;
this.sidebar_dom.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');
}
let active_entry = this.sidebar_elements[entry.dom.id];
active_entry.classList.add('active');
this.currently_active_entry = active_entry;
}
}
let tracker = new TocTracker();
let navbar_updater = new TocNavBarUpdater(tracker);
let sidebar_updater = new TocSidemenu(tracker);

View file

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

View file

@ -1,5 +1,4 @@
<a href="{{post.url}}">
<div class="article_blop">
<div class="article_blop_bg" style="background-image:url({{post.preview_image}});"></div>
@ -16,7 +15,7 @@
</ul>
<span class="article_blop_excerpt">
{{post.excerpt}}
{{ post.brief }}
</span>
</div>
</div>

View file

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

View file

@ -1,10 +1,10 @@
<div id="post_file_bar">
<menu id="post_file_titles">
<div class="navbar">
<menu class="_titles">
<li>
{{ page.basename }}
</li>
</menu>
<menu id="post_file_path">
<menu class="_path" id="main_navbar_path">
{% set split_post = page.path |split('/') %}
{% for i in range(0, split_post|length - 1) %}
<li>
@ -20,7 +20,7 @@
<li style="margin-left: auto;">
<label for="navbar-expander" id="navbar-expand-label"> {{ fa['bars'] | raw }} </label>
<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 }}
@ -28,10 +28,10 @@
</li>
</menu>
<input id="navbar-expander" class="navbar-expand" type="checkbox" hx-preserve>
<div class="navbar-expand">
<input id="navbar-expander" class="expandable" type="checkbox" hx-preserve>
<div class="_details expandable">
Full path:
<ul class="navbar-full-path">
<ul class="_full_path">
{% set split_post = page.path |split('/') %}
{% for i in range(0, split_post|length - 1) %}
<li>
@ -52,8 +52,8 @@
<h4>Folder browser: </h4>
<ul class="navbar-folder-list">
<li class="folder-listing">
<ul class="_folder_list">
<li>
<span>
{{ fa['turn-up'] | raw}}
</span>
@ -61,7 +61,7 @@
<a href={{page.parent.path}}>..</a>
</li>
{{ include('ajax/compact_filelist/listing.html') }}
{{ include('fragments/directory/compact/listing.html') }}
</ul>
</div>
</div>

View file

@ -1,12 +1,12 @@
<li class="folder-listing">
<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/compact_filelist/listing.html?page={{ post.path }}"
hx-get="/ajax/fragments/directory/compact/listing.html?page={{ post.path }}"
hx-target="next ul">
{{ fa['folder'] | raw }}
@ -17,7 +17,7 @@
{{ post.basename }} - {{ post.title }}
</a>
<ul class="folder-listing">
<ul class="folder_listing">
<li>
<span>
Loading...

View file

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

View file

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

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

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

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -18,6 +18,7 @@
<script src="/static/htmx.min.js"></script>
<script id="main_banner_script" src="/static/banner.js"></script>
<script src="/static/toc.js"></script>
{% if page.base %}
<base href="{{ page.base }}">
@ -90,20 +91,34 @@
</menu>
</header>
<main id="main_content_wrapper">
{% block main_content %}
<h3>This here should have been replaced by content.
</h3>
<div id="main_content_flexbox">
<nav id="toc_container" class="table_of_contents navbar">
<div class="_titles">
</div>
<div class="_path">
toc
</div>
If you can see this, complain to your nearest dragon.
{% endblock %}
</main>
<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>
<footer id="main_footer">
{% block main_footer %}
<span> test? </span>
{% endblock %}
</footer>
<script> tracker.reloadHeadings(); </script>
</body>
</html>

80
www/templates/search.html Normal file
View file

@ -0,0 +1,80 @@
{% 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 the type selector to more precisely look for images, blogs, etc.</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 images yet... What a real shame :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}}" target="_blank">
{{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/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"/>
<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"/>
<button>Submit</button>
</form>