diff --git a/docker_dev/Dockerfile b/docker_dev/Dockerfile index 2060d63..22239a4 100644 --- a/docker_dev/Dockerfile +++ b/docker_dev/Dockerfile @@ -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) \ No newline at end of file diff --git a/docker_dev/mysql_schema.sql b/docker_dev/mysql_schema.sql index 97645e6..afc6d22 100644 --- a/docker_dev/mysql_schema.sql +++ b/docker_dev/mysql_schema.sql @@ -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 ( diff --git a/dragon_fire.code-workspace b/dragon_fire.code-workspace index 533b5a2..c6a4a66 100644 --- a/dragon_fire.code-workspace +++ b/dragon_fire.code-workspace @@ -7,5 +7,11 @@ "path": "../dragon_fire_content" } ], - "settings": {} + "settings": { + "conventionalCommits.scopes": [ + "search", + "templates", + "css" + ] + } } \ No newline at end of file diff --git a/www/.htaccess b/www/.htaccess index d17abcd..05ae513 100644 --- a/www/.htaccess +++ b/www/.htaccess @@ -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 / diff --git a/www/composer.lock b/www/composer.lock index d41c77f..b641d4b 100644 --- a/www/composer.lock +++ b/www/composer.lock @@ -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": [], diff --git a/www/src/db_handler/analytics_interface.php b/www/src/db_handler/analytics_interface.php index fbe3400..de629c9 100644 --- a/www/src/db_handler/analytics_interface.php +++ b/www/src/db_handler/analytics_interface.php @@ -1,4 +1,3 @@ - \ No newline at end of file diff --git a/www/src/db_handler/mysql_handler.php b/www/src/db_handler/mysql_handler.php index def19df..bac80c9 100644 --- a/www/src/db_handler/mysql_handler.php +++ b/www/src/db_handler/mysql_handler.php @@ -1,33 +1,16 @@ - 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; + } } ?> \ No newline at end of file diff --git a/www/src/db_handler/mysql_taglist_handling.php b/www/src/db_handler/mysql_taglist_handling.php new file mode 100644 index 0000000..2acd999 --- /dev/null +++ b/www/src/db_handler/mysql_taglist_handling.php @@ -0,0 +1,81 @@ + $search_modifiers, + 'parameter_string' => $search_params + ]; +} + +?> \ No newline at end of file diff --git a/www/src/db_handler/post.php b/www/src/db_handler/post.php index 4e9494e..78935bb 100644 --- a/www/src/db_handler/post.php +++ b/www/src/db_handler/post.php @@ -1,4 +1,3 @@ - '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; diff --git a/www/src/db_handler/post_handler.php b/www/src/db_handler/post_handler.php index 06fd3fc..8c6ad3f 100644 --- a/www/src/db_handler/post_handler.php +++ b/www/src/db_handler/post_handler.php @@ -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; + } } ?> \ No newline at end of file diff --git a/www/src/dbtest.php b/www/src/dbtest.php index ffbc378..01c795a 100644 --- a/www/src/dbtest.php +++ b/www/src/dbtest.php @@ -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'])); diff --git a/www/src/dergdown.php b/www/src/dergdown.php index 8380be7..1f8dbcd 100644 --- a/www/src/dergdown.php +++ b/www/src/dergdown.php @@ -1,24 +1,97 @@ - - 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' => ' +
+

Error in a dergen template!

+ YAML could not be parsed properly:
+ + ' . $ex->getMessage() . '
' + ); + } + + 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' => ' +
+

Error in a dergen template!

