From b4a74a32f4f4ba5ac727b342c267362327392f0d Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Mon, 16 Mar 2026 11:39:26 -0400
Subject: [PATCH] fix(cli): convert newlines to
in static HTML for Tor
compatibility
User-entered line breaks were rendering as literal \n\n in the content
box because the static HTML injection didn't convert newlines to
tags. Since Tor Browser disables JS, the fix must be server-side.
Co-Authored-By: Claude Opus 4.6
---
voxvera/cli.py | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/voxvera/cli.py b/voxvera/cli.py
index 0b256b3..39637a1 100644
--- a/voxvera/cli.py
+++ b/voxvera/cli.py
@@ -530,7 +530,7 @@ def build_assets(
for field in landing_fields:
# Use data from config if available, otherwise use localized default
value = data.get(field, landing_defaults.get(field, ""))
-
+
# If the value from config is just the master default,
# and we are NOT in English, use the localized default instead.
if data.get("lang", "en") != "en":
@@ -539,6 +539,8 @@ def build_assets(
# Support Markdown-style redaction ~~text~~
val_str = re.sub(r"~~(.*?)~~", r'\1', str(value))
+ # Convert newlines to
so line breaks render in static HTML (no JS on Tor)
+ val_str = val_str.replace("\\n", "\n").replace("\n", "
")
html = html.replace(f"{{{{t_landing_{field}}}}}", val_str)
# 2. Statically replace config placeholders {{key}}
@@ -546,6 +548,8 @@ def build_assets(
val_str = str(value)
# Support Markdown-style redaction ~~text~~ for user fields too
val_str = re.sub(r"~~(.*?)~~", r'\1', val_str)
+ # Convert newlines to
so line breaks render in static HTML (no JS on Tor)
+ val_str = val_str.replace("\\n", "\n").replace("\n", "
")
html = html.replace(f"{{{{{key}}}}}", val_str)
# Handle optional file attachment logic
@@ -1067,7 +1071,7 @@ def build_site():
for field in landing_fields:
# Use data from config if available, otherwise use localized default
value = data.get(field, landing_defaults.get(field, ""))
-
+
# If the value from config is just the master default,
# and we are NOT in English, use the localized default instead.
if current_lang != "en":
@@ -1076,6 +1080,8 @@ def build_site():
# Support Markdown-style redaction ~~text~~
val_str = re.sub(r"~~(.*?)~~", r'\1', str(value))
+ # Convert newlines to
so line breaks render in static HTML (no JS on Tor)
+ val_str = val_str.replace("\\n", "\n").replace("\n", "
")
html = html.replace(f"{{{{t_landing_{field}}}}}", val_str)
# 3. Update relative path for download
@@ -1083,7 +1089,9 @@ def build_site():
# 4. Inject site-specific config data (handles any remaining {{key}} placeholders)
for key, value in data.items():
- html = html.replace(f"{{{{{key}}}}}", str(value))
+ val_str = str(value)
+ val_str = val_str.replace("\\n", "\n").replace("\n", "
")
+ html = html.replace(f"{{{{{key}}}}}", val_str)
os.makedirs(dest_site, exist_ok=True)
with open(dest_site / "index.html", "w") as fh: