diff --git a/remarkbox/__init__.py b/remarkbox/__init__.py
index 863097f..a005a2a 100644
--- a/remarkbox/__init__.py
+++ b/remarkbox/__init__.py
@@ -524,8 +524,8 @@ def main(global_config, **settings):
def add_node_order(request):
return request.params.get("order", request.namespace.node_order)
- def add_mathjax(request):
- return "true" if request.namespace.mathjax else "false"
+ def add_katex(request):
+ return "true" if request.namespace.katex else "false"
def add_theme_mode(request):
"""
@@ -604,7 +604,7 @@ def main(global_config, **settings):
config.add_request_method(add_page_size, "page_size", reify=True)
config.add_request_method(add_page_offset, "page_offset", reify=True)
config.add_request_method(add_node_order, "node_order", reify=True)
- config.add_request_method(add_mathjax, "mathjax", reify=True)
+ config.add_request_method(add_katex, "katex", reify=True)
config.add_request_method(add_theme_mode, "theme_mode", reify=True)
# all of the web application routes.
diff --git a/remarkbox/lib/notify.py b/remarkbox/lib/notify.py
index 751f91b..bf53d43 100644
--- a/remarkbox/lib/notify.py
+++ b/remarkbox/lib/notify.py
@@ -229,8 +229,8 @@ def send_immediate_notifications(request, notifications):
},
)
log.info(
- "notification frequency=immediately email={}, count={}".format(
- notification.user.email,
+ "notification frequency=immediately username={}, count={}".format(
+ notification.user.name,
notification.id,
)
)
@@ -285,9 +285,9 @@ def send_digest_notifications(request, notification_dict, frequency="daily"):
},
)
log.info(
- "notification frequency={} email={}, count={}".format(
+ "notification frequency={} username={}, count={}".format(
frequency,
- recipient_email,
+ user.name,
notifications_count,
)
)
diff --git a/remarkbox/lib/render.py b/remarkbox/lib/render.py
index 743d64a..2caf5b4 100644
--- a/remarkbox/lib/render.py
+++ b/remarkbox/lib/render.py
@@ -12,28 +12,14 @@ log = logging.getLogger(__name__)
def remarkbox_tag_acl(namespace):
"""
- Returns a tag_acl document, in this form:
+ Returns a tag_acl document for controlling allowed HTML tags.
- tag_acl = {
- "script": [
- ("type", "math/tex; mode=display", "allow"),
- ],
- }
+ With KaTeX, math is rendered client-side from text delimiters,
+ so we don't need special script tag handling like we did with MathJax.
"""
# defaultdict which assumes new keys are lists.
tag_acl = default_tag_acl()
- allow_mathjax_acl = ("type", "math/tex; mode=display", "allow")
- deny_mathjax_acl = ("type", "math/tex; mode=display", "deny")
-
- log.info("Building remarkbox_tag_acl.")
- mathjax_acl = deny_mathjax_acl
- if namespace.mathjax:
- log.info("This Namespace has Mathjax enabled.")
- mathjax_acl = allow_mathjax_acl
-
- tag_acl["script"].append(mathjax_acl)
-
return tag_acl
@@ -45,14 +31,15 @@ def make_cleaner_from_namespace(namespace):
)
# add some properties onto the cleaner object.
cleaner.link_protection = namespace.link_protection
- cleaner.mathjax = namespace.mathjax
+ cleaner.katex = namespace.katex
cleaner.whitelist_domains.append(namespace.name)
cleaner.absolute_domain = namespace.name
return cleaner
def markdown_to_html(data, namespace=None):
- raw_html = markdown_to_raw_html(data, extra_extensions=["mdx_math"])
+ # No special math extension needed - KaTeX auto-render handles $ delimiters client-side
+ raw_html = markdown_to_raw_html(data)
if namespace:
cleaner = make_cleaner_from_namespace(namespace)
else:
diff --git a/remarkbox/lib/sanitize_html.py b/remarkbox/lib/sanitize_html.py
index cc5dea3..56c2657 100644
--- a/remarkbox/lib/sanitize_html.py
+++ b/remarkbox/lib/sanitize_html.py
@@ -30,22 +30,7 @@ def default_cleaner(tag_acl=None):
We use BeautifulSoup to conditionally whitelist or blacklist
tags based on tag attr_name and attr_value pairs.
- For example, this will whitelist Mathjax script tags:
-
- tag_acl = {
- "script": [
- ("type", "math/tex; mode=display", "allow"),
- ],
- }
-
- While this example will blacklist Mathjax script tags:
-
- tag_acl = {
- "script": [
- ("type", "math/tex; mode=display", "deny"),
- ],
- }
-
+ tag_acl allows controlling which tags are allowed based on attributes.
"""
if tag_acl is None:
tag_acl = {}
diff --git a/remarkbox/models/namespace.py b/remarkbox/models/namespace.py
index a19107f..9f654b3 100644
--- a/remarkbox/models/namespace.py
+++ b/remarkbox/models/namespace.py
@@ -53,7 +53,7 @@ PROTECTED_ATTRIBUTES = {
"hide_unverified": False,
"hide_unless_approved": False,
"hide_powered_by": False,
- "mathjax": False,
+ "katex": False,
"link_protection": False,
"ignore_query_string": False,
"reverse_order": False,
@@ -96,8 +96,8 @@ class Namespace(RBase, Base):
hide_powered_by = Column(Boolean, default=False)
# should the list of root nodes in this namespace be public or hidden?
public = Column(Boolean, default=False)
- # should we enable MathJax?
- mathjax = Column(Boolean, default=False)
+ # should we enable KaTeX for math rendering?
+ katex = Column(Boolean, default=False)
# should we enable Link Protection to prevent comments from having links?
link_protection = Column(Boolean, default=False)
# check this box if the external site does _not_ use
diff --git a/remarkbox/scripts/alembic/versions/7ad8508e50de_rename_mathjax_to_katex.py b/remarkbox/scripts/alembic/versions/7ad8508e50de_rename_mathjax_to_katex.py
new file mode 100644
index 0000000..0c65c95
--- /dev/null
+++ b/remarkbox/scripts/alembic/versions/7ad8508e50de_rename_mathjax_to_katex.py
@@ -0,0 +1,30 @@
+"""Add katex column in rb_namespace
+
+Revision ID: 7ad8508e50de
+Revises: b8f3c9d4e5a1
+Create Date: 2025-01-10 00:00:00.000000
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = "7ad8508e50de"
+down_revision = "b8f3c9d4e5a1"
+branch_labels = None
+depends_on = None
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ # Add new katex column with default OFF
+ op.add_column("rb_namespace", sa.Column("katex", sa.Boolean(), server_default=sa.false()))
+
+ # Turn katex ON for any namespace that had mathjax ON
+ op.execute("UPDATE rb_namespace SET katex = 1 WHERE mathjax = 1")
+
+
+def downgrade():
+ # Copy katex values back to mathjax
+ # Note: SQLite doesn't support DROP COLUMN, so we just sync the data back
+ op.execute("UPDATE rb_namespace SET mathjax = katex")
diff --git a/remarkbox/static/js/custom.js b/remarkbox/static/js/custom.js
index ab28d8a..51b6e2b 100644
--- a/remarkbox/static/js/custom.js
+++ b/remarkbox/static/js/custom.js
@@ -2,7 +2,7 @@
// previewTimer must live outside the functions.
var previewTimer = null;
-function previewAjax(textarea, div, show_raw = false, mathjax = false){
+function previewAjax(textarea, div, show_raw = false, katex = false){
// set div to raw textarea while waiting for remote Markdown rendering.
if (show_raw) {
// bust HTML tags like
{%- endif %}
-{% if request.namespace and request.namespace.mathjax %}
-
-
+
+
{%- endif %}
{%- include 'google-analytics.j2' %}
diff --git a/remarkbox/tests/test_models.py b/remarkbox/tests/test_models.py
index b5ffda9..2ee6acd 100644
--- a/remarkbox/tests/test_models.py
+++ b/remarkbox/tests/test_models.py
@@ -138,20 +138,20 @@ class TestNamespace(unittest.TestCase):
self.namespace.subscription_type = "production"
self.namespace.link_protection = True
self.namespace.hide_powered_by = True
- self.namespace.mathjax = True
+ self.namespace.katex = True
self.assertTrue(self.namespace.link_protection)
self.assertTrue(self.namespace.hide_powered_by)
- self.assertTrue(self.namespace.mathjax)
+ self.assertTrue(self.namespace.katex)
self.assertFalse(self.namespace.memoized_attr_protection)
def test_namespace_development_custom_settings(self):
self.namespace.subscription_type = "development"
self.namespace.link_protection = True
self.namespace.hide_powered_by = True
- self.namespace.mathjax = True
+ self.namespace.katex = True
self.assertFalse(self.namespace.link_protection)
self.assertFalse(self.namespace.hide_powered_by)
- self.assertFalse(self.namespace.mathjax)
+ self.assertFalse(self.namespace.katex)
self.assertTrue(self.namespace.memoized_attr_protection)
def test_namespace_simulate_expired_subscription(self):
@@ -159,10 +159,10 @@ class TestNamespace(unittest.TestCase):
self.namespace.subscription_type = "production"
self.namespace.link_protection = True
self.namespace.hide_powered_by = True
- self.namespace.mathjax = True
+ self.namespace.katex = True
self.assertTrue(self.namespace.link_protection)
self.assertTrue(self.namespace.hide_powered_by)
- self.assertTrue(self.namespace.mathjax)
+ self.assertTrue(self.namespace.katex)
# next the namespace subscription expires with custom settings.
self.namespace.subscription_type = "development"
@@ -170,7 +170,7 @@ class TestNamespace(unittest.TestCase):
self.assertTrue(self.namespace.memoized_attr_protection)
self.assertFalse(self.namespace.link_protection)
self.assertFalse(self.namespace.hide_powered_by)
- self.assertFalse(self.namespace.mathjax)
+ self.assertFalse(self.namespace.katex)
# finally the namespace subscription is renewed with custom settings.
self.namespace.subscription_type = "production"
@@ -178,7 +178,7 @@ class TestNamespace(unittest.TestCase):
self.assertFalse(self.namespace.memoized_attr_protection)
self.assertTrue(self.namespace.link_protection)
self.assertTrue(self.namespace.hide_powered_by)
- self.assertTrue(self.namespace.mathjax)
+ self.assertTrue(self.namespace.katex)
class TestNamespaceRequest(unittest.TestCase):
diff --git a/remarkbox/tests/test_render.py b/remarkbox/tests/test_render.py
index 0091618..a8eaea1 100644
--- a/remarkbox/tests/test_render.py
+++ b/remarkbox/tests/test_render.py
@@ -49,36 +49,26 @@ class TestRenderMarkdown(unittest.TestCase):
def test_make_cleaner_from_custom_namespace(self):
namespace = Namespace("russell.ballestrini.net")
namespace.subscription_type = "production"
- namespace.mathjax = True
+ namespace.katex = True
namespace.link_protection = True
cleaner = make_cleaner_from_namespace(namespace)
self.assertTrue(cleaner.link_protection)
- self.assertIn("script", cleaner.tags)
self.assertEqual("russell.ballestrini.net", cleaner.absolute_domain)
self.assertIn("russell.ballestrini.net", cleaner.whitelist_domains)
- def test_mathjax_enabled_and_disabled(self):
- raw_html = markdown_to_raw_html(SAMPLE_MARKDOWN)
+ def test_katex_enabled_and_disabled(self):
namespace = Namespace("russell.ballestrini.net")
namespace.subscription_type = "production"
- # test mathjax enabled.
- namespace.mathjax = True
+ # test katex enabled.
+ namespace.katex = True
cleaner = make_cleaner_from_namespace(namespace)
- self.assertTrue(cleaner.mathjax)
- clean_html = clean_raw_html(raw_html, cleaner)
- self.assertIn(
- '', clean_html
- )
+ self.assertTrue(cleaner.katex)
- # test mathjax disabled.
- namespace.mathjax = False
+ # test katex disabled.
+ namespace.katex = False
cleaner = make_cleaner_from_namespace(namespace)
- self.assertFalse(cleaner.mathjax)
- clean_html = clean_raw_html(raw_html, cleaner)
- self.assertNotIn(
- '', clean_html
- )
+ self.assertFalse(cleaner.katex)
def test_link_protection_enabled(self):
raw_html = markdown_to_raw_html(SAMPLE_MARKDOWN)
diff --git a/remarkbox/views/authenticated/authenticated.py b/remarkbox/views/authenticated/authenticated.py
index 39067e4..52bf78e 100644
--- a/remarkbox/views/authenticated/authenticated.py
+++ b/remarkbox/views/authenticated/authenticated.py
@@ -63,7 +63,7 @@ def namespace_settings(request):
link_protection_checkbox = p.get("link-protection-checkbox", "off")
reverse_order_checkbox = p.get("reverse-order-checkbox", "off")
group_conversations_checkbox = p.get("group-conversations-checkbox", "off")
- mathjax_checkbox = p.get("mathjax-checkbox", "off")
+ katex_checkbox = p.get("katex-checkbox", "off")
ignore_query_string_checkbox = p.get("ignore-query-string-checkbox", "off")
hide_powered_by_checkbox = p.get("hide-powered-by-checkbox", "off")
@@ -71,7 +71,7 @@ def namespace_settings(request):
link_protection = checkbox_to_bool(link_protection_checkbox)
reverse_order = checkbox_to_bool(reverse_order_checkbox)
group_conversations = checkbox_to_bool(group_conversations_checkbox)
- mathjax = checkbox_to_bool(mathjax_checkbox)
+ katex = checkbox_to_bool(katex_checkbox)
ignore_query_string = checkbox_to_bool(ignore_query_string_checkbox)
hide_powered_by = checkbox_to_bool(hide_powered_by_checkbox)
@@ -195,10 +195,10 @@ def namespace_settings(request):
)
)
- if mathjax != request.namespace.mathjax:
- request.namespace.mathjax = mathjax
+ if katex != request.namespace.katex:
+ request.namespace.katex = katex
request.session.flash(
- ("You turned {} MathJax".format(mathjax_checkbox), "success")
+ ("You turned {} KaTeX".format(katex_checkbox), "success")
)
request.dbsession.add(request.namespace)