+ Rendering engine threw an error:
+ + ' . $ex->getMessage() . '
' + ); + } + + 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); diff --git a/www/src/router.php b/www/src/router.php index c2f3dc7..6db72cc 100644 --- a/www/src/router.php +++ b/www/src/router.php @@ -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'; } diff --git a/www/src/serve/ajax.php b/www/src/serve/ajax.php index 679afe0..29db538 100644 --- a/www/src/serve/ajax.php +++ b/www/src/serve/ajax.php @@ -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); ?> \ No newline at end of file diff --git a/www/src/serve/api.php b/www/src/serve/api.php new file mode 100644 index 0000000..ca95af9 --- /dev/null +++ b/www/src/serve/api.php @@ -0,0 +1,94 @@ +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; +} + + +?> \ No newline at end of file diff --git a/www/src/serve/post.php b/www/src/serve/post.php index 6430550..5cea531 100644 --- a/www/src/serve/post.php +++ b/www/src/serve/post.php @@ -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)) { diff --git a/www/src/setup_db.php b/www/src/setup/db.php similarity index 74% rename from www/src/setup_db.php rename to www/src/setup/db.php index 70b3d88..30a7357 100644 --- a/www/src/setup_db.php +++ b/www/src/setup/db.php @@ -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"; diff --git a/www/src/setup/derg_insert.php b/www/src/setup/derg_insert.php new file mode 100644 index 0000000..8051a3e --- /dev/null +++ b/www/src/setup/derg_insert.php @@ -0,0 +1,43 @@ +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); + } +} + +?> \ No newline at end of file diff --git a/www/src/setup/permissions.php b/www/src/setup/permissions.php new file mode 100644 index 0000000..654f5a8 --- /dev/null +++ b/www/src/setup/permissions.php @@ -0,0 +1,26 @@ + 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']; +} + +?> \ No newline at end of file diff --git a/www/src/setup_site_config.php b/www/src/setup/site_config.php similarity index 100% rename from www/src/setup_site_config.php rename to www/src/setup/site_config.php diff --git a/www/src/setup_twig.php b/www/src/setup/twig.php similarity index 100% rename from www/src/setup_twig.php rename to www/src/setup/twig.php diff --git a/www/static/banner.js b/www/static/banner.js index 3da66de..5ef9ee3 100644 --- a/www/static/banner.js +++ b/www/static/banner.js @@ -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() { diff --git a/www/static/dergstyle.css b/www/static/dergstyle.css index 6ccc0f6..39810d1 100644 --- a/www/static/dergstyle.css +++ b/www/static/dergstyle.css @@ -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%; + } } diff --git a/www/static/modest.css b/www/static/modest.css index d45177d..c1231e2 100644 --- a/www/static/modest.css +++ b/www/static/modest.css @@ -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; + } } \ No newline at end of file diff --git a/www/static/styles/age_gate.css b/www/static/styles/age_gate.css new file mode 100644 index 0000000..bc4728f --- /dev/null +++ b/www/static/styles/age_gate.css @@ -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; + } +} \ No newline at end of file diff --git a/www/static/styles/post_navbar.css b/www/static/styles/post_navbar.css new file mode 100644 index 0000000..d663dac --- /dev/null +++ b/www/static/styles/post_navbar.css @@ -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; + } + } + } +} diff --git a/www/static/styles/search.css b/www/static/styles/search.css new file mode 100644 index 0000000..0141876 --- /dev/null +++ b/www/static/styles/search.css @@ -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; + } + } +} \ No newline at end of file diff --git a/www/static/styles/toc.css b/www/static/styles/toc.css new file mode 100644 index 0000000..bb2ea8e --- /dev/null +++ b/www/static/styles/toc.css @@ -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; + } +} \ No newline at end of file diff --git a/www/static/toc.js b/www/static/toc.js new file mode 100644 index 0000000..dafbee5 --- /dev/null +++ b/www/static/toc.js @@ -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 = " #" + pathItem.level + '' + pathItem.name + '' + + 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 = "" + element.name + ""; + + 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); \ No newline at end of file diff --git a/www/templates/ajax/compact_filelist/listing.html b/www/templates/ajax/compact_filelist/listing.html deleted file mode 100644 index ccdc7c9..0000000 --- a/www/templates/ajax/compact_filelist/listing.html +++ /dev/null @@ -1,4 +0,0 @@ - -{% for post in page.child_posts %} -{{ include('ajax/compact_filelist/entry.html') }} -{% endfor %} \ No newline at end of file diff --git a/www/templates/fragments/article_blop.html b/www/templates/fragments/blog/card.html similarity index 93% rename from www/templates/fragments/article_blop.html rename to www/templates/fragments/blog/card.html index 0000271..f084f0a 100644 --- a/www/templates/fragments/article_blop.html +++ b/www/templates/fragments/blog/card.html @@ -1,5 +1,4 @@ -
@@ -16,7 +15,7 @@ - {{post.excerpt}} + {{ post.brief }}
diff --git a/www/templates/fragments/blog/listing.html b/www/templates/fragments/blog/listing.html new file mode 100644 index 0000000..e69de29 diff --git a/www/templates/fragments/decoration/blog_header.html b/www/templates/fragments/decoration/blog_header.html new file mode 100644 index 0000000..7dd4d48 --- /dev/null +++ b/www/templates/fragments/decoration/blog_header.html @@ -0,0 +1,10 @@ + +
+

+ {{ post.title }} +

+ + {% if post.authors %} + Written by {{ post.authors }} + {% endif %} +
\ No newline at end of file diff --git a/www/templates/fragments/filepath_bar.html b/www/templates/fragments/decoration/navbar.html similarity index 77% rename from www/templates/fragments/filepath_bar.html rename to www/templates/fragments/decoration/navbar.html index f4a43a2..3c769e3 100644 --- a/www/templates/fragments/filepath_bar.html +++ b/www/templates/fragments/decoration/navbar.html @@ -1,10 +1,10 @@ -
- +
{% block main_footer %} test? {% endblock %}
- + diff --git a/www/templates/search.html b/www/templates/search.html new file mode 100644 index 0000000..aea8b48 --- /dev/null +++ b/www/templates/search.html @@ -0,0 +1,80 @@ + + +{% extends "pathed_content.html" %} + +{% block second_title %} +

Dergen Search

+{% endblock %} + +{%block content_article %} +
+

+ Dergle +

+ +
+ + + + +
+ {% for type in search_query.types %} + + + {% endfor %} +
+
+ + {% if display_type == 'no_search' %} +

Search for something :D

+ + + {% elseif display_type == 'image' %} + + {%elseif display_type == 'no results' %} +

How sad. There are no images yet... What a real shame :c

+ {%else%} + + {% endif %} +{%endblock%} diff --git a/www/templates/upload.html b/www/templates/upload.html index bee8710..58bb952 100644 --- a/www/templates/upload.html +++ b/www/templates/upload.html @@ -8,10 +8,10 @@ {%block main_content%}
-
- - - + + + +