##// END OF EJS Templates
Fixed multiline quote. Simplified the quote source retrieving
neko259 -
r2133:0208042a default
parent child Browse files
Show More
@@ -1,299 +1,296 b''
1 1 # coding=utf-8
2 2 from xml import etree
3 3
4 4 import re
5 5 import random
6 6 import bbcode
7 7
8 8 from urllib.parse import unquote
9 9
10 10 from django.core.exceptions import ObjectDoesNotExist
11 11 from django.urls import reverse
12 12 from django.utils.translation import ugettext_lazy as _
13 13
14 14 import boards
15 15 from boards import settings
16 16 from neboard.settings import ALLOWED_HOSTS
17 17
18 18
19 19 __author__ = 'neko259'
20 20
21 21
22 22 REFLINK_PATTERN = re.compile(r'^\d+$')
23 23 GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
24 24 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
25 25 ONE_NEWLINE = '\n'
26 26 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
27 27 LINE_BREAK_HTML = '<div class="br"></div>'
28 28 SPOILER_SPACE = '&nbsp;'
29 REGEX_ANY_LINE_BREAK = re.compile(r'(' + LINE_BREAK_HTML + '|\r|\n)')
30 QUOTE_REPLACEMENT = '\g<1>&gt;'
29 31
30 32 MAX_SPOILER_MULTIPLIER = 2
31 33 MAX_SPOILER_SPACE_COUNT = 20
32 34
33 35
34 36 class TextFormatter:
35 37 """
36 38 An interface for formatter that can be used in the text format panel
37 39 """
38 40
39 41 def __init__(self):
40 42 pass
41 43
42 44 name = ''
43 45
44 46 # Left and right tags for the button preview
45 47 preview_left = ''
46 48 preview_right = ''
47 49
48 50 # Tag name to enclose the text in the form
49 51 tag_name = ''
50 52
51 53 has_input = 'false'
52 54 input_prompt = ''
53 55
54 56
55 57 class AutolinkPattern:
56 58 def handleMatch(self, m):
57 59 link_element = etree.Element('a')
58 60 href = m.group(2)
59 61 link_element.set('href', href)
60 62 link_element.text = href
61 63
62 64 return link_element
63 65
64 66
65 67 class QuotePattern(TextFormatter):
66 68 name = '>q'
67 69 preview_left = '<span class="quote">'
68 70 preview_right = '</span>'
69 71
70 72 tag_name = 'quote'
71 73
72 74
73 75 class SpoilerPattern(TextFormatter):
74 76 name = 'spoiler'
75 77 preview_left = '<span class="spoiler">'
76 78 preview_right = '</span>'
77 79
78 80 tag_name = 'spoiler'
79 81
80 82
81 83 class CommentPattern(TextFormatter):
82 84 name = ''
83 85 preview_left = '<span class="comment">// '
84 86 preview_right = '</span>'
85 87
86 88 tag_name = 'comment'
87 89
88 90
89 91 # TODO Use <s> tag here
90 92 class StrikeThroughPattern(TextFormatter):
91 93 name = 's'
92 94 preview_left = '<span class="strikethrough">'
93 95 preview_right = '</span>'
94 96
95 97 tag_name = 's'
96 98
97 99
98 100 class ItalicPattern(TextFormatter):
99 101 name = 'i'
100 102 preview_left = '<i>'
101 103 preview_right = '</i>'
102 104
103 105 tag_name = 'i'
104 106
105 107
106 108 class BoldPattern(TextFormatter):
107 109 name = 'b'
108 110 preview_left = '<b>'
109 111 preview_right = '</b>'
110 112
111 113 tag_name = 'b'
112 114
113 115
114 116 class CodePattern(TextFormatter):
115 117 name = 'code'
116 118 preview_left = '<code>'
117 119 preview_right = '</code>'
118 120
119 121 tag_name = 'code'
120 122
121 123
122 124 class HintPattern(TextFormatter):
123 125 name = 'hint'
124 126 preview_left = '<span class="hint">'
125 127 preview_right = '</span>'
126 128
127 129 tag_name = 'hint'
128 130
129 131
130 132 class ColorPattern(TextFormatter):
131 133 name = 'color'
132 134 preview_left = '<span style="color:yellow;">'
133 135 preview_right = '</span>'
134 136
135 137 tag_name = 'color'
136 138
137 139 has_input = 'true'
138 140 input_prompt = _('Enter color (name or #ccc code)')
139 141
140 142
141 143 def render_reflink(tag_name, value, options, parent, context):
142 144 result = '>>%s' % value
143 145
144 146 post = None
145 147 if REFLINK_PATTERN.match(value):
146 148 post_id = int(value)
147 149
148 150 try:
149 151 post = boards.models.Post.objects.get(id=post_id)
150 152
151 153 except ObjectDoesNotExist:
152 154 pass
153 155 elif GLOBAL_REFLINK_PATTERN.match(value):
154 156 match = GLOBAL_REFLINK_PATTERN.search(value)
155 157 try:
156 158 global_id = boards.models.GlobalId.objects.get(
157 159 key_type=match.group(1), key=match.group(2),
158 160 local_id=match.group(3))
159 161 post = global_id.post
160 162 except ObjectDoesNotExist:
161 163 pass
162 164
163 165 if post is not None:
164 166 result = post.get_link_view()
165 167
166 168 return result
167 169
168 170
169 171 def render_quote(tag_name, value, options, parent, context):
170 source = ''
171 if 'source' in options:
172 source = options['source']
173 elif 'quote' in options:
174 source = options['quote']
172 source = options.get('quote') or options.get('source')
175 173
176 174 if source:
177 175 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
178 176 else:
179 177 # Insert a ">" at the start of every line
180 178 result = '<span class="quote">&gt;{}</span>'.format(
181 value.replace(LINE_BREAK_HTML,
182 '{}&gt;'.format(LINE_BREAK_HTML)))
179 REGEX_ANY_LINE_BREAK.sub(QUOTE_REPLACEMENT, value))
183 180
184 181 return result
185 182
186 183
187 184 def render_hint(tag_name, value, options, parent, context):
188 185 if 'hint' in options:
189 186 hint = options['hint']
190 187 result = '<span class="hint" title="{}">{}</span>'.format(hint, value)
191 188 else:
192 189 result = value
193 190 return result
194 191
195 192
196 193 def render_notification(tag_name, value, options, parent, content):
197 194 username = value.lower()
198 195
199 196 return '<a href="{}" class="user-cast">@{}</a>'.format(
200 197 reverse('notifications', kwargs={'username': username}), username)
201 198
202 199
203 200 def render_tag(tag_name, value, options, parent, context):
204 201 tag_name = value.lower()
205 202
206 203 tag = boards.models.Tag.objects.get_by_alias(tag_name)
207 204 if tag:
208 205 url = tag.get_view()
209 206 else:
210 207 url = tag_name
211 208
212 209 return url
213 210
214 211
215 212 def render_spoiler(tag_name, value, options, parent, context):
216 213 if settings.get_bool('Forms', 'AdditionalSpoilerSpaces'):
217 214 text_len = len(value)
218 215 space_count = min(random.randint(0, text_len * MAX_SPOILER_MULTIPLIER),
219 216 MAX_SPOILER_SPACE_COUNT)
220 217 side_spaces = SPOILER_SPACE * (space_count // 2)
221 218 else:
222 219 side_spaces = ''
223 220 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces,
224 221 value, side_spaces)
225 222
226 223
227 224 formatters = [
228 225 QuotePattern,
229 226 SpoilerPattern,
230 227 ItalicPattern,
231 228 BoldPattern,
232 229 CommentPattern,
233 230 StrikeThroughPattern,
234 231 CodePattern,
235 232 HintPattern,
236 233 ColorPattern,
237 234 ]
238 235
239 236
240 237 PREPARSE_PATTERNS = {
241 238 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
242 239 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
243 240 r'^//\s?(.+)': r'[comment]\1[/comment]', # Comment "//text"
244 241 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
245 242 }
246 243
247 244 for hostname in ALLOWED_HOSTS:
248 245 if hostname != '*':
249 246 PREPARSE_PATTERNS[r'https?://{}/thread/\d+/#(\d+)/?'.format(hostname)] = r'[post]\1[/post]'
250 247 PREPARSE_PATTERNS[r'https?://{}/thread/(\d+)/?'.format(hostname)] = r'[post]\1[/post]'
251 248
252 249
253 250 class Parser:
254 251 def __init__(self):
255 252 # The newline hack is added because br's margin does not work in all
256 253 # browsers except firefox, when the div's does.
257 254 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
258 255
259 256 self.parser.add_formatter('post', render_reflink, strip=True)
260 257 self.parser.add_formatter('quote', render_quote, strip=True)
261 258 self.parser.add_formatter('hint', render_hint, strip=True)
262 259 self.parser.add_formatter('user', render_notification, strip=True)
263 260 self.parser.add_formatter('tag', render_tag, strip=True)
264 261 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
265 262 self.parser.add_simple_formatter(
266 263 'comment', '<span class="comment">// %(value)s</span>', strip=True)
267 264 self.parser.add_simple_formatter(
268 265 's', '<span class="strikethrough">%(value)s</span>')
269 266 self.parser.add_simple_formatter('code',
270 267 '<pre><code>%(value)s</pre></code>',
271 268 render_embedded=False,
272 269 escape_html=True,
273 270 replace_links=False,
274 271 replace_cosmetic=False)
275 272
276 273 def preparse(self, text):
277 274 """
278 275 Performs manual parsing before the bbcode parser is used.
279 276 Preparsed text is saved as raw and the text before preparsing is lost.
280 277 """
281 278 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
282 279
283 280 for key, value in PREPARSE_PATTERNS.items():
284 281 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
285 282
286 283 for link in REGEX_URL.findall(text):
287 284 new_text = new_text.replace(link, unquote(link))
288 285
289 286 return new_text
290 287
291 288 def parse(self, text):
292 289 return self.parser.format(text)
293 290
294 291
295 292 parser = Parser()
296 293
297 294
298 295 def get_parser():
299 296 return parser
General Comments 0
You need to be logged in to leave comments. Login now