mirror of
https://github.com/minetest/contentdb.git
synced 2024-11-10 01:23:48 +01:00
Add user and package mentions
This commit is contained in:
parent
e02c014890
commit
6a4bf7129d
@ -16,6 +16,7 @@
|
||||
from flask import *
|
||||
from flask_babel import gettext, lazy_gettext
|
||||
|
||||
from app.markdown import get_user_mentions, render_markdown
|
||||
from app.tasks.webhooktasks import post_discord_webhook
|
||||
|
||||
bp = Blueprint("threads", __name__)
|
||||
@ -238,6 +239,15 @@ def view(id):
|
||||
if not current_user in thread.watchers:
|
||||
thread.watchers.append(current_user)
|
||||
|
||||
for mentioned_username in get_user_mentions(render_markdown(comment)):
|
||||
mentioned = User.query.filter_by(username=mentioned_username)
|
||||
if mentioned is None:
|
||||
continue
|
||||
|
||||
msg = "Mentioned by {} in '{}'".format(current_user.display_name, thread.title)
|
||||
addNotification(mentioned, current_user, NotificationType.THREAD_REPLY,
|
||||
msg, thread.getViewURL(), thread.package)
|
||||
|
||||
msg = "New comment on '{}'".format(thread.title)
|
||||
addNotification(thread.watchers, current_user, NotificationType.THREAD_REPLY, msg, thread.getViewURL(), thread.package)
|
||||
|
||||
@ -335,6 +345,15 @@ def new():
|
||||
if is_review_thread:
|
||||
package.review_thread = thread
|
||||
|
||||
for mentioned_username in get_user_mentions(render_markdown(form.comment.data)):
|
||||
mentioned = User.query.filter_by(username=mentioned_username)
|
||||
if mentioned is None:
|
||||
continue
|
||||
|
||||
msg = "Mentioned by {} in new thread '{}'".format(current_user.display_name, thread.title)
|
||||
addNotification(mentioned, current_user, NotificationType.NEW_THREAD,
|
||||
msg, thread.getViewURL(), thread.package)
|
||||
|
||||
notif_msg = "New thread '{}'".format(thread.title)
|
||||
if package is not None:
|
||||
addNotification(package.maintainers, current_user, NotificationType.NEW_THREAD, notif_msg, thread.getViewURL(), package)
|
||||
@ -342,6 +361,7 @@ def new():
|
||||
approvers = User.query.filter(User.rank >= UserRank.APPROVER).all()
|
||||
addNotification(approvers, current_user, NotificationType.EDITOR_MISC, notif_msg, thread.getViewURL(), package)
|
||||
|
||||
|
||||
if is_review_thread:
|
||||
post_discord_webhook.delay(current_user.username,
|
||||
"Opened approval thread: {}".format(thread.getViewURL(absolute=True)), True)
|
||||
|
@ -5,10 +5,11 @@ from bleach import Cleaner
|
||||
from bleach.linkifier import LinkifyFilter
|
||||
from bs4 import BeautifulSoup
|
||||
from markdown import Markdown
|
||||
from flask import Markup
|
||||
from flask import Markup, url_for
|
||||
from markdown.extensions import Extension
|
||||
from markdown.inlinepatterns import SimpleTagInlineProcessor
|
||||
|
||||
from markdown.inlinepatterns import Pattern
|
||||
from xml.etree import ElementTree
|
||||
|
||||
# Based on
|
||||
# https://github.com/Wenzil/mdx_bleach/blob/master/mdx_bleach/whitelist.py
|
||||
@ -40,15 +41,17 @@ ALLOWED_CSS = [
|
||||
"s2", "se", "sh", "si", "sx", "sr", "s1", "ss", "bp", "fm", "vc", "vg", "vi", "vm", "il",
|
||||
]
|
||||
|
||||
|
||||
def allow_class(_tag, name, value):
|
||||
return name == "class" and value in ALLOWED_CSS
|
||||
|
||||
|
||||
ALLOWED_ATTRIBUTES = {
|
||||
"h1": ["id"],
|
||||
"h2": ["id"],
|
||||
"h3": ["id"],
|
||||
"h4": ["id"],
|
||||
"a": ["href", "title"],
|
||||
"a": ["href", "title", "data-username"],
|
||||
"img": ["src", "title", "alt"],
|
||||
"code": allow_class,
|
||||
"div": allow_class,
|
||||
@ -64,23 +67,63 @@ def render_markdown(source):
|
||||
html = md.convert(source)
|
||||
|
||||
cleaner = Cleaner(
|
||||
tags=ALLOWED_TAGS,
|
||||
attributes=ALLOWED_ATTRIBUTES,
|
||||
protocols=ALLOWED_PROTOCOLS,
|
||||
filters=[partial(LinkifyFilter, callbacks=bleach.linkifier.DEFAULT_CALLBACKS)])
|
||||
tags=ALLOWED_TAGS,
|
||||
attributes=ALLOWED_ATTRIBUTES,
|
||||
protocols=ALLOWED_PROTOCOLS,
|
||||
filters=[partial(LinkifyFilter, callbacks=bleach.linkifier.DEFAULT_CALLBACKS)])
|
||||
return cleaner.clean(html)
|
||||
|
||||
|
||||
class DelInsExtension(Extension):
|
||||
def extendMarkdown(self, md):
|
||||
del_proc = SimpleTagInlineProcessor(r'(\~\~)(.+?)(\~\~)', 'del')
|
||||
md.inlinePatterns.register(del_proc, 'del', 200)
|
||||
del_proc = SimpleTagInlineProcessor(r"(\~\~)(.+?)(\~\~)", "del")
|
||||
md.inlinePatterns.register(del_proc, "del", 200)
|
||||
|
||||
ins_proc = SimpleTagInlineProcessor(r'(\+\+)(.+?)(\+\+)', 'ins')
|
||||
md.inlinePatterns.register(ins_proc, 'ins', 200)
|
||||
ins_proc = SimpleTagInlineProcessor(r"(\+\+)(.+?)(\+\+)", "ins")
|
||||
md.inlinePatterns.register(ins_proc, "ins", 200)
|
||||
|
||||
|
||||
MARKDOWN_EXTENSIONS = ["fenced_code", "tables", "codehilite", "toc", DelInsExtension()]
|
||||
RE_PARTS = dict(
|
||||
USER=r"[A-Za-z0-9._-]*\b",
|
||||
REPO=r"[A-Za-z0-9_]+\b"
|
||||
)
|
||||
|
||||
|
||||
class MentionPattern(Pattern):
|
||||
ANCESTOR_EXCLUDES = ("a",)
|
||||
|
||||
def __init__(self, config, md):
|
||||
MENTION_RE = r"(@({USER})(?:\/({REPO}))?)".format(**RE_PARTS)
|
||||
super(MentionPattern, self).__init__(MENTION_RE, md)
|
||||
self.config = config
|
||||
|
||||
def handleMatch(self, m):
|
||||
label = m.group(2)
|
||||
user = m.group(3)
|
||||
package_name = m.group(4)
|
||||
if package_name:
|
||||
el = ElementTree.Element("a")
|
||||
el.text = label
|
||||
el.set("href", url_for("packages.view", author=user, name=package_name))
|
||||
return el
|
||||
else:
|
||||
el = ElementTree.Element("a")
|
||||
el.text = label
|
||||
el.set("href", url_for("users.profile", username=user))
|
||||
el.set("data-username", user)
|
||||
return el
|
||||
|
||||
|
||||
class MentionExtension(Extension):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MentionExtension, self).__init__(*args, **kwargs)
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
md.ESCAPED_CHARS.append("@")
|
||||
md.inlinePatterns.register(MentionPattern(self.getConfigs(), md), "mention", 20)
|
||||
|
||||
|
||||
MARKDOWN_EXTENSIONS = ["fenced_code", "tables", "codehilite", "toc", DelInsExtension(), MentionExtension()]
|
||||
MARKDOWN_EXTENSION_CONFIG = {
|
||||
"fenced_code": {},
|
||||
"tables": {},
|
||||
@ -109,7 +152,7 @@ def get_headings(html: str):
|
||||
root = []
|
||||
stack = []
|
||||
for heading in headings:
|
||||
this = { "link": heading.get("id") or "", "text": heading.text, "children": [] }
|
||||
this = {"link": heading.get("id") or "", "text": heading.text, "children": []}
|
||||
this_level = int(heading.name[1:]) - 1
|
||||
|
||||
while this_level <= len(stack):
|
||||
@ -123,3 +166,9 @@ def get_headings(html: str):
|
||||
stack.append(this)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
def get_user_mentions(html: str) -> set:
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
links = soup.select("a[data-username]")
|
||||
return set([x.get("data-username") for x in links])
|
||||
|
Loading…
Reference in New Issue
Block a user