2020-03-31 17:51:24 +02:00
|
|
|
import hashlib
|
|
|
|
import json
|
2019-02-24 19:44:52 +01:00
|
|
|
import re
|
2019-02-24 19:35:02 +01:00
|
|
|
from datetime import timedelta
|
|
|
|
from functools import wraps
|
2018-11-17 20:21:53 +01:00
|
|
|
from jinja2.utils import Markup, escape
|
2020-03-31 17:51:24 +02:00
|
|
|
from srht.flask import icon, csrf_token, date_handler
|
2020-03-30 17:04:20 +02:00
|
|
|
from srht.markdown import markdown, SRHT_MARKDOWN_VERSION
|
2020-06-29 16:40:35 +02:00
|
|
|
from srht.cache import get_cache, set_cache
|
2018-11-17 20:21:55 +01:00
|
|
|
from todosrht import urls
|
2019-02-24 19:44:52 +01:00
|
|
|
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}"
|
2020-06-29 16:40:35 +02:00
|
|
|
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)
|
2020-06-29 16:40:35 +02:00
|
|
|
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-02-24 19:44:52 +01:00
|
|
|
|
2019-08-21 08:35:44 +02:00
|
|
|
users_map = {u.identifier: u for u in users}
|
2019-03-07 08:02:57 +01:00
|
|
|
tickets_map = {t.ref(): t for t in tickets}
|
2019-02-24 19:44:52 +01:00
|
|
|
|
|
|
|
def urlize_user(match):
|
2019-08-21 08:35:44 +02:00
|
|
|
# TODO: Handle mentions for non-user participants
|
2019-02-24 19:44:52 +01:00
|
|
|
username = match.group(0)
|
|
|
|
if username in users_map:
|
2019-08-24 03:57:35 +02:00
|
|
|
url = urls.participant_url(users_map[username])
|
2019-02-24 19:44:52 +01:00
|
|
|
return f'<a href="{url}">{escape(username)}</a>'
|
|
|
|
|
|
|
|
return username
|
|
|
|
|
|
|
|
def urlize_ticket(match):
|
2019-03-07 08:02:57 +01:00
|
|
|
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>'
|
2019-02-24 19:44:52 +01:00
|
|
|
|
|
|
|
# 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)
|
2018-11-17 20:21:53 +01:00
|
|
|
|
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)
|
|
|
|
|
2019-06-02 21:56:51 +02:00
|
|
|
def label_badge(label, cls="", remove_from_ticket=None, terms=None):
|
2018-11-17 20:21:53 +01:00
|
|
|
"""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}"
|
2019-06-02 21:56:51 +02:00
|
|
|
if terms:
|
|
|
|
search_url = urls.label_search_url(label, terms=terms)
|
|
|
|
else:
|
|
|
|
search_url = urls.label_search_url(label)
|
2018-11-17 20:21:53 +01:00
|
|
|
|
|
|
|
if remove_from_ticket:
|
2018-11-17 20:21:55 +01:00
|
|
|
remove_url = urls.label_remove_url(label, remove_from_ticket)
|
2018-11-17 20:21:53 +01:00
|
|
|
remove_form = f"""
|
|
|
|
<form method="POST" action="{remove_url}">
|
|
|
|
{csrf_token()}
|
2021-08-19 19:06:01 +02:00
|
|
|
<button type="submit" class="btn btn-link" aria-label="Remove">
|
2018-11-17 20:21:53 +01:00
|
|
|
{icon('times')}
|
|
|
|
</button>
|
|
|
|
</form>
|
|
|
|
"""
|
|
|
|
else:
|
|
|
|
remove_form = ""
|
|
|
|
|
|
|
|
return Markup(
|
|
|
|
f"""<span style="{style}" class="{html_class}" href="{search_url}">
|
2020-11-04 18:54:25 +01:00
|
|
|
<a rel="nofollow" href="{search_url}">{name}</a>
|
2018-11-17 20:21:53 +01:00
|
|
|
{remove_form}
|
|
|
|
</span>"""
|
|
|
|
)
|