todo.sr.ht/todosrht/filters.py

113 lines
3.8 KiB
Python
Raw Normal View History

2020-03-31 17:51:24 +02:00
import hashlib
import json
import re
2019-02-24 19:35:02 +01:00
from datetime import timedelta
from functools import wraps
from jinja2.utils import Markup, escape
2020-03-31 17:51:24 +02:00
from srht.flask import icon, csrf_token, date_handler
from srht.markdown import markdown, SRHT_MARKDOWN_VERSION
from srht.cache import get_cache, set_cache
from todosrht import urls
from todosrht.tickets import find_mentioned_users, find_mentioned_tickets
from todosrht.tickets import TICKET_MENTION_PATTERN, USER_MENTION_PATTERN
2019-03-30 09:27:22 +01:00
def cache_rendered_markup(func):
2019-02-24 19:35:02 +01:00
@wraps(func)
2019-03-30 09:27:22 +01:00
def wrap(obj):
class_name = obj.__class__.__name__
2020-03-31 17:51:24 +02:00
sha = hashlib.sha1()
sha.update(json.dumps(obj.to_dict(), default=date_handler).encode())
key = f"todo.sr.ht:cache_rendered_markup:{class_name}:{sha.hexdigest()}:v{SRHT_MARKDOWN_VERSION}"
value = get_cache(key)
2019-02-24 19:35:02 +01:00
if value:
return Markup(value.decode())
2019-03-30 09:27:22 +01:00
value = func(obj)
set_cache(key, timedelta(days=30), value)
2019-02-24 19:35:02 +01:00
return value
return wrap
2019-03-30 09:27:22 +01:00
def render_markup(tracker, text):
users = find_mentioned_users(text)
tickets = find_mentioned_tickets(tracker, text)
2019-08-21 08:35:44 +02:00
users_map = {u.identifier: u for u in users}
tickets_map = {t.ref(): t for t in tickets}
def urlize_user(match):
2019-08-21 08:35:44 +02:00
# TODO: Handle mentions for non-user participants
username = match.group(0)
if username in users_map:
url = urls.participant_url(users_map[username])
return f'<a href="{url}">{escape(username)}</a>'
return username
def urlize_ticket(match):
text = match.group(0)
ticket_id = match.group('ticket_id')
tracker_name = match.group('tracker_name') or tracker.name
owner = match.group('username') or tracker.owner.username
ticket_ref = f"~{owner}/{tracker_name}#{ticket_id}"
if ticket_ref not in tickets_map:
return text
ticket = tickets_map[ticket_ref]
url = urls.ticket_url(ticket)
title = escape(f"{ticket.ref()}: {ticket.title}")
return f'<a href="{url}" title="{title}">{text}</a>'
# Replace ticket and username mentions with linked version
text = re.sub(USER_MENTION_PATTERN, urlize_user, text)
text = re.sub(TICKET_MENTION_PATTERN, urlize_ticket, text)
return markdown(text)
2019-03-30 09:27:22 +01:00
@cache_rendered_markup
def render_comment(comment):
return render_markup(comment.ticket.tracker, comment.text)
@cache_rendered_markup
def render_ticket_description(ticket):
return render_markup(ticket.tracker, ticket.description)
def label_badge(label, cls="", remove_from_ticket=None, terms=None):
"""Return HTML markup rendering a label badge.
Additional HTML classes can be passed via the `cls` parameter.
If a Ticket is passed in `remove_from_ticket`, a removal button will also
be rendered for removing the label from given ticket.
"""
name = escape(label.name)
color = escape(label.text_color)
bg_color = escape(label.color)
html_class = escape(f"label {cls}".strip())
style = f"color: {color}; background-color: {bg_color}"
if terms:
search_url = urls.label_search_url(label, terms=terms)
else:
search_url = urls.label_search_url(label)
if remove_from_ticket:
remove_url = urls.label_remove_url(label, remove_from_ticket)
remove_form = f"""
<form method="POST" action="{remove_url}">
{csrf_token()}
<button type="submit" class="btn btn-link" aria-label="Remove">
{icon('times')}
</button>
</form>
"""
else:
remove_form = ""
return Markup(
f"""<span style="{style}" class="{html_class}" href="{search_url}">
<a rel="nofollow" href="{search_url}">{name}</a>
{remove_form}
</span>"""
)