##// END OF EJS Templates
Fixed multiline quote. Simplified the quote source retrieving
Fixed multiline quote. Simplified the quote source retrieving

File last commit:

r2133:0208042a default
r2133:0208042a default
Show More
mdx_neboard.py
296 lines | 7.7 KiB | text/x-python | PythonLexer
# coding=utf-8
from xml import etree
import re
import random
import bbcode
from urllib.parse import unquote
from django.core.exceptions import ObjectDoesNotExist
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
import boards
from boards import settings
from neboard.settings import ALLOWED_HOSTS
__author__ = 'neko259'
REFLINK_PATTERN = re.compile(r'^\d+$')
GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
ONE_NEWLINE = '\n'
REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
LINE_BREAK_HTML = '<div class="br"></div>'
SPOILER_SPACE = '&nbsp;'
REGEX_ANY_LINE_BREAK = re.compile(r'(' + LINE_BREAK_HTML + '|\r|\n)')
QUOTE_REPLACEMENT = '\g<1>&gt;'
MAX_SPOILER_MULTIPLIER = 2
MAX_SPOILER_SPACE_COUNT = 20
class TextFormatter:
"""
An interface for formatter that can be used in the text format panel
"""
def __init__(self):
pass
name = ''
# Left and right tags for the button preview
preview_left = ''
preview_right = ''
# Tag name to enclose the text in the form
tag_name = ''
has_input = 'false'
input_prompt = ''
class AutolinkPattern:
def handleMatch(self, m):
link_element = etree.Element('a')
href = m.group(2)
link_element.set('href', href)
link_element.text = href
return link_element
class QuotePattern(TextFormatter):
name = '>q'
preview_left = '<span class="quote">'
preview_right = '</span>'
tag_name = 'quote'
class SpoilerPattern(TextFormatter):
name = 'spoiler'
preview_left = '<span class="spoiler">'
preview_right = '</span>'
tag_name = 'spoiler'
class CommentPattern(TextFormatter):
name = ''
preview_left = '<span class="comment">// '
preview_right = '</span>'
tag_name = 'comment'
# TODO Use <s> tag here
class StrikeThroughPattern(TextFormatter):
name = 's'
preview_left = '<span class="strikethrough">'
preview_right = '</span>'
tag_name = 's'
class ItalicPattern(TextFormatter):
name = 'i'
preview_left = '<i>'
preview_right = '</i>'
tag_name = 'i'
class BoldPattern(TextFormatter):
name = 'b'
preview_left = '<b>'
preview_right = '</b>'
tag_name = 'b'
class CodePattern(TextFormatter):
name = 'code'
preview_left = '<code>'
preview_right = '</code>'
tag_name = 'code'
class HintPattern(TextFormatter):
name = 'hint'
preview_left = '<span class="hint">'
preview_right = '</span>'
tag_name = 'hint'
class ColorPattern(TextFormatter):
name = 'color'
preview_left = '<span style="color:yellow;">'
preview_right = '</span>'
tag_name = 'color'
has_input = 'true'
input_prompt = _('Enter color (name or #ccc code)')
def render_reflink(tag_name, value, options, parent, context):
result = '>>%s' % value
post = None
if REFLINK_PATTERN.match(value):
post_id = int(value)
try:
post = boards.models.Post.objects.get(id=post_id)
except ObjectDoesNotExist:
pass
elif GLOBAL_REFLINK_PATTERN.match(value):
match = GLOBAL_REFLINK_PATTERN.search(value)
try:
global_id = boards.models.GlobalId.objects.get(
key_type=match.group(1), key=match.group(2),
local_id=match.group(3))
post = global_id.post
except ObjectDoesNotExist:
pass
if post is not None:
result = post.get_link_view()
return result
def render_quote(tag_name, value, options, parent, context):
source = options.get('quote') or options.get('source')
if source:
result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
else:
# Insert a ">" at the start of every line
result = '<span class="quote">&gt;{}</span>'.format(
REGEX_ANY_LINE_BREAK.sub(QUOTE_REPLACEMENT, value))
return result
def render_hint(tag_name, value, options, parent, context):
if 'hint' in options:
hint = options['hint']
result = '<span class="hint" title="{}">{}</span>'.format(hint, value)
else:
result = value
return result
def render_notification(tag_name, value, options, parent, content):
username = value.lower()
return '<a href="{}" class="user-cast">@{}</a>'.format(
reverse('notifications', kwargs={'username': username}), username)
def render_tag(tag_name, value, options, parent, context):
tag_name = value.lower()
tag = boards.models.Tag.objects.get_by_alias(tag_name)
if tag:
url = tag.get_view()
else:
url = tag_name
return url
def render_spoiler(tag_name, value, options, parent, context):
if settings.get_bool('Forms', 'AdditionalSpoilerSpaces'):
text_len = len(value)
space_count = min(random.randint(0, text_len * MAX_SPOILER_MULTIPLIER),
MAX_SPOILER_SPACE_COUNT)
side_spaces = SPOILER_SPACE * (space_count // 2)
else:
side_spaces = ''
return '<span class="spoiler">{}{}{}</span>'.format(side_spaces,
value, side_spaces)
formatters = [
QuotePattern,
SpoilerPattern,
ItalicPattern,
BoldPattern,
CommentPattern,
StrikeThroughPattern,
CodePattern,
HintPattern,
ColorPattern,
]
PREPARSE_PATTERNS = {
r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
r'^//\s?(.+)': r'[comment]\1[/comment]', # Comment "//text"
r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
}
for hostname in ALLOWED_HOSTS:
if hostname != '*':
PREPARSE_PATTERNS[r'https?://{}/thread/\d+/#(\d+)/?'.format(hostname)] = r'[post]\1[/post]'
PREPARSE_PATTERNS[r'https?://{}/thread/(\d+)/?'.format(hostname)] = r'[post]\1[/post]'
class Parser:
def __init__(self):
# The newline hack is added because br's margin does not work in all
# browsers except firefox, when the div's does.
self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
self.parser.add_formatter('post', render_reflink, strip=True)
self.parser.add_formatter('quote', render_quote, strip=True)
self.parser.add_formatter('hint', render_hint, strip=True)
self.parser.add_formatter('user', render_notification, strip=True)
self.parser.add_formatter('tag', render_tag, strip=True)
self.parser.add_formatter('spoiler', render_spoiler, strip=True)
self.parser.add_simple_formatter(
'comment', '<span class="comment">// %(value)s</span>', strip=True)
self.parser.add_simple_formatter(
's', '<span class="strikethrough">%(value)s</span>')
self.parser.add_simple_formatter('code',
'<pre><code>%(value)s</pre></code>',
render_embedded=False,
escape_html=True,
replace_links=False,
replace_cosmetic=False)
def preparse(self, text):
"""
Performs manual parsing before the bbcode parser is used.
Preparsed text is saved as raw and the text before preparsing is lost.
"""
new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
for key, value in PREPARSE_PATTERNS.items():
new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
for link in REGEX_URL.findall(text):
new_text = new_text.replace(link, unquote(link))
return new_text
def parse(self, text):
return self.parser.format(text)
parser = Parser()
def get_parser():
return parser