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