diff --git a/webapp/templates/partials/mathjaxhead.html.twig b/webapp/templates/partials/mathjaxhead.html.twig new file mode 100644 index 0000000000..128a3fbec0 --- /dev/null +++ b/webapp/templates/partials/mathjaxhead.html.twig @@ -0,0 +1,23 @@ + + diff --git a/webapp/templates/team/index.html.twig b/webapp/templates/team/index.html.twig index ae6244810d..cc5e8d3a66 100644 --- a/webapp/templates/team/index.html.twig +++ b/webapp/templates/team/index.html.twig @@ -46,6 +46,8 @@ } $('[data-flash-messages]').html($flash); + + MathJax.typeset([document.getElementsByClassName('clarification-text')]); } window.initModalClarificationPreviewAdd = function() { diff --git a/webapp/templates/team/partials/clarification_content.html.twig b/webapp/templates/team/partials/clarification_content.html.twig index 7e38c1e53c..a3d2e03e52 100644 --- a/webapp/templates/team/partials/clarification_content.html.twig +++ b/webapp/templates/team/partials/clarification_content.html.twig @@ -24,3 +24,7 @@ {{ form_end(form) }} + + diff --git a/webapp/templates/team/partials/clarification_list.html.twig b/webapp/templates/team/partials/clarification_list.html.twig index a8afe67896..e55b489a85 100644 --- a/webapp/templates/team/partials/clarification_list.html.twig +++ b/webapp/templates/team/partials/clarification_list.html.twig @@ -48,7 +48,7 @@ {%- endif -%} - + {{ clarification.summary | markdown_to_html @@ -57,6 +57,9 @@ | raw }} + {%- endfor %} From b86a52e69cbfca2a637ce6957f23b3d9dd014d06 Mon Sep 17 00:00:00 2001 From: Michael Vasseur <14887731+vmcj@users.noreply.github.com> Date: Sat, 27 Sep 2025 00:53:44 +0200 Subject: [PATCH 3/3] Use our own markdown_to_html convertor Which stashes LaTeX during conversion and pops it afterwards. Otherwise things like `\\` get replaced with `\` which failed with: https://github.com/DOMjudge/domjudge/pull/3099#issuecomment-3293328981 --- webapp/src/Twig/TwigExtension.php | 39 +++++++++++++++++++ webapp/templates/jury/clarification.html.twig | 2 +- webapp/templates/jury/config.html.twig | 2 +- webapp/templates/jury/config_check.html.twig | 2 +- .../jury/export/clarifications.html.twig | 4 +- .../team/partials/clarification.html.twig | 2 +- .../partials/clarification_list.html.twig | 2 +- 7 files changed, 46 insertions(+), 7 deletions(-) diff --git a/webapp/src/Twig/TwigExtension.php b/webapp/src/Twig/TwigExtension.php index f233757237..35c6931683 100644 --- a/webapp/src/Twig/TwigExtension.php +++ b/webapp/src/Twig/TwigExtension.php @@ -27,6 +27,7 @@ use App\Utils\Utils; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\EntityManagerInterface; +use Ramsey\Uuid\Uuid; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\Intl\Countries; use Symfony\Component\Intl\Exception\MissingResourceException; @@ -38,12 +39,18 @@ use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\Extension\GlobalsInterface; +use Twig\Extra\Markdown\MarkdownRuntime; use Twig\Runtime\EscaperRuntime; use Twig\TwigFilter; use Twig\TwigFunction; class TwigExtension extends AbstractExtension implements GlobalsInterface { + /** + * @param array $latexFound + */ + private array $latexFound; + /** * @param array $renderedSources */ @@ -126,6 +133,7 @@ public function getFilters(): array new TwigFilter('medalType', $this->awards->medalType(...)), new TwigFilter('numTableActions', $this->numTableActions(...)), new TwigFilter('extensionToMime', $this->extensionToMime(...)), + new TwigFilter('domjudgeMarkdownToHtml', $this->domjudgeMarkdownToHTML(...), ['is_safe' => ['html']]), ]; } @@ -1381,4 +1389,35 @@ public function extensionToMime(string $extension): string { return DOMJudgeService::EXTENSION_TO_MIMETYPE[$extension]; } + + /** + * Extract all LaTeX code from the given string, sanitize the markdown and + * inject the original LaTeX code back so MathJax can render it. + */ + public function domjudgeMarkdownToHTML(string $markdown): string + { + $latexPlaceholder = Uuid::uuid4()->toString(); + while (str_contains($markdown, $latexPlaceholder)) { + $latexPlaceholder = Uuid::uuid4()->toString(); + } + + $markdown = preg_replace_callback( + '/(\$[\s\S]*?\$)/', + function (array $matches) use ($latexPlaceholder) { + // Store and replace matches + $this->latexFound[] = $matches[1]; + return $latexPlaceholder; + }, + $markdown + ); + + /** @var MarkdownRuntime $runtime */ + $runtime = $this->twig->getRuntime(MarkdownRuntime::class); + $markdown = (string)$runtime->convert($markdown); + + return preg_replace_callback( + '/'.$latexPlaceholder.'/', + fn() => array_shift($this->latexFound), $markdown + ); + } } diff --git a/webapp/templates/jury/clarification.html.twig b/webapp/templates/jury/clarification.html.twig index e7e7c4d24b..70e79957ed 100644 --- a/webapp/templates/jury/clarification.html.twig +++ b/webapp/templates/jury/clarification.html.twig @@ -110,7 +110,7 @@
-
{{ clar.body | markdown_to_html | sanitize_html('app.clarification_sanitizer') }}
+
{{ clar.body | domjudgeMarkdownToHtml | sanitize_html('app.clarification_sanitizer') }}
diff --git a/webapp/templates/jury/config.html.twig b/webapp/templates/jury/config.html.twig index f30cc9b4d6..94284b8898 100644 --- a/webapp/templates/jury/config.html.twig +++ b/webapp/templates/jury/config.html.twig @@ -218,7 +218,7 @@ {{ errors[option.name] }} {% endif %} -
{{ option.description | markdown_to_html }}
+
{{ option.description | domjudgeMarkdownToHtml }}
{% endfor %} diff --git a/webapp/templates/jury/config_check.html.twig b/webapp/templates/jury/config_check.html.twig index eed1487090..fff0e67809 100644 --- a/webapp/templates/jury/config_check.html.twig +++ b/webapp/templates/jury/config_check.html.twig @@ -99,7 +99,7 @@ {% if testresult.escape is not defined or testresult.escape %} {% set description = description | escape %} {% endif %} - {{ description | markdown_to_html }} + {{ description | domjudgeMarkdownToHtml }} diff --git a/webapp/templates/jury/export/clarifications.html.twig b/webapp/templates/jury/export/clarifications.html.twig index 871033868e..25f7bed29d 100644 --- a/webapp/templates/jury/export/clarifications.html.twig +++ b/webapp/templates/jury/export/clarifications.html.twig @@ -98,7 +98,7 @@ Content -
{{ clarification.body | markdown_to_html | sanitize_html('app.clarification_sanitizer') }}
+
{{ clarification.body | domjudgeMarkdownToHtml | sanitize_html('app.clarification_sanitizer') }}
{% if clarification.replies is not empty %} @@ -114,7 +114,7 @@ -
{{ reply.body | markdown_to_html | sanitize_html('app.clarification_sanitizer') }}
+
{{ reply.body | domjudgeMarkdownToHtml | sanitize_html('app.clarification_sanitizer') }}
{% endfor %} diff --git a/webapp/templates/team/partials/clarification.html.twig b/webapp/templates/team/partials/clarification.html.twig index 5004e4c0d4..9045ceadde 100644 --- a/webapp/templates/team/partials/clarification.html.twig +++ b/webapp/templates/team/partials/clarification.html.twig @@ -40,7 +40,7 @@
- {{ clarification.body | markdown_to_html | sanitize_html('app.clarification_sanitizer') }} + {{ clarification.body | domjudgeMarkdownToHtml | sanitize_html('app.clarification_sanitizer') }}
diff --git a/webapp/templates/team/partials/clarification_list.html.twig b/webapp/templates/team/partials/clarification_list.html.twig index e55b489a85..93de2996c2 100644 --- a/webapp/templates/team/partials/clarification_list.html.twig +++ b/webapp/templates/team/partials/clarification_list.html.twig @@ -51,7 +51,7 @@ {{ clarification.summary - | markdown_to_html + | domjudgeMarkdownToHtml | replace({'

': '', '

': ''}) | sanitize_html('app.clarification_sanitizer') | raw