From a68f5182e47a83a67f4788e6a912efb0191e8a7a Mon Sep 17 00:00:00 2001 From: team2 Date: Thu, 26 Feb 2026 20:40:42 +0100 Subject: [PATCH] add new guide service --- PHASE_A_AUDIT.md | 9 +- composer.json | 2 + composer.lock | 581 ++++++++++++++++++++++- config/reference.php | 2 +- public/assets/styles/admin-markdown.css | 162 +++++++ public/assets/styles/base.css | 40 ++ src/Controller/Admin/GuideController.php | 109 +++++ src/Service/MarkdownRenderer.php | 38 ++ templates/admin/base.html.twig | 17 +- templates/admin/guides/index.html.twig | 54 +++ templates/admin/guides/view.html.twig | 15 + 11 files changed, 1017 insertions(+), 12 deletions(-) create mode 100644 public/assets/styles/admin-markdown.css create mode 100644 src/Controller/Admin/GuideController.php create mode 100644 src/Service/MarkdownRenderer.php create mode 100644 templates/admin/guides/index.html.twig create mode 100644 templates/admin/guides/view.html.twig diff --git a/PHASE_A_AUDIT.md b/PHASE_A_AUDIT.md index b15db62..93f6277 100644 --- a/PHASE_A_AUDIT.md +++ b/PHASE_A_AUDIT.md @@ -153,9 +153,6 @@ Sehr sauber: - reload steuerbar - PID-Handling robust -Empfehlung: -Health-Check Endpoint ergänzen. - --- # 7. Tag-System @@ -280,9 +277,8 @@ Sehr gut umgesetzt. # 14. Identifizierte Schwachstellen 1. Vollständiger FAISS-Rebuild bei jedem Ingest -2. Kein Vector-Service Health-Endpoint -3. Keine automatische Index-Korruptionsprüfung -4. Kein Backpressure bei mehreren Ingest-Jobs +2. Keine automatische Index-Korruptionsprüfung +3. Kein Backpressure bei mehreren Ingest-Jobs Keine strukturelle Schwäche. @@ -297,7 +293,6 @@ Keine strukturelle Schwäche. - ChunkWriteService - VectorRebuildService -- Health-Endpoint im Vector-Service - Timeout-Absicherung beim Reload ## Phase C diff --git a/composer.json b/composer.json index 9a78076..e7bc8e4 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,9 @@ "doctrine/doctrine-bundle": "^2.18", "doctrine/doctrine-migrations-bundle": "^3.7", "doctrine/orm": "^3.6", + "league/commonmark": "^2.8", "smalot/pdfparser": "^2.12", + "symfony/asset": "7.4.*", "symfony/console": "^7.4", "symfony/dotenv": "^7.4", "symfony/flex": "^2", diff --git a/composer.lock b/composer.lock index 211175f..cea8f7a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,83 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ffc8b627f07f3eb413d757a4d80af3f2", + "content-hash": "bf516574b65f7c2abdc053c964f769aa", "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, { "name": "doctrine/collections", "version": "2.6.0", @@ -1119,6 +1194,195 @@ }, "time": "2026-02-08T16:21:46+00:00" }, + { + "name": "league/commonmark", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.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", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2025-11-26T21:48:24+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, { "name": "monolog/monolog", "version": "3.10.0", @@ -1222,6 +1486,164 @@ ], "time": "2026-01-02T08:56:05+00:00" }, + { + "name": "nette/schema", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.5" + }, + "time": "2026-02-23T03:47:12+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.3", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.3" + }, + "time": "2026-02-13T03:05:33+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -1523,6 +1945,79 @@ }, "time": "2026-01-08T08:04:04+00:00" }, + { + "name": "symfony/asset", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "d944ae87e4697af05aadeacfc5e603c3c18ef4fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/d944ae87e4697af05aadeacfc5e603c3c18ef4fb", + "reference": "d944ae87e4697af05aadeacfc5e603c3c18ef4fb", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-09T09:33:46+00:00" + }, { "name": "symfony/cache", "version": "v7.4.3", @@ -3959,6 +4454,90 @@ ], "time": "2024-12-23T08:48:59+00:00" }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, { "name": "symfony/polyfill-php83", "version": "v1.33.0", diff --git a/config/reference.php b/config/reference.php index 378cbdd..d934b54 100644 --- a/config/reference.php +++ b/config/reference.php @@ -264,7 +264,7 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param; * formats?: array>, * }, * assets?: bool|array{ // Assets configuration - * enabled?: bool|Param, // Default: false + * enabled?: bool|Param, // Default: true * strict_mode?: bool|Param, // Throw an exception if an entry is missing from the manifest.json. // Default: false * version_strategy?: scalar|null|Param, // Default: null * version?: scalar|null|Param, // Default: null diff --git a/public/assets/styles/admin-markdown.css b/public/assets/styles/admin-markdown.css new file mode 100644 index 0000000..d05af15 --- /dev/null +++ b/public/assets/styles/admin-markdown.css @@ -0,0 +1,162 @@ +/* ========================================================= + Markdown Guide Styling (Admin Dark) + ========================================================= */ + +.markdown-content { + font-size: 0.95rem; + line-height: 1.6; + color: #e2e2e2; +} + +/* --------------------------------------------------------- + Headings + --------------------------------------------------------- */ + +.markdown-content h1, +.markdown-content h2, +.markdown-content h3, +.markdown-content h4 { + font-weight: 600; + margin-top: 2rem; + margin-bottom: 1rem; +} + +.markdown-content h1 { + font-size: 1.8rem; + border-bottom: 1px solid #333; + padding-bottom: 0.5rem; +} + +.markdown-content h2 { + font-size: 1.4rem; + border-bottom: 1px solid #222; + padding-bottom: 0.4rem; +} + +.markdown-content h3 { + font-size: 1.15rem; +} + +/* --------------------------------------------------------- + Paragraphs + --------------------------------------------------------- */ + +.markdown-content p { + margin-bottom: 1rem; +} + +/* --------------------------------------------------------- + Lists + --------------------------------------------------------- */ + +.markdown-content ul, +.markdown-content ol { + padding-left: 1.5rem; + margin-bottom: 1rem; +} + +.markdown-content li { + margin-bottom: 0.4rem; +} + +/* --------------------------------------------------------- + Links + --------------------------------------------------------- */ + +.markdown-content a { + color: #0dcaf0; + text-decoration: none; +} + +.markdown-content a:hover { + text-decoration: underline; +} + +/* --------------------------------------------------------- + Inline Code + --------------------------------------------------------- */ + +.markdown-content code { + background: #1e1e1e; + color: #ffc107; + padding: 3px 6px; + border-radius: 4px; + font-size: 0.85rem; +} + +/* --------------------------------------------------------- + Code Blocks + --------------------------------------------------------- */ + +.markdown-content pre { + background: #111; + padding: 16px; + border-radius: 6px; + overflow-x: auto; + border: 1px solid #222; + margin-bottom: 1.5rem; +} + +.markdown-content pre code { + background: transparent; + padding: 0; + color: #e2e2e2; +} + +/* --------------------------------------------------------- + Tables + --------------------------------------------------------- */ + +.markdown-content table { + width: 100%; + border-collapse: collapse; + margin-bottom: 1.5rem; + font-size: 0.9rem; +} + +.markdown-content th { + background: #1a1a1a; + border: 1px solid #333; + padding: 8px; + text-align: left; + font-weight: 600; +} + +.markdown-content td { + border: 1px solid #333; + padding: 8px; +} + +.markdown-content tr:nth-child(even) { + background: #141414; +} + +/* --------------------------------------------------------- + Blockquotes + --------------------------------------------------------- */ + +.markdown-content blockquote { + border-left: 4px solid #0dcaf0; + padding-left: 1rem; + margin: 1rem 0; + color: #cfcfcf; + background: #151515; +} + +/* --------------------------------------------------------- + Horizontal Rule + --------------------------------------------------------- */ + +.markdown-content hr { + border: none; + border-top: 1px solid #333; + margin: 2rem 0; +} + +.guide-item:hover { + background-color: #151515 !important; +} + +.guide-item .fw-semibold { + letter-spacing: 0.2px; +} \ No newline at end of file diff --git a/public/assets/styles/base.css b/public/assets/styles/base.css index 1b1099d..ce3a710 100644 --- a/public/assets/styles/base.css +++ b/public/assets/styles/base.css @@ -233,4 +233,44 @@ button:disabled { width: 100px; height: 100px; position: absolute; +} + +/* ========================================================= + Admin Dark Base Fix + ========================================================= */ + +body { + color: #e2e2e2; +} + +.card.bg-black, +.card.bg-dark { + color: #e2e2e2; +} + +.list-group-item.bg-black, +.list-group-item.bg-dark { + color: #e2e2e2; +} + +.list-group-item.bg-black a, +.list-group-item.bg-dark a { + color: #e2e2e2; +} + +.text-muted { + color: #9aa0a6 !important; +} + +.guide-item { + color: #e2e2e2 !important; +} + +.guide-item small { + color: #9aa0a6 !important; +} + +.card.bg-black .card-body +{ + color: #e2e2e2 !important; } \ No newline at end of file diff --git a/src/Controller/Admin/GuideController.php b/src/Controller/Admin/GuideController.php new file mode 100644 index 0000000..f7fb1ce --- /dev/null +++ b/src/Controller/Admin/GuideController.php @@ -0,0 +1,109 @@ +guidePath = rtrim( + $params->get('kernel.project_dir'), + '/' + ); + } + + #[Route('', name: 'admin_guides_index')] + public function index(): Response + { + if (!is_dir($this->guidePath)) { + return $this->render('admin/guides/index.html.twig', [ + 'guides' => [], + ]); + } + + $files = glob($this->guidePath . '/*.md') ?: []; + + $guides = []; + + foreach ($files as $file) { + $slug = basename($file, '.md'); + + if (!$this->isValidSlug($slug)) { + continue; + } + + $content = file_get_contents($file) ?: ''; + + $title = $this->extractTitleFromMarkdown($content) + ?? $this->humanizeSlug($slug); + + $guides[] = [ + 'slug' => $slug, + 'title' => $title, + ]; + } + + usort($guides, fn ($a, $b) => strcasecmp($a['title'], $b['title'])); + + return $this->render('admin/guides/index.html.twig', [ + 'guides' => $guides, + ]); + } + + #[Route('/{slug}', name: 'admin_guides_view')] + public function view(string $slug, MarkdownRenderer $renderer): Response + { + if (!$this->isValidSlug($slug)) { + throw $this->createNotFoundException(); + } + + $file = $this->guidePath . '/' . $slug . '.md'; + + if (!is_file($file)) { + throw $this->createNotFoundException(); + } + + $content = file_get_contents($file) ?: ''; + + return $this->render('admin/guides/view.html.twig', [ + 'slug' => $slug, + 'title' => $this->extractTitleFromMarkdown($content) + ?? $this->humanizeSlug($slug), + 'html' => $renderer->render($content), + ]); + } + + // ========================================================= + // Helper + // ========================================================= + + private function extractTitleFromMarkdown(string $content): ?string + { + if (preg_match('/^#\s+(.+)$/m', $content, $matches)) { + return trim($matches[1]); + } + + return null; + } + + private function humanizeSlug(string $slug): string + { + return ucfirst(str_replace('-', ' ', $slug)); + } + + private function isValidSlug(string $slug): bool + { + return (bool) preg_match('/^[a-zA-Z0-9\-_]+$/', $slug); + } +} \ No newline at end of file diff --git a/src/Service/MarkdownRenderer.php b/src/Service/MarkdownRenderer.php new file mode 100644 index 0000000..5d29ae0 --- /dev/null +++ b/src/Service/MarkdownRenderer.php @@ -0,0 +1,38 @@ + 'strip', + 'allow_unsafe_links' => false, + ]; + + $environment = new Environment($config); + + // Core Markdown + $environment->addExtension(new CommonMarkCoreExtension()); + + // GitHub Flavored Markdown (Tables, Strikethrough, Autolinks, Task Lists) + $environment->addExtension(new GithubFlavoredMarkdownExtension()); + + $this->converter = new MarkdownConverter($environment); + } + + public function render(string $markdown): string + { + return (string) $this->converter->convert($markdown); + } +} \ No newline at end of file diff --git a/templates/admin/base.html.twig b/templates/admin/base.html.twig index 072d9f5..63db342 100644 --- a/templates/admin/base.html.twig +++ b/templates/admin/base.html.twig @@ -4,9 +4,11 @@ {% block title %}Admin{% endblock %} - - - + {% block stylesheets %} + + + + {% endblock %} @@ -111,6 +113,15 @@ href="{{ path('admin_model_config_list') }}"> Modell-Generierung +
+
+ System-Guiide +
+ + + How-To & Leitfäden + diff --git a/templates/admin/guides/index.html.twig b/templates/admin/guides/index.html.twig new file mode 100644 index 0000000..8ecd354 --- /dev/null +++ b/templates/admin/guides/index.html.twig @@ -0,0 +1,54 @@ +{% extends 'admin/base.html.twig' %} + +{% block title %}Guides{% endblock %} + +{% block body %} + +
+
+

How-To & Leitfäden

+ + {{ guides|length }} Guide{{ guides|length == 1 ? '' : 's' }} verfügbar + +
+
+ +
+
+ + {% if guides is empty %} +
+ Keine Guides vorhanden. +
+ {% else %} + + + + {% endif %} + +
+
+ +{% endblock %} \ No newline at end of file diff --git a/templates/admin/guides/view.html.twig b/templates/admin/guides/view.html.twig new file mode 100644 index 0000000..6e8079d --- /dev/null +++ b/templates/admin/guides/view.html.twig @@ -0,0 +1,15 @@ +{% extends 'admin/base.html.twig' %} + +{% block title %}Guide{% endblock %} + +{% block body %} + + ← Zurück + + +
+
+ {{ html|raw }} +
+
+{% endblock %} \ No newline at end of file