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