##// END OF EJS Templates
Show even number of spaces before and after spoiler
neko259 -
r1459:be8f90ac default
parent child Browse files
Show More
@@ -1,236 +1,236 b''
1 # coding=utf-8
1 # coding=utf-8
2
2
3 import re
3 import re
4 import random
4 import random
5 import bbcode
5 import bbcode
6
6
7 from urllib.parse import unquote
7 from urllib.parse import unquote
8
8
9 from django.core.exceptions import ObjectDoesNotExist
9 from django.core.exceptions import ObjectDoesNotExist
10 from django.core.urlresolvers import reverse
10 from django.core.urlresolvers import reverse
11
11
12 import boards
12 import boards
13
13
14
14
15 __author__ = 'neko259'
15 __author__ = 'neko259'
16
16
17
17
18 REFLINK_PATTERN = re.compile(r'^\d+$')
18 REFLINK_PATTERN = re.compile(r'^\d+$')
19 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
19 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
20 ONE_NEWLINE = '\n'
20 ONE_NEWLINE = '\n'
21 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
21 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
22 LINE_BREAK_HTML = '<div class="br"></div>'
22 LINE_BREAK_HTML = '<div class="br"></div>'
23
23
24
24
25 class TextFormatter():
25 class TextFormatter():
26 """
26 """
27 An interface for formatter that can be used in the text format panel
27 An interface for formatter that can be used in the text format panel
28 """
28 """
29
29
30 def __init__(self):
30 def __init__(self):
31 pass
31 pass
32
32
33 name = ''
33 name = ''
34
34
35 # Left and right tags for the button preview
35 # Left and right tags for the button preview
36 preview_left = ''
36 preview_left = ''
37 preview_right = ''
37 preview_right = ''
38
38
39 # Left and right characters for the textarea input
39 # Left and right characters for the textarea input
40 format_left = ''
40 format_left = ''
41 format_right = ''
41 format_right = ''
42
42
43
43
44 class AutolinkPattern():
44 class AutolinkPattern():
45 def handleMatch(self, m):
45 def handleMatch(self, m):
46 link_element = etree.Element('a')
46 link_element = etree.Element('a')
47 href = m.group(2)
47 href = m.group(2)
48 link_element.set('href', href)
48 link_element.set('href', href)
49 link_element.text = href
49 link_element.text = href
50
50
51 return link_element
51 return link_element
52
52
53
53
54 class QuotePattern(TextFormatter):
54 class QuotePattern(TextFormatter):
55 name = '>q'
55 name = '>q'
56 preview_left = '<span class="quote">'
56 preview_left = '<span class="quote">'
57 preview_right = '</span>'
57 preview_right = '</span>'
58
58
59 format_left = '[quote]'
59 format_left = '[quote]'
60 format_right = '[/quote]'
60 format_right = '[/quote]'
61
61
62
62
63 class SpoilerPattern(TextFormatter):
63 class SpoilerPattern(TextFormatter):
64 name = 'spoiler'
64 name = 'spoiler'
65 preview_left = '<span class="spoiler">'
65 preview_left = '<span class="spoiler">'
66 preview_right = '</span>'
66 preview_right = '</span>'
67
67
68 format_left = '[spoiler]'
68 format_left = '[spoiler]'
69 format_right = '[/spoiler]'
69 format_right = '[/spoiler]'
70
70
71
71
72 class CommentPattern(TextFormatter):
72 class CommentPattern(TextFormatter):
73 name = ''
73 name = ''
74 preview_left = '<span class="comment">// '
74 preview_left = '<span class="comment">// '
75 preview_right = '</span>'
75 preview_right = '</span>'
76
76
77 format_left = '[comment]'
77 format_left = '[comment]'
78 format_right = '[/comment]'
78 format_right = '[/comment]'
79
79
80
80
81 # TODO Use <s> tag here
81 # TODO Use <s> tag here
82 class StrikeThroughPattern(TextFormatter):
82 class StrikeThroughPattern(TextFormatter):
83 name = 's'
83 name = 's'
84 preview_left = '<span class="strikethrough">'
84 preview_left = '<span class="strikethrough">'
85 preview_right = '</span>'
85 preview_right = '</span>'
86
86
87 format_left = '[s]'
87 format_left = '[s]'
88 format_right = '[/s]'
88 format_right = '[/s]'
89
89
90
90
91 class ItalicPattern(TextFormatter):
91 class ItalicPattern(TextFormatter):
92 name = 'i'
92 name = 'i'
93 preview_left = '<i>'
93 preview_left = '<i>'
94 preview_right = '</i>'
94 preview_right = '</i>'
95
95
96 format_left = '[i]'
96 format_left = '[i]'
97 format_right = '[/i]'
97 format_right = '[/i]'
98
98
99
99
100 class BoldPattern(TextFormatter):
100 class BoldPattern(TextFormatter):
101 name = 'b'
101 name = 'b'
102 preview_left = '<b>'
102 preview_left = '<b>'
103 preview_right = '</b>'
103 preview_right = '</b>'
104
104
105 format_left = '[b]'
105 format_left = '[b]'
106 format_right = '[/b]'
106 format_right = '[/b]'
107
107
108
108
109 class CodePattern(TextFormatter):
109 class CodePattern(TextFormatter):
110 name = 'code'
110 name = 'code'
111 preview_left = '<code>'
111 preview_left = '<code>'
112 preview_right = '</code>'
112 preview_right = '</code>'
113
113
114 format_left = '[code]'
114 format_left = '[code]'
115 format_right = '[/code]'
115 format_right = '[/code]'
116
116
117
117
118 def render_reflink(tag_name, value, options, parent, context):
118 def render_reflink(tag_name, value, options, parent, context):
119 result = '>>%s' % value
119 result = '>>%s' % value
120
120
121 if REFLINK_PATTERN.match(value):
121 if REFLINK_PATTERN.match(value):
122 post_id = int(value)
122 post_id = int(value)
123
123
124 try:
124 try:
125 post = boards.models.Post.objects.get(id=post_id)
125 post = boards.models.Post.objects.get(id=post_id)
126
126
127 result = post.get_link_view()
127 result = post.get_link_view()
128 except ObjectDoesNotExist:
128 except ObjectDoesNotExist:
129 pass
129 pass
130
130
131 return result
131 return result
132
132
133
133
134 def render_quote(tag_name, value, options, parent, context):
134 def render_quote(tag_name, value, options, parent, context):
135 source = ''
135 source = ''
136 if 'source' in options:
136 if 'source' in options:
137 source = options['source']
137 source = options['source']
138 elif 'quote' in options:
138 elif 'quote' in options:
139 source = options['quote']
139 source = options['quote']
140
140
141 if source:
141 if source:
142 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
142 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
143 else:
143 else:
144 # Insert a ">" at the start of every line
144 # Insert a ">" at the start of every line
145 result = '<span class="quote">&gt;{}</span>'.format(
145 result = '<span class="quote">&gt;{}</span>'.format(
146 value.replace(LINE_BREAK_HTML,
146 value.replace(LINE_BREAK_HTML,
147 '{}&gt;'.format(LINE_BREAK_HTML)))
147 '{}&gt;'.format(LINE_BREAK_HTML)))
148
148
149 return result
149 return result
150
150
151
151
152 def render_notification(tag_name, value, options, parent, content):
152 def render_notification(tag_name, value, options, parent, content):
153 username = value.lower()
153 username = value.lower()
154
154
155 return '<a href="{}" class="user-cast">@{}</a>'.format(
155 return '<a href="{}" class="user-cast">@{}</a>'.format(
156 reverse('notifications', kwargs={'username': username}), username)
156 reverse('notifications', kwargs={'username': username}), username)
157
157
158
158
159 def render_tag(tag_name, value, options, parent, context):
159 def render_tag(tag_name, value, options, parent, context):
160 tag_name = value.lower()
160 tag_name = value.lower()
161
161
162 try:
162 try:
163 url = boards.models.Tag.objects.get(name=tag_name).get_view()
163 url = boards.models.Tag.objects.get(name=tag_name).get_view()
164 except ObjectDoesNotExist:
164 except ObjectDoesNotExist:
165 url = tag_name
165 url = tag_name
166
166
167 return url
167 return url
168
168
169
169
170 def render_spoiler(tag_name, value, options, parent, context):
170 def render_spoiler(tag_name, value, options, parent, context):
171 text_len = len(value)
171 text_len = len(value)
172 pre_spaces = '&nbsp;' * max(random.randint(0, text_len // 2), 2)
172 space_count = random.randint(0, text_len * 2)
173 post_spaces = '&nbsp;' * max(random.randint(0, text_len // 2), 2)
173 side_spaces = '&nbsp;' * (space_count // 2)
174 return '<span class="spoiler">{}{}{}</span>'.format(pre_spaces, value,
174 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value,
175 post_spaces)
175 side_spaces)
176
176
177 return quote_element
177 return quote_element
178
178
179
179
180
180
181 formatters = [
181 formatters = [
182 QuotePattern,
182 QuotePattern,
183 SpoilerPattern,
183 SpoilerPattern,
184 ItalicPattern,
184 ItalicPattern,
185 BoldPattern,
185 BoldPattern,
186 CommentPattern,
186 CommentPattern,
187 StrikeThroughPattern,
187 StrikeThroughPattern,
188 CodePattern,
188 CodePattern,
189 ]
189 ]
190
190
191
191
192 PREPARSE_PATTERNS = {
192 PREPARSE_PATTERNS = {
193 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
193 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
194 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
194 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
195 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
195 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
196 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
196 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
197 }
197 }
198
198
199
199
200 class Parser:
200 class Parser:
201 def __init__(self):
201 def __init__(self):
202 # The newline hack is added because br's margin does not work in all
202 # The newline hack is added because br's margin does not work in all
203 # browsers except firefox, when the div's does.
203 # browsers except firefox, when the div's does.
204 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
204 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
205
205
206 self.parser.add_formatter('post', render_reflink, strip=True)
206 self.parser.add_formatter('post', render_reflink, strip=True)
207 self.parser.add_formatter('quote', render_quote, strip=True)
207 self.parser.add_formatter('quote', render_quote, strip=True)
208 self.parser.add_formatter('user', render_notification, strip=True)
208 self.parser.add_formatter('user', render_notification, strip=True)
209 self.parser.add_formatter('tag', render_tag, strip=True)
209 self.parser.add_formatter('tag', render_tag, strip=True)
210 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
210 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
211 self.parser.add_simple_formatter(
211 self.parser.add_simple_formatter(
212 'comment', '<span class="comment">//%(value)s</span>')
212 'comment', '<span class="comment">//%(value)s</span>')
213 self.parser.add_simple_formatter(
213 self.parser.add_simple_formatter(
214 's', '<span class="strikethrough">%(value)s</span>')
214 's', '<span class="strikethrough">%(value)s</span>')
215 # TODO Why not use built-in tag?
215 # TODO Why not use built-in tag?
216 self.parser.add_simple_formatter('code',
216 self.parser.add_simple_formatter('code',
217 '<pre><code>%(value)s</pre></code>',
217 '<pre><code>%(value)s</pre></code>',
218 render_embedded=False)
218 render_embedded=False)
219
219
220 def preparse(self, text):
220 def preparse(self, text):
221 """
221 """
222 Performs manual parsing before the bbcode parser is used.
222 Performs manual parsing before the bbcode parser is used.
223 Preparsed text is saved as raw and the text before preparsing is lost.
223 Preparsed text is saved as raw and the text before preparsing is lost.
224 """
224 """
225 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
225 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
226
226
227 for key, value in PREPARSE_PATTERNS.items():
227 for key, value in PREPARSE_PATTERNS.items():
228 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
228 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
229
229
230 for link in REGEX_URL.findall(text):
230 for link in REGEX_URL.findall(text):
231 new_text = new_text.replace(link, unquote(link))
231 new_text = new_text.replace(link, unquote(link))
232
232
233 return new_text
233 return new_text
234
234
235 def parse(self, text):
235 def parse(self, text):
236 return self.parser.format(text)
236 return self.parser.format(text)
General Comments 0
You need to be logged in to leave comments. Login now