fix: rewrite forum markdown - autolink before block wrapping, merge paragraph lines
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
URLs are now linked before being wrapped in <li>/<blockquote>, and consecutive text lines are joined into paragraphs instead of getting individual <br> tags. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7cbd3bb1e7
commit
d56f1fdae1
@ -10,6 +10,15 @@ import re
|
|||||||
from markupsafe import Markup, escape
|
from markupsafe import Markup, escape
|
||||||
|
|
||||||
|
|
||||||
|
def _autolink(text):
|
||||||
|
"""Convert bare URLs to clickable links. Works on escaped text before HTML wrapping."""
|
||||||
|
return re.sub(
|
||||||
|
r'https?://[^\s<]+',
|
||||||
|
lambda m: f'<a href="{m.group(0)}" target="_blank" rel="noopener noreferrer" class="forum-link">{m.group(0)}</a>',
|
||||||
|
text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_forum_markdown(text):
|
def parse_forum_markdown(text):
|
||||||
"""
|
"""
|
||||||
Convert markdown text to safe HTML.
|
Convert markdown text to safe HTML.
|
||||||
@ -23,12 +32,6 @@ def parse_forum_markdown(text):
|
|||||||
- - list items
|
- - list items
|
||||||
- > quotes
|
- > quotes
|
||||||
- @mentions (highlighted)
|
- @mentions (highlighted)
|
||||||
|
|
||||||
Args:
|
|
||||||
text: Raw markdown text
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Markup object with safe HTML
|
|
||||||
"""
|
"""
|
||||||
if not text:
|
if not text:
|
||||||
return Markup('')
|
return Markup('')
|
||||||
@ -39,59 +42,8 @@ def parse_forum_markdown(text):
|
|||||||
# Escape HTML first for security
|
# Escape HTML first for security
|
||||||
text = str(escape(text))
|
text = str(escape(text))
|
||||||
|
|
||||||
# Process line by line for block elements
|
# Apply inline formatting BEFORE block structure
|
||||||
lines = text.split('\n')
|
# This ensures URLs inside list items get linked
|
||||||
result_lines = []
|
|
||||||
in_list = False
|
|
||||||
in_quote = False
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
stripped = line.strip()
|
|
||||||
|
|
||||||
# Skip empty lines but preserve paragraph spacing
|
|
||||||
if not stripped:
|
|
||||||
if in_list:
|
|
||||||
result_lines.append('</ul>')
|
|
||||||
in_list = False
|
|
||||||
if in_quote:
|
|
||||||
result_lines.append('</blockquote>')
|
|
||||||
in_quote = False
|
|
||||||
result_lines.append('')
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Quote blocks (> text)
|
|
||||||
if stripped.startswith('> '): # Escaped >
|
|
||||||
if not in_quote:
|
|
||||||
result_lines.append('<blockquote class="forum-quote">')
|
|
||||||
in_quote = True
|
|
||||||
result_lines.append(stripped[5:]) # Remove > prefix
|
|
||||||
continue
|
|
||||||
elif in_quote:
|
|
||||||
result_lines.append('</blockquote>')
|
|
||||||
in_quote = False
|
|
||||||
|
|
||||||
# List items (- text)
|
|
||||||
if stripped.startswith('- '):
|
|
||||||
if not in_list:
|
|
||||||
result_lines.append('<ul class="forum-list">')
|
|
||||||
in_list = True
|
|
||||||
result_lines.append(f'<li>{stripped[2:]}</li>')
|
|
||||||
continue
|
|
||||||
elif in_list:
|
|
||||||
result_lines.append('</ul>')
|
|
||||||
in_list = False
|
|
||||||
|
|
||||||
result_lines.append(stripped)
|
|
||||||
|
|
||||||
# Close open blocks
|
|
||||||
if in_list:
|
|
||||||
result_lines.append('</ul>')
|
|
||||||
if in_quote:
|
|
||||||
result_lines.append('</blockquote>')
|
|
||||||
|
|
||||||
text = '\n'.join(result_lines)
|
|
||||||
|
|
||||||
# Inline formatting (order matters!)
|
|
||||||
|
|
||||||
# Code blocks (``` ... ```)
|
# Code blocks (``` ... ```)
|
||||||
text = re.sub(
|
text = re.sub(
|
||||||
@ -118,13 +70,13 @@ def parse_forum_markdown(text):
|
|||||||
url = match.group(2)
|
url = match.group(2)
|
||||||
if url.startswith(('http://', 'https://', '/')):
|
if url.startswith(('http://', 'https://', '/')):
|
||||||
return f'<a href="{url}" target="_blank" rel="noopener noreferrer" class="forum-link">{link_text}</a>'
|
return f'<a href="{url}" target="_blank" rel="noopener noreferrer" class="forum-link">{link_text}</a>'
|
||||||
return match.group(0) # Return original if not safe
|
return match.group(0)
|
||||||
|
|
||||||
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', safe_link, text)
|
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', safe_link, text)
|
||||||
|
|
||||||
# Auto-link bare URLs (must come after [text](url) so already-linked URLs aren't doubled)
|
# Auto-link bare URLs (after [text](url) to avoid doubling)
|
||||||
text = re.sub(
|
text = re.sub(
|
||||||
r'(?<!["\'>=/])(?<!\()https?://[^\s<\)]+',
|
r'(?<!href=")(?<!">)https?://[^\s<]+',
|
||||||
lambda m: f'<a href="{m.group(0)}" target="_blank" rel="noopener noreferrer" class="forum-link">{m.group(0)}</a>',
|
lambda m: f'<a href="{m.group(0)}" target="_blank" rel="noopener noreferrer" class="forum-link">{m.group(0)}</a>',
|
||||||
text
|
text
|
||||||
)
|
)
|
||||||
@ -136,19 +88,70 @@ def parse_forum_markdown(text):
|
|||||||
text
|
text
|
||||||
)
|
)
|
||||||
|
|
||||||
# Convert newlines to <br> but skip lines that are HTML block elements
|
# Now process block structure (lists, quotes, paragraphs)
|
||||||
lines = text.split('\n')
|
lines = text.split('\n')
|
||||||
|
result_lines = []
|
||||||
|
in_list = False
|
||||||
|
in_quote = False
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
stripped = line.strip()
|
||||||
|
|
||||||
|
# Empty line = paragraph break
|
||||||
|
if not stripped:
|
||||||
|
if in_list:
|
||||||
|
result_lines.append('</ul>')
|
||||||
|
in_list = False
|
||||||
|
if in_quote:
|
||||||
|
result_lines.append('</blockquote>')
|
||||||
|
in_quote = False
|
||||||
|
result_lines.append('<br>')
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Quote blocks (> text) — > because already escaped
|
||||||
|
if stripped.startswith('> '):
|
||||||
|
if not in_quote:
|
||||||
|
result_lines.append('<blockquote class="forum-quote">')
|
||||||
|
in_quote = True
|
||||||
|
result_lines.append(stripped[5:])
|
||||||
|
continue
|
||||||
|
elif in_quote:
|
||||||
|
result_lines.append('</blockquote>')
|
||||||
|
in_quote = False
|
||||||
|
|
||||||
|
# List items (- text)
|
||||||
|
if stripped.startswith('- '):
|
||||||
|
if not in_list:
|
||||||
|
result_lines.append('<ul class="forum-list">')
|
||||||
|
in_list = True
|
||||||
|
result_lines.append(f'<li>{stripped[2:]}</li>')
|
||||||
|
continue
|
||||||
|
elif in_list:
|
||||||
|
result_lines.append('</ul>')
|
||||||
|
in_list = False
|
||||||
|
|
||||||
|
result_lines.append(stripped)
|
||||||
|
|
||||||
|
# Close open blocks
|
||||||
|
if in_list:
|
||||||
|
result_lines.append('</ul>')
|
||||||
|
if in_quote:
|
||||||
|
result_lines.append('</blockquote>')
|
||||||
|
|
||||||
|
# Join with spaces — no extra <br> between lines within same paragraph
|
||||||
|
# Consecutive non-block lines are part of the same paragraph
|
||||||
output = []
|
output = []
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(result_lines):
|
||||||
output.append(line)
|
s = line.strip()
|
||||||
# Don't add <br> after block elements or before them
|
# Block elements get their own line, no extra spacing
|
||||||
if i < len(lines) - 1:
|
if any(s.startswith(t) for t in ['<ul', '</ul>', '<li', '</li>', '<blockquote', '</blockquote>', '<pre', '</pre>', '<br>']):
|
||||||
stripped = line.strip()
|
output.append(line)
|
||||||
next_stripped = lines[i + 1].strip() if i + 1 < len(lines) else ''
|
else:
|
||||||
is_block = any(stripped.startswith(t) for t in ['<ul', '</ul>', '<li', '</li>', '<blockquote', '</blockquote>', '<pre', '</pre>'])
|
# Regular text — join with previous regular text using space
|
||||||
next_is_block = any(next_stripped.startswith(t) for t in ['<ul', '</ul>', '<li', '</li>', '<blockquote', '</blockquote>', '<pre', '</pre>'])
|
if output and output[-1] and not any(output[-1].strip().startswith(t) for t in ['<ul', '</ul>', '<li', '</li>', '<blockquote', '</blockquote>', '<pre', '</pre>', '<br>']):
|
||||||
if not is_block and not next_is_block:
|
output[-1] = output[-1] + ' ' + line
|
||||||
output.append('<br>')
|
else:
|
||||||
|
output.append(line)
|
||||||
|
|
||||||
return Markup('\n'.join(output))
|
return Markup('\n'.join(output))
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user