diff --git a/composer.json b/composer.json index 733d3ed..eaf3507 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "league/pipeline": "^1.0", "phpdocumentor/reflection-docblock": "^5.6", "phpstan/phpdoc-parser": "^2.1", + "symfony/asset": "7.2.*", "symfony/console": "*", "symfony/dotenv": "*", "symfony/flex": "^2", @@ -29,6 +30,7 @@ "symfony/uid": "*", "symfony/validator": "*", "symfony/yaml": "*", + "tempest/highlight": "^2.11", "twig/extra-bundle": "^2.12|^3.0", "twig/twig": "^3.0" }, diff --git a/composer.lock b/composer.lock index c94fa3e..2d83399 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "638cbc9841226f386ba27215e24c5410", + "content-hash": "6bdfa9b2a81e0169ca083ef4d344b876", "packages": [ { "name": "dflydev/dot-access-data", @@ -2370,6 +2370,75 @@ }, "time": "2024-09-11T13:17:53+00:00" }, + { + "name": "symfony/asset", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/cb926cd59fefa1f9b4900b3695f0f846797ba5c0", + "reference": "cb926cd59fefa1f9b4900b3695f0f846797ba5c0", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.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.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-25T15:15:23+00:00" + }, { "name": "symfony/cache", "version": "v7.2.6", @@ -6436,6 +6505,64 @@ ], "time": "2025-04-04T10:10:11+00:00" }, + { + "name": "tempest/highlight", + "version": "2.11.4", + "source": { + "type": "git", + "url": "https://github.com/tempestphp/highlight.git", + "reference": "5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tempestphp/highlight/zipball/5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd", + "reference": "5a239a92ad6bd3e506ca86a0de3e99ac9dbcb0dd", + "shasum": "" + }, + "require": { + "php": "^8.3" + }, + "require-dev": { + "assertchris/ellison": "^1.0.2", + "friendsofphp/php-cs-fixer": "^3.21", + "league/commonmark": "^2.4", + "phpstan/phpstan": "^1.10.0", + "phpunit/phpunit": "^10.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "suggest": { + "assertchris/ellison": "Allows you to analyse sentence complexity", + "league/commonmark": "Adds markdown support" + }, + "type": "library", + "autoload": { + "psr-4": { + "Tempest\\Highlight\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brendt@stitcher.io" + } + ], + "description": "Fast, extensible, server-side code highlighting", + "support": { + "issues": "https://github.com/tempestphp/highlight/issues", + "source": "https://github.com/tempestphp/highlight/tree/2.11.4" + }, + "funding": [ + { + "url": "https://github.com/brendt", + "type": "github" + } + ], + "time": "2025-03-19T05:38:35+00:00" + }, { "name": "twig/extra-bundle", "version": "v3.21.0", diff --git a/public/github-light-default.css b/public/github-light-default.css new file mode 100644 index 0000000..409c1e7 --- /dev/null +++ b/public/github-light-default.css @@ -0,0 +1,87 @@ +pre, code { + color: #1f2328; + background-color: #ffffff; +} + +.hl-keyword { + color: #cf222e; +} + +.hl-property { + color: #8250df; +} + +.hl-attribute { + font-style: italic; +} + +.hl-type { + color: #EA4334; +} + +.hl-generic { + color: #9d3af6; +} + +.hl-value { + color: #0a3069; +} + +.hl-literal { + color: #0a3069; +} + +.hl-number { + color: #0a3069; +} + +.hl-variable { + color: #953800; +} + +.hl-comment { + color: #6e7781; +} + +.hl-blur { + filter: blur(2px); +} + +.hl-strong { + font-weight: bold; +} + +.hl-em { + font-style: italic; +} + +.hl-addition { + display: inline-block; + min-width: 100%; + background-color: #00FF0022; +} + +.hl-deletion { + display: inline-block; + min-width: 100%; + background-color: #FF000011; +} + +.hl-gutter { + display: inline-block; + font-size: 0.9em; + color: #555; + padding: 0 1ch; + margin-right: 1ch; + user-select: none; +} + +.hl-gutter-addition { + background-color: #34A853; + color: #fff; +} + +.hl-gutter-deletion { + background-color: #EA4334; + color: #fff; +} diff --git a/src/Service/SnipParser/AbstractParser.php b/src/Service/SnipParser/AbstractParser.php index a5c8ae0..4015290 100644 --- a/src/Service/SnipParser/AbstractParser.php +++ b/src/Service/SnipParser/AbstractParser.php @@ -20,7 +20,7 @@ abstract class AbstractParser implements ParserInterface try { return $this->safeParseView($content); } catch (\Exception $exception) { - return sprintf('
%s
', htmlspecialchars($exception->getMessage())); + return sprintf('
%s
', htmlspecialchars($exception->getMessage())); } } diff --git a/src/Service/SnipParser/Generic/GenericParser.php b/src/Service/SnipParser/Generic/GenericParser.php index f59657c..ba5a5bd 100644 --- a/src/Service/SnipParser/Generic/GenericParser.php +++ b/src/Service/SnipParser/Generic/GenericParser.php @@ -17,9 +17,9 @@ class GenericParser extends AbstractParser $builder = new PipelineBuilder(); $pipeline = $builder ->add(new HtmlEscapeStage()) + ->add(new ReplaceBlocksStage('
', '
', '```')) + ->add(new ReplaceBlocksStage('', '', '``')) ->add(new ReplaceStage(PHP_EOL, '
')) - ->add(new ReplaceBlocksStage('
', '
', '```')) - ->add(new ReplaceBlocksStage('', '', '``')) ->add($this->referenceStage) ->add($this->includeStage) ->build() diff --git a/src/Service/SnipParser/Generic/ReplaceBlocksStage.php b/src/Service/SnipParser/Generic/ReplaceBlocksStage.php index cc5907c..2e8fbeb 100644 --- a/src/Service/SnipParser/Generic/ReplaceBlocksStage.php +++ b/src/Service/SnipParser/Generic/ReplaceBlocksStage.php @@ -4,13 +4,14 @@ namespace App\Service\SnipParser\Generic; use InvalidArgumentException; use League\Pipeline\StageInterface; +use Tempest\Highlight\Highlighter; -class ReplaceBlocksStage implements StageInterface +readonly class ReplaceBlocksStage implements StageInterface { public function __construct( - public readonly string $openTag = '
',
-        public readonly string $closeTag = '
', - public readonly string $delimiter = '```' + public string $openTag = '
',
+        public string $closeTag = '
', + public string $delimiter = '```' ) {} public function __invoke(mixed $payload): string @@ -26,8 +27,9 @@ class ReplaceBlocksStage implements StageInterface { $pattern = sprintf('/%s(.+?)%s/s', preg_quote($this->delimiter), preg_quote($this->delimiter)); - return preg_replace_callback($pattern, function ($matches) { - return $this->openTag . trim($matches[1]) . $this->closeTag; + $highlighter = new Highlighter()->withGutter(); + return preg_replace_callback($pattern, function ($matches) use ($highlighter) { + return $this->openTag . $highlighter->parse(trim($matches[1]), 'php') . $this->closeTag; }, $text); } } \ No newline at end of file diff --git a/src/Service/SnipParser/Html/HtmlParser.php b/src/Service/SnipParser/Html/HtmlParser.php index 491d03f..dab7992 100644 --- a/src/Service/SnipParser/Html/HtmlParser.php +++ b/src/Service/SnipParser/Html/HtmlParser.php @@ -3,11 +3,14 @@ namespace App\Service\SnipParser\Html; use App\Service\SnipParser\AbstractParser; +use Tempest\Highlight\Highlighter; class HtmlParser extends AbstractParser { public function safeParseView(string $content): string { - return sprintf('
%s
', htmlspecialchars($content)); + $highlighter = new Highlighter()->withGutter(); + + return '
' . $highlighter->parse($content, 'html') . '
'; } } \ No newline at end of file diff --git a/src/Service/SnipParser/Markdown/MarkdownParser.php b/src/Service/SnipParser/Markdown/MarkdownParser.php index eb603ce..50361c6 100644 --- a/src/Service/SnipParser/Markdown/MarkdownParser.php +++ b/src/Service/SnipParser/Markdown/MarkdownParser.php @@ -11,6 +11,8 @@ use League\CommonMark\Node\Inline\Text; use League\CommonMark\Node\Query; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Routing\RouterInterface; +use Tempest\Highlight\CommonMark\HighlightExtension; +use Tempest\Highlight\Highlighter; class MarkdownParser extends AbstractParser { @@ -22,7 +24,13 @@ class MarkdownParser extends AbstractParser public function safeParseView(string $content): string { $converter = new GithubFlavoredMarkdownConverter(); - $converter->getEnvironment()->addEventListener(DocumentParsedEvent::class, $this->documentParsed(...)); + $converter + ->getEnvironment() + ->addExtension(new HighlightExtension( + new Highlighter()->withGutter() + )) + ->addEventListener(DocumentParsedEvent::class, $this->documentParsed(...)) + ; return $converter->convert($content); } @@ -32,7 +40,8 @@ class MarkdownParser extends AbstractParser $linkNodes = new Query() ->where(Query::type(Link::class)) - ->findAll($document); + ->findAll($document) + ; foreach ($linkNodes as $linkNode) { $url = $linkNode->getUrl(); diff --git a/templates/snip/base.html.twig b/templates/snip/base.html.twig index 4b21a19..57c9cff 100644 --- a/templates/snip/base.html.twig +++ b/templates/snip/base.html.twig @@ -53,22 +53,4 @@

-{% endblock %} - -{% block css %} - {{ parent() }} - -{% endblock %} - -{% block js %} - {{ parent() }} - - {% endblock %} \ No newline at end of file diff --git a/templates/snip/single.html.twig b/templates/snip/single.html.twig index c9814cc..58f346b 100644 --- a/templates/snip/single.html.twig +++ b/templates/snip/single.html.twig @@ -15,18 +15,5 @@ {% block css %} {{ parent() }} - + {% endblock %} - -{% block js %} - {{ parent() }} - - -{% endblock %} \ No newline at end of file