##// END OF EJS Templates
Added settings to limit posting speed. Added message when the form data is sent and response not yet received
neko259 -
r725:ce7b9618 default
parent child Browse files
Show More
@@ -1,356 +1,357 b''
1 1 import re
2 2 import time
3 3 import hashlib
4 4
5 5 from captcha.fields import CaptchaField
6 6 from django import forms
7 7 from django.forms.util import ErrorList
8 8 from django.utils.translation import ugettext_lazy as _
9 9
10 10 from boards.mdx_neboard import formatters
11 11 from boards.models.post import TITLE_MAX_LENGTH
12 12 from boards.models import User, PostImage
13 13 from neboard import settings
14 14 from boards import utils
15 15 import boards.settings as board_settings
16 16
17 17 VETERAN_POSTING_DELAY = 5
18 18
19 19 ATTRIBUTE_PLACEHOLDER = 'placeholder'
20 20
21 21 LAST_POST_TIME = 'last_post_time'
22 22 LAST_LOGIN_TIME = 'last_login_time'
23 23 TEXT_PLACEHOLDER = _('''Type message here. You can reply to message >>123 like
24 24 this. 2 new lines are required to start new paragraph.''')
25 25 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
26 26
27 27 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
28 28
29 29 LABEL_TITLE = _('Title')
30 30 LABEL_TEXT = _('Text')
31 31 LABEL_TAG = _('Tag')
32 32 LABEL_SEARCH = _('Search')
33 33
34 34 TAG_MAX_LENGTH = 20
35 35
36 36 REGEX_TAG = ur'^[\w\d]+$'
37 37
38 38
39 39 class FormatPanel(forms.Textarea):
40 40 def render(self, name, value, attrs=None):
41 41 output = '<div id="mark-panel">'
42 42 for formatter in formatters:
43 43 output += u'<span class="mark_btn"' + \
44 44 u' onClick="addMarkToMsg(\'' + formatter.format_left + \
45 45 '\', \'' + formatter.format_right + '\')">' + \
46 46 formatter.preview_left + formatter.name + \
47 47 formatter.preview_right + u'</span>'
48 48
49 49 output += '</div>'
50 50 output += super(FormatPanel, self).render(name, value, attrs=None)
51 51
52 52 return output
53 53
54 54
55 55 class PlainErrorList(ErrorList):
56 56 def __unicode__(self):
57 57 return self.as_text()
58 58
59 59 def as_text(self):
60 60 return ''.join([u'(!) %s ' % e for e in self])
61 61
62 62
63 63 class NeboardForm(forms.Form):
64 64
65 65 def as_div(self):
66 66 """
67 67 Returns this form rendered as HTML <as_div>s.
68 68 """
69 69
70 70 return self._html_output(
71 71 # TODO Do not show hidden rows in the list here
72 72 normal_row='<div class="form-row"><div class="form-label">'
73 73 '%(label)s'
74 74 '</div></div>'
75 75 '<div class="form-row"><div class="form-input">'
76 76 '%(field)s'
77 77 '</div></div>'
78 78 '<div class="form-row">'
79 79 '%(help_text)s'
80 80 '</div>',
81 81 error_row='<div class="form-row">'
82 82 '<div class="form-label"></div>'
83 83 '<div class="form-errors">%s</div>'
84 84 '</div>',
85 85 row_ender='</div>',
86 86 help_text_html='%s',
87 87 errors_on_separate_row=True)
88 88
89 89 def as_json_errors(self):
90 90 errors = []
91 91
92 92 for name, field in self.fields.items():
93 93 if self[name].errors:
94 94 errors.append({
95 95 'field': name,
96 96 'errors': self[name].errors.as_text(),
97 97 })
98 98
99 99 return errors
100 100
101 101
102 102 class PostForm(NeboardForm):
103 103
104 104 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
105 105 label=LABEL_TITLE)
106 106 text = forms.CharField(
107 107 widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}),
108 108 required=False, label=LABEL_TEXT)
109 109 image = forms.ImageField(required=False, label=_('Image'),
110 110 widget=forms.ClearableFileInput(
111 111 attrs={'accept': 'image/*'}))
112 112
113 113 # This field is for spam prevention only
114 114 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
115 115 widget=forms.TextInput(attrs={
116 116 'class': 'form-email'}))
117 117
118 118 session = None
119 119 need_to_ban = False
120 120
121 121 def clean_title(self):
122 122 title = self.cleaned_data['title']
123 123 if title:
124 124 if len(title) > TITLE_MAX_LENGTH:
125 125 raise forms.ValidationError(_('Title must have less than %s '
126 126 'characters') %
127 127 str(TITLE_MAX_LENGTH))
128 128 return title
129 129
130 130 def clean_text(self):
131 131 text = self.cleaned_data['text'].strip()
132 132 if text:
133 133 if len(text) > board_settings.MAX_TEXT_LENGTH:
134 134 raise forms.ValidationError(_('Text must have less than %s '
135 135 'characters') %
136 136 str(board_settings
137 137 .MAX_TEXT_LENGTH))
138 138 return text
139 139
140 140 def clean_image(self):
141 141 image = self.cleaned_data['image']
142 142 if image:
143 143 if image.size > board_settings.MAX_IMAGE_SIZE:
144 144 raise forms.ValidationError(
145 145 _('Image must be less than %s bytes')
146 146 % str(board_settings.MAX_IMAGE_SIZE))
147 147
148 148 md5 = hashlib.md5()
149 149 for chunk in image.chunks():
150 150 md5.update(chunk)
151 151 image_hash = md5.hexdigest()
152 152 if PostImage.objects.filter(hash=image_hash).exists():
153 153 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
154 154
155 155 return image
156 156
157 157 def clean(self):
158 158 cleaned_data = super(PostForm, self).clean()
159 159
160 160 if not self.session:
161 161 raise forms.ValidationError('Humans have sessions')
162 162
163 163 if cleaned_data['email']:
164 164 self.need_to_ban = True
165 165 raise forms.ValidationError('A human cannot enter a hidden field')
166 166
167 167 if not self.errors:
168 168 self._clean_text_image()
169 169
170 170 if not self.errors and self.session:
171 171 self._validate_posting_speed()
172 172
173 173 return cleaned_data
174 174
175 175 def _clean_text_image(self):
176 176 text = self.cleaned_data.get('text')
177 177 image = self.cleaned_data.get('image')
178 178
179 179 if (not text) and (not image):
180 180 error_message = _('Either text or image must be entered.')
181 181 self._errors['text'] = self.error_class([error_message])
182 182
183 183 def _validate_posting_speed(self):
184 184 can_post = True
185 185
186 186 # TODO Remove this, it's only for test
187 187 if not 'user_id' in self.session:
188 188 return
189 189
190 190 user = User.objects.get(id=self.session['user_id'])
191 191 if user.is_veteran():
192 192 posting_delay = VETERAN_POSTING_DELAY
193 193 else:
194 194 posting_delay = settings.POSTING_DELAY
195 195
196 if LAST_POST_TIME in self.session:
196 if board_settings.LIMIT_POSTING_SPEED and LAST_POST_TIME in \
197 self.session:
197 198 now = time.time()
198 199 last_post_time = self.session[LAST_POST_TIME]
199 200
200 201 current_delay = int(now - last_post_time)
201 202
202 203 if current_delay < posting_delay:
203 204 error_message = _('Wait %s seconds after last posting') % str(
204 205 posting_delay - current_delay)
205 206 self._errors['text'] = self.error_class([error_message])
206 207
207 208 can_post = False
208 209
209 210 if can_post:
210 211 self.session[LAST_POST_TIME] = time.time()
211 212
212 213
213 214 class ThreadForm(PostForm):
214 215
215 216 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
216 217
217 218 tags = forms.CharField(
218 219 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
219 220 max_length=100, label=_('Tags'), required=True)
220 221
221 222 def clean_tags(self):
222 223 tags = self.cleaned_data['tags'].strip()
223 224
224 225 if not tags or not self.regex_tags.match(tags):
225 226 raise forms.ValidationError(
226 227 _('Inappropriate characters in tags.'))
227 228
228 229 return tags
229 230
230 231 def clean(self):
231 232 cleaned_data = super(ThreadForm, self).clean()
232 233
233 234 return cleaned_data
234 235
235 236
236 237 class PostCaptchaForm(PostForm):
237 238 captcha = CaptchaField()
238 239
239 240 def __init__(self, *args, **kwargs):
240 241 self.request = kwargs['request']
241 242 del kwargs['request']
242 243
243 244 super(PostCaptchaForm, self).__init__(*args, **kwargs)
244 245
245 246 def clean(self):
246 247 cleaned_data = super(PostCaptchaForm, self).clean()
247 248
248 249 success = self.is_valid()
249 250 utils.update_captcha_access(self.request, success)
250 251
251 252 if success:
252 253 return cleaned_data
253 254 else:
254 255 raise forms.ValidationError(_("Captcha validation failed"))
255 256
256 257
257 258 class ThreadCaptchaForm(ThreadForm):
258 259 captcha = CaptchaField()
259 260
260 261 def __init__(self, *args, **kwargs):
261 262 self.request = kwargs['request']
262 263 del kwargs['request']
263 264
264 265 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
265 266
266 267 def clean(self):
267 268 cleaned_data = super(ThreadCaptchaForm, self).clean()
268 269
269 270 success = self.is_valid()
270 271 utils.update_captcha_access(self.request, success)
271 272
272 273 if success:
273 274 return cleaned_data
274 275 else:
275 276 raise forms.ValidationError(_("Captcha validation failed"))
276 277
277 278
278 279 class SettingsForm(NeboardForm):
279 280
280 281 theme = forms.ChoiceField(choices=settings.THEMES,
281 282 label=_('Theme'))
282 283
283 284
284 285 class ModeratorSettingsForm(SettingsForm):
285 286
286 287 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
287 288 'panel'))
288 289
289 290
290 291 class LoginForm(NeboardForm):
291 292
292 293 user_id = forms.CharField()
293 294
294 295 session = None
295 296
296 297 def clean_user_id(self):
297 298 user_id = self.cleaned_data['user_id']
298 299 if user_id:
299 300 users = User.objects.filter(user_id=user_id)
300 301 if len(users) == 0:
301 302 raise forms.ValidationError(_('No such user found'))
302 303
303 304 return user_id
304 305
305 306 def _validate_login_speed(self):
306 307 can_post = True
307 308
308 309 if LAST_LOGIN_TIME in self.session:
309 310 now = time.time()
310 311 last_login_time = self.session[LAST_LOGIN_TIME]
311 312
312 313 current_delay = int(now - last_login_time)
313 314
314 315 if current_delay < board_settings.LOGIN_TIMEOUT:
315 316 error_message = _('Wait %s minutes after last login') % str(
316 317 (board_settings.LOGIN_TIMEOUT - current_delay) / 60)
317 318 self._errors['user_id'] = self.error_class([error_message])
318 319
319 320 can_post = False
320 321
321 322 if can_post:
322 323 self.session[LAST_LOGIN_TIME] = time.time()
323 324
324 325 def clean(self):
325 326 if not self.session:
326 327 raise forms.ValidationError('Humans have sessions')
327 328
328 329 self._validate_login_speed()
329 330
330 331 cleaned_data = super(LoginForm, self).clean()
331 332
332 333 return cleaned_data
333 334
334 335
335 336 class AddTagForm(NeboardForm):
336 337
337 338 tag = forms.CharField(max_length=TAG_MAX_LENGTH, label=LABEL_TAG)
338 339 method = forms.CharField(widget=forms.HiddenInput(), initial='add_tag')
339 340
340 341 def clean_tag(self):
341 342 tag = self.cleaned_data['tag']
342 343
343 344 regex_tag = re.compile(REGEX_TAG, re.UNICODE)
344 345 if not regex_tag.match(tag):
345 346 raise forms.ValidationError(_('Inappropriate characters in tags.'))
346 347
347 348 return tag
348 349
349 350 def clean(self):
350 351 cleaned_data = super(AddTagForm, self).clean()
351 352
352 353 return cleaned_data
353 354
354 355
355 356 class SearchForm(NeboardForm):
356 357 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False) No newline at end of file
1 NO CONTENT: modified file, binary diff hidden
@@ -1,43 +1,32 b''
1 1 # SOME DESCRIPTIVE TITLE.
2 2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 3 # This file is distributed under the same license as the PACKAGE package.
4 4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 5 #
6 6 #, fuzzy
7 7 msgid ""
8 8 msgstr ""
9 9 "Project-Id-Version: PACKAGE VERSION\n"
10 10 "Report-Msgid-Bugs-To: \n"
11 "POT-Creation-Date: 2013-12-21 21:45+0200\n"
11 "POT-Creation-Date: 2014-07-02 13:26+0300\n"
12 12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 14 "Language-Team: LANGUAGE <LL@li.org>\n"
15 15 "Language: \n"
16 16 "MIME-Version: 1.0\n"
17 17 "Content-Type: text/plain; charset=UTF-8\n"
18 18 "Content-Transfer-Encoding: 8bit\n"
19 19 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
20 20 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
21 21
22 #: static/js/refpopup.js:57
22 #: static/js/refpopup.js:58
23 23 msgid "Loading..."
24 24 msgstr "Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ°..."
25 25
26 #: static/js/refpopup.js:76
26 #: static/js/refpopup.js:77
27 27 msgid "Post not found"
28 28 msgstr "Π‘ΠΎΠΎΠ±Ρ‰Π΅Π½ΠΈΠ΅ Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ΠΎ"
29 29
30 #: static/js/thread.js:32
31 msgid "Normal"
32 msgstr "ΠΠΎΡ€ΠΌΠ°Π»ΡŒΠ½Ρ‹ΠΉ"
33
34 #: static/js/thread.js:33
35 msgid "Gallery"
36 msgstr "ГалСрСя"
37
38 #: static/js/thread_update.js:177
39 msgid "[new posts]"
40 msgstr "[Π½ΠΎΠ²Ρ‹Π΅ посты]"
41
42 #~ msgid "Replies"
43 #~ msgstr "ΠžΡ‚Π²Π΅Ρ‚Ρ‹"
30 #: static/js/thread_update.js:279
31 msgid "Sending message..."
32 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΊΠ° сообщСния..." No newline at end of file
@@ -1,18 +1,20 b''
1 1 VERSION = '1.8.1 Kara'
2 2 SITE_NAME = 'Neboard'
3 3
4 4 CACHE_TIMEOUT = 600 # Timeout for caching, if cache is used
5 5 LOGIN_TIMEOUT = 3600 # Timeout between login tries
6 6 MAX_TEXT_LENGTH = 30000 # Max post length in characters
7 7 MAX_IMAGE_SIZE = 8 * 1024 * 1024 # Max image size
8 8
9 9 # Thread bumplimit
10 10 MAX_POSTS_PER_THREAD = 10
11 11 # Old posts will be archived or deleted if this value is reached
12 12 MAX_THREAD_COUNT = 5
13 13 THREADS_PER_PAGE = 3
14 14 DEFAULT_THEME = 'md'
15 15 LAST_REPLIES_COUNT = 3
16 16
17 17 # Enable archiving threads instead of deletion when the thread limit is reached
18 ARCHIVE_THREADS = True No newline at end of file
18 ARCHIVE_THREADS = True
19 # Limit posting speed
20 LIMIT_POSTING_SPEED = False No newline at end of file
@@ -1,272 +1,288 b''
1 1 /*
2 2 @licstart The following is the entire license notice for the
3 3 JavaScript code in this page.
4 4
5 5
6 6 Copyright (C) 2013 neko259
7 7
8 8 The JavaScript code in this page is free software: you can
9 9 redistribute it and/or modify it under the terms of the GNU
10 10 General Public License (GNU GPL) as published by the Free Software
11 11 Foundation, either version 3 of the License, or (at your option)
12 12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15 15
16 16 As additional permission under GNU GPL version 3 section 7, you
17 17 may distribute non-source (e.g., minimized or compacted) forms of
18 18 that code without the copy of the GNU GPL normally required by
19 19 section 4, provided you include this license notice and a URL
20 20 through which recipients can access the Corresponding Source.
21 21
22 22 @licend The above is the entire license notice
23 23 for the JavaScript code in this page.
24 24 */
25 25
26 26 var THREAD_UPDATE_DELAY = 10000;
27 27
28 28 var loading = false;
29 29 var lastUpdateTime = null;
30 var unreadPosts = 0
30 var unreadPosts = 0;
31 31
32 32 function blink(node) {
33 33 var blinkCount = 2;
34 34
35 35 var nodeToAnimate = node;
36 36 for (var i = 0; i < blinkCount; i++) {
37 37 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
38 38 }
39 39 }
40 40
41 41 function updateThread() {
42 42 if (loading) {
43 43 return;
44 44 }
45 45
46 46 loading = true;
47 47
48 48 var threadPosts = $('div.thread').children('.post');
49 49
50 50 var lastPost = threadPosts.last();
51 51 var threadId = threadPosts.first().attr('id');
52 52
53 53 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
54 54 $.getJSON(diffUrl)
55 55 .success(function(data) {
56 56 var bottom = isPageBottom();
57 57
58 58 var lastUpdate = '';
59 59
60 60 var addedPosts = data.added;
61 61 for (var i = 0; i < addedPosts.length; i++) {
62 62 var postText = addedPosts[i];
63 63
64 64 var post = $(postText);
65 65
66 66 if (lastUpdate === '') {
67 67 lastUpdate = post.find('.pub_time').text();
68 68 }
69 69
70 70 post.appendTo(lastPost.parent());
71 71 processNewPost(post);
72 72
73 73 lastPost = post;
74 74 blink(post);
75 75 }
76 76
77 77 var updatedPosts = data.updated;
78 78 for (var i = 0; i < updatedPosts.length; i++) {
79 79 var postText = updatedPosts[i];
80 80
81 81 var post = $(postText);
82 82
83 83 if (lastUpdate === '') {
84 84 lastUpdate = post.find('.pub_time').text();
85 85 }
86 86
87 87 var postId = post.attr('id');
88 88
89 89 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
90 90
91 91 oldPost.replaceWith(post);
92 92 processNewPost(post);
93 93
94 94 blink(post);
95 95 }
96 96
97 97 // TODO Process deleted posts
98 98
99 99 lastUpdateTime = data.last_update;
100 100 loading = false;
101 101
102 102 if (bottom) {
103 103 scrollToBottom();
104 104 }
105 105
106 106 var hasPostChanges = (updatedPosts.length > 0)
107 107 || (addedPosts.length > 0);
108 108 if (hasPostChanges) {
109 109 updateMetadataPanel(lastUpdate);
110 110 }
111 111
112 112 updateBumplimitProgress(data.added.length);
113 113
114 114 if (data.added.length + data.updated.length > 0) {
115 115 showNewPostsTitle(data.added.length);
116 116 }
117 117 })
118 118 .error(function(data) {
119 119 // TODO Show error message that server is unavailable?
120 120
121 121 loading = false;
122 122 });
123 123 }
124 124
125 125 function isPageBottom() {
126 126 var scroll = $(window).scrollTop() / ($(document).height()
127 127 - $(window).height())
128 128
129 129 return scroll == 1
130 130 }
131 131
132 132 function initAutoupdate() {
133 133 loading = false;
134 134
135 135 lastUpdateTime = $('.metapanel').attr('data-last-update');
136 136
137 137 setInterval(updateThread, THREAD_UPDATE_DELAY);
138 138 }
139 139
140 140 function getReplyCount() {
141 141 return $('.thread').children('.post').length
142 142 }
143 143
144 144 function getImageCount() {
145 145 return $('.thread').find('img').length
146 146 }
147 147
148 148 function updateMetadataPanel(lastUpdate) {
149 149 var replyCountField = $('#reply-count');
150 150 var imageCountField = $('#image-count');
151 151
152 152 replyCountField.text(getReplyCount());
153 153 imageCountField.text(getImageCount());
154 154
155 155 if (lastUpdate !== '') {
156 156 var lastUpdateField = $('#last-update');
157 157 lastUpdateField.text(lastUpdate);
158 158 blink(lastUpdateField);
159 159 }
160 160
161 161 blink(replyCountField);
162 162 blink(imageCountField);
163 163 }
164 164
165 165 /**
166 166 * Update bumplimit progress bar
167 167 */
168 168 function updateBumplimitProgress(postDelta) {
169 169 var progressBar = $('#bumplimit_progress');
170 170 if (progressBar) {
171 171 var postsToLimitElement = $('#left_to_limit');
172 172
173 173 var oldPostsToLimit = parseInt(postsToLimitElement.text());
174 174 var postCount = getReplyCount();
175 175 var bumplimit = postCount - postDelta + oldPostsToLimit;
176 176
177 177 var newPostsToLimit = bumplimit - postCount;
178 178 if (newPostsToLimit <= 0) {
179 179 $('.bar-bg').remove();
180 180 $('.thread').children('.post').addClass('dead_post');
181 181 } else {
182 182 postsToLimitElement.text(newPostsToLimit);
183 183 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
184 184 }
185 185 }
186 186 }
187 187
188 188 var documentOriginalTitle = '';
189 189 /**
190 190 * Show 'new posts' text in the title if the document is not visible to a user
191 191 */
192 192 function showNewPostsTitle(newPostCount) {
193 193 if (document.hidden) {
194 194 if (documentOriginalTitle === '') {
195 195 documentOriginalTitle = document.title;
196 196 }
197 197 unreadPosts = unreadPosts + newPostCount;
198 198 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
199 199
200 200 document.addEventListener('visibilitychange', function() {
201 201 if (documentOriginalTitle !== '') {
202 202 document.title = documentOriginalTitle;
203 203 documentOriginalTitle = '';
204 204 unreadPosts = 0;
205 205 }
206 206
207 207 document.removeEventListener('visibilitychange', null);
208 208 });
209 209 }
210 210 }
211 211
212 212 /**
213 213 * Clear all entered values in the form fields
214 214 */
215 215 function resetForm(form) {
216 216 form.find('input:text, input:password, input:file, select, textarea').val('');
217 217 form.find('input:radio, input:checkbox')
218 218 .removeAttr('checked').removeAttr('selected');
219 219 $('.file_wrap').find('.file-thumb').remove();
220 220 }
221 221
222 222 /**
223 223 * When the form is posted, this method will be run as a callback
224 224 */
225 225 function updateOnPost(response, statusText, xhr, form) {
226 226 var json = $.parseJSON(response);
227 227 var status = json.status;
228 228
229 form.children('.form-errors').remove();
229 showAsErrors(form, '');
230 230
231 231 if (status === 'ok') {
232 232 resetForm(form);
233 233 updateThread();
234 234 } else {
235 235 var errors = json.errors;
236 236 for (var i = 0; i < errors.length; i++) {
237 237 var fieldErrors = errors[i];
238 238
239 239 var error = fieldErrors.errors;
240 240
241 var errorList = $('<div class="form-errors">' + error
241 showAsErrors(form, error);
242 }
243 }
244 }
245
246 /**
247 * Show text in the errors row of the form.
248 * @param form
249 * @param text
250 */
251 function showAsErrors(form, text) {
252 form.children('.form-errors').remove();
253
254 if (text.length > 0) {
255 var errorList = $('<div class="form-errors">' + text
242 256 + '<div>');
243 257 errorList.appendTo(form);
244 258 }
245 259 }
246 }
247 260
248 261 /**
249 262 * Run js methods that are usually run on the document, on the new post
250 263 */
251 264 function processNewPost(post) {
252 265 addRefLinkPreview(post[0]);
253 266 highlightCode(post);
254 267 }
255 268
256 269 $(document).ready(function(){
257 270 initAutoupdate();
258 271
259 272 // Post form data over AJAX
260 var threadId = $('div.thread').children('.post').first().attr('id');;
273 var threadId = $('div.thread').children('.post').first().attr('id');
261 274
262 275 var form = $('#form');
263 276
264 277 var options = {
278 beforeSubmit: function(arr, $form, options) {
279 showAsErrors($('form'), gettext('Sending message...'));
280 },
265 281 success: updateOnPost,
266 282 url: '/api/add_post/' + threadId + '/'
267 283 };
268 284
269 285 form.ajaxForm(options);
270 286
271 287 resetForm(form);
272 288 });
General Comments 0
You need to be logged in to leave comments. Login now