##// END OF EJS Templates
Show labels and inputs of rorms in one line
neko259 -
r1132:a1ac9da7 default
parent child Browse files
Show More
@@ -1,382 +1,384 b''
1 import re
1 import re
2 import time
2 import time
3 import pytz
3 import pytz
4
4
5 from django import forms
5 from django import forms
6 from django.core.files.uploadedfile import SimpleUploadedFile
6 from django.core.files.uploadedfile import SimpleUploadedFile
7 from django.core.exceptions import ObjectDoesNotExist
7 from django.core.exceptions import ObjectDoesNotExist
8 from django.forms.util import ErrorList
8 from django.forms.util import ErrorList
9 from django.utils.translation import ugettext_lazy as _
9 from django.utils.translation import ugettext_lazy as _
10 import requests
10 import requests
11
11
12 from boards.mdx_neboard import formatters
12 from boards.mdx_neboard import formatters
13 from boards.models.post import TITLE_MAX_LENGTH
13 from boards.models.post import TITLE_MAX_LENGTH
14 from boards.models import Tag, Post
14 from boards.models import Tag, Post
15 from neboard import settings
15 from neboard import settings
16 import boards.settings as board_settings
16 import boards.settings as board_settings
17
17
18
18
19 CONTENT_TYPE_IMAGE = (
19 CONTENT_TYPE_IMAGE = (
20 'image/jpeg',
20 'image/jpeg',
21 'image/png',
21 'image/png',
22 'image/gif',
22 'image/gif',
23 'image/bmp',
23 'image/bmp',
24 )
24 )
25
25
26 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
26 REGEX_TAGS = re.compile(r'^[\w\s\d]+$', re.UNICODE)
27
27
28 VETERAN_POSTING_DELAY = 5
28 VETERAN_POSTING_DELAY = 5
29
29
30 ATTRIBUTE_PLACEHOLDER = 'placeholder'
30 ATTRIBUTE_PLACEHOLDER = 'placeholder'
31 ATTRIBUTE_ROWS = 'rows'
31 ATTRIBUTE_ROWS = 'rows'
32
32
33 LAST_POST_TIME = 'last_post_time'
33 LAST_POST_TIME = 'last_post_time'
34 LAST_LOGIN_TIME = 'last_login_time'
34 LAST_LOGIN_TIME = 'last_login_time'
35 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
35 TEXT_PLACEHOLDER = _('Type message here. Use formatting panel for more advanced usage.')
36 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
36 TAGS_PLACEHOLDER = _('music images i_dont_like_tags')
37
37
38 LABEL_TITLE = _('Title')
38 LABEL_TITLE = _('Title')
39 LABEL_TEXT = _('Text')
39 LABEL_TEXT = _('Text')
40 LABEL_TAG = _('Tag')
40 LABEL_TAG = _('Tag')
41 LABEL_SEARCH = _('Search')
41 LABEL_SEARCH = _('Search')
42
42
43 ERROR_SPEED = _('Please wait %s seconds before sending message')
43 ERROR_SPEED = _('Please wait %s seconds before sending message')
44
44
45 TAG_MAX_LENGTH = 20
45 TAG_MAX_LENGTH = 20
46
46
47 IMAGE_DOWNLOAD_CHUNK_BYTES = 100000
47 IMAGE_DOWNLOAD_CHUNK_BYTES = 100000
48
48
49 HTTP_RESULT_OK = 200
49 HTTP_RESULT_OK = 200
50
50
51 TEXTAREA_ROWS = 4
51 TEXTAREA_ROWS = 4
52
52
53
53
54 def get_timezones():
54 def get_timezones():
55 timezones = []
55 timezones = []
56 for tz in pytz.common_timezones:
56 for tz in pytz.common_timezones:
57 timezones.append((tz, tz),)
57 timezones.append((tz, tz),)
58 return timezones
58 return timezones
59
59
60
60
61 class FormatPanel(forms.Textarea):
61 class FormatPanel(forms.Textarea):
62 """
62 """
63 Panel for text formatting. Consists of buttons to add different tags to the
63 Panel for text formatting. Consists of buttons to add different tags to the
64 form text area.
64 form text area.
65 """
65 """
66
66
67 def render(self, name, value, attrs=None):
67 def render(self, name, value, attrs=None):
68 output = '<div id="mark-panel">'
68 output = '<div id="mark-panel">'
69 for formatter in formatters:
69 for formatter in formatters:
70 output += '<span class="mark_btn"' + \
70 output += '<span class="mark_btn"' + \
71 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
71 ' onClick="addMarkToMsg(\'' + formatter.format_left + \
72 '\', \'' + formatter.format_right + '\')">' + \
72 '\', \'' + formatter.format_right + '\')">' + \
73 formatter.preview_left + formatter.name + \
73 formatter.preview_left + formatter.name + \
74 formatter.preview_right + '</span>'
74 formatter.preview_right + '</span>'
75
75
76 output += '</div>'
76 output += '</div>'
77 output += super(FormatPanel, self).render(name, value, attrs=None)
77 output += super(FormatPanel, self).render(name, value, attrs=None)
78
78
79 return output
79 return output
80
80
81
81
82 class PlainErrorList(ErrorList):
82 class PlainErrorList(ErrorList):
83 def __unicode__(self):
83 def __unicode__(self):
84 return self.as_text()
84 return self.as_text()
85
85
86 def as_text(self):
86 def as_text(self):
87 return ''.join(['(!) %s ' % e for e in self])
87 return ''.join(['(!) %s ' % e for e in self])
88
88
89
89
90 class NeboardForm(forms.Form):
90 class NeboardForm(forms.Form):
91 """
91 """
92 Form with neboard-specific formatting.
92 Form with neboard-specific formatting.
93 """
93 """
94
94
95 def as_div(self):
95 def as_div(self):
96 """
96 """
97 Returns this form rendered as HTML <as_div>s.
97 Returns this form rendered as HTML <as_div>s.
98 """
98 """
99
99
100 return self._html_output(
100 return self._html_output(
101 # TODO Do not show hidden rows in the list here
101 # TODO Do not show hidden rows in the list here
102 normal_row='<div class="form-row"><div class="form-label">'
102 normal_row='<div class="form-row">'
103 '<div class="form-label">'
103 '%(label)s'
104 '%(label)s'
104 '</div></div>'
105 '</div>'
105 '<div class="form-row"><div class="form-input">'
106 '<div class="form-input">'
106 '%(field)s'
107 '%(field)s'
107 '</div></div>'
108 '</div>'
109 '</div>'
108 '<div class="form-row">'
110 '<div class="form-row">'
109 '%(help_text)s'
111 '%(help_text)s'
110 '</div>',
112 '</div>',
111 error_row='<div class="form-row">'
113 error_row='<div class="form-row">'
112 '<div class="form-label"></div>'
114 '<div class="form-label"></div>'
113 '<div class="form-errors">%s</div>'
115 '<div class="form-errors">%s</div>'
114 '</div>',
116 '</div>',
115 row_ender='</div>',
117 row_ender='</div>',
116 help_text_html='%s',
118 help_text_html='%s',
117 errors_on_separate_row=True)
119 errors_on_separate_row=True)
118
120
119 def as_json_errors(self):
121 def as_json_errors(self):
120 errors = []
122 errors = []
121
123
122 for name, field in list(self.fields.items()):
124 for name, field in list(self.fields.items()):
123 if self[name].errors:
125 if self[name].errors:
124 errors.append({
126 errors.append({
125 'field': name,
127 'field': name,
126 'errors': self[name].errors.as_text(),
128 'errors': self[name].errors.as_text(),
127 })
129 })
128
130
129 return errors
131 return errors
130
132
131
133
132 class PostForm(NeboardForm):
134 class PostForm(NeboardForm):
133
135
134 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
136 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
135 label=LABEL_TITLE)
137 label=LABEL_TITLE)
136 text = forms.CharField(
138 text = forms.CharField(
137 widget=FormatPanel(attrs={
139 widget=FormatPanel(attrs={
138 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
140 ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER,
139 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
141 ATTRIBUTE_ROWS: TEXTAREA_ROWS,
140 }),
142 }),
141 required=False, label=LABEL_TEXT)
143 required=False, label=LABEL_TEXT)
142 image = forms.ImageField(required=False, label=_('Image'),
144 image = forms.ImageField(required=False, label=_('Image'),
143 widget=forms.ClearableFileInput(
145 widget=forms.ClearableFileInput(
144 attrs={'accept': 'image/*'}))
146 attrs={'accept': 'image/*'}))
145 image_url = forms.CharField(required=False, label=_('Image URL'),
147 image_url = forms.CharField(required=False, label=_('Image URL'),
146 widget=forms.TextInput(
148 widget=forms.TextInput(
147 attrs={ATTRIBUTE_PLACEHOLDER:
149 attrs={ATTRIBUTE_PLACEHOLDER:
148 'http://example.com/image.png'}))
150 'http://example.com/image.png'}))
149
151
150 # This field is for spam prevention only
152 # This field is for spam prevention only
151 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
153 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
152 widget=forms.TextInput(attrs={
154 widget=forms.TextInput(attrs={
153 'class': 'form-email'}))
155 'class': 'form-email'}))
154 threads = forms.CharField(required=False, label=_('Additional threads'),
156 threads = forms.CharField(required=False, label=_('Additional threads'),
155 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
157 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER:
156 '123 456 789'}))
158 '123 456 789'}))
157
159
158 session = None
160 session = None
159 need_to_ban = False
161 need_to_ban = False
160
162
161 def clean_title(self):
163 def clean_title(self):
162 title = self.cleaned_data['title']
164 title = self.cleaned_data['title']
163 if title:
165 if title:
164 if len(title) > TITLE_MAX_LENGTH:
166 if len(title) > TITLE_MAX_LENGTH:
165 raise forms.ValidationError(_('Title must have less than %s '
167 raise forms.ValidationError(_('Title must have less than %s '
166 'characters') %
168 'characters') %
167 str(TITLE_MAX_LENGTH))
169 str(TITLE_MAX_LENGTH))
168 return title
170 return title
169
171
170 def clean_text(self):
172 def clean_text(self):
171 text = self.cleaned_data['text'].strip()
173 text = self.cleaned_data['text'].strip()
172 if text:
174 if text:
173 if len(text) > board_settings.MAX_TEXT_LENGTH:
175 if len(text) > board_settings.MAX_TEXT_LENGTH:
174 raise forms.ValidationError(_('Text must have less than %s '
176 raise forms.ValidationError(_('Text must have less than %s '
175 'characters') %
177 'characters') %
176 str(board_settings
178 str(board_settings
177 .MAX_TEXT_LENGTH))
179 .MAX_TEXT_LENGTH))
178 return text
180 return text
179
181
180 def clean_image(self):
182 def clean_image(self):
181 image = self.cleaned_data['image']
183 image = self.cleaned_data['image']
182
184
183 if image:
185 if image:
184 self.validate_image_size(image.size)
186 self.validate_image_size(image.size)
185
187
186 return image
188 return image
187
189
188 def clean_image_url(self):
190 def clean_image_url(self):
189 url = self.cleaned_data['image_url']
191 url = self.cleaned_data['image_url']
190
192
191 image = None
193 image = None
192 if url:
194 if url:
193 image = self._get_image_from_url(url)
195 image = self._get_image_from_url(url)
194
196
195 if not image:
197 if not image:
196 raise forms.ValidationError(_('Invalid URL'))
198 raise forms.ValidationError(_('Invalid URL'))
197 else:
199 else:
198 self.validate_image_size(image.size)
200 self.validate_image_size(image.size)
199
201
200 return image
202 return image
201
203
202 def clean_threads(self):
204 def clean_threads(self):
203 threads_str = self.cleaned_data['threads']
205 threads_str = self.cleaned_data['threads']
204
206
205 if len(threads_str) > 0:
207 if len(threads_str) > 0:
206 threads_id_list = threads_str.split(' ')
208 threads_id_list = threads_str.split(' ')
207
209
208 threads = list()
210 threads = list()
209
211
210 for thread_id in threads_id_list:
212 for thread_id in threads_id_list:
211 try:
213 try:
212 thread = Post.objects.get(id=int(thread_id))
214 thread = Post.objects.get(id=int(thread_id))
213 if not thread.is_opening():
215 if not thread.is_opening():
214 raise ObjectDoesNotExist()
216 raise ObjectDoesNotExist()
215 threads.append(thread)
217 threads.append(thread)
216 except (ObjectDoesNotExist, ValueError):
218 except (ObjectDoesNotExist, ValueError):
217 raise forms.ValidationError(_('Invalid additional thread list'))
219 raise forms.ValidationError(_('Invalid additional thread list'))
218
220
219 return threads
221 return threads
220
222
221 def clean(self):
223 def clean(self):
222 cleaned_data = super(PostForm, self).clean()
224 cleaned_data = super(PostForm, self).clean()
223
225
224 if cleaned_data['email']:
226 if cleaned_data['email']:
225 self.need_to_ban = True
227 self.need_to_ban = True
226 raise forms.ValidationError('A human cannot enter a hidden field')
228 raise forms.ValidationError('A human cannot enter a hidden field')
227
229
228 if not self.errors:
230 if not self.errors:
229 self._clean_text_image()
231 self._clean_text_image()
230
232
231 if not self.errors and self.session:
233 if not self.errors and self.session:
232 self._validate_posting_speed()
234 self._validate_posting_speed()
233
235
234 return cleaned_data
236 return cleaned_data
235
237
236 def get_image(self):
238 def get_image(self):
237 """
239 """
238 Gets image from file or URL.
240 Gets image from file or URL.
239 """
241 """
240
242
241 image = self.cleaned_data['image']
243 image = self.cleaned_data['image']
242 return image if image else self.cleaned_data['image_url']
244 return image if image else self.cleaned_data['image_url']
243
245
244 def _clean_text_image(self):
246 def _clean_text_image(self):
245 text = self.cleaned_data.get('text')
247 text = self.cleaned_data.get('text')
246 image = self.get_image()
248 image = self.get_image()
247
249
248 if (not text) and (not image):
250 if (not text) and (not image):
249 error_message = _('Either text or image must be entered.')
251 error_message = _('Either text or image must be entered.')
250 self._errors['text'] = self.error_class([error_message])
252 self._errors['text'] = self.error_class([error_message])
251
253
252 def _validate_posting_speed(self):
254 def _validate_posting_speed(self):
253 can_post = True
255 can_post = True
254
256
255 posting_delay = settings.POSTING_DELAY
257 posting_delay = settings.POSTING_DELAY
256
258
257 if board_settings.LIMIT_POSTING_SPEED:
259 if board_settings.LIMIT_POSTING_SPEED:
258 now = time.time()
260 now = time.time()
259
261
260 current_delay = 0
262 current_delay = 0
261 need_delay = False
263 need_delay = False
262
264
263 if not LAST_POST_TIME in self.session:
265 if not LAST_POST_TIME in self.session:
264 self.session[LAST_POST_TIME] = now
266 self.session[LAST_POST_TIME] = now
265
267
266 need_delay = True
268 need_delay = True
267 else:
269 else:
268 last_post_time = self.session.get(LAST_POST_TIME)
270 last_post_time = self.session.get(LAST_POST_TIME)
269 current_delay = int(now - last_post_time)
271 current_delay = int(now - last_post_time)
270
272
271 need_delay = current_delay < posting_delay
273 need_delay = current_delay < posting_delay
272
274
273 if need_delay:
275 if need_delay:
274 error_message = ERROR_SPEED % str(posting_delay
276 error_message = ERROR_SPEED % str(posting_delay
275 - current_delay)
277 - current_delay)
276 self._errors['text'] = self.error_class([error_message])
278 self._errors['text'] = self.error_class([error_message])
277
279
278 can_post = False
280 can_post = False
279
281
280 if can_post:
282 if can_post:
281 self.session[LAST_POST_TIME] = now
283 self.session[LAST_POST_TIME] = now
282
284
283 def validate_image_size(self, size: int):
285 def validate_image_size(self, size: int):
284 if size > board_settings.MAX_IMAGE_SIZE:
286 if size > board_settings.MAX_IMAGE_SIZE:
285 raise forms.ValidationError(
287 raise forms.ValidationError(
286 _('Image must be less than %s bytes')
288 _('Image must be less than %s bytes')
287 % str(board_settings.MAX_IMAGE_SIZE))
289 % str(board_settings.MAX_IMAGE_SIZE))
288
290
289 def _get_image_from_url(self, url: str) -> SimpleUploadedFile:
291 def _get_image_from_url(self, url: str) -> SimpleUploadedFile:
290 """
292 """
291 Gets an image file from URL.
293 Gets an image file from URL.
292 """
294 """
293
295
294 img_temp = None
296 img_temp = None
295
297
296 try:
298 try:
297 # Verify content headers
299 # Verify content headers
298 response_head = requests.head(url, verify=False)
300 response_head = requests.head(url, verify=False)
299 content_type = response_head.headers['content-type'].split(';')[0]
301 content_type = response_head.headers['content-type'].split(';')[0]
300 if content_type in CONTENT_TYPE_IMAGE:
302 if content_type in CONTENT_TYPE_IMAGE:
301 length_header = response_head.headers.get('content-length')
303 length_header = response_head.headers.get('content-length')
302 if length_header:
304 if length_header:
303 length = int(length_header)
305 length = int(length_header)
304 self.validate_image_size(length)
306 self.validate_image_size(length)
305 # Get the actual content into memory
307 # Get the actual content into memory
306 response = requests.get(url, verify=False, stream=True)
308 response = requests.get(url, verify=False, stream=True)
307
309
308 # Download image, stop if the size exceeds limit
310 # Download image, stop if the size exceeds limit
309 size = 0
311 size = 0
310 content = b''
312 content = b''
311 for chunk in response.iter_content(IMAGE_DOWNLOAD_CHUNK_BYTES):
313 for chunk in response.iter_content(IMAGE_DOWNLOAD_CHUNK_BYTES):
312 size += len(chunk)
314 size += len(chunk)
313 self.validate_image_size(size)
315 self.validate_image_size(size)
314 content += chunk
316 content += chunk
315
317
316 if response.status_code == HTTP_RESULT_OK and content:
318 if response.status_code == HTTP_RESULT_OK and content:
317 # Set a dummy file name that will be replaced
319 # Set a dummy file name that will be replaced
318 # anyway, just keep the valid extension
320 # anyway, just keep the valid extension
319 filename = 'image.' + content_type.split('/')[1]
321 filename = 'image.' + content_type.split('/')[1]
320 img_temp = SimpleUploadedFile(filename, content,
322 img_temp = SimpleUploadedFile(filename, content,
321 content_type)
323 content_type)
322 except Exception:
324 except Exception:
323 # Just return no image
325 # Just return no image
324 pass
326 pass
325
327
326 return img_temp
328 return img_temp
327
329
328
330
329 class ThreadForm(PostForm):
331 class ThreadForm(PostForm):
330
332
331 tags = forms.CharField(
333 tags = forms.CharField(
332 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
334 widget=forms.TextInput(attrs={ATTRIBUTE_PLACEHOLDER: TAGS_PLACEHOLDER}),
333 max_length=100, label=_('Tags'), required=True)
335 max_length=100, label=_('Tags'), required=True)
334
336
335 def clean_tags(self):
337 def clean_tags(self):
336 tags = self.cleaned_data['tags'].strip()
338 tags = self.cleaned_data['tags'].strip()
337
339
338 if not tags or not REGEX_TAGS.match(tags):
340 if not tags or not REGEX_TAGS.match(tags):
339 raise forms.ValidationError(
341 raise forms.ValidationError(
340 _('Inappropriate characters in tags.'))
342 _('Inappropriate characters in tags.'))
341
343
342 required_tag_exists = False
344 required_tag_exists = False
343 for tag in tags.split():
345 for tag in tags.split():
344 try:
346 try:
345 Tag.objects.get(name=tag.strip().lower(), required=True)
347 Tag.objects.get(name=tag.strip().lower(), required=True)
346 required_tag_exists = True
348 required_tag_exists = True
347 break
349 break
348 except ObjectDoesNotExist:
350 except ObjectDoesNotExist:
349 pass
351 pass
350
352
351 if not required_tag_exists:
353 if not required_tag_exists:
352 all_tags = Tag.objects.filter(required=True)
354 all_tags = Tag.objects.filter(required=True)
353 raise forms.ValidationError(
355 raise forms.ValidationError(
354 _('Need at least one of the tags: ')
356 _('Need at least one of the tags: ')
355 + ', '.join([tag.name for tag in all_tags]))
357 + ', '.join([tag.name for tag in all_tags]))
356
358
357 return tags
359 return tags
358
360
359 def clean(self):
361 def clean(self):
360 cleaned_data = super(ThreadForm, self).clean()
362 cleaned_data = super(ThreadForm, self).clean()
361
363
362 return cleaned_data
364 return cleaned_data
363
365
364
366
365 class SettingsForm(NeboardForm):
367 class SettingsForm(NeboardForm):
366
368
367 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
369 theme = forms.ChoiceField(choices=settings.THEMES, label=_('Theme'))
368 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
370 image_viewer = forms.ChoiceField(choices=settings.IMAGE_VIEWERS, label=_('Image view mode'))
369 username = forms.CharField(label=_('User name'), required=False)
371 username = forms.CharField(label=_('User name'), required=False)
370 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
372 timezone = forms.ChoiceField(choices=get_timezones(), label=_('Time zone'))
371
373
372 def clean_username(self):
374 def clean_username(self):
373 username = self.cleaned_data['username']
375 username = self.cleaned_data['username']
374
376
375 if username and not REGEX_TAGS.match(username):
377 if username and not REGEX_TAGS.match(username):
376 raise forms.ValidationError(_('Inappropriate characters.'))
378 raise forms.ValidationError(_('Inappropriate characters.'))
377
379
378 return username
380 return username
379
381
380
382
381 class SearchForm(NeboardForm):
383 class SearchForm(NeboardForm):
382 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
384 query = forms.CharField(max_length=500, label=LABEL_SEARCH, required=False)
@@ -1,520 +1,525 b''
1 * {
1 * {
2 text-decoration: none;
2 text-decoration: none;
3 font-weight: inherit;
3 font-weight: inherit;
4 }
4 }
5
5
6 b, strong {
6 b, strong {
7 font-weight: bold;
7 font-weight: bold;
8 }
8 }
9
9
10 html {
10 html {
11 background: #555;
11 background: #555;
12 color: #ffffff;
12 color: #ffffff;
13 }
13 }
14
14
15 body {
15 body {
16 margin: 0;
16 margin: 0;
17 }
17 }
18
18
19 #admin_panel {
19 #admin_panel {
20 background: #FF0000;
20 background: #FF0000;
21 color: #00FF00
21 color: #00FF00
22 }
22 }
23
23
24 .input_field_error {
24 .input_field_error {
25 color: #FF0000;
25 color: #FF0000;
26 }
26 }
27
27
28 .title {
28 .title {
29 font-weight: bold;
29 font-weight: bold;
30 color: #ffcc00;
30 color: #ffcc00;
31 }
31 }
32
32
33 .link, a {
33 .link, a {
34 color: #afdcec;
34 color: #afdcec;
35 }
35 }
36
36
37 .block {
37 .block {
38 display: inline-block;
38 display: inline-block;
39 vertical-align: top;
39 vertical-align: top;
40 }
40 }
41
41
42 .tag {
42 .tag {
43 color: #FFD37D;
43 color: #FFD37D;
44 }
44 }
45
45
46 .post_id {
46 .post_id {
47 color: #fff380;
47 color: #fff380;
48 }
48 }
49
49
50 .post, .dead_post, .archive_post, #posts-table {
50 .post, .dead_post, .archive_post, #posts-table {
51 background: #333;
51 background: #333;
52 padding: 10px;
52 padding: 10px;
53 clear: left;
53 clear: left;
54 word-wrap: break-word;
54 word-wrap: break-word;
55 border-top: 1px solid #777;
55 border-top: 1px solid #777;
56 border-bottom: 1px solid #777;
56 border-bottom: 1px solid #777;
57 }
57 }
58
58
59 .post + .post {
59 .post + .post {
60 border-top: none;
60 border-top: none;
61 }
61 }
62
62
63 .dead_post + .dead_post {
63 .dead_post + .dead_post {
64 border-top: none;
64 border-top: none;
65 }
65 }
66
66
67 .archive_post + .archive_post {
67 .archive_post + .archive_post {
68 border-top: none;
68 border-top: none;
69 }
69 }
70
70
71 .metadata {
71 .metadata {
72 padding-top: 5px;
72 padding-top: 5px;
73 margin-top: 10px;
73 margin-top: 10px;
74 border-top: solid 1px #666;
74 border-top: solid 1px #666;
75 color: #ddd;
75 color: #ddd;
76 }
76 }
77
77
78 .navigation_panel, .tag_info {
78 .navigation_panel, .tag_info {
79 background: #222;
79 background: #222;
80 margin-bottom: 5px;
80 margin-bottom: 5px;
81 margin-top: 5px;
81 margin-top: 5px;
82 padding: 10px;
82 padding: 10px;
83 border-bottom: solid 1px #888;
83 border-bottom: solid 1px #888;
84 border-top: solid 1px #888;
84 border-top: solid 1px #888;
85 color: #eee;
85 color: #eee;
86 }
86 }
87
87
88 .navigation_panel .link:first-child {
88 .navigation_panel .link:first-child {
89 border-right: 1px solid #fff;
89 border-right: 1px solid #fff;
90 font-weight: bold;
90 font-weight: bold;
91 margin-right: 1ex;
91 margin-right: 1ex;
92 padding-right: 1ex;
92 padding-right: 1ex;
93 }
93 }
94
94
95 .navigation_panel .right-link {
95 .navigation_panel .right-link {
96 border-left: 1px solid #fff;
96 border-left: 1px solid #fff;
97 border-right: none;
97 border-right: none;
98 float: right;
98 float: right;
99 margin-left: 1ex;
99 margin-left: 1ex;
100 margin-right: 0;
100 margin-right: 0;
101 padding-left: 1ex;
101 padding-left: 1ex;
102 padding-right: 0;
102 padding-right: 0;
103 }
103 }
104
104
105 .navigation_panel .link {
105 .navigation_panel .link {
106 font-weight: bold;
106 font-weight: bold;
107 }
107 }
108
108
109 .navigation_panel::after, .post::after {
109 .navigation_panel::after, .post::after {
110 clear: both;
110 clear: both;
111 content: ".";
111 content: ".";
112 display: block;
112 display: block;
113 height: 0;
113 height: 0;
114 line-height: 0;
114 line-height: 0;
115 visibility: hidden;
115 visibility: hidden;
116 }
116 }
117
117
118 .header {
118 .header {
119 border-bottom: solid 2px #ccc;
119 border-bottom: solid 2px #ccc;
120 margin-bottom: 5px;
120 margin-bottom: 5px;
121 border-top: none;
121 border-top: none;
122 margin-top: 0;
122 margin-top: 0;
123 }
123 }
124
124
125 .footer {
125 .footer {
126 border-top: solid 2px #ccc;
126 border-top: solid 2px #ccc;
127 margin-top: 5px;
127 margin-top: 5px;
128 border-bottom: none;
128 border-bottom: none;
129 margin-bottom: 0;
129 margin-bottom: 0;
130 }
130 }
131
131
132 p, .br {
132 p, .br {
133 margin-top: .5em;
133 margin-top: .5em;
134 margin-bottom: .5em;
134 margin-bottom: .5em;
135 }
135 }
136
136
137 .post-form-w {
137 .post-form-w {
138 background: #333344;
138 background: #333344;
139 border-top: solid 1px #888;
139 border-top: solid 1px #888;
140 border-bottom: solid 1px #888;
140 border-bottom: solid 1px #888;
141 color: #fff;
141 color: #fff;
142 padding: 10px;
142 padding: 10px;
143 margin-bottom: 5px;
143 margin-bottom: 5px;
144 margin-top: 5px;
144 margin-top: 5px;
145 }
145 }
146
146
147 .form-row {
147 .form-row {
148 width: 100%;
148 width: 100%;
149 display: table-row;
149 }
150 }
150
151
151 .form-label {
152 .form-label {
152 padding: .25em 1ex .25em 0;
153 padding: .25em 1ex .25em 0;
153 vertical-align: top;
154 vertical-align: top;
155 display: table-cell;
154 }
156 }
155
157
156 .form-input {
158 .form-input {
157 padding: .25em 0;
159 padding: .25em 0;
160 width: 100%;
161 display: table-cell;
158 }
162 }
159
163
160 .form-errors {
164 .form-errors {
161 font-weight: bolder;
165 font-weight: bolder;
162 vertical-align: middle;
166 vertical-align: middle;
167 display: table-cell;
163 }
168 }
164
169
165 .post-form input:not([name="image"]), .post-form textarea, .post-form select {
170 .post-form input:not([name="image"]), .post-form textarea, .post-form select {
166 background: #333;
171 background: #333;
167 color: #fff;
172 color: #fff;
168 border: solid 1px;
173 border: solid 1px;
169 padding: 0;
174 padding: 0;
170 font: medium sans-serif;
175 font: medium sans-serif;
171 width: 100%;
176 width: 100%;
172 }
177 }
173
178
174 .post-form textarea {
179 .post-form textarea {
175 resize: vertical;
180 resize: vertical;
176 }
181 }
177
182
178 .form-submit {
183 .form-submit {
179 display: table;
184 display: table;
180 margin-bottom: 1ex;
185 margin-bottom: 1ex;
181 margin-top: 1ex;
186 margin-top: 1ex;
182 }
187 }
183
188
184 .form-title {
189 .form-title {
185 font-weight: bold;
190 font-weight: bold;
186 font-size: 2ex;
191 font-size: 2ex;
187 margin-bottom: 0.5ex;
192 margin-bottom: 0.5ex;
188 }
193 }
189
194
190 .post-form input[type="submit"], input[type="submit"] {
195 .post-form input[type="submit"], input[type="submit"] {
191 background: #222;
196 background: #222;
192 border: solid 2px #fff;
197 border: solid 2px #fff;
193 color: #fff;
198 color: #fff;
194 padding: 0.5ex;
199 padding: 0.5ex;
195 }
200 }
196
201
197 input[type="submit"]:hover {
202 input[type="submit"]:hover {
198 background: #060;
203 background: #060;
199 }
204 }
200
205
201 blockquote {
206 blockquote {
202 border-left: solid 2px;
207 border-left: solid 2px;
203 padding-left: 5px;
208 padding-left: 5px;
204 color: #B1FB17;
209 color: #B1FB17;
205 margin: 0;
210 margin: 0;
206 }
211 }
207
212
208 .post > .image {
213 .post > .image {
209 float: left;
214 float: left;
210 margin: 0 1ex .5ex 0;
215 margin: 0 1ex .5ex 0;
211 min-width: 1px;
216 min-width: 1px;
212 text-align: center;
217 text-align: center;
213 display: table-row;
218 display: table-row;
214 }
219 }
215
220
216 .post > .metadata {
221 .post > .metadata {
217 clear: left;
222 clear: left;
218 }
223 }
219
224
220 .get {
225 .get {
221 font-weight: bold;
226 font-weight: bold;
222 color: #d55;
227 color: #d55;
223 }
228 }
224
229
225 * {
230 * {
226 text-decoration: none;
231 text-decoration: none;
227 }
232 }
228
233
229 .dead_post {
234 .dead_post {
230 background-color: #442222;
235 background-color: #442222;
231 }
236 }
232
237
233 .archive_post {
238 .archive_post {
234 background-color: #000;
239 background-color: #000;
235 }
240 }
236
241
237 .mark_btn {
242 .mark_btn {
238 border: 1px solid;
243 border: 1px solid;
239 min-width: 2ex;
244 min-width: 2ex;
240 padding: 2px 2ex;
245 padding: 2px 2ex;
241 }
246 }
242
247
243 .mark_btn:hover {
248 .mark_btn:hover {
244 background: #555;
249 background: #555;
245 }
250 }
246
251
247 .quote {
252 .quote {
248 color: #92cf38;
253 color: #92cf38;
249 font-style: italic;
254 font-style: italic;
250 }
255 }
251
256
252 .multiquote {
257 .multiquote {
253 padding: 3px;
258 padding: 3px;
254 display: inline-block;
259 display: inline-block;
255 background: #222;
260 background: #222;
256 border-style: solid;
261 border-style: solid;
257 border-width: 1px 1px 1px 4px;
262 border-width: 1px 1px 1px 4px;
258 font-size: 0.9em;
263 font-size: 0.9em;
259 }
264 }
260
265
261 .spoiler {
266 .spoiler {
262 background: black;
267 background: black;
263 color: black;
268 color: black;
264 padding: 0 1ex 0 1ex;
269 padding: 0 1ex 0 1ex;
265 }
270 }
266
271
267 .spoiler:hover {
272 .spoiler:hover {
268 color: #ddd;
273 color: #ddd;
269 }
274 }
270
275
271 .comment {
276 .comment {
272 color: #eb2;
277 color: #eb2;
273 }
278 }
274
279
275 a:hover {
280 a:hover {
276 text-decoration: underline;
281 text-decoration: underline;
277 }
282 }
278
283
279 .last-replies {
284 .last-replies {
280 margin-left: 3ex;
285 margin-left: 3ex;
281 margin-right: 3ex;
286 margin-right: 3ex;
282 border-left: solid 1px #777;
287 border-left: solid 1px #777;
283 border-right: solid 1px #777;
288 border-right: solid 1px #777;
284 }
289 }
285
290
286 .last-replies > .post:first-child {
291 .last-replies > .post:first-child {
287 border-top: none;
292 border-top: none;
288 }
293 }
289
294
290 .thread {
295 .thread {
291 margin-bottom: 3ex;
296 margin-bottom: 3ex;
292 margin-top: 1ex;
297 margin-top: 1ex;
293 }
298 }
294
299
295 .post:target {
300 .post:target {
296 border: solid 2px white;
301 border: solid 2px white;
297 }
302 }
298
303
299 pre{
304 pre{
300 white-space:pre-wrap
305 white-space:pre-wrap
301 }
306 }
302
307
303 li {
308 li {
304 list-style-position: inside;
309 list-style-position: inside;
305 }
310 }
306
311
307 .fancybox-skin {
312 .fancybox-skin {
308 position: relative;
313 position: relative;
309 background-color: #fff;
314 background-color: #fff;
310 color: #ddd;
315 color: #ddd;
311 text-shadow: none;
316 text-shadow: none;
312 }
317 }
313
318
314 .fancybox-image {
319 .fancybox-image {
315 border: 1px solid black;
320 border: 1px solid black;
316 }
321 }
317
322
318 .image-mode-tab {
323 .image-mode-tab {
319 background: #444;
324 background: #444;
320 color: #eee;
325 color: #eee;
321 margin-top: 5px;
326 margin-top: 5px;
322 padding: 5px;
327 padding: 5px;
323 border-top: 1px solid #888;
328 border-top: 1px solid #888;
324 border-bottom: 1px solid #888;
329 border-bottom: 1px solid #888;
325 }
330 }
326
331
327 .image-mode-tab > label {
332 .image-mode-tab > label {
328 margin: 0 1ex;
333 margin: 0 1ex;
329 }
334 }
330
335
331 .image-mode-tab > label > input {
336 .image-mode-tab > label > input {
332 margin-right: .5ex;
337 margin-right: .5ex;
333 }
338 }
334
339
335 #posts-table {
340 #posts-table {
336 margin-top: 5px;
341 margin-top: 5px;
337 margin-bottom: 5px;
342 margin-bottom: 5px;
338 }
343 }
339
344
340 .tag_info > h2 {
345 .tag_info > h2 {
341 margin: 0;
346 margin: 0;
342 }
347 }
343
348
344 .post-info {
349 .post-info {
345 color: #ddd;
350 color: #ddd;
346 margin-bottom: 1ex;
351 margin-bottom: 1ex;
347 }
352 }
348
353
349 .moderator_info {
354 .moderator_info {
350 color: #e99d41;
355 color: #e99d41;
351 float: right;
356 float: right;
352 font-weight: bold;
357 font-weight: bold;
353 opacity: 0.4;
358 opacity: 0.4;
354 }
359 }
355
360
356 .moderator_info:hover {
361 .moderator_info:hover {
357 opacity: 1;
362 opacity: 1;
358 }
363 }
359
364
360 .refmap {
365 .refmap {
361 font-size: 0.9em;
366 font-size: 0.9em;
362 color: #ccc;
367 color: #ccc;
363 margin-top: 1em;
368 margin-top: 1em;
364 }
369 }
365
370
366 .fav {
371 .fav {
367 color: yellow;
372 color: yellow;
368 }
373 }
369
374
370 .not_fav {
375 .not_fav {
371 color: #ccc;
376 color: #ccc;
372 }
377 }
373
378
374 .role {
379 .role {
375 text-decoration: underline;
380 text-decoration: underline;
376 }
381 }
377
382
378 .form-email {
383 .form-email {
379 display: none;
384 display: none;
380 }
385 }
381
386
382 .bar-value {
387 .bar-value {
383 background: rgba(50, 55, 164, 0.45);
388 background: rgba(50, 55, 164, 0.45);
384 font-size: 0.9em;
389 font-size: 0.9em;
385 height: 1.5em;
390 height: 1.5em;
386 }
391 }
387
392
388 .bar-bg {
393 .bar-bg {
389 position: relative;
394 position: relative;
390 border-top: solid 1px #888;
395 border-top: solid 1px #888;
391 border-bottom: solid 1px #888;
396 border-bottom: solid 1px #888;
392 margin-top: 5px;
397 margin-top: 5px;
393 overflow: hidden;
398 overflow: hidden;
394 }
399 }
395
400
396 .bar-text {
401 .bar-text {
397 padding: 2px;
402 padding: 2px;
398 position: absolute;
403 position: absolute;
399 left: 0;
404 left: 0;
400 top: 0;
405 top: 0;
401 }
406 }
402
407
403 .page_link {
408 .page_link {
404 background: #444;
409 background: #444;
405 border-top: solid 1px #888;
410 border-top: solid 1px #888;
406 border-bottom: solid 1px #888;
411 border-bottom: solid 1px #888;
407 padding: 5px;
412 padding: 5px;
408 color: #eee;
413 color: #eee;
409 font-size: 2ex;
414 font-size: 2ex;
410 }
415 }
411
416
412 .skipped_replies {
417 .skipped_replies {
413 padding: 5px;
418 padding: 5px;
414 margin-left: 3ex;
419 margin-left: 3ex;
415 margin-right: 3ex;
420 margin-right: 3ex;
416 border-left: solid 1px #888;
421 border-left: solid 1px #888;
417 border-right: solid 1px #888;
422 border-right: solid 1px #888;
418 border-bottom: solid 1px #888;
423 border-bottom: solid 1px #888;
419 background: #000;
424 background: #000;
420 }
425 }
421
426
422 .current_page {
427 .current_page {
423 padding: 2px;
428 padding: 2px;
424 background-color: #afdcec;
429 background-color: #afdcec;
425 color: #000;
430 color: #000;
426 }
431 }
427
432
428 .current_mode {
433 .current_mode {
429 font-weight: bold;
434 font-weight: bold;
430 }
435 }
431
436
432 .gallery_image {
437 .gallery_image {
433 border: solid 1px;
438 border: solid 1px;
434 padding: 0.5ex;
439 padding: 0.5ex;
435 margin: 0.5ex;
440 margin: 0.5ex;
436 text-align: center;
441 text-align: center;
437 }
442 }
438
443
439 code {
444 code {
440 border: dashed 1px #ccc;
445 border: dashed 1px #ccc;
441 background: #111;
446 background: #111;
442 padding: 2px;
447 padding: 2px;
443 font-size: 1.2em;
448 font-size: 1.2em;
444 display: inline-block;
449 display: inline-block;
445 }
450 }
446
451
447 pre {
452 pre {
448 overflow: auto;
453 overflow: auto;
449 }
454 }
450
455
451 .img-full {
456 .img-full {
452 background: #222;
457 background: #222;
453 border: solid 1px white;
458 border: solid 1px white;
454 }
459 }
455
460
456 .tag_item {
461 .tag_item {
457 display: inline-block;
462 display: inline-block;
458 }
463 }
459
464
460 #id_models li {
465 #id_models li {
461 list-style: none;
466 list-style: none;
462 }
467 }
463
468
464 #id_q {
469 #id_q {
465 margin-left: 1ex;
470 margin-left: 1ex;
466 }
471 }
467
472
468 ul {
473 ul {
469 padding-left: 0px;
474 padding-left: 0px;
470 }
475 }
471
476
472 .quote-header {
477 .quote-header {
473 border-bottom: 2px solid #ddd;
478 border-bottom: 2px solid #ddd;
474 margin-bottom: 1ex;
479 margin-bottom: 1ex;
475 padding-bottom: .5ex;
480 padding-bottom: .5ex;
476 color: #ddd;
481 color: #ddd;
477 font-size: 1.2em;
482 font-size: 1.2em;
478 }
483 }
479
484
480 /* Reflink preview */
485 /* Reflink preview */
481 .post_preview {
486 .post_preview {
482 border-left: 1px solid #777;
487 border-left: 1px solid #777;
483 border-right: 1px solid #777;
488 border-right: 1px solid #777;
484 max-width: 600px;
489 max-width: 600px;
485 }
490 }
486
491
487 /* Code highlighter */
492 /* Code highlighter */
488 .hljs {
493 .hljs {
489 color: #fff;
494 color: #fff;
490 background: #000;
495 background: #000;
491 display: inline-block;
496 display: inline-block;
492 }
497 }
493
498
494 .hljs, .hljs-subst, .hljs-tag .hljs-title, .lisp .hljs-title, .clojure .hljs-built_in, .nginx .hljs-title {
499 .hljs, .hljs-subst, .hljs-tag .hljs-title, .lisp .hljs-title, .clojure .hljs-built_in, .nginx .hljs-title {
495 color: #fff;
500 color: #fff;
496 }
501 }
497
502
498 #up {
503 #up {
499 position: fixed;
504 position: fixed;
500 bottom: 5px;
505 bottom: 5px;
501 right: 5px;
506 right: 5px;
502 border: 1px solid #777;
507 border: 1px solid #777;
503 background: #000;
508 background: #000;
504 padding: 4px;
509 padding: 4px;
505 }
510 }
506
511
507 .user-cast {
512 .user-cast {
508 border: solid #ffffff 1px;
513 border: solid #ffffff 1px;
509 padding: .2ex;
514 padding: .2ex;
510 background: #152154;
515 background: #152154;
511 color: #fff;
516 color: #fff;
512 }
517 }
513
518
514 .highlight {
519 .highlight {
515 background-color: #222;
520 background-color: #222;
516 }
521 }
517
522
518 .post-button-form > button:hover {
523 .post-button-form > button:hover {
519 text-decoration: underline;
524 text-decoration: underline;
520 }
525 }
General Comments 0
You need to be logged in to leave comments. Login now