##// END OF EJS Templates
Image search in every image view, using context menu
neko259 -
r1812:f2a9c571 default
parent child Browse files
Show More
1 NO CONTENT: modified file, binary diff hidden
@@ -1,583 +1,586 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 msgid ""
7 7 msgstr ""
8 8 "Project-Id-Version: PACKAGE VERSION\n"
9 9 "Report-Msgid-Bugs-To: \n"
10 10 "POT-Creation-Date: 2015-10-09 23:21+0300\n"
11 11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 14 "Language: ru\n"
15 15 "MIME-Version: 1.0\n"
16 16 "Content-Type: text/plain; charset=UTF-8\n"
17 17 "Content-Transfer-Encoding: 8bit\n"
18 18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20 20
21 21 #: admin.py:22
22 22 msgid "{} posters were banned"
23 23 msgstr ""
24 24
25 25 #: authors.py:9
26 26 msgid "author"
27 27 msgstr "автор"
28 28
29 29 #: authors.py:10
30 30 msgid "developer"
31 31 msgstr "разработчик"
32 32
33 33 #: authors.py:11
34 34 msgid "javascript developer"
35 35 msgstr "разработчик javascript"
36 36
37 37 #: authors.py:12
38 38 msgid "designer"
39 39 msgstr "дизайнер"
40 40
41 41 #: forms.py:30
42 42 msgid "Type message here. Use formatting panel for more advanced usage."
43 43 msgstr ""
44 44 "Вводите сообщение сюда. Используйте панель для более сложного форматирования."
45 45
46 46 #: forms.py:31
47 47 msgid "music images i_dont_like_tags"
48 48 msgstr "музыка картинки теги_не_нужны"
49 49
50 50 #: forms.py:33
51 51 msgid "Title"
52 52 msgstr "Заголовок"
53 53
54 54 #: forms.py:34
55 55 msgid "Text"
56 56 msgstr "Текст"
57 57
58 58 #: forms.py:35
59 59 msgid "Tag"
60 60 msgstr "Метка"
61 61
62 62 #: forms.py:36 templates/boards/base.html:40 templates/search/search.html:7
63 63 msgid "Search"
64 64 msgstr "Поиск"
65 65
66 66 #: forms.py:48
67 67 msgid "File 1"
68 68 msgstr "Файл 1"
69 69
70 70 #: forms.py:48
71 71 msgid "File 2"
72 72 msgstr "Файл 2"
73 73
74 74 #: forms.py:142
75 75 msgid "File URL"
76 76 msgstr "URL файла"
77 77
78 78 #: forms.py:148
79 79 msgid "e-mail"
80 80 msgstr ""
81 81
82 82 #: forms.py:151
83 83 msgid "Additional threads"
84 84 msgstr "Дополнительные темы"
85 85
86 86 #: forms.py:162
87 87 #, python-format
88 88 msgid "Title must have less than %s characters"
89 89 msgstr "Заголовок должен иметь меньше %s символов"
90 90
91 91 #: forms.py:172
92 92 #, python-format
93 93 msgid "Text must have less than %s characters"
94 94 msgstr "Текст должен быть короче %s символов"
95 95
96 96 #: forms.py:192
97 97 msgid "Invalid URL"
98 98 msgstr "Неверный URL"
99 99
100 100 #: forms.py:213
101 101 msgid "Invalid additional thread list"
102 102 msgstr "Неверный список дополнительных тем"
103 103
104 104 #: forms.py:258
105 105 msgid "Either text or file must be entered."
106 106 msgstr "Текст или файл должны быть введены."
107 107
108 108 #: forms.py:317 templates/boards/all_threads.html:153
109 109 #: templates/boards/rss/post.html:10 templates/boards/tags.html:6
110 110 msgid "Tags"
111 111 msgstr "Метки"
112 112
113 113 #: forms.py:324
114 114 msgid "Inappropriate characters in tags."
115 115 msgstr "Недопустимые символы в метках."
116 116
117 117 #: forms.py:344
118 118 msgid "Need at least one section."
119 119 msgstr "Нужен хотя бы один раздел."
120 120
121 121 #: forms.py:356
122 122 msgid "Theme"
123 123 msgstr "Тема"
124 124
125 125 #: forms.py:357
126 126 msgid "Image view mode"
127 127 msgstr "Режим просмотра изображений"
128 128
129 129 #: forms.py:358
130 130 msgid "User name"
131 131 msgstr "Имя пользователя"
132 132
133 133 #: forms.py:359
134 134 msgid "Time zone"
135 135 msgstr "Часовой пояс"
136 136
137 137 #: forms.py:365
138 138 msgid "Inappropriate characters."
139 139 msgstr "Недопустимые символы."
140 140
141 141 #: templates/boards/404.html:6
142 142 msgid "Not found"
143 143 msgstr "Не найдено"
144 144
145 145 #: templates/boards/404.html:12
146 146 msgid "This page does not exist"
147 147 msgstr "Этой страницы не существует"
148 148
149 149 #: templates/boards/all_threads.html:35
150 150 msgid "Details"
151 151 msgstr "Подробности"
152 152
153 153 #: templates/boards/all_threads.html:69
154 154 msgid "Edit tag"
155 155 msgstr "Изменить метку"
156 156
157 157 #: templates/boards/all_threads.html:76
158 158 #, python-format
159 159 msgid "%(count)s active thread"
160 160 msgid_plural "%(count)s active threads"
161 161 msgstr[0] "%(count)s активная тема"
162 162 msgstr[1] "%(count)s активные темы"
163 163 msgstr[2] "%(count)s активных тем"
164 164
165 165 #: templates/boards/all_threads.html:76
166 166 #, python-format
167 167 msgid "%(count)s thread in bumplimit"
168 168 msgid_plural "%(count)s threads in bumplimit"
169 169 msgstr[0] "%(count)s тема в бамплимите"
170 170 msgstr[1] "%(count)s темы в бамплимите"
171 171 msgstr[2] "%(count)s тем в бамплимите"
172 172
173 173 #: templates/boards/all_threads.html:77
174 174 #, python-format
175 175 msgid "%(count)s archived thread"
176 176 msgid_plural "%(count)s archived thread"
177 177 msgstr[0] "%(count)s архивная тема"
178 178 msgstr[1] "%(count)s архивные темы"
179 179 msgstr[2] "%(count)s архивных тем"
180 180
181 181 #: templates/boards/all_threads.html:78 templates/boards/post.html:102
182 182 #, python-format
183 183 #| msgid "%(count)s message"
184 184 #| msgid_plural "%(count)s messages"
185 185 msgid "%(count)s message"
186 186 msgid_plural "%(count)s messages"
187 187 msgstr[0] "%(count)s сообщение"
188 188 msgstr[1] "%(count)s сообщения"
189 189 msgstr[2] "%(count)s сообщений"
190 190
191 191 #: templates/boards/all_threads.html:95 templates/boards/feed.html:30
192 192 #: templates/boards/notifications.html:17 templates/search/search.html:26
193 193 msgid "Previous page"
194 194 msgstr "Предыдущая страница"
195 195
196 196 #: templates/boards/all_threads.html:109
197 197 #, python-format
198 198 msgid "Skipped %(count)s reply. Open thread to see all replies."
199 199 msgid_plural "Skipped %(count)s replies. Open thread to see all replies."
200 200 msgstr[0] "Пропущен %(count)s ответ. Откройте тред, чтобы увидеть все ответы."
201 201 msgstr[1] ""
202 202 "Пропущено %(count)s ответа. Откройте тред, чтобы увидеть все ответы."
203 203 msgstr[2] ""
204 204 "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
205 205
206 206 #: templates/boards/all_threads.html:127 templates/boards/feed.html:40
207 207 #: templates/boards/notifications.html:27 templates/search/search.html:37
208 208 msgid "Next page"
209 209 msgstr "Следующая страница"
210 210
211 211 #: templates/boards/all_threads.html:132
212 212 msgid "No threads exist. Create the first one!"
213 213 msgstr "Нет тем. Создайте первую!"
214 214
215 215 #: templates/boards/all_threads.html:138
216 216 msgid "Create new thread"
217 217 msgstr "Создать новую тему"
218 218
219 219 #: templates/boards/all_threads.html:143 templates/boards/preview.html:16
220 220 #: templates/boards/thread_normal.html:51
221 221 msgid "Post"
222 222 msgstr "Отправить"
223 223
224 224 #: templates/boards/all_threads.html:144 templates/boards/preview.html:6
225 225 #: templates/boards/staticpages/help.html:21
226 226 #: templates/boards/thread_normal.html:52
227 227 msgid "Preview"
228 228 msgstr "Предпросмотр"
229 229
230 230 #: templates/boards/all_threads.html:149
231 231 msgid "Tags must be delimited by spaces. Text or image is required."
232 232 msgstr ""
233 233 "Метки должны быть разделены пробелами. Текст или изображение обязательны."
234 234
235 235 #: templates/boards/all_threads.html:152 templates/boards/thread_normal.html:58
236 236 msgid "Text syntax"
237 237 msgstr "Синтаксис текста"
238 238
239 239 #: templates/boards/all_threads.html:166 templates/boards/feed.html:53
240 240 msgid "Pages:"
241 241 msgstr "Страницы: "
242 242
243 243 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
244 244 msgid "Authors"
245 245 msgstr "Авторы"
246 246
247 247 #: templates/boards/authors.html:26
248 248 msgid "Distributed under the"
249 249 msgstr "Распространяется под"
250 250
251 251 #: templates/boards/authors.html:28
252 252 msgid "license"
253 253 msgstr "лицензией"
254 254
255 255 #: templates/boards/authors.html:30
256 256 msgid "Repository"
257 257 msgstr "Репозиторий"
258 258
259 259 #: templates/boards/base.html:14 templates/boards/base.html.py:41
260 260 msgid "Feed"
261 261 msgstr "Лента"
262 262
263 263 #: templates/boards/base.html:31
264 264 msgid "All threads"
265 265 msgstr "Все темы"
266 266
267 267 #: templates/boards/base.html:37
268 268 msgid "Add tags"
269 269 msgstr "Добавить метки"
270 270
271 271 #: templates/boards/base.html:39
272 272 msgid "Tag management"
273 273 msgstr "Управление метками"
274 274
275 275 #: templates/boards/base.html:39
276 276 msgid "tags"
277 277 msgstr "метки"
278 278
279 279 #: templates/boards/base.html:40
280 280 msgid "search"
281 281 msgstr "поиск"
282 282
283 283 #: templates/boards/base.html:41 templates/boards/feed.html:11
284 284 msgid "feed"
285 285 msgstr "лента"
286 286
287 287 #: templates/boards/base.html:42 templates/boards/random.html:6
288 288 msgid "Random images"
289 289 msgstr "Случайные изображения"
290 290
291 291 #: templates/boards/base.html:42
292 292 msgid "random"
293 293 msgstr "случайные"
294 294
295 295 #: templates/boards/base.html:44
296 296 msgid "favorites"
297 297 msgstr "избранное"
298 298
299 299 #: templates/boards/base.html:48 templates/boards/base.html.py:49
300 300 #: templates/boards/notifications.html:8
301 301 msgid "Notifications"
302 302 msgstr "Уведомления"
303 303
304 304 #: templates/boards/base.html:56 templates/boards/settings.html:8
305 305 msgid "Settings"
306 306 msgstr "Настройки"
307 307
308 308 #: templates/boards/base.html:59
309 309 msgid "Loading..."
310 310 msgstr "Загрузка..."
311 311
312 312 #: templates/boards/base.html:71
313 313 msgid "Admin"
314 314 msgstr "Администрирование"
315 315
316 316 #: templates/boards/base.html:73
317 317 #, python-format
318 318 msgid "Speed: %(ppd)s posts per day"
319 319 msgstr "Скорость: %(ppd)s сообщений в день"
320 320
321 321 #: templates/boards/base.html:75
322 322 msgid "Up"
323 323 msgstr "Вверх"
324 324
325 325 #: templates/boards/feed.html:45
326 326 msgid "No posts exist. Create the first one!"
327 327 msgstr "Нет сообщений. Создайте первое!"
328 328
329 329 #: templates/boards/post.html:33
330 330 msgid "Open"
331 331 msgstr "Открыть"
332 332
333 333 #: templates/boards/post.html:35 templates/boards/post.html.py:46
334 334 msgid "Reply"
335 335 msgstr "Ответить"
336 336
337 337 #: templates/boards/post.html:41
338 338 msgid " in "
339 339 msgstr " в "
340 340
341 341 #: templates/boards/post.html:51
342 342 msgid "Edit"
343 343 msgstr "Изменить"
344 344
345 345 #: templates/boards/post.html:53
346 346 msgid "Edit thread"
347 347 msgstr "Изменить тему"
348 348
349 349 #: templates/boards/post.html:91
350 350 msgid "Replies"
351 351 msgstr "Ответы"
352 352
353 353 #: templates/boards/post.html:103
354 354 #, python-format
355 355 msgid "%(count)s image"
356 356 msgid_plural "%(count)s images"
357 357 msgstr[0] "%(count)s изображение"
358 358 msgstr[1] "%(count)s изображения"
359 359 msgstr[2] "%(count)s изображений"
360 360
361 361 #: templates/boards/rss/post.html:5
362 362 msgid "Post image"
363 363 msgstr "Изображение сообщения"
364 364
365 365 #: templates/boards/settings.html:15
366 366 msgid "You are moderator."
367 367 msgstr "Вы модератор."
368 368
369 369 #: templates/boards/settings.html:19
370 370 msgid "Hidden tags:"
371 371 msgstr "Скрытые метки:"
372 372
373 373 #: templates/boards/settings.html:25
374 374 msgid "No hidden tags."
375 375 msgstr "Нет скрытых меток."
376 376
377 377 #: templates/boards/settings.html:34
378 378 msgid "Save"
379 379 msgstr "Сохранить"
380 380
381 381 #: templates/boards/staticpages/banned.html:6
382 382 msgid "Banned"
383 383 msgstr "Заблокирован"
384 384
385 385 #: templates/boards/staticpages/banned.html:11
386 386 msgid "Your IP address has been banned. Contact the administrator"
387 387 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
388 388
389 389 #: templates/boards/staticpages/help.html:6
390 390 #: templates/boards/staticpages/help.html:10
391 391 msgid "Syntax"
392 392 msgstr "Синтаксис"
393 393
394 394 #: templates/boards/staticpages/help.html:11
395 395 msgid "Italic text"
396 396 msgstr "Курсивный текст"
397 397
398 398 #: templates/boards/staticpages/help.html:12
399 399 msgid "Bold text"
400 400 msgstr "Полужирный текст"
401 401
402 402 #: templates/boards/staticpages/help.html:13
403 403 msgid "Spoiler"
404 404 msgstr "Спойлер"
405 405
406 406 #: templates/boards/staticpages/help.html:14
407 407 msgid "Link to a post"
408 408 msgstr "Ссылка на сообщение"
409 409
410 410 #: templates/boards/staticpages/help.html:15
411 411 msgid "Strikethrough text"
412 412 msgstr "Зачеркнутый текст"
413 413
414 414 #: templates/boards/staticpages/help.html:16
415 415 msgid "Comment"
416 416 msgstr "Комментарий"
417 417
418 418 #: templates/boards/staticpages/help.html:17
419 419 #: templates/boards/staticpages/help.html:18
420 420 msgid "Quote"
421 421 msgstr "Цитата"
422 422
423 423 #: templates/boards/staticpages/help.html:21
424 424 msgid "You can try pasting the text and previewing the result here:"
425 425 msgstr "Вы можете попробовать вставить текст и проверить результат здесь:"
426 426
427 427 #: templates/boards/tags.html:17
428 428 msgid "Sections:"
429 429 msgstr "Разделы:"
430 430
431 431 #: templates/boards/tags.html:30
432 432 msgid "Other tags:"
433 433 msgstr "Другие метки:"
434 434
435 435 #: templates/boards/tags.html:43
436 436 msgid "All tags..."
437 437 msgstr "Все метки..."
438 438
439 439 #: templates/boards/thread.html:14
440 440 msgid "Normal"
441 441 msgstr "Нормальный"
442 442
443 443 #: templates/boards/thread.html:15
444 444 msgid "Gallery"
445 445 msgstr "Галерея"
446 446
447 447 #: templates/boards/thread.html:16
448 448 msgid "Tree"
449 449 msgstr "Дерево"
450 450
451 451 #: templates/boards/thread.html:35
452 452 msgid "message"
453 453 msgid_plural "messages"
454 454 msgstr[0] "сообщение"
455 455 msgstr[1] "сообщения"
456 456 msgstr[2] "сообщений"
457 457
458 458 #: templates/boards/thread.html:38
459 459 msgid "image"
460 460 msgid_plural "images"
461 461 msgstr[0] "изображение"
462 462 msgstr[1] "изображения"
463 463 msgstr[2] "изображений"
464 464
465 465 #: templates/boards/thread.html:40
466 466 msgid "Last update: "
467 467 msgstr "Последнее обновление: "
468 468
469 469 #: templates/boards/thread_gallery.html:36
470 470 msgid "No images."
471 471 msgstr "Нет изображений."
472 472
473 473 #: templates/boards/thread_normal.html:30
474 474 msgid "posts to bumplimit"
475 475 msgstr "сообщений до бамплимита"
476 476
477 477 #: templates/boards/thread_normal.html:44
478 478 msgid "Reply to thread"
479 479 msgstr "Ответить в тему"
480 480
481 481 #: templates/boards/thread_normal.html:44
482 482 msgid "to message "
483 483 msgstr "на сообщение"
484 484
485 485 #: templates/boards/thread_normal.html:59
486 486 msgid "Close form"
487 487 msgstr "Закрыть форму"
488 488
489 489 #: templates/search/search.html:17
490 490 msgid "Ok"
491 491 msgstr "Ок"
492 492
493 493 #: utils.py:120
494 494 #, python-format
495 495 msgid "File must be less than %s but is %s."
496 496 msgstr "Файл должен быть менее %s, но его размер %s."
497 497
498 498 msgid "Please wait %(delay)d second before sending message"
499 499 msgid_plural "Please wait %(delay)d seconds before sending message"
500 500 msgstr[0] "Пожалуйста подождите %(delay)d секунду перед отправкой сообщения"
501 501 msgstr[1] "Пожалуйста подождите %(delay)d секунды перед отправкой сообщения"
502 502 msgstr[2] "Пожалуйста подождите %(delay)d секунд перед отправкой сообщения"
503 503
504 504 msgid "New threads"
505 505 msgstr "Новые темы"
506 506
507 507 #, python-format
508 508 msgid "Max file size is %(size)s."
509 509 msgstr "Максимальный размер файла %(size)s."
510 510
511 511 msgid "Size of media:"
512 512 msgstr "Размер медиа:"
513 513
514 514 msgid "Statistics"
515 515 msgstr "Статистика"
516 516
517 517 msgid "Invalid PoW."
518 518 msgstr "Неверный PoW."
519 519
520 520 msgid "Stale PoW."
521 521 msgstr "PoW устарел."
522 522
523 523 msgid "Show"
524 524 msgstr "Показывать"
525 525
526 526 msgid "Hide"
527 527 msgstr "Скрывать"
528 528
529 529 msgid "Add to favorites"
530 530 msgstr "Добавить в избранное"
531 531
532 532 msgid "Remove from favorites"
533 533 msgstr "Убрать из избранного"
534 534
535 535 msgid "Monochrome"
536 536 msgstr "Монохромный"
537 537
538 538 msgid "Subsections: "
539 539 msgstr "Подразделы: "
540 540
541 541 msgid "Change file source"
542 542 msgstr "Изменить источник файла"
543 543
544 544 msgid "interesting"
545 545 msgstr "интересное"
546 546
547 547 msgid "images"
548 548 msgstr "изображения"
549 549
550 550 msgid "Delete post"
551 551 msgstr "Удалить пост"
552 552
553 553 msgid "Delete thread"
554 554 msgstr "Удалить тему"
555 555
556 556 msgid "Messages per day/week/month:"
557 557 msgstr "Сообщений за день/неделю/месяц:"
558 558
559 559 msgid "Subscribe to thread"
560 560 msgstr "Подписаться на тему"
561 561
562 562 msgid "Active threads:"
563 563 msgstr "Активные темы:"
564 564
565 565 msgid "No active threads today."
566 566 msgstr "Сегодня нет активных тем."
567 567
568 568 msgid "Insert URLs on separate lines."
569 569 msgstr "Вставляйте ссылки на отдельных строках."
570 570
571 571 msgid "You can post no more than %(files)d file."
572 572 msgid_plural "You can post no more than %(files)d files."
573 573 msgstr[0] "Вы можете отправить не более %(files)d файла."
574 574 msgstr[1] "Вы можете отправить не более %(files)d файлов."
575 575 msgstr[2] "Вы можете отправить не более %(files)d файлов."
576 576
577 577 #, python-format
578 578 msgid "Max file number is %(max_files)s."
579 579 msgstr "Максимальное количество файлов %(max_files)s."
580 580
581 581 msgid "Moderation"
582 582 msgstr "Модерация"
583 583
584 msgid "Duplicates search"
585 msgstr "Поиск дубликатов"
586
@@ -1,209 +1,255 b''
1 1 import re
2 2
3 3 from django.contrib.staticfiles import finders
4 4 from django.contrib.staticfiles.templatetags.staticfiles import static
5 5 from django.core.files.images import get_image_dimensions
6 6 from django.template.defaultfilters import filesizeformat
7 from django.core.urlresolvers import reverse
8 from django.utils.translation import ugettext_lazy as _, ungettext_lazy
7 9
8 10 from boards.utils import get_domain, cached_result
11 from boards import settings
9 12
10 13
11 14 FILE_STUB_IMAGE = 'images/file.png'
12 15 FILE_STUB_URL = 'url'
13 16 FILE_FILEFORMAT = 'images/fileformats/{}.png'
14 17
15 18
16 19 FILE_TYPES_VIDEO = (
17 20 'webm',
18 21 'mp4',
19 22 'mpeg',
20 23 'ogv',
21 24 )
22 25 FILE_TYPE_SVG = 'svg'
23 26 FILE_TYPES_AUDIO = (
24 27 'ogg',
25 28 'mp3',
26 29 'opus',
27 30 )
28 31 FILE_TYPES_IMAGE = (
29 32 'jpeg',
30 33 'jpg',
31 34 'png',
32 35 'bmp',
33 36 'gif',
34 37 )
35 38
36 39 PLAIN_FILE_FORMATS = {
37 40 'zip': 'archive',
38 41 'tar': 'archive',
39 42 'gz': 'archive',
40 43 'mid' : 'midi',
41 44 }
42 45
43 46 URL_PROTOCOLS = {
44 47 'magnet': 'magnet',
45 48 }
46 49
47 50 CSS_CLASS_IMAGE = 'image'
48 51 CSS_CLASS_THUMB = 'thumb'
49 52
50 53
51 54 def get_viewers():
52 55 return AbstractViewer.__subclasses__()
53 56
54 57
55 58 def get_static_dimensions(filename):
56 59 file_path = finders.find(filename)
57 60 return get_image_dimensions(file_path)
58 61
59 62
60 63 # TODO Move this to utils
61 64 def file_exists(filename):
62 65 return finders.find(filename) is not None
63 66
64 67
65 68 class AbstractViewer:
66 69 def __init__(self, file, file_type, hash, url):
67 70 self.file = file
68 71 self.file_type = file_type
69 72 self.hash = hash
70 73 self.url = url
71 74
72 75 @staticmethod
73 76 def supports(file_type):
74 77 return True
75 78
76 79 def get_view(self):
77 return '<div class="image">'\
80 return '<div class="image" id="file{}">'\
78 81 '{}'\
79 '<div class="image-metadata"><a href="{}" download >{}, {}</a></div>'\
80 '</div>'.format(self.get_format_view(), self.file.url,
81 self.file_type, filesizeformat(self.file.size))
82 '<div class="image-metadata"><a href="{}" download >{}, {}</a>'\
83 ' <a class="file-menu" href="#">🔍 </a></div>'\
84 '</div>'.format(self.hash, self.get_format_view(), self.file.url,
85 self.file_type, filesizeformat(self.file.size))\
86 + self._build_context_menu(self.get_context_menu())
82 87
83 88 def get_format_view(self):
84 89 image_name = PLAIN_FILE_FORMATS.get(self.file_type, self.file_type)
85 90 file_name = FILE_FILEFORMAT.format(image_name)
86 91
87 92 if file_exists(file_name):
88 93 image = file_name
89 94 else:
90 95 image = FILE_STUB_IMAGE
91 96
92 97 w, h = get_static_dimensions(image)
93 98
94 99 return '<a href="{}">'\
95 100 '<img class="url-image" src="{}" width="{}" height="{}"/>'\
96 101 '</a>'.format(self.file.url, static(image), w, h)
97 102
103 def get_context_menu(self):
104 items = []
105 items.append({
106 'name': 'duplicates',
107 'label': _('Duplicates search'),
108 'url': reverse('feed') + '?image_hash=' + self.hash,
109 })
110
111 return items
112
113 def _build_context_menu(self, items):
114 if not items:
115 return ''
116
117 cm_items = []
118 for item in items:
119 cm_items.append(('{}: {{name: "{}", callback: function(key, opt) {{window.location="{}";}}}}')\
120 .format(item['name'], item['label'], item['url']))
121 return '<script>$.contextMenu({{trigger: "left", selector: "#file{} .file-menu", items: {{ {} }} }});</script>'.format(self.hash, ', '.join(cm_items))
122
98 123
99 124 class VideoViewer(AbstractViewer):
100 125 @staticmethod
101 126 def supports(file_type):
102 127 return file_type in FILE_TYPES_VIDEO
103 128
104 129 def get_format_view(self):
105 130 return '<video width="200" height="150" controls src="{}"></video>'\
106 131 .format(self.file.url)
107 132
108 133
109 134 class AudioViewer(AbstractViewer):
110 135 @staticmethod
111 136 def supports(file_type):
112 137 return file_type in FILE_TYPES_AUDIO
113 138
114 139 def get_format_view(self):
115 140 return '<audio controls src="{}"></audio>'.format(self.file.url)
116 141
117 142
118 143 class SvgViewer(AbstractViewer):
119 144 @staticmethod
120 145 def supports(file_type):
121 146 return file_type == FILE_TYPE_SVG
122 147
123 148 def get_format_view(self):
124 149 return '<a class="thumb" href="{}">'\
125 150 '<img class="post-image-preview" width="200" height="150" src="{}" />'\
126 151 '</a>'.format(self.file.url, self.file.url)
127 152
128 153
129 154 class ImageViewer(AbstractViewer):
130 155 @staticmethod
131 156 def supports(file_type):
132 157 return file_type in FILE_TYPES_IMAGE
133 158
134 159 def get_format_view(self):
135 160 metadata = '{}, {}'.format(self.file.name.split('.')[-1],
136 161 filesizeformat(self.file.size))
137 162 width, height = get_image_dimensions(self.file.file)
138 163 preview_path = self.file.path.replace('.', '.200x150.')
139 164 pre_width, pre_height = get_image_dimensions(preview_path)
140 165
141 166 split = self.file.url.rsplit('.', 1)
142 167 w, h = 200, 150
143 168 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
144 169
145 170 return '<a class="{}" href="{full}">' \
146 171 '<img class="post-image-preview"' \
147 172 ' src="{}"' \
148 173 ' alt="{}"' \
149 174 ' width="{}"' \
150 175 ' height="{}"' \
151 176 ' data-width="{}"' \
152 177 ' data-height="{}" />' \
153 178 '</a>' \
154 179 .format(CSS_CLASS_THUMB,
155 180 thumb_url,
156 181 self.hash,
157 182 str(pre_width),
158 183 str(pre_height), str(width), str(height),
159 184 full=self.file.url, image_meta=metadata)
160 185
186 def get_context_menu(self):
187 items = super().get_context_menu()
188
189 image_url = settings.get('External', 'ImageSearchHost') + self.file.url
190
191 items.append({
192 'name': 'google',
193 'label': 'Google',
194 'url': 'https://www.google.com/searchbyimage?image_url={}'.format(image_url),
195 })
196 items.append({
197 'name': 'iqdb',
198 'label': 'iqdb',
199 'url': 'http://iqdb.org/?url={}'.format(image_url)
200 })
201
202 return items
203
161 204
162 205 class UrlViewer(AbstractViewer):
163 206 @staticmethod
164 207 def supports(file_type):
165 208 return file_type is None
166 209
167 210 def get_view(self):
168 211 return '<div class="image">' \
169 212 '{}' \
170 213 '<div class="image-metadata">{}</div>' \
171 214 '</div>'.format(self.get_format_view(), get_domain(self.url))
172 215
173 216 def get_format_view(self):
174 217 protocol = self.url.split(':')[0]
175 218
176 219 domain = get_domain(self.url)
177 220
178 221 if protocol in URL_PROTOCOLS:
179 222 url_image_name = URL_PROTOCOLS.get(protocol)
180 223 elif domain:
181 224 url_image_name = self._find_image_for_domains(domain) or FILE_STUB_URL
182 225 else:
183 226 url_image_name = FILE_STUB_URL
184 227
185 228 image_path = 'images/{}.png'.format(url_image_name)
186 229 image = static(image_path)
187 230 w, h = get_static_dimensions(image_path)
188 231
189 232 return '<a href="{}">' \
190 233 '<img class="url-image" src="{}" width="{}" height="{}"/>' \
191 234 '</a>'.format(self.url, image, w, h)
192 235
236 def get_context_menu(self):
237 return []
238
193 239 @cached_result()
194 240 def _find_image_for_domains(self, domain):
195 241 """
196 242 Searches for the domain image for every domain level except top.
197 243 E.g. for l3.example.co.uk it will search for l3.example.co.uk, then
198 244 example.co.uk, then co.uk
199 245 """
200 246 levels = domain.split('.')
201 247 while len(levels) > 1:
202 248 domain = '.'.join(levels)
203 249
204 250 filename = 'images/domains/{}.png'.format(domain)
205 251 if file_exists(filename):
206 252 return 'domains/' + domain
207 253 else:
208 254 del levels[0]
209 255
@@ -1,40 +1,38 b''
1 1 {% extends "boards/thread.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load static from staticfiles %}
5 5 {% load board %}
6 6 {% load tz %}
7 7
8 8 {% block head %}
9 9 <meta name="robots" content="noindex">
10 10 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }}
11 11 - {{ site_name }}</title>
12 12 {% endblock %}
13 13
14 14 {% block thread_content %}
15 15 {% get_current_language as LANGUAGE_CODE %}
16 16 {% get_current_timezone as TIME_ZONE %}
17 17
18 18 <div id="posts-table">
19 19 {% if posts %}
20 20 {% for post in posts %}
21 21 {% for image in post.get_images %}
22 22 <div class="gallery_image">
23 23 {% autoescape off %}
24 24 {{ image.get_view }}
25 25 <div class="gallery_image_metadata">
26 26 {{ image.get_size.0 }}x{{ image.get_size.1 }}
27 {% image_actions image.file.url %},
28 [<a href="{% url 'feed' %}?image_hash={{ image.hash }}">{{ site_name }}</a>]
29 27 <br />
30 28 <a href="{{ post.get_absolute_url }}">>>{{ post.id }}</a>
31 29 </div>
32 30 {% endautoescape %}
33 31 </div>
34 32 {% endfor %}
35 33 {% endfor %}
36 34 {% else %}
37 35 {% trans 'No images.' %}
38 36 {% endif %}
39 37 </div>
40 38 {% endblock %}
@@ -1,123 +1,100 b''
1 1 import re
2 2
3 3 from django.shortcuts import get_object_or_404
4 4 from django import template
5 5 from django.utils.text import re_tag
6 6 from django.core.urlresolvers import reverse
7 7
8 8 from boards.mdx_neboard import LINE_BREAK_HTML
9 9 from boards import settings
10 10
11 11
12 12 IMG_ACTION_URL = '[<a href="{}">{}</a>]'
13 13 REGEX_NEWLINE = re.compile(LINE_BREAK_HTML)
14 14 TRUNCATOR = '...'
15 15 HTML4_SINGLETS =(
16 16 'br', 'col', 'link', 'base', 'img', 'param', 'area', 'hr', 'input'
17 17 )
18 18
19 19
20 20 register = template.Library()
21 21
22 actions = [
23 {
24 'name': 'google',
25 'link': 'https://www.google.com/searchbyimage?image_url={}',
26 },
27 {
28 'name': 'iqdb',
29 'link': 'http://iqdb.org/?url={}',
30 },
31 ]
32
33 22
34 23 @register.simple_tag(name='post_url')
35 24 def post_url(*args, **kwargs):
36 25 post_id = args[0]
37 26
38 27 post = get_object_or_404('Post', id=post_id)
39 28
40 29 return post.get_absolute_url()
41 30
42 31
43 @register.simple_tag(name='image_actions')
44 def image_actions(*args, **kwargs):
45 image_link = args[0]
46 host = settings.get('External', 'ImageSearchHost')
47 if host.endswith('/'):
48 host = host[:-1]
49 image_link = settings.get('External', 'ImageSearchHost') + image_link
50
51 return ', '.join([IMG_ACTION_URL.format(
52 action['link'].format(image_link), action['name']) for action in actions])
53
54
55 32 @register.inclusion_tag('boards/post.html', name='post_view', takes_context=True)
56 33 def post_view(context, post, *args, **kwargs):
57 34 kwargs['perms'] = context['perms']
58 35 return post.get_view_params(*args, **kwargs)
59 36
60 37
61 38 @register.simple_tag(name='page_url')
62 39 def page_url(paginator, page_number, *args, **kwargs):
63 40 if paginator.supports_urls():
64 41 return paginator.get_page_url(page_number)
65 42
66 43
67 44 @register.filter(name='truncatenewlines_html')
68 45 def truncatenewlines_html(value, arg):
69 46 end_pos = 0
70 47 start_pos = 0
71 48 match_count = 0
72 49
73 50 # Collect places for truncation
74 51 while match_count <= arg:
75 52 m = REGEX_NEWLINE.search(value, end_pos)
76 53 if m is None:
77 54 break
78 55 else:
79 56 match_count += 1
80 57 end_pos = m.end()
81 58 start_pos = m.start()
82 59
83 60 # Find and close open tags
84 61 if match_count > arg:
85 62 truncate_pos = start_pos
86 63
87 64 open_tags = []
88 65 text = value[:truncate_pos]
89 66 current_pos = 0
90 67 while True:
91 68 tag = re_tag.search(text, current_pos)
92 69 if tag is None:
93 70 break
94 71 else:
95 72 closing_tag, tagname, self_closing = tag.groups()
96 73 tagname = tagname.lower()
97 74 if self_closing or tagname in HTML4_SINGLETS:
98 75 pass
99 76 elif closing_tag:
100 77 # Check for match in open tags list
101 78 try:
102 79 i = open_tags.index(tagname)
103 80 except ValueError:
104 81 pass
105 82 else:
106 83 # SGML: An end tag closes, back to the matching start tag,
107 84 # all unclosed intervening start tags with omitted end tags
108 85 open_tags = open_tags[i + 1:]
109 86 else:
110 87 # Add it to the start of the open tags list
111 88 open_tags.insert(0, tagname)
112 89
113 90 current_pos = tag.end()
114 91
115 92 if not text.endswith(TRUNCATOR):
116 93 text += TRUNCATOR
117 94 for tag in open_tags:
118 95 text += '</{}>'.format(tag)
119 96 else:
120 97 text = value
121 98
122 99 return text
123 100
General Comments 0
You need to be logged in to leave comments. Login now