# HG changeset patch # User neko259 # Date 2013-12-24 19:49:04 # Node ID 8531d7b001392289a6b761f38c73a257606552ad # Parent c3103f8c5bc739ef0a629a046a3c8d02ba91d5b3 # Parent 09be641de67870603d6fa89abbd45246335c1893 Merged with 1.5 dev branch diff --git a/boards/locale/ru/LC_MESSAGES/django.mo b/boards/locale/ru/LC_MESSAGES/django.mo index c261e969a2581d88830e94887233446e14859117..2de823afe523dbf77810d57e518222190c44c25a GIT binary patch literal 5904 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 AoB#j- diff --git a/boards/locale/ru/LC_MESSAGES/django.po b/boards/locale/ru/LC_MESSAGES/django.po --- a/boards/locale/ru/LC_MESSAGES/django.po +++ b/boards/locale/ru/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-11-27 12:34+0200\n" +"POT-Creation-Date: 2013-12-24 20:39+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -34,77 +34,72 @@ msgstr "разработчик javascript" msgid "designer" msgstr "дизайнер" -#: forms.py:48 templates/boards/posting_general.html:209 -#: templates/boards/thread.html:101 +#: forms.py:72 msgid "Title" msgstr "Заголовок" -#: forms.py:50 templates/boards/posting_general.html:224 -#: templates/boards/thread.html:116 +#: forms.py:74 msgid "Text" msgstr "Текст" -#: forms.py:51 templates/boards/posting_general.html:229 -#: templates/boards/thread.html:121 +#: forms.py:75 msgid "Image" msgstr "Изображение" -#: forms.py:54 templates/boards/posting_general.html:239 -#: templates/boards/thread.html:126 +#: forms.py:78 msgid "e-mail" msgstr "" -#: forms.py:65 +#: forms.py:89 #, python-format msgid "Title must have less than %s characters" msgstr "Заголовок должен иметь меньше %s символов" -#: forms.py:74 +#: forms.py:98 #, python-format msgid "Text must have less than %s characters" msgstr "Текст должен быть короче %s символов" -#: forms.py:85 +#: forms.py:109 #, python-format msgid "Image must be less than %s bytes" msgstr "Изображение должно быть менее %s байт" -#: forms.py:112 +#: forms.py:136 msgid "Either text or image must be entered." msgstr "Текст или картинка должны быть введены." -#: forms.py:125 +#: forms.py:149 #, python-format msgid "Wait %s seconds after last posting" msgstr "Подождите %s секунд после последнего постинга" -#: forms.py:139 templates/boards/post.html:60 -#: templates/boards/posting_general.html:234 templates/boards/tags.html:6 +#: forms.py:163 templates/boards/post.html:61 templates/boards/tags.html:6 #: templates/boards/rss/post.html:10 msgid "Tags" msgstr "Теги" -#: forms.py:147 +#: forms.py:171 msgid "Inappropriate characters in tags." msgstr "Недопустимые символы в тегах." -#: forms.py:175 forms.py:196 +#: forms.py:199 forms.py:220 msgid "Captcha validation failed" msgstr "Проверка капчи провалена" -#: forms.py:202 +#: forms.py:226 msgid "Theme" msgstr "Тема" -#: forms.py:207 +#: forms.py:231 msgid "Enable moderation panel" msgstr "Включить панель модерации" -#: forms.py:222 +#: forms.py:246 msgid "No such user found" msgstr "Данный пользователь не найден" -#: forms.py:236 +#: forms.py:260 #, python-format msgid "Wait %s minutes after last login" msgstr "Подождите %s минут после последнего входа" @@ -137,15 +132,15 @@ msgstr "Репозиторий" msgid "Feed" msgstr "Лента" -#: templates/boards/base.html:36 +#: templates/boards/base.html:31 msgid "All threads" msgstr "Все темы" -#: templates/boards/base.html:41 +#: templates/boards/base.html:36 msgid "Tag management" msgstr "Управление тегами" -#: templates/boards/base.html:43 +#: templates/boards/base.html:38 msgid "Settings" msgstr "Настройки" @@ -171,92 +166,65 @@ msgstr "ID пользователя" msgid "Insert your user id above" msgstr "Вставьте свой ID пользователя выше" -#: templates/boards/post.html:34 templates/boards/posting_general.html:100 -#: templates/boards/thread.html:59 +#: templates/boards/post.html:35 templates/boards/posting_general.html:103 +#: templates/boards/thread.html:68 msgid "Delete" msgstr "Удалить" -#: templates/boards/post.html:37 templates/boards/posting_general.html:104 -#: templates/boards/thread.html:62 +#: templates/boards/post.html:38 templates/boards/posting_general.html:107 +#: templates/boards/thread.html:71 msgid "Ban IP" msgstr "Заблокировать IP" -#: templates/boards/post.html:50 templates/boards/posting_general.html:113 -#: templates/boards/posting_general.html:172 templates/boards/thread.html:71 +#: templates/boards/post.html:51 templates/boards/posting_general.html:116 +#: templates/boards/posting_general.html:180 templates/boards/thread.html:80 msgid "Replies" msgstr "Ответы" -#: templates/boards/posting_general.html:63 +#: templates/boards/posting_general.html:64 msgid "Previous page" msgstr "Предыдущая страница" -#: templates/boards/posting_general.html:94 +#: templates/boards/posting_general.html:97 msgid "Reply" msgstr "Ответ" -#: templates/boards/posting_general.html:122 templates/boards/thread.html:154 -msgid "replies" -msgstr "ответов" - -#: templates/boards/posting_general.html:123 templates/boards/thread.html:155 +#: templates/boards/posting_general.html:125 templates/boards/thread.html:130 +#: templates/boards/thread_gallery.html:52 msgid "images" msgstr "изображений" -#: templates/boards/posting_general.html:138 +#: templates/boards/posting_general.html:142 #, python-format msgid "Skipped %(count)s replies. Open thread to see all replies." msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы." -#: templates/boards/posting_general.html:195 +#: templates/boards/posting_general.html:203 msgid "Next page" msgstr "Следующая страница" -#: templates/boards/posting_general.html:200 +#: templates/boards/posting_general.html:208 msgid "No threads exist. Create the first one!" msgstr "Нет тем. Создайте первую!" -#: templates/boards/posting_general.html:206 +#: templates/boards/posting_general.html:214 msgid "Create new thread" msgstr "Создать новую тему" -#: templates/boards/posting_general.html:214 templates/boards/thread.html:106 -msgid "Formatting" -msgstr "Форматирование" - -#: templates/boards/posting_general.html:216 templates/boards/thread.html:108 -msgid "quote" -msgstr "цитата" - -#: templates/boards/posting_general.html:217 templates/boards/thread.html:109 -msgid "italic" -msgstr "курсив" - -#: templates/boards/posting_general.html:218 templates/boards/thread.html:110 -msgid "bold" -msgstr "полужирный" - -#: templates/boards/posting_general.html:219 templates/boards/thread.html:111 -msgid "spoiler" -msgstr "спойлер" - -#: templates/boards/posting_general.html:220 templates/boards/thread.html:112 -msgid "comment" -msgstr "комментарий" - -#: templates/boards/posting_general.html:252 templates/boards/thread.html:140 +#: templates/boards/posting_general.html:218 templates/boards/thread.html:112 msgid "Post" msgstr "Отправить" -#: templates/boards/posting_general.html:254 +#: templates/boards/posting_general.html:222 msgid "Tags must be delimited by spaces. Text or image is required." msgstr "" "Теги должны быть разделены пробелами. Текст или изображение обязательны." -#: templates/boards/posting_general.html:257 templates/boards/thread.html:142 +#: templates/boards/posting_general.html:225 templates/boards/thread.html:116 msgid "Text syntax" msgstr "Синтаксис текста" -#: templates/boards/posting_general.html:267 +#: templates/boards/posting_general.html:235 msgid "Pages:" msgstr "Страницы: " @@ -292,15 +260,27 @@ msgstr "тем" msgid "No tags found." msgstr "Теги не найдены." -#: templates/boards/thread.html:24 +#: templates/boards/thread.html:22 templates/boards/thread_gallery.html:20 +msgid "Normal mode" +msgstr "Нормальный режим" + +#: templates/boards/thread.html:23 templates/boards/thread_gallery.html:21 +msgid "Gallery mode" +msgstr "Режим галереи" + +#: templates/boards/thread.html:31 msgid "posts to bumplimit" msgstr "сообщений до бамплимита" -#: templates/boards/thread.html:98 +#: templates/boards/thread.html:106 msgid "Reply to thread" msgstr "Ответить в тему" -#: templates/boards/thread.html:156 +#: templates/boards/thread.html:129 templates/boards/thread_gallery.html:51 +msgid "replies" +msgstr "ответов" + +#: templates/boards/thread.html:131 templates/boards/thread_gallery.html:53 msgid "Last update: " msgstr "Последнее обновление: " @@ -353,6 +333,24 @@ msgstr "Ссылка на сообщение" msgid "Strikethrough text" msgstr "Зачеркнутый текст" +#~ msgid "Formatting" +#~ msgstr "Форматирование" + +#~ msgid "quote" +#~ msgstr "цитата" + +#~ msgid "italic" +#~ msgstr "курсив" + +#~ msgid "bold" +#~ msgstr "полужирный" + +#~ msgid "spoiler" +#~ msgstr "спойлер" + +#~ msgid "comment" +#~ msgstr "комментарий" + #~ msgid "Tag: " #~ msgstr "Тег: " diff --git a/boards/locale/ru/LC_MESSAGES/djangojs.mo b/boards/locale/ru/LC_MESSAGES/djangojs.mo index f54a84b5df0bd1d59557c847115a6b039705b611..5a71c8cdbcca6cf836cb63bc08bd7f181f42260d GIT binary patch literal 779 zc$`g9zi-n(6uv?M$w&-<#KLe4L{!xU+bvS%#-%24LWG?}Nm>c24o7nhM($l?Uw}#- z3QVX-41kyz5DRQA5S2n323CeMe?b2U{sErTpzy5kzW3vMch7f+XUAV7u#)kG{Y7ZR_EE^nbF&K}|pZ~IoN&L7Sw8)KaJ0?wGVXanel6=Id zgeFp;l3Cc)jVWT7#5AyJSku=gbyL?tNb2lC5RY`XU8hd`Dk(VlX4Ue`lXt2WSMz8r zNi(864yhE8g^LTGhbxZn;+*R{WpCao6zXoHF$YrK?$S0RO-9!&>^YUvLOOEJ^X9`K zE28#D1Ecz7-yQv*k=3=l;F57TZoLBn$sWn8osb567O&i+QOu;h&@7UfkuDu;l|{tm zDN5e-QZE;_a_?+n7&o|?r+u6>1HHgwxIa3+IOSHEN%2ELAUYz^W$ z+x%C`+0&^z=+JY9jyE=rT0o;uK#u`E=ESUq)JwIkzN=oc33!3j8wfuqo5@qPtG=o2 z^y$@G)razaDomcMgJetXA@xr6;oC``sh&DeUsVqS4VG=Sr@jobeYBLW>Jzj;=?Cod KIC+w6Eu+7X+3$4# diff --git a/boards/locale/ru/LC_MESSAGES/djangojs.po b/boards/locale/ru/LC_MESSAGES/djangojs.po --- a/boards/locale/ru/LC_MESSAGES/djangojs.po +++ b/boards/locale/ru/LC_MESSAGES/djangojs.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2013-11-13 17:25+0200\n" +"POT-Creation-Date: 2013-12-21 21:45+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -19,11 +19,11 @@ msgstr "" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -#: static/js/refpopup.js:60 +#: static/js/refpopup.js:57 msgid "Loading..." msgstr "Загрузка..." -#: static/js/refpopup.js:86 +#: static/js/refpopup.js:76 msgid "Post not found" msgstr "Сообщение не найдено" @@ -35,5 +35,9 @@ msgstr "Нормальный" msgid "Gallery" msgstr "Галерея" +#: static/js/thread_update.js:177 +msgid "[new posts]" +msgstr "[новые посты]" + #~ msgid "Replies" #~ msgstr "Ответы" diff --git a/boards/middlewares.py b/boards/middlewares.py --- a/boards/middlewares.py +++ b/boards/middlewares.py @@ -34,7 +34,8 @@ class MinifyHTMLMiddleware(object): except AttributeError: compress_html = False - if TYPE_HTML in response[RESPONSE_CONTENT_TYPE] and compress_html: + if RESPONSE_CONTENT_TYPE in response\ + and TYPE_HTML in response[RESPONSE_CONTENT_TYPE] and compress_html: response.content = strip_spaces_between_tags( response.content.strip()) return response \ No newline at end of file diff --git a/boards/migrations/0017_auto__add_field_post_image_pre_width__add_field_post_image_pre_height.py b/boards/migrations/0017_auto__add_field_post_image_pre_width__add_field_post_image_pre_height.py new file mode 100644 --- /dev/null +++ b/boards/migrations/0017_auto__add_field_post_image_pre_width__add_field_post_image_pre_height.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'Post.image_pre_width' + db.add_column(u'boards_post', 'image_pre_width', + self.gf('django.db.models.fields.IntegerField')(default=0), + keep_default=False) + + # Adding field 'Post.image_pre_height' + db.add_column(u'boards_post', 'image_pre_height', + self.gf('django.db.models.fields.IntegerField')(default=0), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'Post.image_pre_width' + db.delete_column(u'boards_post', 'image_pre_width') + + # Deleting field 'Post.image_pre_height' + db.delete_column(u'boards_post', 'image_pre_height') + + + models = { + 'boards.ban': { + 'Meta': {'object_name': 'Ban'}, + 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'}) + }, + 'boards.post': { + 'Meta': {'object_name': 'Post'}, + '_text_rendered': ('django.db.models.fields.TextField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}), + 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), + 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}), + 'poster_user_agent': ('django.db.models.fields.TextField', [], {}), + 'pub_time': ('django.db.models.fields.DateTimeField', [], {}), + 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}), + 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}), + 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}), + 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'}) + }, + 'boards.setting': { + 'Meta': {'object_name': 'Setting'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}), + 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'boards.tag': { + 'Meta': {'object_name': 'Tag'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"}) + }, + 'boards.thread': { + 'Meta': {'object_name': 'Thread'}, + 'bump_time': ('django.db.models.fields.DateTimeField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}), + 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'}) + }, + 'boards.user': { + 'Meta': {'object_name': 'User'}, + 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}), + 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'rank': ('django.db.models.fields.IntegerField', [], {}), + 'registration_time': ('django.db.models.fields.DateTimeField', [], {}), + 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + } + } + + complete_apps = ['boards'] \ No newline at end of file diff --git a/boards/models/post.py b/boards/models/post.py --- a/boards/models/post.py +++ b/boards/models/post.py @@ -86,12 +86,18 @@ class PostManager(models.Manager): def delete_post(self, post): """ - Delete post and update its thread + Delete post and update or delete its thread """ + + thread = post.thread_new - thread = post.thread_new - thread.last_edit_time = timezone.now() - thread.save() + if thread.get_opening_post() == self: + thread.replies.delete() + + thread.delete() + else: + thread.last_edit_time = timezone.now() + thread.save() post.delete() @@ -235,10 +241,15 @@ class Post(models.Model): image_width = models.IntegerField(default=0) image_height = models.IntegerField(default=0) + image_pre_width = models.IntegerField(default=0) + image_pre_height = models.IntegerField(default=0) + image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename, blank=True, sizes=(IMAGE_THUMB_SIZE,), width_field='image_width', - height_field='image_height') + height_field='image_height', + preview_width_field='image_pre_width', + preview_height_field='image_pre_height') poster_ip = models.GenericIPAddressField() poster_user_agent = models.TextField() diff --git a/boards/static/css/base.css b/boards/static/css/base.css --- a/boards/static/css/base.css +++ b/boards/static/css/base.css @@ -27,3 +27,7 @@ z-index: 300; position:absolute; } + +.gallery_image { + display: inline-block; +} \ No newline at end of file diff --git a/boards/static/css/md/base_page.css b/boards/static/css/md/base_page.css --- a/boards/static/css/md/base_page.css +++ b/boards/static/css/md/base_page.css @@ -173,8 +173,6 @@ blockquote { min-width: 1px; text-align: center; display: table-row; - - height: 150px; } .post > .metadata { @@ -356,3 +354,15 @@ li { .skipped_replies { margin: 5px; } + +.current_page, .current_mode { + border: solid 1px #afdcec; + padding: 2px; +} + +.gallery_image { + border: solid 1px; + padding: 0.5ex; + margin: 0.5ex; + text-align: center; +} \ No newline at end of file diff --git a/boards/static/css/pg/base_page.css b/boards/static/css/pg/base_page.css --- a/boards/static/css/pg/base_page.css +++ b/boards/static/css/pg/base_page.css @@ -171,8 +171,6 @@ blockquote { min-width: 1px; text-align: center; display: table-row; - - height: 150px; } .post > .metadata { @@ -342,3 +340,8 @@ input[type="submit"]:hover { .skipped_replies { margin: 5px; } + +.current_page, .current_mode { + border: solid 1px #000; + padding: 2px; +} \ No newline at end of file diff --git a/boards/static/css/sw/base_page.css b/boards/static/css/sw/base_page.css --- a/boards/static/css/sw/base_page.css +++ b/boards/static/css/sw/base_page.css @@ -330,4 +330,28 @@ li { .mark_btn:last-child { border-right: 1px solid #182F6F; +} + +.current_page { + border-bottom: 1px solid #FFF; + padding: 0px 0.5ex; +} + +.image-mode-tab a { + text-decoration: none; +} +.image-mode-tab .current_mode::before { + content: "✓ "; + padding: 0 0 0 .5ex; + color: #182F6F; + background: #FFF; +} +.image-mode-tab .current_mode { + padding: 0 .5ex 0 0; + color: #182F6F; + background: #FFF; +} + +.gallery_image_metadata { + margin-bottom: 1em; } \ No newline at end of file diff --git a/boards/static/js/thread.js b/boards/static/js/thread.js --- a/boards/static/js/thread.js +++ b/boards/static/js/thread.js @@ -23,33 +23,6 @@ for the JavaScript code in this page. */ -function addGalleryPanel() { - var gallery = $('a[class="thumb"]').clone(true), - normal = $('.post').clone(true); - - $('.navigation_panel').filter(':first').after( - '
' + - '' + - '' + - '
' - ); - - $('input[name="image-mode"]').change(function() { - //gallery mode - if($(this).val() === '1') { - $('.thread').replaceWith( - $('
').append(gallery) - ); - } - //normal mode - else { - $('#posts-table').replaceWith( - $('
').append(normal) - ); - } - }); -} - function moveCaretToEnd(el) { if (typeof el.selectionStart == "number") { el.selectionStart = el.selectionEnd = el.value.length; @@ -72,10 +45,3 @@ function addQuickReply(postId) { $("html, body").animate({ scrollTop: $(textAreaId).offset().top }, "slow"); } - - - -$(document).ready(function(){ - addGalleryPanel(); - initAutoupdate(); -}); diff --git a/boards/static/js/thread_update.js b/boards/static/js/thread_update.js --- a/boards/static/js/thread_update.js +++ b/boards/static/js/thread_update.js @@ -97,6 +97,10 @@ function updateThread() { updateBumplimitProgress(data.added.length); updatePostBumpableStatus(); + + if (data.added.length + data.updated.length > 0) { + showNewPostsTitle(); + } }) .error(function(data) { // TODO Show error message that server is unavailable? @@ -162,3 +166,27 @@ function updatePostBumpableStatus() { $('.thread').find('.post').addClass('dead_post'); } } + +var documentOriginalTitle = ''; +/** + * Show 'new posts' text in the title if the document is not visible to a user + */ +function showNewPostsTitle() { + if (document.hidden) { + documentOriginalTitle = document.title; + document.title = gettext('[new posts]') + ' ' + document.title; + + document.addEventListener('visibilitychange', function() { + if (documentOriginalTitle !== '') { + document.title = documentOriginalTitle; + documentOriginalTitle = ''; + } + + document.removeEventListener('visibilitychange', null); + }); + } +} + +$(document).ready(function(){ + initAutoupdate(); +}); diff --git a/boards/templates/boards/posting_general.html b/boards/templates/boards/posting_general.html --- a/boards/templates/boards/posting_general.html +++ b/boards/templates/boards/posting_general.html @@ -79,6 +79,8 @@ href="{{ thread.op.image.url }}">{{ thread.op.id }} @@ -99,7 +101,7 @@ [{% trans 'Delete' %}] - ({{ thread.thread.poster_ip }}) + ({{ thread.op.poster_ip }}) [{% trans 'Ban IP' %}] @@ -154,6 +156,8 @@ href="{{ post.image.url }}">{{ post.id }} @@ -227,17 +231,23 @@ {% block metapanel %} - Neboard 1.4.1 - {% trans "Pages:" %} + Neboard 1.5 Aker + {% trans "Pages:" %}[ {% for page in pages %} - [{{ page }}] + ">{{ page }} + {% if not forloop.last %},{% endif %} {% endfor %} + ] [RSS] diff --git a/boards/templates/boards/thread.html b/boards/templates/boards/thread.html --- a/boards/templates/boards/thread.html +++ b/boards/templates/boards/thread.html @@ -6,7 +6,7 @@ {% load board %} {% block head %} - Neboard - {{ thread.get_replies.0.get_title }} + Neboard - {{ thread.get_opening_post.get_title }} {% endblock %} {% block content %} @@ -17,6 +17,12 @@ {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %} + + + {% if bumpable %}
@@ -40,6 +46,8 @@ href="{{ post.image.url }}">{{ post.id }} diff --git a/boards/templates/boards/thread_gallery.html b/boards/templates/boards/thread_gallery.html new file mode 100644 --- /dev/null +++ b/boards/templates/boards/thread_gallery.html @@ -0,0 +1,66 @@ +{% extends "boards/base.html" %} + +{% load i18n %} +{% load cache %} +{% load static from staticfiles %} +{% load board %} + +{% block head %} + Neboard - {{ thread.get_opening_post.get_title }} +{% endblock %} + +{% block content %} + {% spaceless %} + {% get_current_language as LANGUAGE_CODE %} + + + + {% cache 600 thread_gallery_view thread.id thread.last_edit_time LANGUAGE_CODE %} + + +
+ {% for post in thread.get_replies %} + {% if post.image %} + + {% endif %} + {% endfor %} +
+ {% endcache %} + + {% endspaceless %} +{% endblock %} + +{% block metapanel %} + + {% get_current_language as LANGUAGE_CODE %} + + + {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %} + {{ thread.get_reply_count }} {% trans 'replies' %}, + {{ thread.get_images_count }} {% trans 'images' %}. + {% trans 'Last update: ' %}{{ thread.last_edit_time }} + [RSS] + {% endcache %} + + +{% endblock %} diff --git a/boards/templatetags/board.py b/boards/templatetags/board.py --- a/boards/templatetags/board.py +++ b/boards/templatetags/board.py @@ -6,6 +6,17 @@ from django import template register = template.Library() +actions = [ + { + 'name': 'google', + 'link': 'http://google.com/searchbyimage?image_url=%s', + }, + { + 'name': 'iqdb', + 'link': 'http://iqdb.org/?url=%s', + }, +] + @register.simple_tag(name='post_url') def post_url(*args, **kwargs): @@ -21,3 +32,18 @@ def post_url(*args, **kwargs): link = reverse(thread, kwargs={'post_id': post_id}) return link + + +@register.simple_tag(name='image_actions') +def image_actions(*args, **kwargs): + image_link = args[0] + if len(args) > 1: + image_link = 'http://' + args[1] + image_link # TODO https? + + result = '' + + for action in actions: + result += '[' + \ + action['name'] + ']' + + return result diff --git a/boards/thumbs.py b/boards/thumbs.py --- a/boards/thumbs.py +++ b/boards/thumbs.py @@ -3,6 +3,7 @@ django-thumbs by Antonio Melé http://django.es """ +from django.core.files.images import ImageFile from django.db.models import ImageField from django.db.models.fields.files import ImageFieldFile from PIL import Image @@ -13,13 +14,13 @@ import cStringIO def generate_thumb(img, thumb_size, format): """ Generates a thumbnail image and returns a ContentFile object with the thumbnail - + Parameters: =========== img File object - + thumb_size desired thumbnail size, ie: (200,120) - + format format of the original image ('jpeg','gif','png',...) (this format will be used for the generated thumbnail, too) """ @@ -41,7 +42,7 @@ def generate_thumb(img, thumb_size, form # crop it image2 = image.crop( (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize)) - # load is necessary after crop + # load is necessary after crop image2.load() # thumbnail of the cropped image (with ANTIALIAS to make it look better) image2.thumbnail(thumb_size, Image.ANTIALIAS) @@ -160,7 +161,9 @@ class ImageWithThumbsField(ImageField): """ def __init__(self, verbose_name=None, name=None, width_field=None, - height_field=None, sizes=None, **kwargs): + height_field=None, sizes=None, + preview_width_field=None, preview_height_field=None, + **kwargs): self.verbose_name = verbose_name self.name = name self.width_field = width_field @@ -168,6 +171,46 @@ class ImageWithThumbsField(ImageField): self.sizes = sizes super(ImageField, self).__init__(**kwargs) + if sizes is not None and len(sizes) == 1: + self.preview_width_field = preview_width_field + self.preview_height_field = preview_height_field + + def update_dimension_fields(self, instance, force=False, *args, **kwargs): + """ + Update original image dimension fields and thumb dimension fields + (only if 1 thumb size is defined) + """ + + super(ImageWithThumbsField, self).update_dimension_fields(instance, + force, *args, + **kwargs) + thumb_width_field = self.preview_width_field + thumb_height_field = self.preview_height_field + + if thumb_width_field is None or thumb_height_field is None \ + or len(self.sizes) != 1: + return + + original_width = getattr(instance, self.width_field) + original_height = getattr(instance, self.height_field) + + if original_width > 0 and original_height > 0: + thumb_width, thumb_height = self.sizes[0] + + w_scale = float(thumb_width) / original_width + h_scale = float(thumb_height) / original_height + scale_ratio = min(w_scale, h_scale) + + if scale_ratio >= 1: + thumb_width_ratio = original_width + thumb_height_ratio = original_height + else: + thumb_width_ratio = int(original_width * scale_ratio) + thumb_height_ratio = int(original_height * scale_ratio) + + setattr(instance, thumb_width_field, thumb_width_ratio) + setattr(instance, thumb_height_field, thumb_height_ratio) + from south.modelsinspector import add_introspection_rules add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"]) diff --git a/boards/urls.py b/boards/urls.py --- a/boards/urls.py +++ b/boards/urls.py @@ -31,6 +31,7 @@ urlpatterns = patterns('', # /boards/thread/ url(r'^thread/(?P\w+)/$', views.thread, name='thread'), + url(r'^thread/(?P\w+)/(?P\w+)/$', views.thread, name='thread_mode'), url(r'^settings/$', views.settings, name='settings'), url(r'^tags/$', views.all_tags, name='tags'), url(r'^captcha/', include('captcha.urls')), diff --git a/boards/views/__init__.py b/boards/views/__init__.py --- a/boards/views/__init__.py +++ b/boards/views/__init__.py @@ -7,7 +7,7 @@ import re from django.core import serializers from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, Http404 from django.http.response import HttpResponse from django.template import RequestContext from django.shortcuts import render, redirect, get_object_or_404 @@ -30,6 +30,8 @@ import neboard BAN_REASON_SPAM = 'Autoban: spam bot' +MODE_GALLERY = 'gallery' +MODE_NORMAL = 'normal' def index(request, page=0): @@ -62,6 +64,7 @@ def index(request, page=0): # TODO Make this generic for tag and threads list pages context['threads'] = None if len(threads) == 0 else threads context['form'] = form + context['current_page'] = int(page) page_count = Post.objects.get_thread_page_count() context['pages'] = range(page_count) @@ -154,6 +157,7 @@ def tag(request, tag_name, page=0): context = _init_default_context(request) context['threads'] = None if len(threads) == 0 else threads context['tag'] = tag + context['current_page'] = int(page) page_count = Post.objects.get_thread_page_count(tag=tag) context['pages'] = range(page_count) @@ -169,7 +173,7 @@ def tag(request, tag_name, page=0): context) -def thread(request, post_id): +def thread(request, post_id, mode=MODE_NORMAL): """Get all thread posts""" if utils.need_include_captcha(request): @@ -209,7 +213,14 @@ def thread(request, post_id): context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time) context["thread"] = thread_to_show - return render(request, 'boards/thread.html', context) + if MODE_NORMAL == mode: + document = 'boards/thread.html' + elif MODE_GALLERY == mode: + document = 'boards/thread_gallery.html' + else: + raise Http404 + + return render(request, document, context) def login(request): diff --git a/changelog.markdown b/changelog.markdown new file mode 100644 --- /dev/null +++ b/changelog.markdown @@ -0,0 +1,7 @@ +# 1.5 Aker # +* Saving image previews size. No space will be shown below images in some +styles. +* Showing notification in page title when new posts are loaded into the open +thread. +* Thread moderation fixes +* Added new gallery with search links and image metadata \ No newline at end of file diff --git a/readme.markdown b/readme.markdown --- a/readme.markdown +++ b/readme.markdown @@ -27,7 +27,7 @@ Site: http://neboard.me/ # INSTALLATION # 1. Install all dependencies over pip or system-wide -2. Setup a database in neboard/settings.py +2. Setup a database in `neboard/settings.py` 3. Run `./manage.py syncdb` and ensure the database was created 4. Run `./manage.py migrate boards` to apply all south migrations @@ -41,6 +41,15 @@ See django-admin command help for detail Also consider using wsgi or fcgi interfaces on production servers. +# UPGRADE # + +1. Backup your project data. +2. Save the settings in `neboard/settings.py` and `boards/settings.py` +3. Copy the project contents over the old project directory +4. Run migrations by `./manage.py migrate boards` + +You can also just clone the mercurial project and pull it to update + # CONCLUSION # Enjoy our software and thank you! diff --git a/todo.txt b/todo.txt --- a/todo.txt +++ b/todo.txt @@ -12,6 +12,7 @@ denied". Use second only for autoban for [DONE] Split up post model into post and thread, and move everything that is used only in 1st post to thread model. [DONE] Show board speed in the lower panel (posts per day) +[DONE] Save image thumbnails size to the separate field [NOT STARTED] Tree view (JS) [NOT STARTED] Adding tags to images filename @@ -22,7 +23,6 @@ and move everything that is used only in [NOT STARTED] Javascript disabling engine [NOT STARTED] Group tags by first letter in all tags list [NOT STARTED] Character counter in the post field -[NOT STARTED] Save image thumbnails size to the separate field [NOT STARTED] Whitelist functionality. Permin autoban of an address [NOT STARTED] Statistics module. Count views (optional, may result in bad performance), posts per day/week/month, users (or IPs)