PCv5/app/utils/markdown_extensions/pclinks.py

152 lines
4.5 KiB
Python

'''
PClinks Extension for Python-Markdown
======================================
Converts [[type:id]] to relative links.
Based on <https://Python-Markdown.github.io/extensions/wikilinks>.
Original code Copyright [Waylan Limberg](http://achinghead.com/).
License: [BSD](https://opensource.org/licenses/bsd-license.php)
'''
from markdown.extensions import Extension
from markdown.inlinepatterns import InlineProcessor
from uuid import UUID
import xml.etree.ElementTree as etree
from flask import url_for, render_template
from flask_login import current_user
from app.utils.unicode_names import normalize
from app.utils.filters.humanize import humanize
from app.models.poll import Poll
from app.models.topic import Topic
from app.models.user import Member
from app.models.attachment import Attachment
class PCLinkExtension(Extension):
def __init__(self, **kwargs):
self.config = {
# 'base_url': ['/', 'String to append to beginning or URL.'],
# 'end_url': ['/', 'String to append to end of URL.'],
# 'html_class': ['pclink', 'CSS hook. Leave blank for none.'],
}
super().__init__(**kwargs)
def extendMarkdown(self, md):
self.md = md
# append to end of inline patterns
PCLINK_RE = r'<([a-z]+): ?([\w-]+)>'
pclinkPattern = PCLinksInlineProcessor(PCLINK_RE, self.getConfigs())
pclinkPattern.md = md
md.inlinePatterns.register(pclinkPattern, 'pclink', 135)
class PCLinksInlineProcessor(InlineProcessor):
def __init__(self, pattern, config):
super().__init__(pattern)
self.config = config
self.handles = {
'membre': handleUser, 'user': handleUser, 'u': handleUser,
'sondage': handlePoll, 'poll': handlePoll,
'topic': handleTopic, 't': handleTopic,
'fichier': handleFile, 'file': handleFile, 'f': handleFile,
}
def handleMatch(self, m, data):
link_type = m.group(1).strip()
if link_type in self.handles:
content_id = m.group(2).strip()
a = self.handles[link_type](content_id, data)
else:
a = ''
return a, m.start(0), m.end(0)
# pclinks are links defined as [[type:content_id]]
# To add a custom handle, create a function and add it to processor's handles
# A custom handle takes two arguments:
# - content_id: as defined
# - context: the block in which the link has been found
# It should return:
# - either a string, which will be html-escaped
# - either an xml.etree.ElementTree
def handlePoll(content_id, context):
try:
id = int(content_id)
except ValueError:
return "[ID de sondage invalide]"
poll = Poll.query.get(content_id)
if poll is None:
return "[Sondage non trouvé]"
html = render_template('widgets/poll.html', poll=poll)
html = html.replace('\n', '') # Needed to avoid lots of <br> due to etree
return etree.fromstring(html)
def handleTopic(content_id, context):
try:
id = int(content_id)
except ValueError:
return "[ID de topic invalide]"
topic = Topic.query.get(content_id)
if topic is None:
return "[Topic non trouvé]"
a = etree.Element('a')
a.text = topic.title
a.set('href', url_for('forum_topic', f=topic.forum, page=(topic,1)))
a.set('class', 'topic-link')
return a
def handleUser(content_id, context):
try:
norm = normalize(content_id)
except ValueError:
return "[Nom d'utilisateur invalide]"
member = Member.query.filter_by(norm=norm).first()
if member is None:
return "[Utilisateur non trouvé]"
a = etree.Element('a')
a.text = member.name
a.set('href', url_for('user', username=member.norm))
a.set('class', 'profile-link')
return a
def handleFile(content_id, context):
try:
UUID(content_id)
except ValueError:
return "[ID de fichier invalide]"
file = Attachment.query.get(content_id)
if file is None:
return "[Fichier non trouvé]"
# Manage permissions
# TODO: use Guest methods when implemented
if current_user.is_authenticated:
if not current_user.can_access_file(file):
return "[Accès au fichier refusé]"
else:
if not file.comment.thread.is_default_accessible():
return "[Accès au fichier refusé]"
html = render_template('widgets/download_button.html', file=file)
html = html.replace('\n', '') # Needed to avoid lots of <br> due to etree
return etree.fromstring(html)