mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-03 11:47:28 +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 import *
|
||||||
from flask_babel import gettext, lazy_gettext
|
from flask_babel import gettext, lazy_gettext
|
||||||
|
|
||||||
|
from app.markdown import get_user_mentions, render_markdown
|
||||||
from app.tasks.webhooktasks import post_discord_webhook
|
from app.tasks.webhooktasks import post_discord_webhook
|
||||||
|
|
||||||
bp = Blueprint("threads", __name__)
|
bp = Blueprint("threads", __name__)
|
||||||
@ -238,6 +239,15 @@ def view(id):
|
|||||||
if not current_user in thread.watchers:
|
if not current_user in thread.watchers:
|
||||||
thread.watchers.append(current_user)
|
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)
|
msg = "New comment on '{}'".format(thread.title)
|
||||||
addNotification(thread.watchers, current_user, NotificationType.THREAD_REPLY, msg, thread.getViewURL(), thread.package)
|
addNotification(thread.watchers, current_user, NotificationType.THREAD_REPLY, msg, thread.getViewURL(), thread.package)
|
||||||
|
|
||||||
@ -335,6 +345,15 @@ def new():
|
|||||||
if is_review_thread:
|
if is_review_thread:
|
||||||
package.review_thread = 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)
|
notif_msg = "New thread '{}'".format(thread.title)
|
||||||
if package is not None:
|
if package is not None:
|
||||||
addNotification(package.maintainers, current_user, NotificationType.NEW_THREAD, notif_msg, thread.getViewURL(), package)
|
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()
|
approvers = User.query.filter(User.rank >= UserRank.APPROVER).all()
|
||||||
addNotification(approvers, current_user, NotificationType.EDITOR_MISC, notif_msg, thread.getViewURL(), package)
|
addNotification(approvers, current_user, NotificationType.EDITOR_MISC, notif_msg, thread.getViewURL(), package)
|
||||||
|
|
||||||
|
|
||||||
if is_review_thread:
|
if is_review_thread:
|
||||||
post_discord_webhook.delay(current_user.username,
|
post_discord_webhook.delay(current_user.username,
|
||||||
"Opened approval thread: {}".format(thread.getViewURL(absolute=True)), True)
|
"Opened approval thread: {}".format(thread.getViewURL(absolute=True)), True)
|
||||||
|
@ -5,10 +5,11 @@ from bleach import Cleaner
|
|||||||
from bleach.linkifier import LinkifyFilter
|
from bleach.linkifier import LinkifyFilter
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from markdown import Markdown
|
from markdown import Markdown
|
||||||
from flask import Markup
|
from flask import Markup, url_for
|
||||||
from markdown.extensions import Extension
|
from markdown.extensions import Extension
|
||||||
from markdown.inlinepatterns import SimpleTagInlineProcessor
|
from markdown.inlinepatterns import SimpleTagInlineProcessor
|
||||||
|
from markdown.inlinepatterns import Pattern
|
||||||
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
# Based on
|
# Based on
|
||||||
# https://github.com/Wenzil/mdx_bleach/blob/master/mdx_bleach/whitelist.py
|
# 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",
|
"s2", "se", "sh", "si", "sx", "sr", "s1", "ss", "bp", "fm", "vc", "vg", "vi", "vm", "il",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def allow_class(_tag, name, value):
|
def allow_class(_tag, name, value):
|
||||||
return name == "class" and value in ALLOWED_CSS
|
return name == "class" and value in ALLOWED_CSS
|
||||||
|
|
||||||
|
|
||||||
ALLOWED_ATTRIBUTES = {
|
ALLOWED_ATTRIBUTES = {
|
||||||
"h1": ["id"],
|
"h1": ["id"],
|
||||||
"h2": ["id"],
|
"h2": ["id"],
|
||||||
"h3": ["id"],
|
"h3": ["id"],
|
||||||
"h4": ["id"],
|
"h4": ["id"],
|
||||||
"a": ["href", "title"],
|
"a": ["href", "title", "data-username"],
|
||||||
"img": ["src", "title", "alt"],
|
"img": ["src", "title", "alt"],
|
||||||
"code": allow_class,
|
"code": allow_class,
|
||||||
"div": allow_class,
|
"div": allow_class,
|
||||||
@ -64,23 +67,63 @@ def render_markdown(source):
|
|||||||
html = md.convert(source)
|
html = md.convert(source)
|
||||||
|
|
||||||
cleaner = Cleaner(
|
cleaner = Cleaner(
|
||||||
tags=ALLOWED_TAGS,
|
tags=ALLOWED_TAGS,
|
||||||
attributes=ALLOWED_ATTRIBUTES,
|
attributes=ALLOWED_ATTRIBUTES,
|
||||||
protocols=ALLOWED_PROTOCOLS,
|
protocols=ALLOWED_PROTOCOLS,
|
||||||
filters=[partial(LinkifyFilter, callbacks=bleach.linkifier.DEFAULT_CALLBACKS)])
|
filters=[partial(LinkifyFilter, callbacks=bleach.linkifier.DEFAULT_CALLBACKS)])
|
||||||
return cleaner.clean(html)
|
return cleaner.clean(html)
|
||||||
|
|
||||||
|
|
||||||
class DelInsExtension(Extension):
|
class DelInsExtension(Extension):
|
||||||
def extendMarkdown(self, md):
|
def extendMarkdown(self, md):
|
||||||
del_proc = SimpleTagInlineProcessor(r'(\~\~)(.+?)(\~\~)', 'del')
|
del_proc = SimpleTagInlineProcessor(r"(\~\~)(.+?)(\~\~)", "del")
|
||||||
md.inlinePatterns.register(del_proc, 'del', 200)
|
md.inlinePatterns.register(del_proc, "del", 200)
|
||||||
|
|
||||||
ins_proc = SimpleTagInlineProcessor(r'(\+\+)(.+?)(\+\+)', 'ins')
|
ins_proc = SimpleTagInlineProcessor(r"(\+\+)(.+?)(\+\+)", "ins")
|
||||||
md.inlinePatterns.register(ins_proc, 'ins', 200)
|
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 = {
|
MARKDOWN_EXTENSION_CONFIG = {
|
||||||
"fenced_code": {},
|
"fenced_code": {},
|
||||||
"tables": {},
|
"tables": {},
|
||||||
@ -109,7 +152,7 @@ def get_headings(html: str):
|
|||||||
root = []
|
root = []
|
||||||
stack = []
|
stack = []
|
||||||
for heading in headings:
|
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
|
this_level = int(heading.name[1:]) - 1
|
||||||
|
|
||||||
while this_level <= len(stack):
|
while this_level <= len(stack):
|
||||||
@ -123,3 +166,9 @@ def get_headings(html: str):
|
|||||||
stack.append(this)
|
stack.append(this)
|
||||||
|
|
||||||
return root
|
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