feat(messages): auto-linkify URLs in message 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
URLs in messages are now automatically converted to clickable links opening in a new tab. Works for both old plain-text and new Quill HTML messages. Uses linkify Jinja2 filter that only processes text nodes outside existing <a>/<img> tags. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
73d9de8c9c
commit
b8f18c94e5
4
app.py
4
app.py
@ -225,6 +225,10 @@ def ensure_url_filter(url):
|
|||||||
return f'https://{url}'
|
return f'https://{url}'
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
# Register linkify filter for messages
|
||||||
|
from utils.helpers import linkify_urls
|
||||||
|
app.jinja_env.filters['linkify'] = linkify_urls
|
||||||
|
|
||||||
# Register forum markdown filter
|
# Register forum markdown filter
|
||||||
from utils.markdown import register_markdown_filter
|
from utils.markdown import register_markdown_filter
|
||||||
register_markdown_filter(app)
|
register_markdown_filter(app)
|
||||||
|
|||||||
@ -450,7 +450,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="thread-msg-body">{{ msg.content|safe }}</div>
|
<div class="thread-msg-body">{{ msg.content|linkify }}</div>
|
||||||
{% if msg.attachments %}
|
{% if msg.attachments %}
|
||||||
<div class="attachments-section" style="margin: 0 var(--spacing-md) var(--spacing-sm); padding-top: var(--spacing-sm);">
|
<div class="attachments-section" style="margin: 0 var(--spacing-md) var(--spacing-sm); padding-top: var(--spacing-sm);">
|
||||||
{% for att in msg.attachments %}
|
{% for att in msg.attachments %}
|
||||||
@ -510,7 +510,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="message-card-body">
|
<div class="message-card-body">
|
||||||
<div class="message-body">{{ message.content|safe }}</div>
|
<div class="message-body">{{ message.content|linkify }}</div>
|
||||||
|
|
||||||
{% if message.attachments %}
|
{% if message.attachments %}
|
||||||
<div class="attachments-section">
|
<div class="attachments-section">
|
||||||
|
|||||||
@ -33,6 +33,59 @@ def sanitize_html(content):
|
|||||||
return bleach.clean(content, tags=_ALLOWED_TAGS, attributes=_ALLOWED_ATTRS, strip=True)
|
return bleach.clean(content, tags=_ALLOWED_TAGS, attributes=_ALLOWED_ATTRS, strip=True)
|
||||||
|
|
||||||
|
|
||||||
|
def linkify_urls(html):
|
||||||
|
"""
|
||||||
|
Auto-link URLs in HTML content that are not already inside <a> or <img> tags.
|
||||||
|
Links to nordabiznes.pl open in new tab as trusted internal links.
|
||||||
|
"""
|
||||||
|
if not html:
|
||||||
|
return html
|
||||||
|
|
||||||
|
from markupsafe import Markup
|
||||||
|
|
||||||
|
# Split HTML into tags and text, only process text outside <a>/<img> tags
|
||||||
|
url_pattern = re.compile(r'(https?://[^\s<>"\']+)')
|
||||||
|
tag_pattern = re.compile(r'<(/?)(\w+)([^>]*)>')
|
||||||
|
|
||||||
|
result = []
|
||||||
|
pos = 0
|
||||||
|
in_a_tag = False
|
||||||
|
|
||||||
|
for match in tag_pattern.finditer(html):
|
||||||
|
start, end = match.start(), match.end()
|
||||||
|
is_closing = match.group(1) == '/'
|
||||||
|
tag_name = match.group(2).lower()
|
||||||
|
|
||||||
|
# Process text before this tag
|
||||||
|
if start > pos:
|
||||||
|
text_chunk = html[pos:start]
|
||||||
|
if in_a_tag:
|
||||||
|
result.append(text_chunk)
|
||||||
|
else:
|
||||||
|
result.append(url_pattern.sub(
|
||||||
|
lambda m: '<a href="{0}" target="_blank" style="color:var(--primary);word-break:break-all;">{0}</a>'.format(m.group(0)),
|
||||||
|
text_chunk
|
||||||
|
))
|
||||||
|
|
||||||
|
result.append(match.group(0))
|
||||||
|
pos = end
|
||||||
|
|
||||||
|
if tag_name in ('a', 'img'):
|
||||||
|
in_a_tag = not is_closing
|
||||||
|
|
||||||
|
# Process remaining text
|
||||||
|
if pos < len(html):
|
||||||
|
text_chunk = html[pos:]
|
||||||
|
if not in_a_tag:
|
||||||
|
text_chunk = url_pattern.sub(
|
||||||
|
lambda m: '<a href="{0}" target="_blank" style="color:var(--primary);word-break:break-all;">{0}</a>'.format(m.group(0)),
|
||||||
|
text_chunk
|
||||||
|
)
|
||||||
|
result.append(text_chunk)
|
||||||
|
|
||||||
|
return Markup(''.join(result))
|
||||||
|
|
||||||
|
|
||||||
def sanitize_input(text, max_length=1000):
|
def sanitize_input(text, max_length=1000):
|
||||||
"""
|
"""
|
||||||
Sanitize user input - remove potentially dangerous characters.
|
Sanitize user input - remove potentially dangerous characters.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user