feat(forum): beautify Google Maps URLs + fix edit/quote raw content
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
Google Maps URLs can be 800+ chars of tracking data that poisoned the
forum UI. Extract the place name from /maps/place/NAME/ (or fall back
to coordinates) and render as '📍 Name'. Full URL remains in the href.
Two secondary fixes:
- Edit/quote modals were reading .innerText of the rendered reply,
which baked the current render (including any stale/broken HTML from
older bad renders) back into the textarea. Switched to emitting the
raw DB content via {{ content|tojson }} so what you edit is what you
wrote.
- @mention regex was matching '@54.1234' inside Maps URLs and similar.
Tightened to require a letter start and non-slash/non-word lookbehind
so coords and email-style strings pass through untouched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dcdaa8d338
commit
958b967df2
@ -1154,7 +1154,7 @@
|
||||
{% if not topic.is_locked %}
|
||||
<div class="user-actions">
|
||||
{% if topic.author_id == current_user.id or current_user.can_moderate_forum() %}
|
||||
<button type="button" class="action-btn" onclick="openEditModal('topic', {{ topic.id }}, document.getElementById('topicContent').innerText)">
|
||||
<button type="button" class="action-btn" onclick="openEditModal('topic', {{ topic.id }}, {{ topic.content|tojson }})">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
Edytuj
|
||||
</button>
|
||||
@ -1223,7 +1223,7 @@
|
||||
<button type="button" class="admin-btn admin-btn-sm" onclick="toggleSolution({{ reply.id }})" title="{% if reply.is_solution %}Usuń oznaczenie{% else %}Oznacz jako rozwiązanie{% endif %}">
|
||||
✓
|
||||
</button>
|
||||
<button type="button" class="admin-btn admin-btn-sm" onclick="openEditModal('reply', {{ reply.id }}, document.querySelector('#reply-{{ reply.id }} .reply-content').innerText)">
|
||||
<button type="button" class="admin-btn admin-btn-sm" onclick="openEditModal('reply', {{ reply.id }}, {{ reply.content|tojson }})">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="14" height="14"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
</button>
|
||||
{% if reply.is_deleted %}
|
||||
@ -1301,7 +1301,7 @@
|
||||
{% if not topic.is_locked %}
|
||||
<div class="user-actions">
|
||||
{% if reply.author_id == current_user.id %}
|
||||
<button type="button" class="action-btn" onclick="openEditModal('reply', {{ reply.id }}, document.querySelector('#reply-{{ reply.id }} .reply-content').innerText)">
|
||||
<button type="button" class="action-btn" onclick="openEditModal('reply', {{ reply.id }}, {{ reply.content|tojson }})">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="14" height="14"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||||
Edytuj
|
||||
</button>
|
||||
@ -1310,7 +1310,7 @@
|
||||
Usuń
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button" class="action-btn" onclick="quoteReply('{{ reply.author.name or reply.author.email.split('@')[0] }}', document.querySelector('#reply-{{ reply.id }} .reply-content').innerText)">
|
||||
<button type="button" class="action-btn" onclick="quoteReply('{{ reply.author.name or reply.author.email.split('@')[0] }}', {{ reply.content|tojson }})">
|
||||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" width="14" height="14"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>
|
||||
Cytuj
|
||||
</button>
|
||||
|
||||
@ -7,16 +7,37 @@ Supports: bold, italic, code, links, auto-links, lists, quotes, @mentions
|
||||
"""
|
||||
|
||||
import re
|
||||
import urllib.parse
|
||||
from markupsafe import Markup, escape
|
||||
|
||||
|
||||
def _link_display(url):
|
||||
"""Shorten a URL to a human-friendly label while keeping the full href.
|
||||
|
||||
Google Maps URLs are especially unreadable — a single place link can be
|
||||
800 characters of tracking data. Extract the place name (or coordinates)
|
||||
and render it as `📍 Name` instead.
|
||||
"""
|
||||
if 'google.' in url and '/maps/' in url:
|
||||
m = re.search(r'/maps/place/([^/@?]+)', url)
|
||||
if m:
|
||||
name = urllib.parse.unquote(m.group(1)).replace('+', ' ').strip()
|
||||
if name:
|
||||
return f'📍 {name}'
|
||||
m = re.search(r'@(-?\d+\.\d+),(-?\d+\.\d+)', url)
|
||||
if m:
|
||||
return f'📍 Mapa ({m.group(1)}, {m.group(2)})'
|
||||
return '📍 Google Maps'
|
||||
return url
|
||||
|
||||
|
||||
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 wrap(m):
|
||||
url = m.group(0)
|
||||
display = _link_display(url)
|
||||
return f'<a href="{url}" target="_blank" rel="noopener noreferrer" class="forum-link">{display}</a>'
|
||||
return re.sub(r'https?://[^\s<]+', wrap, text)
|
||||
|
||||
|
||||
def parse_forum_markdown(text, current_user_name=None):
|
||||
@ -77,12 +98,14 @@ def parse_forum_markdown(text, current_user_name=None):
|
||||
|
||||
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', safe_link, text)
|
||||
|
||||
# Auto-link bare URLs (after [text](url) to avoid doubling)
|
||||
text = re.sub(
|
||||
r'(?<!href=")(?<!">)https?://[^\s<]+',
|
||||
lambda m: f'<a href="{m.group(0)}" target="_blank" rel="noopener noreferrer" class="forum-link">{m.group(0)}</a>',
|
||||
text
|
||||
)
|
||||
# Auto-link bare URLs (after [text](url) to avoid doubling).
|
||||
# Beautify Google Maps URLs via _link_display so the visible label is
|
||||
# a pin + place name instead of an 800-char tracking URL.
|
||||
def _wrap_autolink(m):
|
||||
url = m.group(0)
|
||||
display = _link_display(url)
|
||||
return f'<a href="{url}" target="_blank" rel="noopener noreferrer" class="forum-link">{display}</a>'
|
||||
text = re.sub(r'(?<!href=")(?<!">)https?://[^\s<]+', _wrap_autolink, text)
|
||||
|
||||
# @mentions - highlight them; mark self-mentions with extra class
|
||||
self_variants = set()
|
||||
@ -95,7 +118,10 @@ def parse_forum_markdown(text, current_user_name=None):
|
||||
cls = 'forum-mention forum-mention-self' if handle in self_variants else 'forum-mention'
|
||||
return f'<span class="{cls}">@{m.group(1)}</span>'
|
||||
|
||||
text = re.sub(r'@([\w.\-]+)', _render_mention, text)
|
||||
# Mentions must start with a letter and not be preceded by `/` or another
|
||||
# word char — this prevents matching `@54.123` from Google Maps URLs or
|
||||
# `email@host` style strings that happen to land in plaintext.
|
||||
text = re.sub(r'(?<![/\w])@([a-zA-Z][\w.\-]*)', _render_mention, text)
|
||||
|
||||
# Now process block structure (lists, quotes, paragraphs)
|
||||
lines = text.split('\n')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user