##// END OF EJS Templates
Subscribe to threads you created or replied by default
neko259 -
r2083:08fb6dfe default
parent child Browse files
Show More
@@ -1,537 +1,538 b''
1 import logging
1 import logging
2
2
3 import pytz
3 import pytz
4 import re
4 import re
5 from PIL import Image
5 from PIL import Image
6 from django import forms
6 from django import forms
7 from django.core.files.images import get_image_dimensions
7 from django.core.files.images import get_image_dimensions
8 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
8 from django.core.files.uploadedfile import SimpleUploadedFile, UploadedFile
9 from django.forms.utils import ErrorList
9 from django.forms.utils import ErrorList
10 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
10 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
11
11
12 import boards.settings as board_settings
12 import boards.settings as board_settings
13 from boards import utils
13 from boards import utils
14 from boards.abstracts.constants import REGEX_TAGS
14 from boards.abstracts.constants import REGEX_TAGS
15 from boards.abstracts.settingsmanager import get_settings_manager, \
15 from boards.abstracts.settingsmanager import get_settings_manager, \
16 SETTING_CONFIRMED_USER
16 SETTING_CONFIRMED_USER
17 from boards.abstracts.sticker_factory import get_attachment_by_alias
17 from boards.abstracts.sticker_factory import get_attachment_by_alias
18 from boards.forms.fields import UrlFileField
18 from boards.forms.fields import UrlFileField
19 from boards.forms.validators import TimeValidator, PowValidator
19 from boards.forms.validators import TimeValidator, PowValidator
20 from boards.mdx_neboard import formatters
20 from boards.mdx_neboard import formatters
21 from boards.models import Attachment
21 from boards.models import Attachment
22 from boards.models import Tag
22 from boards.models import Tag
23 from boards.models.attachment import StickerPack
23 from boards.models.attachment import StickerPack
24 from boards.models.attachment.downloaders import download, REGEX_MAGNET
24 from boards.models.attachment.downloaders import download, REGEX_MAGNET
25 from boards.models.attachment.viewers import FILE_TYPES_IMAGE
25 from boards.models.attachment.viewers import FILE_TYPES_IMAGE
26 from boards.models.post import TITLE_MAX_LENGTH
26 from boards.models.post import TITLE_MAX_LENGTH
27 from boards.settings import SECTION_FORMS
27 from boards.settings import SECTION_FORMS
28 from boards.utils import validate_file_size, get_file_mimetype, \
28 from boards.utils import validate_file_size, get_file_mimetype, \
29 FILE_EXTENSION_DELIMITER, get_tripcode_from_text
29 FILE_EXTENSION_DELIMITER, get_tripcode_from_text
30
30
31 FORMAT_PANEL_BUTTON = '<span class="mark_btn" ' \
31 FORMAT_PANEL_BUTTON = '<span class="mark_btn" ' \
32 'onClick="addMarkToMsg(\'{}\', \'{}\')">{}{}{}</span>'
32 'onClick="addMarkToMsg(\'{}\', \'{}\')">{}{}{}</span>'
33
33
34
34
35 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
35 REGEX_USERNAMES = re.compile(r'^[\w\s\d,]+$', re.UNICODE)
36 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
36 REGEX_URL = re.compile(r'^(http|https|ftp):\/\/', re.UNICODE)
37
37
38 VETERAN_POSTING_DELAY = 5
38 VETERAN_POSTING_DELAY = 5
39
39
40 ATTRIBUTE_PLACEHOLDER = 'placeholder'
40 ATTRIBUTE_PLACEHOLDER = 'placeholder'
41 ATTRIBUTE_ROWS = 'rows'
41 ATTRIBUTE_ROWS = 'rows'
42
42
43 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
43 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
44 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
44 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
45
45
46 LABEL_TITLE = _('Title')
46 LABEL_TITLE = _('Title')
47 LABEL_TEXT = _('Text')
47 LABEL_TEXT = _('Text')
48 LABEL_TAG = _('Tag')
48 LABEL_TAG = _('Tag')
49 LABEL_SEARCH = _('Search')
49 LABEL_SEARCH = _('Search')
50 LABEL_FILE = _('File')
50 LABEL_FILE = _('File')
51 LABEL_DUPLICATES = _('Check for duplicates')
51 LABEL_DUPLICATES = _('Check for duplicates')
52 LABEL_URL = _('Do not download URLs')
52 LABEL_URL = _('Do not download URLs')
53
53
54 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
54 ERROR_MANY_FILES = 'You can post no more than %(files)d file.'
55 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
55 ERROR_MANY_FILES_PLURAL = 'You can post no more than %(files)d files.'
56 ERROR_DUPLICATES = 'Some files are already present on the board.'
56 ERROR_DUPLICATES = 'Some files are already present on the board.'
57
57
58 TAG_MAX_LENGTH = 20
58 TAG_MAX_LENGTH = 20
59
59
60 TEXTAREA_ROWS = 4
60 TEXTAREA_ROWS = 4
61
61
62 TRIPCODE_DELIM = '##'
62 TRIPCODE_DELIM = '##'
63
63
64 # TODO Maybe this may be converted into the database table?
64 # TODO Maybe this may be converted into the database table?
65 MIMETYPE_EXTENSIONS = {
65 MIMETYPE_EXTENSIONS = {
66 'image/jpeg': 'jpeg',
66 'image/jpeg': 'jpeg',
67 'image/png': 'png',
67 'image/png': 'png',
68 'image/gif': 'gif',
68 'image/gif': 'gif',
69 'video/webm': 'webm',
69 'video/webm': 'webm',
70 'application/pdf': 'pdf',
70 'application/pdf': 'pdf',
71 'x-diff': 'diff',
71 'x-diff': 'diff',
72 'image/svg+xml': 'svg',
72 'image/svg+xml': 'svg',
73 'application/x-shockwave-flash': 'swf',
73 'application/x-shockwave-flash': 'swf',
74 'image/x-ms-bmp': 'bmp',
74 'image/x-ms-bmp': 'bmp',
75 'image/bmp': 'bmp',
75 'image/bmp': 'bmp',
76 }
76 }
77
77
78 DOWN_MODE_DOWNLOAD = 'DOWNLOAD'
78 DOWN_MODE_DOWNLOAD = 'DOWNLOAD'
79 DOWN_MODE_DOWNLOAD_UNIQUE = 'DOWNLOAD_UNIQUE'
79 DOWN_MODE_DOWNLOAD_UNIQUE = 'DOWNLOAD_UNIQUE'
80 DOWN_MODE_URL = 'URL'
80 DOWN_MODE_URL = 'URL'
81 DOWN_MODE_TRY = 'TRY'
81 DOWN_MODE_TRY = 'TRY'
82
82
83
83
84 logger = logging.getLogger('boards.forms')
84 logger = logging.getLogger('boards.forms')
85
85
86
86
87 def get_timezones():
87 def get_timezones():
88 timezones = []
88 timezones = []
89 for tz in pytz.common_timezones:
89 for tz in pytz.common_timezones:
90 timezones.append((tz, tz),)
90 timezones.append((tz, tz),)
91 return timezones
91 return timezones
92
92
93
93
94 class FormatPanel(forms.Textarea):
94 class FormatPanel(forms.Textarea):
95 """
95 """
96 Panel for text formatting. Consists of buttons to add different tags to the
96 Panel for text formatting. Consists of buttons to add different tags to the
97 form text area.
97 form text area.
98 """
98 """
99
99
100 def render(self, name, value, attrs=None):
100 def render(self, name, value, attrs=None):
101 output_template = '<div id="mark-panel">{}</div>'
101 output_template = '<div id="mark-panel">{}</div>'
102
102
103 buttons = [self._get_button(formatter) for formatter in formatters]
103 buttons = [self._get_button(formatter) for formatter in formatters]
104
104
105 output = output_template.format(''.join(buttons))
105 output = output_template.format(''.join(buttons))
106
106
107 output += super(FormatPanel, self).render(name, value, attrs=attrs)
107 output += super(FormatPanel, self).render(name, value, attrs=attrs)
108
108
109 return output
109 return output
110
110
111 def _get_button(self, formatter):
111 def _get_button(self, formatter):
112 return FORMAT_PANEL_BUTTON.format(
112 return FORMAT_PANEL_BUTTON.format(
113 formatter.format_left,
113 formatter.format_left,
114 formatter.format_right,
114 formatter.format_right,
115 formatter.preview_left,
115 formatter.preview_left,
116 formatter.name,
116 formatter.name,
117 formatter.preview_right)
117 formatter.preview_right)
118
118
119
119
120 class PlainErrorList(ErrorList):
120 class PlainErrorList(ErrorList):
121 def __unicode__(self):
121 def __unicode__(self):
122 return self.as_text()
122 return self.as_text()
123
123
124 def as_text(self):
124 def as_text(self):
125 return ''.join(['(!) %s ' % e for e in self])
125 return ''.join(['(!) %s ' % e for e in self])
126
126
127
127
128 class NeboardForm(forms.Form):
128 class NeboardForm(forms.Form):
129 """
129 """
130 Form with neboard-specific formatting.
130 Form with neboard-specific formatting.
131 """
131 """
132 required_css_class = 'required-field'
132 required_css_class = 'required-field'
133
133
134 def as_div(self):
134 def as_div(self):
135 """
135 """
136 Returns this form rendered as HTML <as_div>s.
136 Returns this form rendered as HTML <as_div>s.
137 """
137 """
138
138
139 return self._html_output(
139 return self._html_output(
140 # TODO Do not show hidden rows in the list here
140 # TODO Do not show hidden rows in the list here
141 normal_row='<div class="form-row">'
141 normal_row='<div class="form-row">'
142 '<div class="form-label">'
142 '<div class="form-label">'
143 '%(label)s'
143 '%(label)s'
144 '</div>'
144 '</div>'
145 '<div class="form-input">'
145 '<div class="form-input">'
146 '%(field)s'
146 '%(field)s'
147 '</div>'
147 '</div>'
148 '</div>'
148 '</div>'
149 '<div class="form-row">'
149 '<div class="form-row">'
150 '%(help_text)s'
150 '%(help_text)s'
151 '</div>',
151 '</div>',
152 error_row='<div class="form-row">'
152 error_row='<div class="form-row">'
153 '<div class="form-label"></div>'
153 '<div class="form-label"></div>'
154 '<div class="form-errors">%s</div>'
154 '<div class="form-errors">%s</div>'
155 '</div>',
155 '</div>',
156 row_ender='</div>',
156 row_ender='</div>',
157 help_text_html='%s',
157 help_text_html='%s',
158 errors_on_separate_row=True)
158 errors_on_separate_row=True)
159
159
160 def as_json_errors(self):
160 def as_json_errors(self):
161 errors = []
161 errors = []
162
162
163 for name, field in list(self.fields.items()):
163 for name, field in list(self.fields.items()):
164 if self[name].errors:
164 if self[name].errors:
165 errors.append({
165 errors.append({
166 'field': name,
166 'field': name,
167 'errors': self[name].errors.as_text(),
167 'errors': self[name].errors.as_text(),
168 })
168 })
169
169
170 return errors
170 return errors
171
171
172
172
173 class PostForm(NeboardForm):
173 class PostForm(NeboardForm):
174
174
175 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
175 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
176 label=LABEL_TITLE,
176 label=LABEL_TITLE,
177 widget=forms.TextInput(
177 widget=forms.TextInput(
178 attrs={ATTRIBUTE_PLACEHOLDER: 'Title{}tripcode'.format(TRIPCODE_DELIM)}))
178 attrs={ATTRIBUTE_PLACEHOLDER: 'Title{}tripcode'.format(TRIPCODE_DELIM)}))
179 text = forms.CharField(
179 text = forms.CharField(
180 widget=FormatPanel(attrs={
180 widget=FormatPanel(attrs={
181 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
181 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
182 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
182 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
183 }),
183 }),
184 required=False, label=LABEL_TEXT)
184 required=False, label=LABEL_TEXT)
185 download_mode = forms.ChoiceField(
185 download_mode = forms.ChoiceField(
186 choices=(
186 choices=(
187 (DOWN_MODE_TRY, _('Download or insert as URLs')),
187 (DOWN_MODE_TRY, _('Download or insert as URLs')),
188 (DOWN_MODE_DOWNLOAD, _('Download')),
188 (DOWN_MODE_DOWNLOAD, _('Download')),
189 (DOWN_MODE_DOWNLOAD_UNIQUE, _('Download and check for uniqueness')),
189 (DOWN_MODE_DOWNLOAD_UNIQUE, _('Download and check for uniqueness')),
190 (DOWN_MODE_URL, _('Insert as URLs')),
190 (DOWN_MODE_URL, _('Insert as URLs')),
191 ),
191 ),
192 initial=DOWN_MODE_TRY,
192 initial=DOWN_MODE_TRY,
193 label=_('File process mode'))
193 label=_('File process mode'))
194 file = UrlFileField(required=False, label=LABEL_FILE)
194 file = UrlFileField(required=False, label=LABEL_FILE)
195
195
196 # This field is for spam prevention only
196 # This field is for spam prevention only
197 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
197 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
198 widget=forms.TextInput(attrs={
198 widget=forms.TextInput(attrs={
199 'class': 'form-email'}))
199 'class': 'form-email'}))
200 subscribe = forms.BooleanField(required=False, label=_('Subscribe to thread'))
200 subscribe = forms.BooleanField(required=False,
201 label=_('Subscribe to thread'), initial=True)
201
202
202 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
203 guess = forms.CharField(widget=forms.HiddenInput(), required=False)
203 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
204 timestamp = forms.CharField(widget=forms.HiddenInput(), required=False)
204 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
205 iteration = forms.CharField(widget=forms.HiddenInput(), required=False)
205
206
206 need_to_ban = False
207 need_to_ban = False
207
208
208 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
209 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
209 initial=None, error_class=ErrorList, label_suffix=None,
210 initial=None, error_class=ErrorList, label_suffix=None,
210 empty_permitted=False, field_order=None,
211 empty_permitted=False, field_order=None,
211 use_required_attribute=None, renderer=None, session=None):
212 use_required_attribute=None, renderer=None, session=None):
212 super().__init__(data, files, auto_id, prefix, initial, error_class,
213 super().__init__(data, files, auto_id, prefix, initial, error_class,
213 label_suffix, empty_permitted, field_order,
214 label_suffix, empty_permitted, field_order,
214 use_required_attribute, renderer)
215 use_required_attribute, renderer)
215
216
216 self.session = session
217 self.session = session
217
218
218 def clean_title(self):
219 def clean_title(self):
219 title = self.cleaned_data['title']
220 title = self.cleaned_data['title']
220 if title:
221 if title:
221 if len(title) > TITLE_MAX_LENGTH:
222 if len(title) > TITLE_MAX_LENGTH:
222 raise forms.ValidationError(_('Title must have less than %s '
223 raise forms.ValidationError(_('Title must have less than %s '
223 'characters') %
224 'characters') %
224 str(TITLE_MAX_LENGTH))
225 str(TITLE_MAX_LENGTH))
225 return title
226 return title
226
227
227 def clean_text(self):
228 def clean_text(self):
228 text = self.cleaned_data['text'].strip()
229 text = self.cleaned_data['text'].strip()
229 if text:
230 if text:
230 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
231 max_length = board_settings.get_int(SECTION_FORMS, 'MaxTextLength')
231 if len(text) > max_length:
232 if len(text) > max_length:
232 raise forms.ValidationError(_('Text must have less than %s '
233 raise forms.ValidationError(_('Text must have less than %s '
233 'characters') % str(max_length))
234 'characters') % str(max_length))
234 return text
235 return text
235
236
236 def clean_file(self):
237 def clean_file(self):
237 return self._clean_files(self.cleaned_data['file'])
238 return self._clean_files(self.cleaned_data['file'])
238
239
239 def clean(self):
240 def clean(self):
240 cleaned_data = super(PostForm, self).clean()
241 cleaned_data = super(PostForm, self).clean()
241
242
242 if cleaned_data['email']:
243 if cleaned_data['email']:
243 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
244 if board_settings.get_bool(SECTION_FORMS, 'Autoban'):
244 self.need_to_ban = True
245 self.need_to_ban = True
245 raise forms.ValidationError('A human cannot enter a hidden field')
246 raise forms.ValidationError('A human cannot enter a hidden field')
246
247
247 if not self.errors:
248 if not self.errors:
248 self._clean_text_file()
249 self._clean_text_file()
249
250
250 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
251 limit_speed = board_settings.get_bool(SECTION_FORMS, 'LimitPostingSpeed')
251 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
252 limit_first = board_settings.get_bool(SECTION_FORMS, 'LimitFirstPosting')
252
253
253 settings_manager = get_settings_manager(self)
254 settings_manager = get_settings_manager(self)
254 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting(SETTING_CONFIRMED_USER)):
255 if not self.errors and limit_speed or (limit_first and not settings_manager.get_setting(SETTING_CONFIRMED_USER)):
255 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
256 pow_difficulty = board_settings.get_int(SECTION_FORMS, 'PowDifficulty')
256 if pow_difficulty > 0:
257 if pow_difficulty > 0:
257 validator = PowValidator(
258 validator = PowValidator(
258 self.session, cleaned_data['timestamp'],
259 self.session, cleaned_data['timestamp'],
259 cleaned_data['iteration'], cleaned_data['guess'],
260 cleaned_data['iteration'], cleaned_data['guess'],
260 cleaned_data['text'])
261 cleaned_data['text'])
261 else:
262 else:
262 validator = TimeValidator(self.session)
263 validator = TimeValidator(self.session)
263
264
264 validator.validate()
265 validator.validate()
265 for error in validator.get_errors():
266 for error in validator.get_errors():
266 self._add_general_error(error)
267 self._add_general_error(error)
267
268
268 settings_manager.set_setting(SETTING_CONFIRMED_USER, True)
269 settings_manager.set_setting(SETTING_CONFIRMED_USER, True)
269 if self.cleaned_data['download_mode'] == DOWN_MODE_DOWNLOAD_UNIQUE:
270 if self.cleaned_data['download_mode'] == DOWN_MODE_DOWNLOAD_UNIQUE:
270 self._check_file_duplicates(self.get_files())
271 self._check_file_duplicates(self.get_files())
271
272
272 return cleaned_data
273 return cleaned_data
273
274
274 def get_files(self):
275 def get_files(self):
275 """
276 """
276 Gets file from form or URL.
277 Gets file from form or URL.
277 """
278 """
278
279
279 files = []
280 files = []
280 for file in self.cleaned_data['file']:
281 for file in self.cleaned_data['file']:
281 if isinstance(file, UploadedFile):
282 if isinstance(file, UploadedFile):
282 files.append(file)
283 files.append(file)
283
284
284 return files
285 return files
285
286
286 def get_file_urls(self):
287 def get_file_urls(self):
287 files = []
288 files = []
288 for file in self.cleaned_data['file']:
289 for file in self.cleaned_data['file']:
289 if type(file) == str:
290 if type(file) == str:
290 files.append(file)
291 files.append(file)
291
292
292 return files
293 return files
293
294
294 def get_tripcode(self):
295 def get_tripcode(self):
295 title = self.cleaned_data['title']
296 title = self.cleaned_data['title']
296 if title is not None and TRIPCODE_DELIM in title:
297 if title is not None and TRIPCODE_DELIM in title:
297 tripcode = get_tripcode_from_text(title.split(TRIPCODE_DELIM, maxsplit=1)[1])
298 tripcode = get_tripcode_from_text(title.split(TRIPCODE_DELIM, maxsplit=1)[1])
298 else:
299 else:
299 tripcode = ''
300 tripcode = ''
300 return tripcode
301 return tripcode
301
302
302 def get_title(self):
303 def get_title(self):
303 title = self.cleaned_data['title']
304 title = self.cleaned_data['title']
304 if title is not None and TRIPCODE_DELIM in title:
305 if title is not None and TRIPCODE_DELIM in title:
305 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
306 return title.split(TRIPCODE_DELIM, maxsplit=1)[0]
306 else:
307 else:
307 return title
308 return title
308
309
309 def get_images(self):
310 def get_images(self):
310 return self.cleaned_data.get('stickers', [])
311 return self.cleaned_data.get('stickers', [])
311
312
312 def is_subscribe(self):
313 def is_subscribe(self):
313 return self.cleaned_data['subscribe']
314 return self.cleaned_data['subscribe']
314
315
315 def _update_file_extension(self, file):
316 def _update_file_extension(self, file):
316 if file:
317 if file:
317 mimetype = get_file_mimetype(file)
318 mimetype = get_file_mimetype(file)
318 extension = MIMETYPE_EXTENSIONS.get(mimetype)
319 extension = MIMETYPE_EXTENSIONS.get(mimetype)
319 if extension:
320 if extension:
320 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
321 filename = file.name.split(FILE_EXTENSION_DELIMITER, 1)[0]
321 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
322 new_filename = filename + FILE_EXTENSION_DELIMITER + extension
322
323
323 file.name = new_filename
324 file.name = new_filename
324 else:
325 else:
325 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
326 logger.info('Unrecognized file mimetype: {}'.format(mimetype))
326
327
327 def _clean_files(self, inputs):
328 def _clean_files(self, inputs):
328 files = []
329 files = []
329
330
330 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
331 max_file_count = board_settings.get_int(SECTION_FORMS, 'MaxFileCount')
331 if len(inputs) > max_file_count:
332 if len(inputs) > max_file_count:
332 raise forms.ValidationError(
333 raise forms.ValidationError(
333 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
334 ungettext_lazy(ERROR_MANY_FILES, ERROR_MANY_FILES,
334 max_file_count) % {'files': max_file_count})
335 max_file_count) % {'files': max_file_count})
335
336
336 size = 0
337 size = 0
337 for file_input in inputs:
338 for file_input in inputs:
338 if isinstance(file_input, UploadedFile):
339 if isinstance(file_input, UploadedFile):
339 file = self._clean_file_file(file_input)
340 file = self._clean_file_file(file_input)
340 size += file.size
341 size += file.size
341 files.append(file)
342 files.append(file)
342 else:
343 else:
343 files.append(self._clean_file_url(file_input))
344 files.append(self._clean_file_url(file_input))
344
345
345 for file in files:
346 for file in files:
346 self._validate_image_dimensions(file)
347 self._validate_image_dimensions(file)
347 validate_file_size(size)
348 validate_file_size(size)
348
349
349 return files
350 return files
350
351
351 def _validate_image_dimensions(self, file):
352 def _validate_image_dimensions(self, file):
352 if isinstance(file, UploadedFile):
353 if isinstance(file, UploadedFile):
353 mimetype = get_file_mimetype(file)
354 mimetype = get_file_mimetype(file)
354 if mimetype.split('/')[-1] in FILE_TYPES_IMAGE:
355 if mimetype.split('/')[-1] in FILE_TYPES_IMAGE:
355 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
356 Image.warnings.simplefilter('error', Image.DecompressionBombWarning)
356 try:
357 try:
357 print(get_image_dimensions(file))
358 print(get_image_dimensions(file))
358 except Exception:
359 except Exception:
359 raise forms.ValidationError('Possible decompression bomb or large image.')
360 raise forms.ValidationError('Possible decompression bomb or large image.')
360
361
361 def _clean_file_file(self, file):
362 def _clean_file_file(self, file):
362 self._update_file_extension(file)
363 self._update_file_extension(file)
363
364
364 return file
365 return file
365
366
366 def _clean_file_url(self, url):
367 def _clean_file_url(self, url):
367 file = None
368 file = None
368
369
369 if url:
370 if url:
370 mode = self.cleaned_data['download_mode']
371 mode = self.cleaned_data['download_mode']
371 if mode == DOWN_MODE_URL:
372 if mode == DOWN_MODE_URL:
372 return url
373 return url
373
374
374 try:
375 try:
375 image = get_attachment_by_alias(url, self.session)
376 image = get_attachment_by_alias(url, self.session)
376 if image is not None:
377 if image is not None:
377 if 'stickers' not in self.cleaned_data:
378 if 'stickers' not in self.cleaned_data:
378 self.cleaned_data['stickers'] = []
379 self.cleaned_data['stickers'] = []
379 self.cleaned_data['stickers'].append(image)
380 self.cleaned_data['stickers'].append(image)
380 return
381 return
381
382
382 if file is None:
383 if file is None:
383 file = self._get_file_from_url(url)
384 file = self._get_file_from_url(url)
384 if not file:
385 if not file:
385 raise forms.ValidationError(_('Invalid URL'))
386 raise forms.ValidationError(_('Invalid URL'))
386 self._update_file_extension(file)
387 self._update_file_extension(file)
387 except forms.ValidationError as e:
388 except forms.ValidationError as e:
388 # Assume we will get the plain URL instead of a file and save it
389 # Assume we will get the plain URL instead of a file and save it
389 if mode == DOWN_MODE_TRY and (REGEX_URL.match(url) or REGEX_MAGNET.match(url)):
390 if mode == DOWN_MODE_TRY and (REGEX_URL.match(url) or REGEX_MAGNET.match(url)):
390 logger.info('Error in forms: {}'.format(e))
391 logger.info('Error in forms: {}'.format(e))
391 return url
392 return url
392 else:
393 else:
393 raise e
394 raise e
394
395
395 return file
396 return file
396
397
397 def _clean_text_file(self):
398 def _clean_text_file(self):
398 text = self.cleaned_data.get('text')
399 text = self.cleaned_data.get('text')
399 file = self.get_files()
400 file = self.get_files()
400 file_url = self.get_file_urls()
401 file_url = self.get_file_urls()
401 images = self.get_images()
402 images = self.get_images()
402
403
403 if (not text) and (not file) and (not file_url) and len(images) == 0:
404 if (not text) and (not file) and (not file_url) and len(images) == 0:
404 error_message = _('Either text or file must be entered.')
405 error_message = _('Either text or file must be entered.')
405 self._add_general_error(error_message)
406 self._add_general_error(error_message)
406
407
407 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
408 def _get_file_from_url(self, url: str) -> SimpleUploadedFile:
408 """
409 """
409 Gets an file file from URL.
410 Gets an file file from URL.
410 """
411 """
411
412
412 try:
413 try:
413 return download(url)
414 return download(url)
414 except forms.ValidationError as e:
415 except forms.ValidationError as e:
415 raise e
416 raise e
416 except Exception as e:
417 except Exception as e:
417 raise forms.ValidationError(e)
418 raise forms.ValidationError(e)
418
419
419 def _check_file_duplicates(self, files):
420 def _check_file_duplicates(self, files):
420 for file in files:
421 for file in files:
421 file_hash = utils.get_file_hash(file)
422 file_hash = utils.get_file_hash(file)
422 if Attachment.objects.get_existing_duplicate(file_hash, file):
423 if Attachment.objects.get_existing_duplicate(file_hash, file):
423 self._add_general_error(_(ERROR_DUPLICATES))
424 self._add_general_error(_(ERROR_DUPLICATES))
424
425
425 def _add_general_error(self, message):
426 def _add_general_error(self, message):
426 self.add_error('text', forms.ValidationError(message))
427 self.add_error('text', forms.ValidationError(message))
427
428
428
429
429 class ThreadForm(PostForm):
430 class ThreadForm(PostForm):
430
431
431 tags = forms.CharField(
432 tags = forms.CharField(
432 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
433 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
433 max_length=100, label=_('Tags'), required=True)
434 max_length=100, label=_('Tags'), required=True)
434 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
435 monochrome = forms.BooleanField(label=_('Monochrome'), required=False)
435 stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False)
436 stickerpack = forms.BooleanField(label=_('Sticker Pack'), required=False)
436
437
437 def clean_tags(self):
438 def clean_tags(self):
438 tags = self.cleaned_data['tags'].strip()
439 tags = self.cleaned_data['tags'].strip()
439
440
440 if not tags or not REGEX_TAGS.match(tags):
441 if not tags or not REGEX_TAGS.match(tags):
441 raise forms.ValidationError(
442 raise forms.ValidationError(
442 _('Inappropriate characters in tags.'))
443 _('Inappropriate characters in tags.'))
443
444
444 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
445 default_tag_name = board_settings.get(SECTION_FORMS, 'DefaultTag')\
445 .strip().lower()
446 .strip().lower()
446
447
447 required_tag_exists = False
448 required_tag_exists = False
448 tag_set = set()
449 tag_set = set()
449 for tag_string in tags.split():
450 for tag_string in tags.split():
450 tag_name = tag_string.strip().lower()
451 tag_name = tag_string.strip().lower()
451 if tag_name == default_tag_name:
452 if tag_name == default_tag_name:
452 required_tag_exists = True
453 required_tag_exists = True
453 tag, created = Tag.objects.get_or_create_with_alias(
454 tag, created = Tag.objects.get_or_create_with_alias(
454 name=tag_name, required=True)
455 name=tag_name, required=True)
455 else:
456 else:
456 tag, created = Tag.objects.get_or_create_with_alias(name=tag_name)
457 tag, created = Tag.objects.get_or_create_with_alias(name=tag_name)
457 tag_set.add(tag)
458 tag_set.add(tag)
458
459
459 # If this is a new tag, don't check for its parents because nobody
460 # If this is a new tag, don't check for its parents because nobody
460 # added them yet
461 # added them yet
461 if not created:
462 if not created:
462 tag_set |= set(tag.get_all_parents())
463 tag_set |= set(tag.get_all_parents())
463
464
464 for tag in tag_set:
465 for tag in tag_set:
465 if tag.required:
466 if tag.required:
466 required_tag_exists = True
467 required_tag_exists = True
467 break
468 break
468
469
469 # Use default tag if no section exists
470 # Use default tag if no section exists
470 if not required_tag_exists:
471 if not required_tag_exists:
471 default_tag, created = Tag.objects.get_or_create_with_alias(
472 default_tag, created = Tag.objects.get_or_create_with_alias(
472 name=default_tag_name, required=True)
473 name=default_tag_name, required=True)
473 tag_set.add(default_tag)
474 tag_set.add(default_tag)
474
475
475 return tag_set
476 return tag_set
476
477
477 def clean(self):
478 def clean(self):
478 cleaned_data = super(ThreadForm, self).clean()
479 cleaned_data = super(ThreadForm, self).clean()
479
480
480 return cleaned_data
481 return cleaned_data
481
482
482 def is_monochrome(self):
483 def is_monochrome(self):
483 return self.cleaned_data['monochrome']
484 return self.cleaned_data['monochrome']
484
485
485 def clean_stickerpack(self):
486 def clean_stickerpack(self):
486 stickerpack = self.cleaned_data['stickerpack']
487 stickerpack = self.cleaned_data['stickerpack']
487 if stickerpack:
488 if stickerpack:
488 tripcode = self.get_tripcode()
489 tripcode = self.get_tripcode()
489 if not tripcode:
490 if not tripcode:
490 raise forms.ValidationError(_(
491 raise forms.ValidationError(_(
491 'Tripcode should be specified to own a stickerpack.'))
492 'Tripcode should be specified to own a stickerpack.'))
492 title = self.get_title()
493 title = self.get_title()
493 if not title:
494 if not title:
494 raise forms.ValidationError(_(
495 raise forms.ValidationError(_(
495 'Title should be specified as a stickerpack name.'))
496 'Title should be specified as a stickerpack name.'))
496 if not REGEX_TAGS.match(title):
497 if not REGEX_TAGS.match(title):
497 raise forms.ValidationError(_('Inappropriate sticker pack name.'))
498 raise forms.ValidationError(_('Inappropriate sticker pack name.'))
498
499
499 existing_pack = StickerPack.objects.filter(name=title).first()
500 existing_pack = StickerPack.objects.filter(name=title).first()
500 if existing_pack:
501 if existing_pack:
501 if existing_pack.tripcode != tripcode:
502 if existing_pack.tripcode != tripcode:
502 raise forms.ValidationError(_(
503 raise forms.ValidationError(_(
503 'A sticker pack with this name already exists and is'
504 'A sticker pack with this name already exists and is'
504 ' owned by another tripcode.'))
505 ' owned by another tripcode.'))
505 if not existing_pack.tripcode:
506 if not existing_pack.tripcode:
506 raise forms.ValidationError(_(
507 raise forms.ValidationError(_(
507 'This sticker pack can only be updated by an '
508 'This sticker pack can only be updated by an '
508 'administrator.'))
509 'administrator.'))
509
510
510 return stickerpack
511 return stickerpack
511
512
512 def is_stickerpack(self):
513 def is_stickerpack(self):
513 return self.cleaned_data['stickerpack']
514 return self.cleaned_data['stickerpack']
514
515
515
516
516 class SettingsForm(NeboardForm):
517 class SettingsForm(NeboardForm):
517
518
518 theme = forms.ChoiceField(
519 theme = forms.ChoiceField(
519 choices=board_settings.get_list_dict('View', 'Themes'),
520 choices=board_settings.get_list_dict('View', 'Themes'),
520 label=_('Theme'))
521 label=_('Theme'))
521 image_viewer = forms.ChoiceField(
522 image_viewer = forms.ChoiceField(
522 choices=board_settings.get_list_dict('View', 'ImageViewers'),
523 choices=board_settings.get_list_dict('View', 'ImageViewers'),
523 label=_('Image view mode'))
524 label=_('Image view mode'))
524 username = forms.CharField(label=_('User name'), required=False)
525 username = forms.CharField(label=_('User name'), required=False)
525 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
526 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
526
527
527 def clean_username(self):
528 def clean_username(self):
528 username = self.cleaned_data['username']
529 username = self.cleaned_data['username']
529
530
530 if username and not REGEX_USERNAMES.match(username):
531 if username and not REGEX_USERNAMES.match(username):
531 raise forms.ValidationError(_('Inappropriate characters.'))
532 raise forms.ValidationError(_('Inappropriate characters.'))
532
533
533 return username
534 return username
534
535
535
536
536 class SearchForm(NeboardForm):
537 class SearchForm(NeboardForm):
537 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
538 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
@@ -1,329 +1,328 b''
1 var ITEM_FILE_SOURCE = 'fileSource';
1 var ITEM_FILE_SOURCE = 'fileSource';
2 var URL_STICKERS = '/api/stickers';
2 var URL_STICKERS = '/api/stickers';
3 var MIN_INPUT_LENGTH = 3;
3 var MIN_INPUT_LENGTH = 3;
4 var URL_DELIMITER = '\n';
4 var URL_DELIMITER = '\n';
5 // TODO This needs to be the same for attachment download time limit.
5 // TODO This needs to be the same for attachment download time limit.
6 var POST_AJAX_TIMEOUT = 30000;
6 var POST_AJAX_TIMEOUT = 30000;
7 var REPLY_TO_MSG = '.reply-to-message';
7 var REPLY_TO_MSG = '.reply-to-message';
8
8
9 var pastedImages = [];
9 var pastedImages = [];
10
10
11 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
11 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
12
12
13 $('body').on('change', 'input[name=image]', function(event) {
13 $('body').on('change', 'input[name=image]', function(event) {
14 var file = event.target.files[0];
14 var file = event.target.files[0];
15
15
16 if(file.type.match('image.*')) {
16 if(file.type.match('image.*')) {
17 var fileReader = new FileReader();
17 var fileReader = new FileReader();
18
18
19 fileReader.addEventListener("load", function(event) {
19 fileReader.addEventListener("load", function(event) {
20 var wrapper = $('.file_wrap');
20 var wrapper = $('.file_wrap');
21
21
22 wrapper.find('.file-thumb').remove();
22 wrapper.find('.file-thumb').remove();
23 wrapper.append(
23 wrapper.append(
24 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
24 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
25 );
25 );
26 });
26 });
27
27
28 fileReader.readAsDataURL(file);
28 fileReader.readAsDataURL(file);
29 }
29 }
30 });
30 });
31
31
32 var form = $('#form');
32 var form = $('#form');
33 $('textarea').keypress(function(event) {
33 $('textarea').keypress(function(event) {
34 if ((event.which == 10 || event.which == 13) && event.ctrlKey) {
34 if ((event.which == 10 || event.which == 13) && event.ctrlKey) {
35 form.find('input[type=submit]').click();
35 form.find('input[type=submit]').click();
36 }
36 }
37 });
37 });
38
38
39 $('#preview-button').click(function() {
39 $('#preview-button').click(function() {
40 var data = {
40 var data = {
41 raw_text: $('textarea#id_text').val()
41 raw_text: $('textarea#id_text').val()
42 }
42 }
43
43
44 var diffUrl = '/api/preview/';
44 var diffUrl = '/api/preview/';
45
45
46 $.post(diffUrl,
46 $.post(diffUrl,
47 data,
47 data,
48 function(data) {
48 function(data) {
49 var previewTextBlock = $('#preview-text');
49 var previewTextBlock = $('#preview-text');
50 previewTextBlock.html(data);
50 previewTextBlock.html(data);
51 previewTextBlock.show();
51 previewTextBlock.show();
52
52
53 addScriptsToPost(previewTextBlock);
53 addScriptsToPost(previewTextBlock);
54 })
54 })
55 });
55 });
56
56
57 /**
57 /**
58 * Show text in the errors row of the form.
58 * Show text in the errors row of the form.
59 * @param form
59 * @param form
60 * @param text
60 * @param text
61 */
61 */
62 function showAsErrors(form, text) {
62 function showAsErrors(form, text) {
63 form.children('.form-errors').remove();
63 form.children('.form-errors').remove();
64
64
65 if (text.length > 0) {
65 if (text.length > 0) {
66 var errorList = $('<div class="form-errors">' + text + '<div>');
66 var errorList = $('<div class="form-errors">' + text + '<div>');
67 errorList.appendTo(form);
67 errorList.appendTo(form);
68 }
68 }
69 }
69 }
70
70
71 function addHiddenInput(form, name, value) {
71 function addHiddenInput(form, name, value) {
72 form.find('input[name=' + name + ']').val(value);
72 form.find('input[name=' + name + ']').val(value);
73 }
73 }
74
74
75 function selectFileChoice() {
75 function selectFileChoice() {
76 var file_input = $('#id_file');
76 var file_input = $('#id_file');
77 var url_input = $('#id_file_url');
77 var url_input = $('#id_file_url');
78
78
79 var file_input_row = file_input.parent().parent();
79 var file_input_row = file_input.parent().parent();
80 var url_input_row = url_input.parent().parent();
80 var url_input_row = url_input.parent().parent();
81
81
82 file_input_row.toggle();
82 file_input_row.toggle();
83 url_input_row.toggle();
83 url_input_row.toggle();
84 url_input.val('');
84 url_input.val('');
85 file_input.val('');
85 file_input.val('');
86
86
87 var source;
87 var source;
88 if (file_input_row.is(':visible')) {
88 if (file_input_row.is(':visible')) {
89 source = 'file';
89 source = 'file';
90 } else {
90 } else {
91 source = 'url';
91 source = 'url';
92 }
92 }
93 localStorage.setItem(ITEM_FILE_SOURCE, source);
93 localStorage.setItem(ITEM_FILE_SOURCE, source);
94 }
94 }
95
95
96 function getPostTextarea() {
96 function getPostTextarea() {
97 return $('textarea#id_text');
97 return $('textarea#id_text');
98 }
98 }
99
99
100 function addOnImagePaste() {
100 function addOnImagePaste() {
101 $('#id_file_1').on('paste', function(event) {
101 $('#id_file_1').on('paste', function(event) {
102 var items = (event.clipboardData || event.originalEvent.clipboardData).items;
102 var items = (event.clipboardData || event.originalEvent.clipboardData).items;
103 for (index in items) {
103 for (index in items) {
104 var item = items[index];
104 var item = items[index];
105 if (item.kind === 'file') {
105 if (item.kind === 'file') {
106 var blob = item.getAsFile();
106 var blob = item.getAsFile();
107
107
108 pastedImages.push(blob);
108 pastedImages.push(blob);
109
109
110 var pastedImagesList = $('#pasted-images');
110 var pastedImagesList = $('#pasted-images');
111 if (pastedImagesList.length === 0) {
111 if (pastedImagesList.length === 0) {
112 pastedImagesList = $('<div id="pasted-images" />');
112 pastedImagesList = $('<div id="pasted-images" />');
113 $('#id_file_1').parent().append(pastedImagesList);
113 $('#id_file_1').parent().append(pastedImagesList);
114 }
114 }
115
115
116 var fr = new FileReader();
116 var fr = new FileReader();
117 fr.onload = function () {
117 fr.onload = function () {
118 var img = $('<img class="image-preview" />');
118 var img = $('<img class="image-preview" />');
119 img.attr('src', fr.result);
119 img.attr('src', fr.result);
120 pastedImagesList.append(img);
120 pastedImagesList.append(img);
121 img.on("click", function() {
121 img.on("click", function() {
122 // Remove the image from all lists
122 // Remove the image from all lists
123 var itemIndex = $(this).index();
123 var itemIndex = $(this).index();
124 pastedImages.splice(itemIndex, 1);
124 pastedImages.splice(itemIndex, 1);
125 $(this).remove();
125 $(this).remove();
126 });
126 });
127 };
127 };
128 fr.readAsDataURL(blob);
128 fr.readAsDataURL(blob);
129 }
129 }
130 }
130 }
131 });
131 });
132 }
132 }
133
133
134 /**
134 /**
135 * When the form is posted, this method will be run as a callback
135 * When the form is posted, this method will be run as a callback
136 */
136 */
137 function updateOnPost(response, statusText, xhr, form) {
137 function updateOnPost(response, statusText, xhr, form) {
138 var json = $.parseJSON(response);
138 var json = $.parseJSON(response);
139 var status = json.status;
139 var status = json.status;
140 var url = json.url;
140 var url = json.url;
141
141
142 showAsErrors(form, '');
142 showAsErrors(form, '');
143 $('.post-form-w').unblock();
143 $('.post-form-w').unblock();
144
144
145 if (status === 'ok') {
145 if (status === 'ok') {
146 if (url) {
146 if (url) {
147 document.location = url;
147 document.location = url;
148 } else {
148 } else {
149 resetForm();
149 resetForm();
150 getThreadDiff();
150 getThreadDiff();
151 scrollToBottom();
151 scrollToBottom();
152 }
152 }
153 } else {
153 } else {
154 var errors = json.errors;
154 var errors = json.errors;
155 for (var i = 0; i < errors.length; i++) {
155 for (var i = 0; i < errors.length; i++) {
156 var fieldErrors = errors[i];
156 var fieldErrors = errors[i];
157
157
158 var error = fieldErrors.errors;
158 var error = fieldErrors.errors;
159
159
160 showAsErrors(form, error);
160 showAsErrors(form, error);
161 }
161 }
162 }
162 }
163 }
163 }
164
164
165 function initAjaxForm(openingPostId) {
165 function initAjaxForm(openingPostId) {
166 var form = $('#form');
166 var form = $('#form');
167
167
168 var url = '/api/add_post/';
168 var url = '/api/add_post/';
169 if (openingPostId) {
169 if (openingPostId) {
170 url += openingPostId + '/';
170 url += openingPostId + '/';
171 }
171 }
172
172
173 if (form.length > 0) {
173 if (form.length > 0) {
174 var options = {
174 var options = {
175 beforeSubmit: function(arr, form, options) {
175 beforeSubmit: function(arr, form, options) {
176 $('.post-form-w').block({ message: gettext('Sending message...') });
176 $('.post-form-w').block({ message: gettext('Sending message...') });
177
177
178 $.each(pastedImages, function(i, blob) {
178 $.each(pastedImages, function(i, blob) {
179 arr.push({
179 arr.push({
180 name: "file_0",
180 name: "file_0",
181 value: blob
181 value: blob
182 });
182 });
183 });
183 });
184 },
184 },
185 success: updateOnPost,
185 success: updateOnPost,
186 error: function(xhr, textStatus, errorString) {
186 error: function(xhr, textStatus, errorString) {
187 var errorText = gettext('Server error: ') + textStatus;
187 var errorText = gettext('Server error: ') + textStatus;
188 if (errorString) {
188 if (errorString) {
189 errorText += ' / ' + errorString;
189 errorText += ' / ' + errorString;
190 }
190 }
191 showAsErrors(form, errorText);
191 showAsErrors(form, errorText);
192 $('.post-form-w').unblock();
192 $('.post-form-w').unblock();
193 },
193 },
194 url: url,
194 url: url,
195 timeout: POST_AJAX_TIMEOUT
195 timeout: POST_AJAX_TIMEOUT
196 };
196 };
197
197
198 form.ajaxForm(options);
198 form.ajaxForm(options);
199 }
199 }
200 }
200 }
201
201
202 function getForm() {
202 function getForm() {
203 return $('.post-form-w');
203 return $('.post-form-w');
204 }
204 }
205
205
206 /**
206 /**
207 * Clear all entered values in the form fields
207 * Clear all entered values in the form fields
208 */
208 */
209 function resetForm() {
209 function resetForm() {
210 var form = getForm();
210 var form = getForm();
211
211
212 form.find('input:text, input:password, input:file, textarea').val('');
212 form.find('input:text, input:password, input:file, textarea').val('');
213 form.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
214 pastedImages = [];
213 pastedImages = [];
215 $('#pasted-images').remove();
214 $('#pasted-images').remove();
216 $('.file_wrap').find('.file-thumb').remove();
215 $('.file_wrap').find('.file-thumb').remove();
217 $('#preview-text').hide();
216 $('#preview-text').hide();
218
217
219 resetFormPosition(form);
218 resetFormPosition(form);
220 }
219 }
221
220
222 function resetFormPosition(form) {
221 function resetFormPosition(form) {
223 form.insertBefore($('footer'));
222 form.insertBefore($('footer'));
224
223
225 $(REPLY_TO_MSG).hide();
224 $(REPLY_TO_MSG).hide();
226 }
225 }
227
226
228 $(document).ready(function() {
227 $(document).ready(function() {
229 var powDifficulty = parseInt($('body').attr('data-pow-difficulty'));
228 var powDifficulty = parseInt($('body').attr('data-pow-difficulty'));
230 if (powDifficulty > 0 && typeof SharedWorker != 'undefined') {
229 if (powDifficulty > 0 && typeof SharedWorker != 'undefined') {
231 var worker = new SharedWorker($('.post-form').attr('data-pow-script'));
230 var worker = new SharedWorker($('.post-form').attr('data-pow-script'));
232 worker.port.onmessage = function(e) {
231 worker.port.onmessage = function(e) {
233 var form = $('#form');
232 var form = $('#form');
234 addHiddenInput(form, 'timestamp', e.data.timestamp);
233 addHiddenInput(form, 'timestamp', e.data.timestamp);
235 addHiddenInput(form, 'iteration', e.data.iteration);
234 addHiddenInput(form, 'iteration', e.data.iteration);
236 addHiddenInput(form, 'guess', e.data.guess);
235 addHiddenInput(form, 'guess', e.data.guess);
237
236
238 form.submit();
237 form.submit();
239 $('.post-form-w').unblock();
238 $('.post-form-w').unblock();
240 };
239 };
241 worker.onerror = function(event){
240 worker.onerror = function(event){
242 throw new Error(event.message + " (" + event.filename + ":" + event.lineno + ")");
241 throw new Error(event.message + " (" + event.filename + ":" + event.lineno + ")");
243 };
242 };
244 worker.port.start();
243 worker.port.start();
245
244
246 var form = $('#form');
245 var form = $('#form');
247 var submitButton = form.find('input[type=submit]');
246 var submitButton = form.find('input[type=submit]');
248 submitButton.click(function() {
247 submitButton.click(function() {
249 showAsErrors(form, gettext('Computing PoW...'));
248 showAsErrors(form, gettext('Computing PoW...'));
250 $('.post-form-w').block({ message: gettext('Computing PoW...') })
249 $('.post-form-w').block({ message: gettext('Computing PoW...') })
251
250
252 var msg = $('textarea#id_text').val().trim();
251 var msg = $('textarea#id_text').val().trim();
253
252
254 var data = {
253 var data = {
255 msg: msg,
254 msg: msg,
256 difficulty: parseInt($('body').attr('data-pow-difficulty')),
255 difficulty: parseInt($('body').attr('data-pow-difficulty')),
257 hasher: $('.post-form').attr('data-hasher')
256 hasher: $('.post-form').attr('data-hasher')
258 };
257 };
259 worker.port.postMessage(data);
258 worker.port.postMessage(data);
260
259
261 return false;
260 return false;
262 });
261 });
263 }
262 }
264
263
265 var $fileSourceButton = $('#file-source-button');
264 var $fileSourceButton = $('#file-source-button');
266 if (window.localStorage) {
265 if (window.localStorage) {
267 var source = localStorage.getItem(ITEM_FILE_SOURCE);
266 var source = localStorage.getItem(ITEM_FILE_SOURCE);
268 if (source == null) {
267 if (source == null) {
269 source = 'file';
268 source = 'file';
270 }
269 }
271 if (source == 'file') {
270 if (source == 'file') {
272 $('#id_file_url').parent().parent().hide();
271 $('#id_file_url').parent().parent().hide();
273 } else {
272 } else {
274 $('#id_file').parent().parent().hide();
273 $('#id_file').parent().parent().hide();
275 }
274 }
276
275
277 $fileSourceButton.click(function() {
276 $fileSourceButton.click(function() {
278 selectFileChoice();
277 selectFileChoice();
279 });
278 });
280 } else {
279 } else {
281 $fileSourceButton.hide();
280 $fileSourceButton.hide();
282 }
281 }
283
282
284 addOnImagePaste();
283 addOnImagePaste();
285
284
286 // Stickers autocomplete
285 // Stickers autocomplete
287 function split( val ) {
286 function split( val ) {
288 return val.split(URL_DELIMITER);
287 return val.split(URL_DELIMITER);
289 }
288 }
290
289
291 function extractLast( term ) {
290 function extractLast( term ) {
292 return split(term).pop();
291 return split(term).pop();
293 }
292 }
294
293
295 $('#id_file_1').autocomplete({
294 $('#id_file_1').autocomplete({
296 source: function( request, response ) {
295 source: function( request, response ) {
297 $.getJSON(URL_STICKERS, {
296 $.getJSON(URL_STICKERS, {
298 term: extractLast( request.term )
297 term: extractLast( request.term )
299 }, response);
298 }, response);
300 },
299 },
301 search: function() {
300 search: function() {
302 // custom minLength
301 // custom minLength
303 var term = extractLast( this.value );
302 var term = extractLast( this.value );
304 if (term.length < MIN_INPUT_LENGTH) {
303 if (term.length < MIN_INPUT_LENGTH) {
305 return false;
304 return false;
306 }
305 }
307 },
306 },
308 focus: function() {
307 focus: function() {
309 // prevent value inserted on focus
308 // prevent value inserted on focus
310 return false;
309 return false;
311 },
310 },
312 select: function( event, ui ) {
311 select: function( event, ui ) {
313 var terms = split( this.value );
312 var terms = split( this.value );
314 // remove the current input
313 // remove the current input
315 terms.pop();
314 terms.pop();
316 // add the selected item
315 // add the selected item
317 terms.push( ui.item.alias );
316 terms.push( ui.item.alias );
318 // add placeholder to get the comma-and-space at the end
317 // add placeholder to get the comma-and-space at the end
319 terms.push("");
318 terms.push("");
320 this.value = terms.join(URL_DELIMITER);
319 this.value = terms.join(URL_DELIMITER);
321 return false;
320 return false;
322 }
321 }
323 })
322 })
324 .autocomplete( "instance" )._renderItem = function( ul, item ) {
323 .autocomplete( "instance" )._renderItem = function( ul, item ) {
325 return $( "<li>" )
324 return $( "<li>" )
326 .append( "<div>" + '<img src="' + item.thumb + '">' + '<br />' + item.alias + "</div>" )
325 .append( "<div>" + '<img src="' + item.thumb + '">' + '<br />' + item.alias + "</div>" )
327 .appendTo( ul );
326 .appendTo( ul );
328 };
327 };
329 });
328 });
General Comments 0
You need to be logged in to leave comments. Login now