Repository: comic/grand-challenge.org

Issue:
Email markdown trouble with rendering to HTML
Any header like markdown (i.e. `### TITLE`) gets automagically rendered to include an anchor link:

![097bab37-96ea-4d01-a193-851521e3a125](https://github.com/comic/grand-challenge.org/assets/7871310/efe38ae7-6a55-4773-b9af-a4a52e1d011d)

A quick workaround is manually defining the `<hx></hx>` tags in the Markdown so the plugin does not parse them.


Discussion:
Fixing this actually goes deeper than I initially thought. We currently have a single `md2html` call. Replacing this with something that turns off certain features of the markdown rendering to HTML is reasonably simple. However, the form widget then because problematic because it uses (via xhtml ) the `MARKDOWNX_MARKDOWNIFY_FUNCTION` which is a settings constant that currently points towards: `grandchallenge.core.templatetags.bleach.md2html`.

Not sure how to get a switch into that xhtml call/target. The best would be to create a fully new view that copies what the default view of the package does... way too much work for something that has a very limited use case. 

Fixing it now with the acceptance that the quick preview on the detail page doesn't render it completely correct. It doesn't render it correct in the first place because any HTML found within can still use the classes (e.g. 'shadow') of the main GC style links.

Resolving patch:
diff --git a/app/config/settings.py b/app/config/settings.py
--- a/app/config/settings.py
+++ b/app/config/settings.py
@@ -11,7 +11,6 @@
 from django.core.exceptions import ImproperlyConfigured
 from django.urls import reverse
 from machina import MACHINA_MAIN_STATIC_DIR, MACHINA_MAIN_TEMPLATE_DIR
-from markdown.extensions.toc import TocExtension
 from sentry_sdk.integrations.celery import CeleryIntegration
 from sentry_sdk.integrations.django import DjangoIntegration
 from sentry_sdk.integrations.logging import ignore_logger
@@ -785,9 +784,6 @@
     "markdown.extensions.sane_lists",
     "markdown.extensions.codehilite",
     "markdown.extensions.attr_list",
-    TocExtension(
-        permalink=True, permalink_class="headerlink text-muted small pl-1"
-    ),
     BS4Extension(),
 ]
 MARKDOWNX_MARKDOWNIFY_FUNCTION = (
diff --git a/app/grandchallenge/core/templatetags/bleach.py b/app/grandchallenge/core/templatetags/bleach.py
--- a/app/grandchallenge/core/templatetags/bleach.py
+++ b/app/grandchallenge/core/templatetags/bleach.py
@@ -4,6 +4,7 @@
 from django.conf import settings
 from django.utils.safestring import mark_safe
 from markdown import markdown as render_markdown
+from markdown.extensions.toc import TocExtension
 
 from grandchallenge.core.utils.markdown import LinkBlankTargetExtension
 
@@ -28,14 +29,37 @@ def clean(html: str):
 
 
 @register.filter
-def md2html(markdown: str | None, link_blank_target=False):
+def md2email_html(markdown: str | None):
+    """Converts markdown to clean html intended for emailing"""
+    return md2html(
+        markdown,
+        link_blank_target=True,
+        create_permalink_for_headers=False,
+    )
+
+
+@register.filter
+def md2html(
+    markdown: str | None,
+    *,
+    link_blank_target=False,
+    create_permalink_for_headers=True,
+):
     """Convert markdown to clean html"""
 
-    extensions = settings.MARKDOWNX_MARKDOWN_EXTENSIONS
+    extensions = [*settings.MARKDOWNX_MARKDOWN_EXTENSIONS]
 
     if link_blank_target:
         extensions.append(LinkBlankTargetExtension())
 
+    if create_permalink_for_headers:
+        extensions.append(
+            TocExtension(
+                permalink=True,
+                permalink_class="headerlink text-muted small pl-1",
+            )
+        )
+
     html = render_markdown(
         text=markdown or "",
         extensions=extensions,
diff --git a/app/grandchallenge/emails/tasks.py b/app/grandchallenge/emails/tasks.py
--- a/app/grandchallenge/emails/tasks.py
+++ b/app/grandchallenge/emails/tasks.py
@@ -9,7 +9,7 @@
 from django.utils.html import strip_tags
 from django.utils.timezone import now
 
-from grandchallenge.core.templatetags.bleach import md2html
+from grandchallenge.core.templatetags.bleach import md2email_html
 from grandchallenge.emails.models import Email
 from grandchallenge.emails.utils import SendActionChoices
 from grandchallenge.subdomains.utils import reverse
@@ -87,7 +87,7 @@ def send_bulk_email(action, email_pk):
         return
     subject = email.subject
     body = email.body
-    html_body = md2html(body)
+    html_body = md2email_html(body)
     receivers = get_receivers(action=action)
     paginator = Paginator(receivers, 100)
     site = Site.objects.get_current()


Test patch: