##// END OF EJS Templates
Added a setting to enable additional spaces inside spoiler tag
neko259 -
r1832:10b12be1 default
parent child Browse files
Show More
@@ -1,48 +1,49 b''
1 [Version]
1 [Version]
2 Version = 4.1.0 2017
2 Version = 4.1.0 2017
3 SiteName = Neboard DEV
3 SiteName = Neboard DEV
4
4
5 [Cache]
5 [Cache]
6 # Timeout for caching, if cache is used
6 # Timeout for caching, if cache is used
7 CacheTimeout = 600
7 CacheTimeout = 600
8
8
9 [Forms]
9 [Forms]
10 # Max post length in characters
10 # Max post length in characters
11 MaxTextLength = 30000
11 MaxTextLength = 30000
12 MaxFileSize = 8000000
12 MaxFileSize = 8000000
13 LimitFirstPosting = true
13 LimitFirstPosting = true
14 LimitPostingSpeed = false
14 LimitPostingSpeed = false
15 PowDifficulty = 0
15 PowDifficulty = 0
16 # Delay in seconds
16 # Delay in seconds
17 PostingDelay = 30
17 PostingDelay = 30
18 Autoban = false
18 Autoban = false
19 DefaultTag = test
19 DefaultTag = test
20 MaxFileCount = 1
20 MaxFileCount = 1
21 AdditionalSpoilerSpaces = false
21
22
22 [Messages]
23 [Messages]
23 # Thread bumplimit
24 # Thread bumplimit
24 MaxPostsPerThread = 10
25 MaxPostsPerThread = 10
25 ThreadArchiveDays = 300
26 ThreadArchiveDays = 300
26 AnonymousMode = false
27 AnonymousMode = false
27
28
28 [View]
29 [View]
29 DefaultTheme = md
30 DefaultTheme = md
30 DefaultImageViewer = simple
31 DefaultImageViewer = simple
31 LastRepliesCount = 3
32 LastRepliesCount = 3
32 ThreadsPerPage = 3
33 ThreadsPerPage = 3
33 PostsPerPage = 10
34 PostsPerPage = 10
34 ImagesPerPageGallery = 20
35 ImagesPerPageGallery = 20
35 MaxFavoriteThreads = 20
36 MaxFavoriteThreads = 20
36 MaxLandingThreads = 20
37 MaxLandingThreads = 20
37 Themes=md:Mystic Dark,md_centered:Mystic Dark (centered),sw:Snow White,pg:Photon Grey,ad:Amanita Dark,iw:Inocibe White
38 Themes=md:Mystic Dark,md_centered:Mystic Dark (centered),sw:Snow White,pg:Photon Grey,ad:Amanita Dark,iw:Inocibe White
38 ImageViewers=simple:Simple,popup:Popup
39 ImageViewers=simple:Simple,popup:Popup
39
40
40 [Storage]
41 [Storage]
41 # Enable archiving threads instead of deletion when the thread limit is reached
42 # Enable archiving threads instead of deletion when the thread limit is reached
42 ArchiveThreads = true
43 ArchiveThreads = true
43
44
44 [RSS]
45 [RSS]
45 MaxItems = 20
46 MaxItems = 20
46
47
47 [External]
48 [External]
48 ImageSearchHost=
49 ImageSearchHost=
@@ -1,276 +1,282 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 from boards import settings
13
14
14
15
15 __author__ = 'neko259'
16 __author__ = 'neko259'
16
17
17
18
18 REFLINK_PATTERN = re.compile(r'^\d+$')
19 REFLINK_PATTERN = re.compile(r'^\d+$')
19 GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
20 GLOBAL_REFLINK_PATTERN = re.compile(r'(\w+)::([^:]+)::(\d+)')
20 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
21 MULTI_NEWLINES_PATTERN = re.compile(r'(\r?\n){2,}')
21 ONE_NEWLINE = '\n'
22 ONE_NEWLINE = '\n'
22 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
23 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
23 LINE_BREAK_HTML = '<div class="br"></div>'
24 LINE_BREAK_HTML = '<div class="br"></div>'
24 SPOILER_SPACE = '&nbsp;'
25 SPOILER_SPACE = '&nbsp;'
25
26
26 MAX_SPOILER_MULTIPLIER = 2
27 MAX_SPOILER_MULTIPLIER = 2
28 MAX_SPOILER_SPACE_COUNT = 20
27
29
28
30
29 class TextFormatter():
31 class TextFormatter():
30 """
32 """
31 An interface for formatter that can be used in the text format panel
33 An interface for formatter that can be used in the text format panel
32 """
34 """
33
35
34 def __init__(self):
36 def __init__(self):
35 pass
37 pass
36
38
37 name = ''
39 name = ''
38
40
39 # Left and right tags for the button preview
41 # Left and right tags for the button preview
40 preview_left = ''
42 preview_left = ''
41 preview_right = ''
43 preview_right = ''
42
44
43 # Left and right characters for the textarea input
45 # Left and right characters for the textarea input
44 format_left = ''
46 format_left = ''
45 format_right = ''
47 format_right = ''
46
48
47
49
48 class AutolinkPattern():
50 class AutolinkPattern():
49 def handleMatch(self, m):
51 def handleMatch(self, m):
50 link_element = etree.Element('a')
52 link_element = etree.Element('a')
51 href = m.group(2)
53 href = m.group(2)
52 link_element.set('href', href)
54 link_element.set('href', href)
53 link_element.text = href
55 link_element.text = href
54
56
55 return link_element
57 return link_element
56
58
57
59
58 class QuotePattern(TextFormatter):
60 class QuotePattern(TextFormatter):
59 name = '>q'
61 name = '>q'
60 preview_left = '<span class="quote">'
62 preview_left = '<span class="quote">'
61 preview_right = '</span>'
63 preview_right = '</span>'
62
64
63 format_left = '[quote]'
65 format_left = '[quote]'
64 format_right = '[/quote]'
66 format_right = '[/quote]'
65
67
66
68
67 class SpoilerPattern(TextFormatter):
69 class SpoilerPattern(TextFormatter):
68 name = 'spoiler'
70 name = 'spoiler'
69 preview_left = '<span class="spoiler">'
71 preview_left = '<span class="spoiler">'
70 preview_right = '</span>'
72 preview_right = '</span>'
71
73
72 format_left = '[spoiler]'
74 format_left = '[spoiler]'
73 format_right = '[/spoiler]'
75 format_right = '[/spoiler]'
74
76
75
77
76 class CommentPattern(TextFormatter):
78 class CommentPattern(TextFormatter):
77 name = ''
79 name = ''
78 preview_left = '<span class="comment">// '
80 preview_left = '<span class="comment">// '
79 preview_right = '</span>'
81 preview_right = '</span>'
80
82
81 format_left = '[comment]'
83 format_left = '[comment]'
82 format_right = '[/comment]'
84 format_right = '[/comment]'
83
85
84
86
85 # TODO Use <s> tag here
87 # TODO Use <s> tag here
86 class StrikeThroughPattern(TextFormatter):
88 class StrikeThroughPattern(TextFormatter):
87 name = 's'
89 name = 's'
88 preview_left = '<span class="strikethrough">'
90 preview_left = '<span class="strikethrough">'
89 preview_right = '</span>'
91 preview_right = '</span>'
90
92
91 format_left = '[s]'
93 format_left = '[s]'
92 format_right = '[/s]'
94 format_right = '[/s]'
93
95
94
96
95 class ItalicPattern(TextFormatter):
97 class ItalicPattern(TextFormatter):
96 name = 'i'
98 name = 'i'
97 preview_left = '<i>'
99 preview_left = '<i>'
98 preview_right = '</i>'
100 preview_right = '</i>'
99
101
100 format_left = '[i]'
102 format_left = '[i]'
101 format_right = '[/i]'
103 format_right = '[/i]'
102
104
103
105
104 class BoldPattern(TextFormatter):
106 class BoldPattern(TextFormatter):
105 name = 'b'
107 name = 'b'
106 preview_left = '<b>'
108 preview_left = '<b>'
107 preview_right = '</b>'
109 preview_right = '</b>'
108
110
109 format_left = '[b]'
111 format_left = '[b]'
110 format_right = '[/b]'
112 format_right = '[/b]'
111
113
112
114
113 class CodePattern(TextFormatter):
115 class CodePattern(TextFormatter):
114 name = 'code'
116 name = 'code'
115 preview_left = '<code>'
117 preview_left = '<code>'
116 preview_right = '</code>'
118 preview_right = '</code>'
117
119
118 format_left = '[code]'
120 format_left = '[code]'
119 format_right = '[/code]'
121 format_right = '[/code]'
120
122
121
123
122 class HintPattern(TextFormatter):
124 class HintPattern(TextFormatter):
123 name = 'hint'
125 name = 'hint'
124 preview_left = '<span class="hint">'
126 preview_left = '<span class="hint">'
125 preview_right = '</span>'
127 preview_right = '</span>'
126
128
127 format_left = '[hint]'
129 format_left = '[hint]'
128 format_right = '[/hint]'
130 format_right = '[/hint]'
129
131
130
132
131 def render_reflink(tag_name, value, options, parent, context):
133 def render_reflink(tag_name, value, options, parent, context):
132 result = '>>%s' % value
134 result = '>>%s' % value
133
135
134 post = None
136 post = None
135 if REFLINK_PATTERN.match(value):
137 if REFLINK_PATTERN.match(value):
136 post_id = int(value)
138 post_id = int(value)
137
139
138 try:
140 try:
139 post = boards.models.Post.objects.get(id=post_id)
141 post = boards.models.Post.objects.get(id=post_id)
140
142
141 except ObjectDoesNotExist:
143 except ObjectDoesNotExist:
142 pass
144 pass
143 elif GLOBAL_REFLINK_PATTERN.match(value):
145 elif GLOBAL_REFLINK_PATTERN.match(value):
144 match = GLOBAL_REFLINK_PATTERN.search(value)
146 match = GLOBAL_REFLINK_PATTERN.search(value)
145 try:
147 try:
146 global_id = boards.models.GlobalId.objects.get(
148 global_id = boards.models.GlobalId.objects.get(
147 key_type=match.group(1), key=match.group(2),
149 key_type=match.group(1), key=match.group(2),
148 local_id=match.group(3))
150 local_id=match.group(3))
149 post = global_id.post
151 post = global_id.post
150 except ObjectDoesNotExist:
152 except ObjectDoesNotExist:
151 pass
153 pass
152
154
153 if post is not None:
155 if post is not None:
154 result = post.get_link_view()
156 result = post.get_link_view()
155
157
156 return result
158 return result
157
159
158
160
159 def render_quote(tag_name, value, options, parent, context):
161 def render_quote(tag_name, value, options, parent, context):
160 source = ''
162 source = ''
161 if 'source' in options:
163 if 'source' in options:
162 source = options['source']
164 source = options['source']
163 elif 'quote' in options:
165 elif 'quote' in options:
164 source = options['quote']
166 source = options['quote']
165
167
166 if source:
168 if source:
167 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
169 result = '<div class="multiquote"><div class="quote-header">%s</div><div class="quote-text">%s</div></div>' % (source, value)
168 else:
170 else:
169 # Insert a ">" at the start of every line
171 # Insert a ">" at the start of every line
170 result = '<span class="quote">&gt;{}</span>'.format(
172 result = '<span class="quote">&gt;{}</span>'.format(
171 value.replace(LINE_BREAK_HTML,
173 value.replace(LINE_BREAK_HTML,
172 '{}&gt;'.format(LINE_BREAK_HTML)))
174 '{}&gt;'.format(LINE_BREAK_HTML)))
173
175
174 return result
176 return result
175
177
176
178
177 def render_hint(tag_name, value, options, parent, context):
179 def render_hint(tag_name, value, options, parent, context):
178 if 'hint' in options:
180 if 'hint' in options:
179 hint = options['hint']
181 hint = options['hint']
180 result = '<span class="hint" title="{}">{}</span>'.format(hint, value)
182 result = '<span class="hint" title="{}">{}</span>'.format(hint, value)
181 else:
183 else:
182 result = value
184 result = value
183 return result
185 return result
184
186
185
187
186 def render_notification(tag_name, value, options, parent, content):
188 def render_notification(tag_name, value, options, parent, content):
187 username = value.lower()
189 username = value.lower()
188
190
189 return '<a href="{}" class="user-cast">@{}</a>'.format(
191 return '<a href="{}" class="user-cast">@{}</a>'.format(
190 reverse('notifications', kwargs={'username': username}), username)
192 reverse('notifications', kwargs={'username': username}), username)
191
193
192
194
193 def render_tag(tag_name, value, options, parent, context):
195 def render_tag(tag_name, value, options, parent, context):
194 tag_name = value.lower()
196 tag_name = value.lower()
195
197
196 try:
198 try:
197 url = boards.models.Tag.objects.get(name=tag_name).get_view()
199 url = boards.models.Tag.objects.get(name=tag_name).get_view()
198 except ObjectDoesNotExist:
200 except ObjectDoesNotExist:
199 url = tag_name
201 url = tag_name
200
202
201 return url
203 return url
202
204
203
205
204 def render_spoiler(tag_name, value, options, parent, context):
206 def render_spoiler(tag_name, value, options, parent, context):
205 text_len = len(value)
207 if settings.get_bool('Forms', 'AdditionalSpoilerSpaces'):
206 space_count = random.randint(0, text_len * MAX_SPOILER_MULTIPLIER)
208 text_len = len(value)
207 side_spaces = SPOILER_SPACE * (space_count // 2)
209 space_count = min(random.randint(0, text_len * MAX_SPOILER_MULTIPLIER),
208 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces, value,
210 MAX_SPOILER_SPACE_COUNT)
209 side_spaces)
211 side_spaces = SPOILER_SPACE * (space_count // 2)
212 else:
213 side_spaces = ''
214 return '<span class="spoiler">{}{}{}</span>'.format(side_spaces,
215 value, side_spaces)
210
216
211
217
212 formatters = [
218 formatters = [
213 QuotePattern,
219 QuotePattern,
214 SpoilerPattern,
220 SpoilerPattern,
215 ItalicPattern,
221 ItalicPattern,
216 BoldPattern,
222 BoldPattern,
217 CommentPattern,
223 CommentPattern,
218 StrikeThroughPattern,
224 StrikeThroughPattern,
219 CodePattern,
225 CodePattern,
220 HintPattern,
226 HintPattern,
221 ]
227 ]
222
228
223
229
224 PREPARSE_PATTERNS = {
230 PREPARSE_PATTERNS = {
225 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
231 r'(?<!>)>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
226 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
232 r'^>([^>].+)': r'[quote]\1[/quote]', # Quote ">text"
227 r'^//\s?(.+)': r'[comment]\1[/comment]', # Comment "//text"
233 r'^//\s?(.+)': r'[comment]\1[/comment]', # Comment "//text"
228 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
234 r'\B@(\w+)': r'[user]\1[/user]', # User notification "@user"
229 }
235 }
230
236
231
237
232 class Parser:
238 class Parser:
233 def __init__(self):
239 def __init__(self):
234 # The newline hack is added because br's margin does not work in all
240 # The newline hack is added because br's margin does not work in all
235 # browsers except firefox, when the div's does.
241 # browsers except firefox, when the div's does.
236 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
242 self.parser = bbcode.Parser(newline=LINE_BREAK_HTML)
237
243
238 self.parser.add_formatter('post', render_reflink, strip=True)
244 self.parser.add_formatter('post', render_reflink, strip=True)
239 self.parser.add_formatter('quote', render_quote, strip=True)
245 self.parser.add_formatter('quote', render_quote, strip=True)
240 self.parser.add_formatter('hint', render_hint, strip=True)
246 self.parser.add_formatter('hint', render_hint, strip=True)
241 self.parser.add_formatter('user', render_notification, strip=True)
247 self.parser.add_formatter('user', render_notification, strip=True)
242 self.parser.add_formatter('tag', render_tag, strip=True)
248 self.parser.add_formatter('tag', render_tag, strip=True)
243 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
249 self.parser.add_formatter('spoiler', render_spoiler, strip=True)
244 self.parser.add_simple_formatter(
250 self.parser.add_simple_formatter(
245 'comment', '<span class="comment">// %(value)s</span>', strip=True)
251 'comment', '<span class="comment">// %(value)s</span>', strip=True)
246 self.parser.add_simple_formatter(
252 self.parser.add_simple_formatter(
247 's', '<span class="strikethrough">%(value)s</span>')
253 's', '<span class="strikethrough">%(value)s</span>')
248 self.parser.add_simple_formatter('code',
254 self.parser.add_simple_formatter('code',
249 '<pre><code>%(value)s</pre></code>',
255 '<pre><code>%(value)s</pre></code>',
250 render_embedded=False,
256 render_embedded=False,
251 escape_html=True,
257 escape_html=True,
252 replace_links=False,
258 replace_links=False,
253 replace_cosmetic=False)
259 replace_cosmetic=False)
254
260
255 def preparse(self, text):
261 def preparse(self, text):
256 """
262 """
257 Performs manual parsing before the bbcode parser is used.
263 Performs manual parsing before the bbcode parser is used.
258 Preparsed text is saved as raw and the text before preparsing is lost.
264 Preparsed text is saved as raw and the text before preparsing is lost.
259 """
265 """
260 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
266 new_text = MULTI_NEWLINES_PATTERN.sub(ONE_NEWLINE, text)
261
267
262 for key, value in PREPARSE_PATTERNS.items():
268 for key, value in PREPARSE_PATTERNS.items():
263 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
269 new_text = re.sub(key, value, new_text, flags=re.MULTILINE)
264
270
265 for link in REGEX_URL.findall(text):
271 for link in REGEX_URL.findall(text):
266 new_text = new_text.replace(link, unquote(link))
272 new_text = new_text.replace(link, unquote(link))
267
273
268 return new_text
274 return new_text
269
275
270 def parse(self, text):
276 def parse(self, text):
271 return self.parser.format(text)
277 return self.parser.format(text)
272
278
273
279
274 parser = Parser()
280 parser = Parser()
275 def get_parser():
281 def get_parser():
276 return parser
282 return parser
General Comments 0
You need to be logged in to leave comments. Login now