##// END OF EJS Templates
Merged with default branch
neko259 -
r137:fe230882 merge 1.1
parent child Browse files
Show More
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,32 b''
1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 #
6 #, fuzzy
7 msgid ""
8 msgstr ""
9 "Project-Id-Version: PACKAGE VERSION\n"
10 "Report-Msgid-Bugs-To: \n"
11 "POT-Creation-Date: 2013-08-27 23:21+0300\n"
12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 "Language-Team: LANGUAGE <LL@li.org>\n"
15 "Language: \n"
16 "MIME-Version: 1.0\n"
17 "Content-Type: text/plain; charset=UTF-8\n"
18 "Content-Transfer-Encoding: 8bit\n"
19 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
20 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
21
22 #: static/js/thread.js:17
23 msgid "Normal"
24 msgstr "Нормальный"
25
26 #: static/js/thread.js:24
27 msgid "Gallery"
28 msgstr "Галерея"
29
30 #: static/js/thread.js:125
31 msgid "Replies"
32 msgstr "Ответы"
@@ -0,0 +1,95 b''
1 # -*- coding: utf-8 -*-
2 import datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6
7
8 class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding model 'Tag'
12 db.create_table(u'boards_tag', (
13 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
15 ))
16 db.send_create_signal(u'boards', ['Tag'])
17
18 # Adding model 'Post'
19 db.create_table(u'boards_post', (
20 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
21 ('title', self.gf('django.db.models.fields.CharField')(max_length=50)),
22 ('pub_time', self.gf('django.db.models.fields.DateTimeField')()),
23 ('text', self.gf('markupfield.fields.MarkupField')(rendered_field=True)),
24 ('text_markup_type', self.gf('django.db.models.fields.CharField')(default='markdown', max_length=30)),
25 ('image', self.gf('boards.thumbs.ImageWithThumbsField')(max_length=100, blank=True)),
26 ('poster_ip', self.gf('django.db.models.fields.IPAddressField')(max_length=15)),
27 ('_text_rendered', self.gf('django.db.models.fields.TextField')()),
28 ('poster_user_agent', self.gf('django.db.models.fields.TextField')()),
29 ('parent', self.gf('django.db.models.fields.BigIntegerField')()),
30 ('last_edit_time', self.gf('django.db.models.fields.DateTimeField')()),
31 ))
32 db.send_create_signal(u'boards', ['Post'])
33
34 # Adding M2M table for field tags on 'Post'
35 m2m_table_name = db.shorten_name(u'boards_post_tags')
36 db.create_table(m2m_table_name, (
37 ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
38 ('post', models.ForeignKey(orm[u'boards.post'], null=False)),
39 ('tag', models.ForeignKey(orm[u'boards.tag'], null=False))
40 ))
41 db.create_unique(m2m_table_name, ['post_id', 'tag_id'])
42
43 # Adding model 'Admin'
44 db.create_table(u'boards_admin', (
45 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
46 ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
47 ('password', self.gf('django.db.models.fields.CharField')(max_length=100)),
48 ))
49 db.send_create_signal(u'boards', ['Admin'])
50
51
52 def backwards(self, orm):
53 # Deleting model 'Tag'
54 db.delete_table(u'boards_tag')
55
56 # Deleting model 'Post'
57 db.delete_table(u'boards_post')
58
59 # Removing M2M table for field tags on 'Post'
60 db.delete_table(db.shorten_name(u'boards_post_tags'))
61
62 # Deleting model 'Admin'
63 db.delete_table(u'boards_admin')
64
65
66 models = {
67 u'boards.admin': {
68 'Meta': {'object_name': 'Admin'},
69 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
70 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
71 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'})
72 },
73 u'boards.post': {
74 'Meta': {'object_name': 'Post'},
75 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
76 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
77 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
78 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
79 'parent': ('django.db.models.fields.BigIntegerField', [], {}),
80 'poster_ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}),
81 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
82 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
83 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['boards.Tag']", 'symmetrical': 'False'}),
84 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
85 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
86 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
87 },
88 u'boards.tag': {
89 'Meta': {'object_name': 'Tag'},
90 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
91 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
92 }
93 }
94
95 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,64 b''
1 # -*- coding: utf-8 -*-
2 import datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6
7
8 class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding model 'Ban'
12 db.create_table(u'boards_ban', (
13 (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 ('ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39)),
15 ))
16 db.send_create_signal(u'boards', ['Ban'])
17
18
19 # Changing field 'Post.poster_ip'
20 db.alter_column(u'boards_post', 'poster_ip', self.gf('django.db.models.fields.GenericIPAddressField')(max_length=39))
21
22 def backwards(self, orm):
23 # Deleting model 'Ban'
24 db.delete_table(u'boards_ban')
25
26
27 # Changing field 'Post.poster_ip'
28 db.alter_column(u'boards_post', 'poster_ip', self.gf('django.db.models.fields.IPAddressField')(max_length=15))
29
30 models = {
31 u'boards.admin': {
32 'Meta': {'object_name': 'Admin'},
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
35 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'})
36 },
37 u'boards.ban': {
38 'Meta': {'object_name': 'Ban'},
39 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
40 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'})
41 },
42 u'boards.post': {
43 'Meta': {'object_name': 'Post'},
44 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
45 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
46 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
47 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
48 'parent': ('django.db.models.fields.BigIntegerField', [], {}),
49 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
50 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
51 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
52 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['boards.Tag']", 'symmetrical': 'False'}),
53 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
54 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
55 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
56 },
57 u'boards.tag': {
58 'Meta': {'object_name': 'Tag'},
59 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
60 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
61 }
62 }
63
64 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,66 b''
1 # -*- coding: utf-8 -*-
2 import datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6
7
8 class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding field 'Post.image_width'
12 db.add_column(u'boards_post', 'image_width',
13 self.gf('django.db.models.fields.IntegerField')(default=0),
14 keep_default=False)
15
16 # Adding field 'Post.image_height'
17 db.add_column(u'boards_post', 'image_height',
18 self.gf('django.db.models.fields.IntegerField')(default=0),
19 keep_default=False)
20
21
22 def backwards(self, orm):
23 # Deleting field 'Post.image_width'
24 db.delete_column(u'boards_post', 'image_width')
25
26 # Deleting field 'Post.image_height'
27 db.delete_column(u'boards_post', 'image_height')
28
29
30 models = {
31 u'boards.admin': {
32 'Meta': {'object_name': 'Admin'},
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
35 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'})
36 },
37 u'boards.ban': {
38 'Meta': {'object_name': 'Ban'},
39 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
40 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'})
41 },
42 u'boards.post': {
43 'Meta': {'object_name': 'Post'},
44 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
45 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
46 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
47 'image_height': ('django.db.models.fields.IntegerField', [], {}),
48 'image_width': ('django.db.models.fields.IntegerField', [], {}),
49 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
50 'parent': ('django.db.models.fields.BigIntegerField', [], {}),
51 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
52 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
53 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
54 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['boards.Tag']", 'symmetrical': 'False'}),
55 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
56 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
57 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'})
58 },
59 u'boards.tag': {
60 'Meta': {'object_name': 'Tag'},
61 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
62 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
63 }
64 }
65
66 complete_apps = ['boards'] No newline at end of file
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,38 b''
1 function addRefLinkMap() {
2 var postByNum = [], refMap = [];
3
4 $('.post').each(function() {
5 var self = $(this);
6
7 var postNum = self.attr('id');
8 //add post by id
9 postByNum[postNum] = self;
10 //add ref link
11 self.find('p').children('a').each(function() {
12 if($(this).text().indexOf('>>') == 0) {
13 var refNum = $(this).text().match(/\d+/);
14
15 if(postByNum[refNum]) {
16 if(!refMap[refNum])
17 refMap[refNum] = [];
18
19 //if !exist
20 if((',' + refMap[refNum].toString() + ',').indexOf(',' + postNum + ',') < 0) {
21 refMap[refNum].push(postNum);
22 };
23 }
24 }
25 });
26 });
27
28 var label_replies = gettext('Replies') + ':';
29 for(var pNum in refMap) {
30 if(typeof refMap[pNum] === 'object') {
31 //append refmap panel
32 if(!$("#refmap_"+pNum).length) {
33 var data = label_replies + refMap[pNum].toString().replace(/(\d+)/g, ' <a href="/jump/$1/">>>$1</a>');
34 $('#'+pNum+'').find('.message').after($('<div class="refmap" id="refmap_'+pNum+'">'+data+'</div>'));
35 }
36 }
37 }
38 }
@@ -1,6 +1,7 b''
1 from django.contrib import admin
1 from django.contrib import admin
2 from boards.models import Post, Tag, User
2 from boards.models import Post, Tag, User, Ban
3
3
4 admin.site.register(Post)
4 admin.site.register(Post)
5 admin.site.register(Tag)
5 admin.site.register(Tag)
6 admin.site.register(User)
6 admin.site.register(User)
7 admin.site.register(Ban)
@@ -1,14 +1,24 b''
1 __author__ = 'neko259'
1 __author__ = 'neko259'
2
2
3 authors = {
3 authors = {
4 'neko259': {
4 'neko259': {
5 'name': 'Pavel Ryapolov',
5 'name': 'Pavel Ryapolov',
6 'contacts': ['neko259@gmail.com'],
6 'contacts': ['neko259@gmail.com'],
7 'roles': ['author', 'developer'],
7 'roles': ['author', 'developer'],
8 },
8 },
9 'ilyas': {
9 'ilyas': {
10 'name': 'Ilyas Babayev',
10 'name': 'Ilyas Babayev',
11 'contacts': ['zamesilyasa@gmail.com'],
11 'contacts': ['zamesilyasa@gmail.com'],
12 'roles': ['author', 'developer'],
12 'roles': ['author', 'developer'],
13 }
13 },
14 'ritsufag': {
15 'name': 'Aiko Kirino',
16 'contacts': ['ritsufag@gmail.com'],
17 'roles': ['javascript developer', 'designer'],
18 },
19 'Tenno Seremel': {
20 'name': 'anonymous',
21 'contacts': ['html@serenareem.net'],
22 'roles': ['javascript developer', 'designer'],
23 },
14 } No newline at end of file
24 }
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,180 +1,205 b''
1 # SOME DESCRIPTIVE TITLE.
1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package.
3 # This file is distributed under the same license as the PACKAGE package.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 #
5 #
6 msgid ""
6 msgid ""
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2013-08-21 20:49+0300\n"
10 "POT-Creation-Date: 2013-08-30 18:54+0300\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: ru\n"
14 "Language: ru\n"
15 "MIME-Version: 1.0\n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
17 "Content-Transfer-Encoding: 8bit\n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20
20
21 #: templates/boards/404.html:6
22 msgid "Not found"
23 msgstr "Не найдено"
24
25 #: templates/boards/404.html:12
26 msgid "This page does not exist"
27 msgstr "Этой страницы не существует"
28
21 #: templates/boards/authors.html:6
29 #: templates/boards/authors.html:6
22 msgid "Authors"
30 msgid "Authors"
23 msgstr "Авторы"
31 msgstr "Авторы"
24
32
25 #: templates/boards/authors.html:24
33 #: templates/boards/authors.html:24
26 msgid "Distributed under the"
34 msgid "Distributed under the"
27 msgstr "Распространяется под"
35 msgstr "Распространяется под"
28
36
29 #: templates/boards/authors.html:26
37 #: templates/boards/authors.html:26
30 msgid "license"
38 msgid "license"
31 msgstr "лицензией"
39 msgstr "лицензией"
32
40
33 #: templates/boards/authors.html:28
41 #: templates/boards/authors.html:28
34 msgid "Repository"
42 msgid "Repository"
35 msgstr "Репозиторий"
43 msgstr "Репозиторий"
36
44
45 #: templates/boards/banned.html:6
46 msgid "Banned"
47 msgstr "Заблокирован"
48
49 #: templates/boards/banned.html:11
50 msgid "Your IP address has been banned. Contact the administrator"
51 msgstr "Ваш IP адрес был заблокирован. Свяжитесь с администратором"
52
37 #: templates/boards/base.html:12
53 #: templates/boards/base.html:12
38 msgid "Feed"
54 msgid "Feed"
39 msgstr "Лента"
55 msgstr "Лента"
40
56
41 #: templates/boards/base.html:27
57 #: templates/boards/base.html:36
42 msgid "All threads"
58 msgid "All threads"
43 msgstr "Все темы"
59 msgstr "Все темы"
44
60
45 #: templates/boards/base.html:33
61 #: templates/boards/base.html:42
46 msgid "Settings"
62 msgid "Settings"
47 msgstr "Настройки"
63 msgstr "Настройки"
48
64
49 #: templates/boards/base.html:41
65 #: templates/boards/base.html:50
50 msgid "Up"
66 msgid "Up"
51 msgstr "Вверх"
67 msgstr "Вверх"
52
68
53 #: templates/boards/posting_general.html:18
69 #: templates/boards/posting_general.html:18
54 msgid "Tag: "
70 msgid "Tag: "
55 msgstr "Тег: "
71 msgstr "Тег: "
56
72
57 #: templates/boards/posting_general.html:35
73 #: templates/boards/posting_general.html:35
58 #: templates/boards/posting_general.html:87 templates/boards/thread.html:27
74 #: templates/boards/posting_general.html:81 templates/boards/thread.html:27
59 #: templates/boards/rss/post.html:5
75 #: templates/boards/rss/post.html:5
60 msgid "Post image"
76 msgid "Post image"
61 msgstr "Изображение сообщения"
77 msgstr "Изображение сообщения"
62
78
63 #: templates/boards/posting_general.html:46
79 #: templates/boards/posting_general.html:48
64 msgid "Reply"
80 msgid "Reply"
65 msgstr "Ответ"
81 msgstr "Ответ"
66
82
67 #: templates/boards/posting_general.html:52 templates/boards/thread.html:49
83 #: templates/boards/posting_general.html:52 templates/boards/thread.html:49
68 msgid "Delete"
84 msgid "Delete"
69 msgstr "Удалить"
85 msgstr "Удалить"
70
86
71 #: templates/boards/posting_general.html:61 templates/boards/thread.html:119
87 #: templates/boards/posting_general.html:55 templates/boards/thread.html:108
72 msgid "replies"
88 msgid "replies"
73 msgstr "ответов"
89 msgstr "ответов"
74
90
75 #: templates/boards/posting_general.html:62 templates/boards/thread.html:120
91 #: templates/boards/posting_general.html:56 templates/boards/thread.html:109
76 msgid "images"
92 msgid "images"
77 msgstr "изображений"
93 msgstr "изображений"
78
94
79 #: templates/boards/posting_general.html:64
95 #: templates/boards/posting_general.html:58
80 #: templates/boards/posting_general.html:135 templates/boards/thread.html:59
96 #: templates/boards/posting_general.html:131 templates/boards/thread.html:48
81 #: templates/boards/rss/post.html:10
97 #: templates/boards/rss/post.html:10
82 msgid "Tags"
98 msgid "Tags"
83 msgstr "Теги"
99 msgstr "Теги"
84
100
85 #: templates/boards/posting_general.html:117
101 #: templates/boards/posting_general.html:113
86 msgid "Create new thread"
102 msgid "Create new thread"
87 msgstr "Создать новую тему"
103 msgstr "Создать новую тему"
88
104
89 #: templates/boards/posting_general.html:120 templates/boards/thread.html:81
105 #: templates/boards/posting_general.html:116 templates/boards/thread.html:70
90 msgid "Title"
106 msgid "Title"
91 msgstr "Заголовок"
107 msgstr "Заголовок"
92
108
93 #: templates/boards/posting_general.html:125 templates/boards/thread.html:86
109 #: templates/boards/posting_general.html:121 templates/boards/thread.html:75
94 msgid "Text"
110 msgid "Text"
95 msgstr "Текст"
111 msgstr "Текст"
96
112
97 #: templates/boards/posting_general.html:130 templates/boards/thread.html:91
113 #: templates/boards/posting_general.html:126 templates/boards/thread.html:80
98 msgid "Image"
114 msgid "Image"
99 msgstr "Изображение"
115 msgstr "Изображение"
100
116
101 #: templates/boards/posting_general.html:145 templates/boards/thread.html:102
117 #: templates/boards/posting_general.html:141 templates/boards/thread.html:91
102 msgid "Post"
118 msgid "Post"
103 msgstr "Отправить"
119 msgstr "Отправить"
104
120
105 #: templates/boards/posting_general.html:147
121 #: templates/boards/posting_general.html:143
106 msgid "Tags must be delimited by spaces. Text or image is required."
122 msgid "Tags must be delimited by spaces. Text or image is required."
107 msgstr ""
123 msgstr ""
108 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
124 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
109
125
110 #: templates/boards/posting_general.html:150 templates/boards/thread.html:104
126 #: templates/boards/posting_general.html:146 templates/boards/thread.html:93
111 msgid "Basic markdown syntax."
127 msgid "Basic markdown syntax."
112 msgstr "Базовый синтаксис markdown."
128 msgstr "Базовый синтаксис markdown."
113
129
114 #: templates/boards/posting_general.html:160
130 #: templates/boards/posting_general.html:156
115 msgid "Pages:"
131 msgid "Pages:"
116 msgstr "Страницы: "
132 msgstr "Страницы: "
117
133
118 #: templates/boards/settings.html:12
134 #: templates/boards/settings.html:12
119 msgid "User:"
135 msgid "User:"
120 msgstr "Пользователь:"
136 msgstr "Пользователь:"
121
137
122 #: templates/boards/settings.html:14
138 #: templates/boards/settings.html:14
123 msgid "You are moderator."
139 msgid "You are moderator."
124 msgstr "Вы модератор."
140 msgstr "Вы модератор."
125
141
126 #: templates/boards/settings.html:20
142 #: templates/boards/settings.html:20
127 msgid "Theme"
143 msgid "Theme"
128 msgstr "Тема"
144 msgstr "Тема"
129
145
130 #: templates/boards/settings.html:36
146 #: templates/boards/settings.html:36
131 msgid "Save"
147 msgid "Save"
132 msgstr "Сохранить"
148 msgstr "Сохранить"
133
149
134 #: templates/boards/tags.html:7
150 #: templates/boards/tags.html:7
135 msgid "tags"
151 msgid "tags"
136 msgstr "тегов"
152 msgstr "тегов"
137
153
138 #: templates/boards/thread.html:39
154 #: templates/boards/thread.html:39
139 msgid "Get!"
155 msgid "Get!"
140 msgstr "Гет!"
156 msgstr "Гет!"
141
157
142 #: templates/boards/thread.html:78
158 #: templates/boards/thread.html:67
143 msgid "Reply to thread"
159 msgid "Reply to thread"
144 msgstr "Ответить в тему"
160 msgstr "Ответить в тему"
145
161
146 #: templates/boards/thread.html:105
162 #: templates/boards/thread.html:94
147 msgid "Example: "
163 msgid "Example: "
148 msgstr "Пример: "
164 msgstr "Пример: "
149
165
150 #: templates/boards/thread.html:105
166 #: templates/boards/thread.html:94
151 msgid "italic"
167 msgid "italic"
152 msgstr "курсив"
168 msgstr "курсив"
153
169
154 #: templates/boards/thread.html:106
170 #: templates/boards/thread.html:95
155 msgid "bold"
171 msgid "bold"
156 msgstr "полужирный"
172 msgstr "полужирный"
157
173
158 #: templates/boards/thread.html:107
174 #: templates/boards/thread.html:96
159 msgid "Quotes can be inserted with"
175 msgid "Quotes can be inserted with"
160 msgstr "Цитаты могут быть вставлены при помощи"
176 msgstr "Цитаты могут быть вставлены при помощи"
161
177
162 #: templates/boards/thread.html:108
178 #: templates/boards/thread.html:97
163 msgid "Links to answers can be inserted with"
179 msgid "Links to answers can be inserted with"
164 msgstr "Ссылки на ответы могут быть вставлены с помощью"
180 msgstr "Ссылки на ответы могут быть вставлены с помощью"
165
181
166 #: templates/boards/thread.html:121
182 #: templates/boards/thread.html:110
167 msgid "Last update: "
183 msgid "Last update: "
168 msgstr "Последнее обновление: "
184 msgstr "Последнее обновление: "
169
185
186 #~ msgid "Get!"
187 #~ msgstr "Гет!"
188
170 #~ msgid "View"
189 #~ msgid "View"
171 #~ msgstr "Просмотр"
190 #~ msgstr "Просмотр"
172
191
173 #~ msgid "gets"
192 #~ msgid "gets"
174 #~ msgstr "гетов"
193 #~ msgstr "гетов"
175
194
176 #~ msgid "author"
195 #~ msgid "author"
177 #~ msgstr "автор"
196 #~ msgstr "автор"
178
197
179 #~ msgid "developer"
198 #~ msgid "developer"
180 #~ msgstr "разработчик"
199 #~ msgstr "разработчик"
200
201 #~ msgid "javascript developer"
202 #~ msgstr "разработчик javascript"
203
204 #~ msgid "designer"
205 #~ msgstr "дизайнер"
@@ -1,330 +1,327 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.http import Http404
8 from django.http import Http404
9 from django.utils import timezone
9 from django.utils import timezone
10 from markupfield.fields import MarkupField
10 from markupfield.fields import MarkupField
11 from threading import Thread
11 from threading import Thread
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30 REGEX_PRETTY = re.compile(r'^\d(0)+$')
31 REGEX_SAME = re.compile(r'^(.)\1+$')
32
33 RANK_ADMIN = 0
30 RANK_ADMIN = 0
34 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
35 RANK_USER = 100
32 RANK_USER = 100
36
33
37
38 class PostManager(models.Manager):
34 class PostManager(models.Manager):
39 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
35 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
40 ip=NO_IP, tags=None, user=None):
36 ip=NO_IP, tags=None, user=None):
41 post = self.create(title=title,
37 post = self.create(title=title,
42 text=text,
38 text=text,
43 pub_time=timezone.now(),
39 pub_time=timezone.now(),
44 parent=parent_id,
40 parent=parent_id,
45 image=image,
41 image=image,
46 poster_ip=ip,
42 poster_ip=ip,
47 poster_user_agent=UNKNOWN_UA,
43 poster_user_agent=UNKNOWN_UA,
48 last_edit_time=timezone.now(),
44 last_edit_time=timezone.now(),
49 user=user)
45 user=user)
50
46
51 if tags:
47 if tags:
52 map(post.tags.add, tags)
48 map(post.tags.add, tags)
53
49
54 if parent_id != NO_PARENT:
50 if parent_id != NO_PARENT:
55 self._bump_thread(parent_id)
51 self._bump_thread(parent_id)
56 else:
52 else:
57 self._delete_old_threads()
53 self._delete_old_threads()
58
54
59 return post
55 return post
60
56
61 def delete_post(self, post):
57 def delete_post(self, post):
62 children = self.filter(parent=post.id)
58 children = self.filter(parent=post.id)
63 for child in children:
59 for child in children:
64 self.delete_post(child)
60 self.delete_post(child)
65 post.delete()
61 post.delete()
66
62
67 def delete_posts_by_ip(self, ip):
63 def delete_posts_by_ip(self, ip):
68 posts = self.filter(poster_ip=ip)
64 posts = self.filter(poster_ip=ip)
69 for post in posts:
65 for post in posts:
70 self.delete_post(post)
66 self.delete_post(post)
71
67
72 def get_threads(self, tag=None, page=ALL_PAGES,
68 def get_threads(self, tag=None, page=ALL_PAGES,
73 order_by='-last_edit_time'):
69 order_by='-last_edit_time'):
74 if tag:
70 if tag:
75 threads = self.filter(parent=NO_PARENT, tags=tag)
71 threads = self.filter(parent=NO_PARENT, tags=tag)
76
72
77 # TODO Throw error 404 if no threads for tag found?
73 # TODO Throw error 404 if no threads for tag found?
78 else:
74 else:
79 threads = self.filter(parent=NO_PARENT)
75 threads = self.filter(parent=NO_PARENT)
80
76
81 threads = threads.order_by(order_by)
77 threads = threads.order_by(order_by)
82
78
83 if page != ALL_PAGES:
79 if page != ALL_PAGES:
84 thread_count = len(threads)
80 thread_count = len(threads)
85
81
86 if page < self.get_thread_page_count(tag=tag):
82 if page < self.get_thread_page_count(tag=tag):
87 start_thread = page * settings.THREADS_PER_PAGE
83 start_thread = page * settings.THREADS_PER_PAGE
88 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
84 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
89 thread_count)
85 thread_count)
90 threads = threads[start_thread:end_thread]
86 threads = threads[start_thread:end_thread]
91
87
92 return threads
88 return threads
93
89
94 def get_thread(self, opening_post_id):
90 def get_thread(self, opening_post_id):
95 try:
91 try:
96 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
92 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
97 except Post.DoesNotExist:
93 except Post.DoesNotExist:
98 raise Http404
94 raise Http404
99
95
100 if opening_post.parent == NO_PARENT:
96 if opening_post.parent == NO_PARENT:
101 replies = self.filter(parent=opening_post_id)
97 replies = self.filter(parent=opening_post_id)
102
98
103 thread = [opening_post]
99 thread = [opening_post]
104 thread.extend(replies)
100 thread.extend(replies)
105
101
106 return thread
102 return thread
107
103
108 def exists(self, post_id):
104 def exists(self, post_id):
109 posts = self.filter(id=post_id)
105 posts = self.filter(id=post_id)
110
106
111 return posts.count() > 0
107 return posts.count() > 0
112
108
113 def get_thread_page_count(self, tag=None):
109 def get_thread_page_count(self, tag=None):
114 if tag:
110 if tag:
115 threads = self.filter(parent=NO_PARENT, tags=tag)
111 threads = self.filter(parent=NO_PARENT, tags=tag)
116 else:
112 else:
117 threads = self.filter(parent=NO_PARENT)
113 threads = self.filter(parent=NO_PARENT)
118
114
119 return int(math.ceil(threads.count() / float(
115 return int(math.ceil(threads.count() / float(
120 settings.THREADS_PER_PAGE)))
116 settings.THREADS_PER_PAGE)))
121
117
122 def _delete_old_threads(self):
118 def _delete_old_threads(self):
123 """
119 """
124 Preserves maximum thread count. If there are too many threads,
120 Preserves maximum thread count. If there are too many threads,
125 delete the old ones.
121 delete the old ones.
126 """
122 """
127
123
128 # TODO Move old threads to the archive instead of deleting them.
124 # TODO Move old threads to the archive instead of deleting them.
129 # Maybe make some 'old' field in the model to indicate the thread
125 # Maybe make some 'old' field in the model to indicate the thread
130 # must not be shown and be able for replying.
126 # must not be shown and be able for replying.
131
127
132 threads = self.get_threads()
128 threads = self.get_threads()
133 thread_count = len(threads)
129 thread_count = len(threads)
134
130
135 if thread_count > settings.MAX_THREAD_COUNT:
131 if thread_count > settings.MAX_THREAD_COUNT:
136 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
132 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
137 old_threads = threads[thread_count - num_threads_to_delete:]
133 old_threads = threads[thread_count - num_threads_to_delete:]
138
134
139 for thread in old_threads:
135 for thread in old_threads:
140 self.delete_post(thread)
136 self.delete_post(thread)
141
137
142 def _bump_thread(self, thread_id):
138 def _bump_thread(self, thread_id):
143 thread = self.get(id=thread_id)
139 thread = self.get(id=thread_id)
144
140
145 if thread.can_bump():
141 if thread.can_bump():
146 thread.last_edit_time = timezone.now()
142 thread.last_edit_time = timezone.now()
147 thread.save()
143 thread.save()
148
144
149
145
150 class TagManager(models.Manager):
146 class TagManager(models.Manager):
151 def get_not_empty_tags(self):
147 def get_not_empty_tags(self):
152 all_tags = self.all().order_by('name')
148 all_tags = self.all().order_by('name')
153 tags = []
149 tags = []
154 for tag in all_tags:
150 for tag in all_tags:
155 if not tag.is_empty():
151 if not tag.is_empty():
156 tags.append(tag)
152 tags.append(tag)
157
153
158 return tags
154 return tags
159
155
160 def get_popular_tags(self):
156 def get_popular_tags(self):
161 all_tags = self.get_not_empty_tags()
157 all_tags = self.get_not_empty_tags()
162
158
163 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
159 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
164 reverse=True)
160 reverse=True)
165
161
166 return sorted_tags[:settings.POPULAR_TAGS]
162 return sorted_tags[:settings.POPULAR_TAGS]
167
163
168
164
169 class Tag(models.Model):
165 class Tag(models.Model):
170 """
166 """
171 A tag is a text node assigned to the post. The tag serves as a board
167 A tag is a text node assigned to the post. The tag serves as a board
172 section. There can be multiple tags for each message
168 section. There can be multiple tags for each message
173 """
169 """
174
170
175 objects = TagManager()
171 objects = TagManager()
176
172
177 name = models.CharField(max_length=100)
173 name = models.CharField(max_length=100)
178 # TODO Connect the tag to its posts to check the number of threads for
174 # TODO Connect the tag to its posts to check the number of threads for
179 # the tag.
175 # the tag.
180
176
181 def __unicode__(self):
177 def __unicode__(self):
182 return self.name
178 return self.name
183
179
184 def is_empty(self):
180 def is_empty(self):
185 return self.get_post_count() == 0
181 return self.get_post_count() == 0
186
182
187 def get_post_count(self):
183 def get_post_count(self):
188 posts_with_tag = Post.objects.get_threads(tag=self)
184 posts_with_tag = Post.objects.get_threads(tag=self)
189 return posts_with_tag.count()
185 return posts_with_tag.count()
190
186
191 def get_popularity(self):
187 def get_popularity(self):
192 posts_with_tag = Post.objects.get_threads(tag=self)
188 posts_with_tag = Post.objects.get_threads(tag=self)
193 reply_count = 0
189 reply_count = 0
194 for post in posts_with_tag:
190 for post in posts_with_tag:
195 reply_count += post.get_reply_count()
191 reply_count += post.get_reply_count()
196 reply_count += OPENING_POST_POPULARITY_WEIGHT
192 reply_count += OPENING_POST_POPULARITY_WEIGHT
197
193
198 return reply_count
194 return reply_count
199
195
200
196
201 class Post(models.Model):
197 class Post(models.Model):
202 """A post is a message."""
198 """A post is a message."""
203
199
204 objects = PostManager()
200 objects = PostManager()
205
201
206 def _update_image_filename(self, filename):
202 def _update_image_filename(self, filename):
207 """Get unique image filename"""
203 """Get unique image filename"""
208
204
209 path = IMAGES_DIRECTORY
205 path = IMAGES_DIRECTORY
210 new_name = str(int(time.mktime(time.gmtime())))
206 new_name = str(int(time.mktime(time.gmtime())))
211 new_name += str(int(random() * 1000))
207 new_name += str(int(random() * 1000))
212 new_name += FILE_EXTENSION_DELIMITER
208 new_name += FILE_EXTENSION_DELIMITER
213 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
209 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
214
210
215 return os.path.join(path, new_name)
211 return os.path.join(path, new_name)
216
212
217 title = models.CharField(max_length=TITLE_MAX_LENGTH)
213 title = models.CharField(max_length=TITLE_MAX_LENGTH)
218 pub_time = models.DateTimeField()
214 pub_time = models.DateTimeField()
219 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
215 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
220 escape_html=False)
216 escape_html=False)
217
218 image_width = models.IntegerField(default=0)
219 image_height = models.IntegerField(default=0)
220
221 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
221 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
222 blank=True, sizes=(IMAGE_THUMB_SIZE,))
222 blank=True, sizes=(IMAGE_THUMB_SIZE,),
223 poster_ip = models.IPAddressField()
223 width_field='image_width',
224 height_field='image_height')
225
226 poster_ip = models.GenericIPAddressField()
224 poster_user_agent = models.TextField()
227 poster_user_agent = models.TextField()
225 parent = models.BigIntegerField()
228 parent = models.BigIntegerField()
226 tags = models.ManyToManyField(Tag)
229 tags = models.ManyToManyField(Tag)
227 last_edit_time = models.DateTimeField()
230 last_edit_time = models.DateTimeField()
228 user = models.ForeignKey(User, null=True, default=None)
231 user = models.ForeignKey(User, null=True, default=None)
229
232
230 def __unicode__(self):
233 def __unicode__(self):
231 return '#' + str(self.id) + ' ' + self.title + ' (' + self.text.raw + \
234 return '#' + str(self.id) + ' ' + self.title + ' (' + \
232 ')'
235 self.text.raw[:50] + ')'
233
236
234 def _get_replies(self):
237 def _get_replies(self):
235 return Post.objects.filter(parent=self.id)
238 return Post.objects.filter(parent=self.id)
236
239
237 def get_reply_count(self):
240 def get_reply_count(self):
238 return self._get_replies().count()
241 return self._get_replies().count()
239
242
240 def get_images_count(self):
243 def get_images_count(self):
241 images_count = 1 if self.image else 0
244 images_count = 1 if self.image else 0
242 for reply in self._get_replies():
245 for reply in self._get_replies():
243 if reply.image:
246 if reply.image:
244 images_count += 1
247 images_count += 1
245
248
246 return images_count
249 return images_count
247
250
248 def get_gets_count(self):
251 def get_gets_count(self):
249 gets_count = 1 if self.is_get() else 0
252 gets_count = 1 if self.is_get() else 0
250 for reply in self._get_replies():
253 for reply in self._get_replies():
251 if reply.is_get():
254 if reply.is_get():
252 gets_count += 1
255 gets_count += 1
253
256
254 return gets_count
257 return gets_count
255
258
256 def is_get(self):
257 """If the post has pretty id (1, 1000, 77777), than it is called GET"""
258
259 first = self.id == 1
260
261 id_str = str(self.id)
262 pretty = REGEX_PRETTY.match(id_str)
263 same_digits = REGEX_SAME.match(id_str)
264
265 return first or pretty or same_digits
266
267 def can_bump(self):
259 def can_bump(self):
268 """Check if the thread can be bumped by replying"""
260 """Check if the thread can be bumped by replying"""
269
261
270 replies_count = len(Post.objects.get_thread(self.id))
262 replies_count = len(Post.objects.get_thread(self.id))
271
263
272 return replies_count <= settings.MAX_POSTS_PER_THREAD
264 return replies_count <= settings.MAX_POSTS_PER_THREAD
273
265
274 def get_last_replies(self):
266 def get_last_replies(self):
275 if settings.LAST_REPLIES_COUNT > 0:
267 if settings.LAST_REPLIES_COUNT > 0:
276 reply_count = self.get_reply_count()
268 reply_count = self.get_reply_count()
277
269
278 if reply_count > 0:
270 if reply_count > 0:
279 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
271 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
280 reply_count)
272 reply_count)
281 last_replies = self._get_replies()[reply_count
273 last_replies = self._get_replies()[reply_count
282 - reply_count_to_show:]
274 - reply_count_to_show:]
283
275
284 return last_replies
276 return last_replies
285
277
286
278
287 class User(models.Model):
279 class User(models.Model):
288
280
289 user_id = models.CharField(max_length=50)
281 user_id = models.CharField(max_length=50)
290 rank = models.IntegerField()
282 rank = models.IntegerField()
291
283
292 registration_time = models.DateTimeField()
284 registration_time = models.DateTimeField()
293 last_access_time = models.DateTimeField()
285 last_access_time = models.DateTimeField()
294
286
295 fav_tags = models.ManyToManyField(Tag)
287 fav_tags = models.ManyToManyField(Tag)
296 fav_threads = models.ManyToManyField(Post)
288 fav_threads = models.ManyToManyField(Post)
297
289
298 def save_setting(self, name, value):
290 def save_setting(self, name, value):
299 setting, created = Setting.objects.get_or_create(name=name, user=self)
291 setting, created = Setting.objects.get_or_create(name=name, user=self)
300 setting.value = value
292 setting.value = value
301 setting.save()
293 setting.save()
302
294
303 return setting
295 return setting
304
296
305 def get_setting(self, name):
297 def get_setting(self, name):
306 settings = Setting.objects.filter(name=name, user=self)
298 settings = Setting.objects.filter(name=name, user=self)
307 if len(settings) > 0:
299 if len(settings) > 0:
308 setting = settings[0]
300 setting = settings[0]
309 else:
301 else:
310 setting = None
302 setting = None
311
303
312 if setting:
304 if setting:
313 setting_value = setting.value
305 setting_value = setting.value
314 else:
306 else:
315 setting_value = None
307 setting_value = None
316
308
317 return setting_value
309 return setting_value
318
310
319 def is_moderator(self):
311 def is_moderator(self):
320 return RANK_MODERATOR >= self.rank
312 return RANK_MODERATOR >= self.rank
321
313
322 def __unicode__(self):
314 def __unicode__(self):
323 return self.user_id
315 return self.user_id
324
316
325
326 class Setting(models.Model):
317 class Setting(models.Model):
327
318
328 name = models.CharField(max_length=50)
319 name = models.CharField(max_length=50)
329 value = models.CharField(max_length=50)
320 value = models.CharField(max_length=50)
330 user = models.ForeignKey(User)
321 user = models.ForeignKey(User)
322
323 class Ban(models.Model):
324 ip = models.GenericIPAddressField()
325
326 def __unicode__(self):
327 return self.ip
@@ -1,280 +1,286 b''
1 html {
1 html {
2 background: #555;
2 background: #555;
3 color: #ffffff;
3 color: #ffffff;
4 }
4 }
5
5
6 #admin_panel {
6 #admin_panel {
7 background: #FF0000;
7 background: #FF0000;
8 color: #00FF00
8 color: #00FF00
9 }
9 }
10
10
11 .input_field {
11 .input_field {
12
12
13 }
13 }
14
14
15 .input_field_name {
15 .input_field_name {
16
16
17 }
17 }
18
18
19 .input_field_error {
19 .input_field_error {
20 color: #FF0000;
20 color: #FF0000;
21 }
21 }
22
22
23
23
24 .title {
24 .title {
25 font-weight: bold;
25 font-weight: bold;
26 color: #ffcc00;
26 color: #ffcc00;
27 }
27 }
28
28
29 .link, a {
29 .link, a {
30 color: #afdcec;
30 color: #afdcec;
31 }
31 }
32
32
33 .block {
33 .block {
34 display: inline-block;
34 display: inline-block;
35 vertical-align: top;
35 vertical-align: top;
36 }
36 }
37
37
38 .tag {
38 .tag {
39 color: #b4cfec;
39 color: #b4cfec;
40 }
40 }
41
41
42 .post_id {
42 .post_id {
43 color: #fff380;
43 color: #fff380;
44 }
44 }
45
45
46 .post, .dead_post {
46 .post, .dead_post {
47 background: #333;
47 background: #333;
48 margin: 5px;
48 margin: 5px;
49 padding: 10px;
49 padding: 10px;
50 border-radius: 5px;
50 border: solid 1px #888;
51 clear: left;
51 clear: left;
52 word-wrap: break-word;
52 word-wrap: break-word;
53 }
53 }
54
54
55 .metadata {
55 .metadata {
56 padding: 5px;
56 padding: 5px;
57 margin-top: 10px;
57 margin-top: 10px;
58 border: solid 1px #666;
58 border: solid 1px #666;
59 font-size: 0.9em;
59 font-size: 0.9em;
60 color: #ddd;
60 color: #ddd;
61 display: table;
61 display: table;
62 }
62 }
63
63
64 .navigation_panel, .tag_info {
64 .navigation_panel, .tag_info {
65 background: #444;
65 background: #444;
66 margin: 5px;
66 margin: 5px;
67 padding: 10px;
67 padding: 10px;
68 border-radius: 5px;
68 border: solid 1px #888;
69 color: #eee;
69 color: #eee;
70 }
70 }
71
71
72 .navigation_panel .link {
72 .navigation_panel .link {
73 border-right: 1px solid #fff;
73 border-right: 1px solid #fff;
74 font-weight: bold;
74 font-weight: bold;
75 margin-right: 1ex;
75 margin-right: 1ex;
76 padding-right: 1ex;
76 padding-right: 1ex;
77 }
77 }
78 .navigation_panel .link:last-child {
78 .navigation_panel .link:last-child {
79 border-left: 1px solid #fff;
79 border-left: 1px solid #fff;
80 border-right: none;
80 border-right: none;
81 float: right;
81 float: right;
82 margin-left: 1ex;
82 margin-left: 1ex;
83 margin-right: 0;
83 margin-right: 0;
84 padding-left: 1ex;
84 padding-left: 1ex;
85 padding-right: 0;
85 padding-right: 0;
86 }
86 }
87
87
88 .navigation_panel::after, .post::after {
88 .navigation_panel::after, .post::after {
89 clear: both;
89 clear: both;
90 content: ".";
90 content: ".";
91 display: block;
91 display: block;
92 height: 0;
92 height: 0;
93 line-height: 0;
93 line-height: 0;
94 visibility: hidden;
94 visibility: hidden;
95 }
95 }
96
96
97 p {
97 p {
98 margin-top: .5em;
98 margin-top: .5em;
99 margin-bottom: .5em;
99 margin-bottom: .5em;
100 }
100 }
101
101
102 .post-form-w {
102 .post-form-w {
103 display: table;
103 display: table;
104 background: #333344;
104 background: #333344;
105 border-radius: 5px;
105 border: solid 1px #888;
106 color: #fff;
106 color: #fff;
107 padding: 10px;
107 padding: 10px;
108 margin: 5px
108 margin: 5px
109 }
109 }
110
110
111 .form-row {
111 .form-row {
112 display: table-row;
112 display: table-row;
113 }
113 }
114
114
115 .form-label, .form-input, .form-errors {
115 .form-label, .form-input, .form-errors {
116 display: table-cell;
116 display: table-cell;
117 }
117 }
118
118
119 .form-label {
119 .form-label {
120 padding: .25em 1ex .25em 0;
120 padding: .25em 1ex .25em 0;
121 vertical-align: top;
121 vertical-align: top;
122 }
122 }
123
123
124 .form-input {
124 .form-input {
125 padding: .25em 0;
125 padding: .25em 0;
126 }
126 }
127
127
128 .form-errors {
128 .form-errors {
129 padding-left: 1ex;
129 padding-left: 1ex;
130 font-weight: bold;
130 font-weight: bold;
131 vertical-align: top;
131 vertical-align: top;
132 }
132 }
133
133
134 .post-form input, .post-form textarea {
134 .post-form input, .post-form textarea {
135 background: #333;
135 background: #333;
136 color: #fff;
136 color: #fff;
137 border: solid 1px;
137 border: solid 1px;
138 padding: 0;
138 padding: 0;
139 width: 100%;
139 width: 100%;
140 }
140 }
141
141
142 .form-submit {
142 .form-submit {
143 border-bottom: 2px solid #ddd;
143 border-bottom: 2px solid #ddd;
144 margin-bottom: .5em;
144 margin-bottom: .5em;
145 padding-bottom: .5em;
145 padding-bottom: .5em;
146 }
146 }
147
147
148 .form-title {
148 .form-title {
149 font-weight: bold;
149 font-weight: bold;
150 }
150 }
151
151
152 input[type="submit"] {
152 input[type="submit"] {
153 background: #222;
153 background: #222;
154 border: solid 1px #fff;
154 border: solid 1px #fff;
155 color: #fff;
155 color: #fff;
156 }
156 }
157
157
158 blockquote {
158 blockquote {
159 border-left: solid 2px;
159 border-left: solid 2px;
160 padding-left: 5px;
160 padding-left: 5px;
161 color: #B1FB17;
161 color: #B1FB17;
162 margin: 0;
162 margin: 0;
163 }
163 }
164
164
165 .post > .image {
165 .post > .image {
166 float: left;
166 float: left;
167 margin: 0 1ex .5ex 0;
167 margin: 0 1ex .5ex 0;
168 min-width: 1px;
168 min-width: 1px;
169 height: 150px;
170 text-align: center;
169 text-align: center;
171 display: table-row;
170 display: table-row;
172 }
171 }
173
172
174 .post > .metadata {
173 .post > .metadata {
175 clear: left;
174 clear: left;
176 }
175 }
177
176
178 .get {
177 .get {
179 font-weight: bold;
178 font-weight: bold;
180 color: #d55;
179 color: #d55;
181 }
180 }
182
181
183 * {
182 * {
184 text-decoration: none;
183 text-decoration: none;
185 }
184 }
186
185
187 .dead_post {
186 .dead_post {
188 background-color: #442222;
187 background-color: #442222;
189 }
188 }
190
189
191 .quote {
190 .quote {
192 color: #92cf38;
191 color: #92cf38;
192 font-style: italic;
193 }
193 }
194
194
195 .spoiler {
195 .spoiler {
196 background: white;
196 background: white;
197 color: white;
197 color: white;
198 }
198 }
199
199
200 .spoiler:hover {
200 .spoiler:hover {
201 background: black;
201 color: black;
202 }
202 }
203
203
204 .comment {
204 .comment {
205 color: #eb2;
205 color: #eb2;
206 font-style: italic;
206 font-style: italic;
207 }
207 }
208
208
209 a:hover {
209 a:hover {
210 text-decoration: underline;
210 text-decoration: underline;
211 }
211 }
212
212
213 .last-replies {
213 .last-replies {
214 margin-left: 3ex;
214 margin-left: 3ex;
215 }
215 }
216
216
217 .thread {
217 .thread {
218 margin-bottom: 3ex;
218 margin-bottom: 3ex;
219 }
219 }
220
220
221 .post:target {
221 .post:target {
222 border: solid 2px white;
222 border: solid 2px white;
223 }
223 }
224
224
225 pre{
225 pre{
226 white-space:pre-wrap
226 white-space:pre-wrap
227 }
227 }
228
228
229 li {
229 li {
230 list-style-position: inside;
230 list-style-position: inside;
231 }
231 }
232
232
233 .fancybox-skin {
233 .fancybox-skin {
234 position: relative;
234 position: relative;
235 background-color: #fff;
235 background-color: #fff;
236 color: #ddd;
236 color: #ddd;
237 text-shadow: none;
237 text-shadow: none;
238 }
238 }
239
239
240 .fancybox-image {
240 .fancybox-image {
241 border: 1px solid black;
241 border: 1px solid black;
242 }
242 }
243
243
244 .image-mode-tab {
244 .image-mode-tab {
245 background: #444;
245 background: #444;
246 color: #eee;
246 color: #eee;
247 display: table;
247 display: table;
248 margin: 5px;
248 margin: 5px;
249 padding: 5px;
249 padding: 5px;
250 border-radius: 5px;
250 border: 1px solid #888;
251 }
251 }
252
252
253 .image-mode-tab > label {
253 .image-mode-tab > label {
254 margin: 0 1ex;
254 margin: 0 1ex;
255 }
255 }
256
256
257 .image-mode-tab > label > input {
257 .image-mode-tab > label > input {
258 margin-right: .5ex;
258 margin-right: .5ex;
259 }
259 }
260
260
261 #posts-table {
261 #posts-table {
262 margin: 5px;
262 margin: 5px;
263 }
263 }
264
264
265 .tag_info {
265 .tag_info {
266 display: table;
266 display: table;
267 }
267 }
268
268
269 .tag_info > h2 {
269 .tag_info > h2 {
270 margin: 0;
270 margin: 0;
271 }
271 }
272
272
273 .post-info {
273 .post-info {
274 color: #ddd;
274 color: #ddd;
275 }
275 }
276
276
277 .moderator_info {
277 .moderator_info {
278 color: #e99d41;
278 color: #e99d41;
279 float: right;
279 float: right;
280 } No newline at end of file
280 }
281
282 .refmap {
283 font-size: 0.9em;
284 color: #ccc;
285 margin-top: 1em;
286 }
@@ -1,269 +1,275 b''
1 * {
1 * {
2 font-size: inherit;
2 font-size: inherit;
3 margin: 0;
3 margin: 0;
4 padding: 0;
4 padding: 0;
5 }
5 }
6 html {
6 html {
7 background: #fff;
7 background: #fff;
8 color: #000;
8 color: #000;
9 font: medium sans-serif;
9 font: medium sans-serif;
10 }
10 }
11 a {
11 a {
12 color: inherit;
12 color: inherit;
13 text-decoration: underline;
13 text-decoration: underline;
14 }
14 }
15 li {
15 li {
16 list-style-position: inside;
16 list-style-position: inside;
17 }
17 }
18
18
19 #admin_panel {
19 #admin_panel {
20 background: #182F6F;
20 background: #182F6F;
21 color: #fff;
21 color: #fff;
22 padding: .5ex 1ex .5ex 1ex;
22 padding: .5ex 1ex .5ex 1ex;
23 }
23 }
24
24
25 .navigation_panel {
25 .navigation_panel {
26 background: #182F6F;
26 background: #182F6F;
27 color: #B4CFEC;
27 color: #B4CFEC;
28 margin-bottom: 1em;
28 margin-bottom: 1em;
29 padding: .5ex 1ex 1ex 1ex;
29 padding: .5ex 1ex 1ex 1ex;
30 }
30 }
31 .navigation_panel::after {
31 .navigation_panel::after {
32 clear: both;
32 clear: both;
33 content: ".";
33 content: ".";
34 display: block;
34 display: block;
35 height: 0;
35 height: 0;
36 line-height: 0;
36 line-height: 0;
37 visibility: hidden;
37 visibility: hidden;
38 }
38 }
39
39
40 .navigation_panel a:link, .navigation_panel a:visited, .navigation_panel a:hover {
40 .navigation_panel a:link, .navigation_panel a:visited, .navigation_panel a:hover {
41 text-decoration: none;
41 text-decoration: none;
42 }
42 }
43
43
44 .navigation_panel .link {
44 .navigation_panel .link {
45 border-right: 1px solid #fff;
45 border-right: 1px solid #fff;
46 color: #fff;
46 color: #fff;
47 font-weight: bold;
47 font-weight: bold;
48 margin-right: 1ex;
48 margin-right: 1ex;
49 padding-right: 1ex;
49 padding-right: 1ex;
50 }
50 }
51 .navigation_panel .link:last-child {
51 .navigation_panel .link:last-child {
52 border-left: 1px solid #fff;
52 border-left: 1px solid #fff;
53 border-right: none;
53 border-right: none;
54 float: right;
54 float: right;
55 margin-left: 1ex;
55 margin-left: 1ex;
56 margin-right: 0;
56 margin-right: 0;
57 padding-left: 1ex;
57 padding-left: 1ex;
58 padding-right: 0;
58 padding-right: 0;
59 }
59 }
60
60
61 .navigation_panel .tag {
61 .navigation_panel .tag {
62 color: #fff;
62 color: #fff;
63 }
63 }
64
64
65 .input_field {
65 .input_field {
66
66
67 }
67 }
68
68
69 .input_field_name {
69 .input_field_name {
70
70
71 }
71 }
72
72
73 .input_field_error {
73 .input_field_error {
74 color: #FF0000;
74 color: #FF0000;
75 }
75 }
76
76
77
77
78 .title {
78 .title {
79 color: #182F6F;
79 color: #182F6F;
80 font-weight: bold;
80 font-weight: bold;
81 }
81 }
82
82
83 .post-form-w {
83 .post-form-w {
84 background: #182F6F;
84 background: #182F6F;
85 border-radius: 1ex;
85 border-radius: 1ex;
86 color: #fff;
86 color: #fff;
87 margin: 1em 1ex;
87 margin: 1em 1ex;
88 padding: 1ex;
88 padding: 1ex;
89 }
89 }
90 .post-form {
90 .post-form {
91 display: table;
91 display: table;
92 border-collapse: collapse;
92 border-collapse: collapse;
93 width: 100%;
93 width: 100%;
94
94
95 }
95 }
96 .form-row {
96 .form-row {
97 display: table-row;
97 display: table-row;
98 }
98 }
99 .form-label, .form-input {
99 .form-label, .form-input {
100 display: table-cell;
100 display: table-cell;
101 vertical-align: top;
101 vertical-align: top;
102 }
102 }
103 .form-label {
103 .form-label {
104 padding: .25em 1ex .25em 0;
104 padding: .25em 1ex .25em 0;
105 }
105 }
106 .form-input {
106 .form-input {
107 padding: .25em 0;
107 padding: .25em 0;
108 }
108 }
109 .form-input > * {
109 .form-input > * {
110 background: #fff;
110 background: #fff;
111 color: #000;
111 color: #000;
112 border: none;
112 border: none;
113 padding: 0;
113 padding: 0;
114 resize: vertical;
114 resize: vertical;
115 width: 100%;
115 width: 100%;
116 }
116 }
117 .form-submit {
117 .form-submit {
118 border-bottom: 1px solid #666;
118 border-bottom: 1px solid #666;
119 margin-bottom: .5em;
119 margin-bottom: .5em;
120 padding-bottom: .5em;
120 padding-bottom: .5em;
121 }
121 }
122 .form-title {
122 .form-title {
123 font-weight: bold;
123 font-weight: bold;
124 margin-bottom: .5em;
124 margin-bottom: .5em;
125 }
125 }
126 .post-form .settings_item {
126 .post-form .settings_item {
127 margin: .5em 0;
127 margin: .5em 0;
128 }
128 }
129 .form-submit input {
129 .form-submit input {
130 margin-top: .5em;
130 margin-top: .5em;
131 padding: .2em 1ex;
131 padding: .2em 1ex;
132 }
132 }
133 .form-label {
133 .form-label {
134 text-align: right;
134 text-align: right;
135 }
135 }
136
136
137 .block {
137 .block {
138 display: inline-block;
138 display: inline-block;
139 vertical-align: top;
139 vertical-align: top;
140 }
140 }
141
141
142 .post_id {
142 .post_id {
143 color: #a00;
143 color: #a00;
144 }
144 }
145
145
146 .post {
146 .post {
147 clear: left;
147 clear: left;
148 margin: 0 1ex 1em 1ex;
148 margin: 0 1ex 1em 1ex;
149 overflow-x: auto;
149 overflow-x: auto;
150 word-wrap: break-word;
150 word-wrap: break-word;
151 }
151 }
152 .last-replies > .post, #posts > .post {
152 .last-replies > .post, #posts > .post {
153 border-bottom: 1px solid #182F6F;
153 border-bottom: 1px solid #182F6F;
154 padding-bottom: 1em;
154 padding-bottom: 1em;
155 }
155 }
156 #posts > .post:last-child {
156 #posts > .post:last-child {
157 border-bottom: none;
157 border-bottom: none;
158 padding-bottom: 0;
158 padding-bottom: 0;
159 }
159 }
160
160
161 .metadata {
161 .metadata {
162 background: #C0E4E8;
162 background: #C0E4E8;
163 border: 1px solid #7F9699;
163 border: 1px solid #7F9699;
164 border-radius: .4ex;
164 border-radius: .4ex;
165 display: table;
165 display: table;
166 margin-top: .5em;
166 margin-top: .5em;
167 padding: .4em;
167 padding: .4em;
168 }
168 }
169
169
170 .post ul, .post ol {
170 .post ul, .post ol {
171 margin: .5em 0 .5em 3ex;
171 margin: .5em 0 .5em 3ex;
172 }
172 }
173 .post li {
173 .post li {
174 margin: .2em 0;
174 margin: .2em 0;
175 }
175 }
176 .post p {
176 .post p {
177 margin: .5em 0;
177 margin: .5em 0;
178 }
178 }
179 .post blockquote {
179 .post blockquote {
180 border-left: 3px solid #182F6F;
180 border-left: 3px solid #182F6F;
181 margin: .5em 0 .5em 3ex;
181 margin: .5em 0 .5em 3ex;
182 padding-left: 1ex;
182 padding-left: 1ex;
183 }
183 }
184 .post blockquote > blockquote {
184 .post blockquote > blockquote {
185 padding-top: .1em;
185 padding-top: .1em;
186 }
186 }
187
187
188 .post > .image {
188 .post > .image {
189 float: left;
189 float: left;
190 margin-right: 1ex;
190 margin-right: 1ex;
191 }
191 }
192 .post > .metadata {
192 .post > .metadata {
193 clear: left;
193 clear: left;
194 }
194 }
195
195
196 .post > .message .get {
196 .post > .message .get {
197 color: #182F6F; font-weight: bold;
197 color: #182F6F; font-weight: bold;
198 }
198 }
199
199
200 .dead_post > .metadata {
200 .dead_post > .metadata {
201 background: #eee;
201 background: #eee;
202 }
202 }
203
203
204 .quote {
204 .quote {
205 color: #182F6F;
205 color: #182F6F;
206 }
206 }
207
207
208 .spoiler {
208 .spoiler {
209 background: black;
209 background: black;
210 color: black;
210 color: black;
211 }
211 }
212
212
213 .spoiler:hover {
213 .spoiler:hover {
214 background: #ffffff;
214 background: #ffffff;
215 }
215 }
216
216
217 .comment {
217 .comment {
218 color: #557055;
218 color: #557055;
219 }
219 }
220
220
221 .last-replies {
221 .last-replies {
222 margin-left: 6ex;
222 margin-left: 6ex;
223 }
223 }
224
224
225 .thread > .post > .message > .post-info {
225 .thread > .post > .message > .post-info {
226 border-bottom: 2px solid #182F6F;
226 border-bottom: 2px solid #182F6F;
227 padding-bottom: .5em;
227 padding-bottom: .5em;
228 }
228 }
229
229
230 .last-replies > .post:last-child {
230 .last-replies > .post:last-child {
231 border-bottom: none;
231 border-bottom: none;
232 padding-bottom: 0;
232 padding-bottom: 0;
233 }
233 }
234
234
235 :target .post_id {
235 :target .post_id {
236 background: #182F6F;
236 background: #182F6F;
237 color: #FFF;
237 color: #FFF;
238 text-decoration: none;
238 text-decoration: none;
239 }
239 }
240
240
241 .image-mode-tab {
241 .image-mode-tab {
242 background: #182F6F;
242 background: #182F6F;
243 color: #FFF;
243 color: #FFF;
244 display: table;
244 display: table;
245 margin: 1em auto 1em 0;
245 margin: 1em auto 1em 0;
246 padding: .2em .5ex;
246 padding: .2em .5ex;
247 }
247 }
248
248
249 .image-mode-tab > label {
249 .image-mode-tab > label {
250 margin: 0 1ex;
250 margin: 0 1ex;
251 }
251 }
252
252
253 .image-mode-tab > label > input {
253 .image-mode-tab > label > input {
254 margin-right: .5ex;
254 margin-right: .5ex;
255 }
255 }
256
256
257 .tag_info {
257 .tag_info {
258 margin: 1em 0;
258 margin: 1em 0;
259 text-align: center;
259 text-align: center;
260 }
260 }
261
261
262 .form-errors {
262 .form-errors {
263 margin-left: 1ex;
263 margin-left: 1ex;
264 }
264 }
265
265
266 .moderator_info {
266 .moderator_info {
267 font-weight: bold;
267 font-weight: bold;
268 float: right;
268 float: right;
269 } No newline at end of file
269 }
270
271 .refmap {
272 border: 1px dashed #aaa;
273 padding: 0.5em;
274 display: table;
275 }
@@ -1,14 +1,16 b''
1 $( document ).ready(function() {
1 $( document ).ready(function() {
2 $("a[href='#top']").click(function() {
2 $("a[href='#top']").click(function() {
3 $("html, body").animate({ scrollTop: 0 }, "slow");
3 $("html, body").animate({ scrollTop: 0 }, "slow");
4 return false;
4 return false;
5 });
5 });
6
6
7 $(".fancy").fancybox({
7 $(".fancy").fancybox({
8 closeBtn: false,
8 closeBtn: false,
9 closeClick: true,
9 closeClick: true,
10 padding: 7,
10 padding: 0,
11 openEffect: 'none',
11 openEffect: 'none',
12 closeEffect: 'none'
12 closeEffect: 'none'
13 });
13 });
14 }) No newline at end of file
14
15 addRefLinkMap();
16 })
@@ -1,80 +1,103 b''
1 var image_mode = 0;
1 var image_mode = 0;
2 var normal_dom, table_dom;
2 var normal_dom, table_dom;
3
3
4 function add_panel(after)
4 function add_panel(after)
5 {
5 {
6 var nav_top = $(after);
6 var nav_top = $(after);
7 if (nav_top.length === 0) return;
7 if (nav_top.length === 0) return;
8 nav_top = nav_top[0];
8 nav_top = nav_top[0];
9
9
10 var tab_bar = $('<div class="image-mode-tab" role="radiogroup" aria-label="Image mode"></div>');
10 var tab_bar = $('<div class="image-mode-tab" role="radiogroup" aria-label="Image mode"></div>');
11
11
12 var tab;
12 var tab;
13
13
14 tab = $('<input type="radio" class="image-mode-normal" name="image-mode" value="0" checked="checked"/>');
14 tab = $('<input type="radio" class="image-mode-normal" name="image-mode" value="0" checked="checked"/>');
15 tab.on("change", tab_handler);
15 tab.on("change", tab_handler);
16 tab = $('<label>Normal</label>').prepend(tab);
16
17 var label_normal = gettext('Normal');
18 tab = $('<label>' + label_normal + '</label>').prepend(tab);
17 tab_bar.append(tab);
19 tab_bar.append(tab);
18
20
19 tab = $('<input type="radio" class="image-mode-table" name="image-mode" value="1"/>');
21 tab = $('<input type="radio" class="image-mode-table" name="image-mode" value="1"/>');
20 tab.on("change", tab_handler);
22 tab.on("change", tab_handler);
21 tab = $('<label>Gallery</label>').prepend(tab);
23
24 var label_gallery = gettext('Gallery');
25 tab = $('<label>' + label_gallery + '</label>').prepend(tab);
22 tab_bar.append(tab);
26 tab_bar.append(tab);
23
27
24 tab_bar.insertAfter(nav_top);
28 tab_bar.insertAfter(nav_top);
25 }
29 }
26
30
27 function tab_handler(ev)
31 function tab_handler(ev)
28 {
32 {
29 var current_el = $(this);
33 var current_el = $(this);
30
34
31 if (!current_el.prop('checked')) return;
35 if (!current_el.prop('checked')) return;
32
36
33 var new_mode = parseInt(current_el.val(), 10);
37 var new_mode = parseInt(current_el.val(), 10);
34 if (new_mode === image_mode) return;
38 if (new_mode === image_mode) return;
35 image_mode = new_mode;
39 image_mode = new_mode;
36
40
37 make_normal_dom();
41 make_normal_dom();
38 make_table_dom();
42 make_table_dom();
39
43
40 switch(new_mode) {
44 switch(new_mode) {
41 case 0:
45 case 0:
42 $('#posts-table').replaceWith(normal_dom);
46 $('#posts-table').replaceWith(normal_dom);
43 break;
47 break;
44 case 1:
48 case 1:
45 $('#posts').replaceWith(table_dom);
49 $('#posts').replaceWith(table_dom);
46 break;
50 break;
47 }
51 }
48 }
52 }
49
53
50 function make_normal_dom()
54 function make_normal_dom()
51 {
55 {
52 if (typeof normal_dom === 'undefined') {
56 if (typeof normal_dom === 'undefined') {
53 normal_dom = $('#posts').clone(true);
57 normal_dom = $('#posts').clone(true);
54 }
58 }
55 }
59 }
56
60
57 function make_table_dom()
61 function make_table_dom()
58 {
62 {
59 if (typeof table_dom !== 'undefined') return;
63 if (typeof table_dom !== 'undefined') return;
60
64
61 table_dom = $('<div id="posts-table"></div>');
65 table_dom = $('<div id="posts-table"></div>');
62 $('#posts > .post > .image > a').each(
66 $('#posts > .post > .image > a').each(
63 function(){
67 function(){
64 table_dom.append(
68 table_dom.append(
65 $(this).clone().attr('target', '_blank')
69 $(this).clone().attr('target', '_blank')
66 );
70 );
67 }
71 }
68 );
72 );
69 }
73 }
70
74
75 function moveCaretToEnd(el) {
76 if (typeof el.selectionStart == "number") {
77 el.selectionStart = el.selectionEnd = el.value.length;
78 } else if (typeof el.createTextRange != "undefined") {
79 el.focus();
80 var range = el.createTextRange();
81 range.collapse(false);
82 range.select();
83 }
84 }
85
71 function addQuickReply(postId) {
86 function addQuickReply(postId) {
72 var textToAdd = '>>' + postId + '\n\n';
87 var textToAdd = '>>' + postId + '\n\n';
73 $('#id_text').val($('#id_text').val()+ textToAdd);
88 var textAreaId = '#id_text';
89 $(textAreaId).val($(textAreaId).val()+ textToAdd);
74
90
75 $("html, body").animate({ scrollTop: $('#id_text').offset().top }, "slow");
91 var textarea = document.getElementById('id_text');
92 $(textAreaId).focus();
93 moveCaretToEnd(textarea);
94
95 $("html, body").animate({ scrollTop: $(textAreaId).offset().top }, "slow");
76 }
96 }
77
97
98
99
78 $(document).ready(function(){
100 $(document).ready(function(){
79 add_panel('.navigation_panel');
101 add_panel('.navigation_panel');
80 }); No newline at end of file
102 addRefLinkMap();
103 });
@@ -1,30 +1,14 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4
4
5 {% block head %}
5 {% block head %}
6 <title>{% trans "Authors" %}</title>
6 <title>{% trans "Not found" %}</title>
7 {% endblock %}
7 {% endblock %}
8
8
9 {% block content %}
9 {% block content %}
10 <div class="post">
10 <div class="post">
11 <h2>Authors</h2>
11 <p><img src="{{ STATIC_URL }}images/404.png" width="200" /></p>
12 {% for nick, values in authors.items %}
12 <p>{% trans 'This page does not exist' %}</p>
13 <p>
14 <b>{{ nick }}</b> ({{ values.name }}):
15 {% for value in values.contacts %}
16 <a href="mailto:{{ value }}">{{ value }}</a>
17 {% endfor %} -
18 {% for role in values.roles %}
19 {% trans role %}
20 {% endfor %}
21 </p>
22 {% endfor %}
23 <br />
24 <p>{% trans "Distributed under the" %}
25 <a href="http://www.gnu.org/licenses/gpl.html" >GNU GPLv3</a>
26 {% trans "license" %}</p>
27 <p><a href="https://bitbucket.org/neko259/neboard">
28 {% trans "Repository" %}</a></p>
29 </div>
13 </div>
30 {% endblock %} No newline at end of file
14 {% endblock %}
@@ -1,30 +1,13 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4
4
5 {% block head %}
5 {% block head %}
6 <title>{% trans "Authors" %}</title>
6 <title>{% trans "Banned" %}</title>
7 {% endblock %}
7 {% endblock %}
8
8
9 {% block content %}
9 {% block content %}
10 <div class="post">
10 <div class="post">
11 <h2>Authors</h2>
11 {% trans 'Your IP address has been banned. Contact the administrator' %}
12 {% for nick, values in authors.items %}
13 <p>
14 <b>{{ nick }}</b> ({{ values.name }}):
15 {% for value in values.contacts %}
16 <a href="mailto:{{ value }}">{{ value }}</a>
17 {% endfor %} -
18 {% for role in values.roles %}
19 {% trans role %}
20 {% endfor %}
21 </p>
22 {% endfor %}
23 <br />
24 <p>{% trans "Distributed under the" %}
25 <a href="http://www.gnu.org/licenses/gpl.html" >GNU GPLv3</a>
26 {% trans "license" %}</p>
27 <p><a href="https://bitbucket.org/neko259/neboard">
28 {% trans "Repository" %}</a></p>
29 </div>
12 </div>
30 {% endblock %} No newline at end of file
13 {% endblock %}
@@ -1,45 +1,47 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3
3
4 <!DOCTYPE html>
4 <!DOCTYPE html>
5 <html>
5 <html>
6 <head>
6 <head>
7 <link rel="stylesheet" type="text/css"
7 <link rel="stylesheet" type="text/css"
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
9 <link rel="stylesheet" type="text/css"
9 <link rel="stylesheet" type="text/css"
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
11 <link rel="alternate" type="application/rss+xml" href="rss/" title="
11 <link rel="alternate" type="application/rss+xml" href="rss/" title="
12 {% trans 'Feed' %}"/>
12 {% trans 'Feed' %}"/>
13
13
14 <link rel="icon" type="image/png"
14 <link rel="icon" type="image/png"
15 href="{{ STATIC_URL }}favicon.png">
15 href="{{ STATIC_URL }}favicon.png">
16
16
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
18 <meta charset="utf-8"/>
18 <meta charset="utf-8"/>
19 {% block head %}{% endblock %}
19 {% block head %}{% endblock %}
20 </head>
20 </head>
21 <body>
21 <body>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
24 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
25 <script src="{{ STATIC_URL }}js/refmaps.js"></script>
24 <script src="{{ STATIC_URL }}js/main.js"></script>
26 <script src="{{ STATIC_URL }}js/main.js"></script>
25
27
26 <div class="navigation_panel">
28 <div class="navigation_panel">
27 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
28 {% for tag in tags %}
30 {% for tag in tags %}
29 <a class="tag" href="{% url 'tag' tag_name=tag.name %}">
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}">
30 {{ tag.name }}</a>({{ tag.get_post_count }})
32 {{ tag.name }}</a>({{ tag.get_post_count }})
31 {% endfor %}
33 {% endfor %}
32 <a class="tag" href="{% url 'tags' %}">[...]</a>
34 <a class="tag" href="{% url 'tags' %}">[...]</a>
33 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
35 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
34 </div>
36 </div>
35
37
36 {% block content %}{% endblock %}
38 {% block content %}{% endblock %}
37
39
38 <div class="navigation_panel">
40 <div class="navigation_panel">
39 {% block metapanel %}{% endblock %}
41 {% block metapanel %}{% endblock %}
40 [<a href="rss/">RSS</a>]
42 [<a href="rss/">RSS</a>]
41 <a class="link" href="#top">{% trans 'Up' %}</a>
43 <a class="link" href="#top">{% trans 'Up' %}</a>
42 </div>
44 </div>
43
45
44 </body>
46 </body>
45 </html> No newline at end of file
47 </html>
@@ -1,172 +1,176 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 {% if tag %}
7 {% if tag %}
8 <title>Neboard - {{ tag }}</title>
8 <title>Neboard - {{ tag }}</title>
9 {% else %}
9 {% else %}
10 <title>Neboard</title>
10 <title>Neboard</title>
11 {% endif %}
11 {% endif %}
12 {% endblock %}
12 {% endblock %}
13
13
14 {% block content %}
14 {% block content %}
15
15
16 {% if tag %}
16 {% if tag %}
17 <div class="tag_info">
17 <div class="tag_info">
18 <h2>{% trans 'Tag: ' %}{{ tag }}</h2>
18 <h2>{% trans 'Tag: ' %}{{ tag }}</h2>
19 </div>
19 </div>
20 {% endif %}
20 {% endif %}
21
21
22 {% if threads %}
22 {% if threads %}
23 {% for thread in threads %}
23 {% for thread in threads %}
24 <div class="thread">
24 <div class="thread">
25 {% if thread.can_bump %}
25 {% if thread.can_bump %}
26 <div class="post">
26 <div class="post" id="{{thread.id}}">
27 {% else %}
27 {% else %}
28 <div class="post dead_post">
28 <div class="post dead_post" id="{{ thread.id }}">
29 {% endif %}
29 {% endif %}
30 {% if thread.image %}
30 {% if thread.image %}
31 <div class="image">
31 <div class="image">
32 <a class="fancy"
32 <a class="fancy"
33 href="{{ thread.image.url }}"><img
33 href="{{ thread.image.url }}"><img
34 src="{{ thread.image.url_200x150 }}"
34 src="{{ thread.image.url_200x150 }}"
35 alt="{% trans 'Post image' %}" />
35 alt="{% trans 'Post image' %}"
36 data-width="{{ thread.image_width }}"
37 data-height="{{ thread.image_height }}" />
36 </a>
38 </a>
37 </div>
39 </div>
38 {% endif %}
40 {% endif %}
39 <div class="message">
41 <div class="message">
40 <div class="post-info">
42 <div class="post-info">
41 <span class="title">{{ thread.title }}</span>
43 <span class="title">{{ thread.title }}</span>
42 <a class="post_id" href="{% url 'thread' thread.id %}">
44 <a class="post_id" href="{% url 'thread' thread.id %}"
43 (#{{ thread.id }})</a>
45 >(#{{ thread.id }})</a>
44 [{{ thread.pub_time }}]
46 [{{ thread.pub_time }}]
45 [<a class="link" href="{% url 'thread' thread.id %}#form"
47 [<a class="link" href="{% url 'thread' thread.id %}#form"
46 >{% trans "Reply" %}</a>]
48 >{% trans "Reply" %}</a>]
47
49
48 {% if user.is_moderator %}
50 {% if user.is_moderator %}
49 <span class="moderator_info">
51 <span class="moderator_info">
50 ({{ thread.poster_ip }})
52 ({{ thread.poster_ip }})
51 [<a href="{% url 'delete' post_id=thread.id %}"
53 [<a href="{% url 'delete' post_id=thread.id %}"
52 >{% trans 'Delete' %}</a>]
54 >{% trans 'Delete' %}</a>]
53 </span>
55 </span>
54 {% endif %}
56 {% endif %}
55 </div>
57 </div>
56 {% autoescape off %}
58 {% autoescape off %}
57 {{ thread.text.rendered|truncatewords_html:50 }}
59 {{ thread.text.rendered|truncatewords_html:50 }}
58 {% endautoescape %}
60 {% endautoescape %}
59 </div>
61 </div>
60 <div class="metadata">
62 <div class="metadata">
61 {{ thread.get_reply_count }} {% trans 'replies' %},
63 {{ thread.get_reply_count }} {% trans 'replies' %},
62 {{ thread.get_images_count }} {% trans 'images' %}.
64 {{ thread.get_images_count }} {% trans 'images' %}.
63 {% if thread.tags.all %}
65 {% if thread.tags.all %}
64 <span class="tags">{% trans 'Tags' %}:
66 <span class="tags">{% trans 'Tags' %}:
65 {% for tag in thread.tags.all %}
67 {% for tag in thread.tags.all %}
66 <a class="tag" href="
68 <a class="tag" href="
67 {% url 'tag' tag_name=tag.name %}">
69 {% url 'tag' tag_name=tag.name %}">
68 {{ tag.name }}</a>
70 {{ tag.name }}</a>
69 {% endfor %}
71 {% endfor %}
70 </span>
72 </span>
71 {% endif %}
73 {% endif %}
72 </div>
74 </div>
73 </div>
75 </div>
74 {% if thread.get_last_replies %}
76 {% if thread.get_last_replies %}
75 <div class="last-replies">
77 <div class="last-replies">
76 {% for post in thread.get_last_replies %}
78 {% for post in thread.get_last_replies %}
77 {% if thread.can_bump %}
79 {% if thread.can_bump %}
78 <div class="post">
80 <div class="post" id="{{ post.id }}">
79 {% else %}
81 {% else %}
80 <div class="post dead_post">
82 <div class="post dead_post id="{{ post.id }}"">
81 {% endif %}
83 {% endif %}
82 {% if post.image %}
84 {% if post.image %}
83 <div class="image">
85 <div class="image">
84 <a class="fancy"
86 <a class="fancy"
85 href="{{ post.image.url }}"><img
87 href="{{ post.image.url }}"><img
86 src=" {{ post.image.url_200x150 }}"
88 src=" {{ post.image.url_200x150 }}"
87 alt="{% trans 'Post image' %}" />
89 alt="{% trans 'Post image' %}"
90 data-width="{{ post.image_width }}"
91 data-height="{{ post.image_height }}"/>
88 </a>
92 </a>
89 </div>
93 </div>
90 {% endif %}
94 {% endif %}
91 <div class="message">
95 <div class="message">
92 <div class="post-info">
96 <div class="post-info">
93 <span class="title">{{ post.title }}</span>
97 <span class="title">{{ post.title }}</span>
94 <a class="post_id" href="
98 <a class="post_id" href="
95 {% url 'thread' thread.id %}#{{ post.id }}">
99 {% url 'thread' thread.id %}#{{ post.id }}">
96 (#{{ post.id }})</a>
100 (#{{ post.id }})</a>
97 [{{ post.pub_time }}]
101 [{{ post.pub_time }}]
98 </div>
102 </div>
99 {% autoescape off %}
103 {% autoescape off %}
100 {{ post.text.rendered|truncatewords_html:50 }}
104 {{ post.text.rendered|truncatewords_html:50 }}
101 {% endautoescape %}
105 {% endautoescape %}
102 </div>
106 </div>
103 </div>
107 </div>
104 {% endfor %}
108 {% endfor %}
105 </div>
109 </div>
106 {% endif %}
110 {% endif %}
107 </div>
111 </div>
108 {% endfor %}
112 {% endfor %}
109 {% else %}
113 {% else %}
110 No threads found.
114 No threads found.
111 <hr />
115 <hr />
112 {% endif %}
116 {% endif %}
113
117
114 <form enctype="multipart/form-data" method="post">{% csrf_token %}
118 <form enctype="multipart/form-data" method="post">{% csrf_token %}
115 <div class="post-form-w">
119 <div class="post-form-w">
116
120
117 <div class="form-title">{% trans "Create new thread" %}</div>
121 <div class="form-title">{% trans "Create new thread" %}</div>
118 <div class="post-form">
122 <div class="post-form">
119 <div class="form-row">
123 <div class="form-row">
120 <div class="form-label">{% trans 'Title' %}</div>
124 <div class="form-label">{% trans 'Title' %}</div>
121 <div class="form-input">{{ form.title }}</div>
125 <div class="form-input">{{ form.title }}</div>
122 <div class="form-errors">{{ form.title.errors }}</div>
126 <div class="form-errors">{{ form.title.errors }}</div>
123 </div>
127 </div>
124 <div class="form-row">
128 <div class="form-row">
125 <div class="form-label">{% trans 'Text' %}</div>
129 <div class="form-label">{% trans 'Text' %}</div>
126 <div class="form-input">{{ form.text }}</div>
130 <div class="form-input">{{ form.text }}</div>
127 <div class="form-errors">{{ form.text.errors }}</div>
131 <div class="form-errors">{{ form.text.errors }}</div>
128 </div>
132 </div>
129 <div class="form-row">
133 <div class="form-row">
130 <div class="form-label">{% trans 'Image' %}</div>
134 <div class="form-label">{% trans 'Image' %}</div>
131 <div class="form-input">{{ form.image }}</div>
135 <div class="form-input">{{ form.image }}</div>
132 <div class="form-errors">{{ form.image.errors }}</div>
136 <div class="form-errors">{{ form.image.errors }}</div>
133 </div>
137 </div>
134 <div class="form-row">
138 <div class="form-row">
135 <div class="form-label">{% trans 'Tags' %}</div>
139 <div class="form-label">{% trans 'Tags' %}</div>
136 <div class="form-input">{{ form.tags }}</div>
140 <div class="form-input">{{ form.tags }}</div>
137 <div class="form-errors">{{ form.tags.errors }}</div>
141 <div class="form-errors">{{ form.tags.errors }}</div>
138 </div>
142 </div>
139 <div class="form-row">
143 <div class="form-row">
140 {{ form.captcha }}
144 {{ form.captcha }}
141 <div class="form-errors">{{ form.captcha.errors }}</div>
145 <div class="form-errors">{{ form.captcha.errors }}</div>
142 </div>
146 </div>
143 </div>
147 </div>
144 <div class="form-submit">
148 <div class="form-submit">
145 <input type="submit" value="{% trans "Post" %}"/></div>
149 <input type="submit" value="{% trans "Post" %}"/></div>
146 <div>
150 <div>
147 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
151 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
148 </div>
152 </div>
149 <div><a href="http://daringfireball.net/projects/markdown/basics">
153 <div><a href="http://daringfireball.net/projects/markdown/basics">
150 {% trans 'Basic markdown syntax.' %}</a></div>
154 {% trans 'Basic markdown syntax.' %}</a></div>
151 </div>
155 </div>
152 </form>
156 </form>
153
157
154 {% endblock %}
158 {% endblock %}
155
159
156 {% block metapanel %}
160 {% block metapanel %}
157
161
158 <span class="metapanel">
162 <span class="metapanel">
159 <b><a href="{% url "authors" %}">Neboard</a> pre1.0</b>
163 <b><a href="{% url "authors" %}">Neboard</a> pre1.0</b>
160 {% trans "Pages:" %}
164 {% trans "Pages:" %}
161 {% for page in pages %}
165 {% for page in pages %}
162 [<a href="
166 [<a href="
163 {% if tag %}
167 {% if tag %}
164 {% url "tag" tag_name=tag page=page %}
168 {% url "tag" tag_name=tag page=page %}
165 {% else %}
169 {% else %}
166 {% url "index" page=page %}
170 {% url "index" page=page %}
167 {% endif %}
171 {% endif %}
168 ">{{ page }}</a>]
172 ">{{ page }}</a>]
169 {% endfor %}
173 {% endfor %}
170 </span>
174 </span>
171
175
172 {% endblock %}
176 {% endblock %}
@@ -1,124 +1,121 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 <title>Neboard - {{ posts.0.title }}</title>
7 <title>Neboard - {{ posts.0.title }}</title>
8 {% endblock %}
8 {% endblock %}
9
9
10 {% block content %}
10 {% block content %}
11 <script src="{{ STATIC_URL }}js/thread.js"></script>
11 <script src="{{ STATIC_URL }}js/thread.js"></script>
12
12
13 {% if posts %}
13 {% if posts %}
14 <div id="posts">
14 <div id="posts">
15 {% for post in posts %}
15 {% for post in posts %}
16 {% if posts.0.can_bump %}
16 {% if posts.0.can_bump %}
17 <div class="post" id="{{ post.id }}">
17 <div class="post" id="{{ post.id }}">
18 {% else %}
18 {% else %}
19 <div class="post dead_post" id="{{ post.id }}">
19 <div class="post dead_post" id="{{ post.id }}">
20 {% endif %}
20 {% endif %}
21 {% if post.image %}
21 {% if post.image %}
22 <div class="image">
22 <div class="image">
23 <a
23 <a
24 class="fancy"
24 class="fancy"
25 href="{{ post.image.url }}"><img
25 href="{{ post.image.url }}"><img
26 src="{{ post.image.url_200x150 }}"
26 src="{{ post.image.url_200x150 }}"
27 alt="{% trans 'Post image' %}" />
27 alt="{% trans 'Post image' %}"
28 data-width="{{ post.image_width }}"
29 data-height="{{ post.image_height }}"/>
28 </a>
30 </a>
29 </div>
31 </div>
30 {% endif %}
32 {% endif %}
31 <div class="message">
33 <div class="message">
32 <div class="post-info">
34 <div class="post-info">
33 <span class="title">{{ post.title }}</span>
35 <span class="title">{{ post.title }}</span>
34 <a class="post_id" href="#{{ post.id }}">
36 <a class="post_id" href="#{{ post.id }}">
35 (#{{ post.id }})</a>
37 (#{{ post.id }})</a>
36 [{{ post.pub_time }}]
38 [{{ post.pub_time }}]
37 {% if post.is_get %}
38 <span class="get">
39 {% trans "Get!" %}
40 </span>
41 {% endif %}
42 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
39 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
43 ; return false;">&gt;&gt;</a>]
40 ; return false;">&gt;&gt;</a>]
44
41
45 {% if user.is_moderator %}
42 {% if user.is_moderator %}
46 <span class="moderator_info">
43 <span class="moderator_info">
47 ({{ post.poster_ip }})
44 ({{ post.poster_ip }})
48 [<a href="{% url 'delete' post_id=post.id %}"
45 [<a href="{% url 'delete' post_id=post.id %}"
49 >{% trans 'Delete' %}</a>]
46 >{% trans 'Delete' %}</a>]
50 </span>
47 </span>
51 {% endif %}
48 {% endif %}
52 </div>
49 </div>
53 {% autoescape off %}
50 {% autoescape off %}
54 {{ post.text.rendered }}
51 {{ post.text.rendered }}
55 {% endautoescape %}
52 {% endautoescape %}
56 </div>
53 </div>
57 {% if post.tags.all %}
54 {% if post.tags.all %}
58 <div class="metadata">
55 <div class="metadata">
59 <span class="tags">{% trans 'Tags' %}:
56 <span class="tags">{% trans 'Tags' %}:
60 {% for tag in post.tags.all %}
57 {% for tag in post.tags.all %}
61 <a class="tag" href="{% url 'tag' tag.name %}">
58 <a class="tag" href="{% url 'tag' tag.name %}">
62 {{ tag.name }}</a>
59 {{ tag.name }}</a>
63 {% endfor %}
60 {% endfor %}
64 </span>
61 </span>
65 </div>
62 </div>
66 {% endif %}
63 {% endif %}
67 </div>
64 </div>
68 {% endfor %}
65 {% endfor %}
69 </div>
66 </div>
70 {% else %}
67 {% else %}
71 No thread found.
68 No thread found.
72 <hr />
69 <hr />
73 {% endif %}
70 {% endif %}
74
71
75 <form id="form" enctype="multipart/form-data" method="post"
72 <form id="form" enctype="multipart/form-data" method="post"
76 >{% csrf_token %}
73 >{% csrf_token %}
77 <div class="post-form-w">
74 <div class="post-form-w">
78 <div class="form-title">{% trans "Reply to thread" %}</div>
75 <div class="form-title">{% trans "Reply to thread" %}</div>
79 <div class="post-form">
76 <div class="post-form">
80 <div class="form-row">
77 <div class="form-row">
81 <div class="form-label">{% trans 'Title' %}</div>
78 <div class="form-label">{% trans 'Title' %}</div>
82 <div class="form-input">{{ form.title }}</div>
79 <div class="form-input">{{ form.title }}</div>
83 <div class="form-errors">{{ form.title.errors }}</div>
80 <div class="form-errors">{{ form.title.errors }}</div>
84 </div>
81 </div>
85 <div class="form-row">
82 <div class="form-row">
86 <div class="form-label">{% trans 'Text' %}</div>
83 <div class="form-label">{% trans 'Text' %}</div>
87 <div class="form-input">{{ form.text }}</div>
84 <div class="form-input">{{ form.text }}</div>
88 <div class="form-errors">{{ form.text.errors }}</div>
85 <div class="form-errors">{{ form.text.errors }}</div>
89 </div>
86 </div>
90 <div class="form-row">
87 <div class="form-row">
91 <div class="form-label">{% trans 'Image' %}</div>
88 <div class="form-label">{% trans 'Image' %}</div>
92 <div class="form-input">{{ form.image }}</div>
89 <div class="form-input">{{ form.image }}</div>
93 <div class="form-errors">{{ form.image.errors }}</div>
90 <div class="form-errors">{{ form.image.errors }}</div>
94 </div>
91 </div>
95 <div class="form-row">
92 <div class="form-row">
96 {{ form.captcha }}
93 {{ form.captcha }}
97 <div class="form-errors">{{ form.captcha.errors }}</div>
94 <div class="form-errors">{{ form.captcha.errors }}</div>
98 </div>
95 </div>
99 </div>
96 </div>
100
97
101 <div class="form-submit"><input type="submit"
98 <div class="form-submit"><input type="submit"
102 value="{% trans "Post" %}"/></div>
99 value="{% trans "Post" %}"/></div>
103 <div><a href="http://daringfireball.net/projects/markdown/basics">
100 <div><a href="http://daringfireball.net/projects/markdown/basics">
104 {% trans 'Basic markdown syntax.' %}</a></div>
101 {% trans 'Basic markdown syntax.' %}</a></div>
105 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
102 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
106 **<b>{% trans 'bold' %}</b>**</div>
103 **<b>{% trans 'bold' %}</b>**</div>
107 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
104 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
108 <div>{% trans 'Links to answers can be inserted with' %}
105 <div>{% trans 'Links to answers can be inserted with' %}
109 "&gt;&gt;123"
106 "&gt;&gt;123"
110 </div>
107 </div>
111 </div>
108 </div>
112 </form>
109 </form>
113
110
114 {% endblock %}
111 {% endblock %}
115
112
116 {% block metapanel %}
113 {% block metapanel %}
117
114
118 <span class="metapanel">
115 <span class="metapanel">
119 {{ posts.0.get_reply_count }} {% trans 'replies' %},
116 {{ posts.0.get_reply_count }} {% trans 'replies' %},
120 {{ posts.0.get_images_count }} {% trans 'images' %}.
117 {{ posts.0.get_images_count }} {% trans 'images' %}.
121 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
118 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
122 </span>
119 </span>
123
120
124 {% endblock %} No newline at end of file
121 {% endblock %}
@@ -1,173 +1,173 b''
1 # -*- encoding: utf-8 -*-
1 # -*- encoding: utf-8 -*-
2 """
2 """
3 django-thumbs by Antonio Melé
3 django-thumbs by Antonio Melé
4 http://django.es
4 http://django.es
5 """
5 """
6 from django.db.models import ImageField
6 from django.db.models import ImageField
7 from django.db.models.fields.files import ImageFieldFile
7 from django.db.models.fields.files import ImageFieldFile
8 from PIL import Image
8 from PIL import Image
9 from django.core.files.base import ContentFile
9 from django.core.files.base import ContentFile
10 import cStringIO
10 import cStringIO
11
11
12
12
13 def generate_thumb(img, thumb_size, format):
13 def generate_thumb(img, thumb_size, format):
14 """
14 """
15 Generates a thumbnail image and returns a ContentFile object with the thumbnail
15 Generates a thumbnail image and returns a ContentFile object with the thumbnail
16
16
17 Parameters:
17 Parameters:
18 ===========
18 ===========
19 img File object
19 img File object
20
20
21 thumb_size desired thumbnail size, ie: (200,120)
21 thumb_size desired thumbnail size, ie: (200,120)
22
22
23 format format of the original image ('jpeg','gif','png',...)
23 format format of the original image ('jpeg','gif','png',...)
24 (this format will be used for the generated thumbnail, too)
24 (this format will be used for the generated thumbnail, too)
25 """
25 """
26
26
27 img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
27 img.seek(0) # see http://code.djangoproject.com/ticket/8222 for details
28 image = Image.open(img)
28 image = Image.open(img)
29
29
30 # get size
30 # get size
31 thumb_w, thumb_h = thumb_size
31 thumb_w, thumb_h = thumb_size
32 # If you want to generate a square thumbnail
32 # If you want to generate a square thumbnail
33 if thumb_w == thumb_h:
33 if thumb_w == thumb_h:
34 # quad
34 # quad
35 xsize, ysize = image.size
35 xsize, ysize = image.size
36 # get minimum size
36 # get minimum size
37 minsize = min(xsize, ysize)
37 minsize = min(xsize, ysize)
38 # largest square possible in the image
38 # largest square possible in the image
39 xnewsize = (xsize - minsize) / 2
39 xnewsize = (xsize - minsize) / 2
40 ynewsize = (ysize - minsize) / 2
40 ynewsize = (ysize - minsize) / 2
41 # crop it
41 # crop it
42 image2 = image.crop(
42 image2 = image.crop(
43 (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize))
43 (xnewsize, ynewsize, xsize - xnewsize, ysize - ynewsize))
44 # load is necessary after crop
44 # load is necessary after crop
45 image2.load()
45 image2.load()
46 # thumbnail of the cropped image (with ANTIALIAS to make it look better)
46 # thumbnail of the cropped image (with ANTIALIAS to make it look better)
47 image2.thumbnail(thumb_size, Image.ANTIALIAS)
47 image2.thumbnail(thumb_size, Image.ANTIALIAS)
48 else:
48 else:
49 # not quad
49 # not quad
50 image2 = image
50 image2 = image
51 image2.thumbnail(thumb_size, Image.ANTIALIAS)
51 image2.thumbnail(thumb_size, Image.ANTIALIAS)
52
52
53 io = cStringIO.StringIO()
53 io = cStringIO.StringIO()
54 # PNG and GIF are the same, JPG is JPEG
54 # PNG and GIF are the same, JPG is JPEG
55 if format.upper() == 'JPG':
55 if format.upper() == 'JPG':
56 format = 'JPEG'
56 format = 'JPEG'
57
57
58 image2.save(io, format)
58 image2.save(io, format)
59 return ContentFile(io.getvalue())
59 return ContentFile(io.getvalue())
60
60
61
61
62 class ImageWithThumbsFieldFile(ImageFieldFile):
62 class ImageWithThumbsFieldFile(ImageFieldFile):
63 """
63 """
64 See ImageWithThumbsField for usage example
64 See ImageWithThumbsField for usage example
65 """
65 """
66
66
67 def __init__(self, *args, **kwargs):
67 def __init__(self, *args, **kwargs):
68 super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
68 super(ImageWithThumbsFieldFile, self).__init__(*args, **kwargs)
69 self.sizes = self.field.sizes
69 self.sizes = self.field.sizes
70
70
71 if self.sizes:
71 if self.sizes:
72 def get_size(self, size):
72 def get_size(self, size):
73 if not self:
73 if not self:
74 return ''
74 return ''
75 else:
75 else:
76 split = self.url.rsplit('.', 1)
76 split = self.url.rsplit('.', 1)
77 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
77 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
78 return thumb_url
78 return thumb_url
79
79
80 for size in self.sizes:
80 for size in self.sizes:
81 (w, h) = size
81 (w, h) = size
82 setattr(self, 'url_%sx%s' % (w, h), get_size(self, size))
82 setattr(self, 'url_%sx%s' % (w, h), get_size(self, size))
83
83
84 def save(self, name, content, save=True):
84 def save(self, name, content, save=True):
85 super(ImageWithThumbsFieldFile, self).save(name, content, save)
85 super(ImageWithThumbsFieldFile, self).save(name, content, save)
86
86
87 if self.sizes:
87 if self.sizes:
88 for size in self.sizes:
88 for size in self.sizes:
89 (w, h) = size
89 (w, h) = size
90 split = self.name.rsplit('.', 1)
90 split = self.name.rsplit('.', 1)
91 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
91 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
92
92
93 # you can use another thumbnailing function if you like
93 # you can use another thumbnailing function if you like
94 thumb_content = generate_thumb(content, size, split[1])
94 thumb_content = generate_thumb(content, size, split[1])
95
95
96 thumb_name_ = self.storage.save(thumb_name, thumb_content)
96 thumb_name_ = self.storage.save(thumb_name, thumb_content)
97
97
98 if not thumb_name == thumb_name_:
98 if not thumb_name == thumb_name_:
99 raise ValueError(
99 raise ValueError(
100 'There is already a file named %s' % thumb_name)
100 'There is already a file named %s' % thumb_name)
101
101
102 def delete(self, save=True):
102 def delete(self, save=True):
103 name = self.name
103 name = self.name
104 super(ImageWithThumbsFieldFile, self).delete(save)
104 super(ImageWithThumbsFieldFile, self).delete(save)
105 if self.sizes:
105 if self.sizes:
106 for size in self.sizes:
106 for size in self.sizes:
107 (w, h) = size
107 (w, h) = size
108 split = name.rsplit('.', 1)
108 split = name.rsplit('.', 1)
109 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
109 thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1])
110 try:
110 try:
111 self.storage.delete(thumb_name)
111 self.storage.delete(thumb_name)
112 except:
112 except:
113 pass
113 pass
114
114
115
115
116 class ImageWithThumbsField(ImageField):
116 class ImageWithThumbsField(ImageField):
117 attr_class = ImageWithThumbsFieldFile
117 attr_class = ImageWithThumbsFieldFile
118 """
118 """
119 Usage example:
119 Usage example:
120 ==============
120 ==============
121 photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
121 photo = ImageWithThumbsField(upload_to='images', sizes=((125,125),(300,200),)
122
122
123 To retrieve image URL, exactly the same way as with ImageField:
123 To retrieve image URL, exactly the same way as with ImageField:
124 my_object.photo.url
124 my_object.photo.url
125 To retrieve thumbnails URL's just add the size to it:
125 To retrieve thumbnails URL's just add the size to it:
126 my_object.photo.url_125x125
126 my_object.photo.url_125x125
127 my_object.photo.url_300x200
127 my_object.photo.url_300x200
128
128
129 Note: The 'sizes' attribute is not required. If you don't provide it,
129 Note: The 'sizes' attribute is not required. If you don't provide it,
130 ImageWithThumbsField will act as a normal ImageField
130 ImageWithThumbsField will act as a normal ImageField
131
131
132 How it works:
132 How it works:
133 =============
133 =============
134 For each size in the 'sizes' atribute of the field it generates a
134 For each size in the 'sizes' atribute of the field it generates a
135 thumbnail with that size and stores it following this format:
135 thumbnail with that size and stores it following this format:
136
136
137 available_filename.[width]x[height].extension
137 available_filename.[width]x[height].extension
138
138
139 Where 'available_filename' is the available filename returned by the storage
139 Where 'available_filename' is the available filename returned by the storage
140 backend for saving the original file.
140 backend for saving the original file.
141
141
142 Following the usage example above: For storing a file called "photo.jpg" it saves:
142 Following the usage example above: For storing a file called "photo.jpg" it saves:
143 photo.jpg (original file)
143 photo.jpg (original file)
144 photo.125x125.jpg (first thumbnail)
144 photo.125x125.jpg (first thumbnail)
145 photo.300x200.jpg (second thumbnail)
145 photo.300x200.jpg (second thumbnail)
146
146
147 With the default storage backend if photo.jpg already exists it will use these filenames:
147 With the default storage backend if photo.jpg already exists it will use these filenames:
148 photo_.jpg
148 photo_.jpg
149 photo_.125x125.jpg
149 photo_.125x125.jpg
150 photo_.300x200.jpg
150 photo_.300x200.jpg
151
151
152 Note: django-thumbs assumes that if filename "any_filename.jpg" is available
152 Note: django-thumbs assumes that if filename "any_filename.jpg" is available
153 filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
153 filenames with this format "any_filename.[widht]x[height].jpg" will be available, too.
154
154
155 To do:
155 To do:
156 ======
156 ======
157 Add method to regenerate thubmnails
157 Add method to regenerate thubmnails
158
158
159
159 """
160 """
160
161
161 def __init__(self, verbose_name=None, name=None, width_field=None,
162 def __init__(self, verbose_name=None, name=None, width_field=None,
162 height_field=None, sizes=None, **kwargs):
163 height_field=None, sizes=None, **kwargs):
163 self.verbose_name = verbose_name
164 self.verbose_name = verbose_name
164 self.name = name
165 self.name = name
165 self.width_field = width_field
166 self.width_field = width_field
166 self.height_field = height_field
167 self.height_field = height_field
167 self.sizes = sizes
168 self.sizes = sizes
168 super(ImageField, self).__init__(**kwargs)
169 super(ImageField, self).__init__(**kwargs)
169
170
170
171
171 from south.modelsinspector import add_introspection_rules
172 from south.modelsinspector import add_introspection_rules
172
173 add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"])
173 add_introspection_rules([], ["^boards\.thumbs\.ImageWithThumbsField"]) No newline at end of file
@@ -1,37 +1,44 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4
4
5 js_info_dict = {
6 'packages': ('boards',),
7 }
8
5 urlpatterns = patterns('',
9 urlpatterns = patterns('',
6
10
7 # /boards/
11 # /boards/
8 url(r'^$', views.index, name='index'),
12 url(r'^$', views.index, name='index'),
9 # /boards/page/
13 # /boards/page/
10 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
14 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
11
15
12 # login page
16 # login page
13 url(r'^login$', views.login, name='login'),
17 url(r'^login$', views.login, name='login'),
14 # logout page
18 # logout page
15 url(r'^logout$', views.logout, name='logout'),
19 url(r'^logout$', views.logout, name='logout'),
16
20
17 # /boards/tag/tag_name/
21 # /boards/tag/tag_name/
18 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
22 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
19 # /boards/tag/tag_id/page/
23 # /boards/tag/tag_id/page/
20 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
24 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
21 # /boards/thread/
25 # /boards/thread/
22 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
26 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
23 # /boards/theme/theme_name/
27 # /boards/theme/theme_name/
24 url(r'^settings$', views.settings, name='settings'),
28 url(r'^settings$', views.settings, name='settings'),
25 url(r'^tags$', views.all_tags, name='tags'),
29 url(r'^tags$', views.all_tags, name='tags'),
26 url(r'^captcha/', include('captcha.urls')),
30 url(r'^captcha/', include('captcha.urls')),
27 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
31 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
28 url(r'^authors/$', views.authors, name='authors'),
32 url(r'^authors/$', views.authors, name='authors'),
29 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
33 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
34 url(r'^banned/$', views.you_are_banned, name='banned'),
30
35
31 # RSS feeds
36 # RSS feeds
32 url(r'^rss/$', AllThreadsFeed()),
37 url(r'^rss/$', AllThreadsFeed()),
33 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
38 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
34 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
39 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
35 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
40 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
36 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
41 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
37 ) No newline at end of file
42
43 url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
44 )
@@ -1,292 +1,305 b''
1 import hashlib
1 import hashlib
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.template import RequestContext
3 from django.template import RequestContext
4 from django.shortcuts import render, redirect, get_object_or_404
4 from django.shortcuts import render, redirect, get_object_or_404
5 from django.http import HttpResponseRedirect
5 from django.http import HttpResponseRedirect
6 from django.utils import timezone
6 from django.utils import timezone
7
7
8 from boards import forms
8 from boards import forms
9 import boards
9 import boards
10 from boards import utils
10 from boards import utils
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
12 ThreadCaptchaForm, PostCaptchaForm
12 ThreadCaptchaForm, PostCaptchaForm
13
13
14 from boards.models import Post, Admin, Tag, User, RANK_USER, RANK_MODERATOR, NO_PARENT
14 from boards.models import Post, Admin, Tag, Ban, User, RANK_USER, RANK_MODERATOR, NO_PARENT
15 from boards import authors
15 from boards import authors
16 import neboard
16 import neboard
17
17
18
18
19 def index(request, page=0):
19 def index(request, page=0):
20 context = _init_default_context(request)
20 context = _init_default_context(request)
21
21
22 if utils.need_include_captcha(request):
22 if utils.need_include_captcha(request):
23 threadFormClass = ThreadCaptchaForm
23 threadFormClass = ThreadCaptchaForm
24 kwargs = {'request': request}
24 kwargs = {'request': request}
25 else:
25 else:
26 threadFormClass = ThreadForm
26 threadFormClass = ThreadForm
27 kwargs = {}
27 kwargs = {}
28
28
29 if request.method == 'POST':
29 if request.method == 'POST':
30 form = threadFormClass(request.POST, request.FILES,
30 form = threadFormClass(request.POST, request.FILES,
31 error_class=PlainErrorList, **kwargs)
31 error_class=PlainErrorList, **kwargs)
32
32
33 if form.is_valid():
33 if form.is_valid():
34 return _new_post(request, form)
34 return _new_post(request, form)
35 else:
35 else:
36 form = threadFormClass(error_class=PlainErrorList, **kwargs)
36 form = threadFormClass(error_class=PlainErrorList, **kwargs)
37
37
38 threads = Post.objects.get_threads(page=int(page))
38 threads = Post.objects.get_threads(page=int(page))
39
39
40 context['threads'] = None if len(threads) == 0 else threads
40 context['threads'] = None if len(threads) == 0 else threads
41 context['form'] = form
41 context['form'] = form
42 context['pages'] = range(Post.objects.get_thread_page_count())
42 context['pages'] = range(Post.objects.get_thread_page_count())
43
43
44 return render(request, 'boards/posting_general.html',
44 return render(request, 'boards/posting_general.html',
45 context)
45 context)
46
46
47
47
48 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
48 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
49 """Add a new post (in thread or as a reply)."""
49 """Add a new post (in thread or as a reply)."""
50
50
51 ip = _get_client_ip(request)
52 is_banned = Ban.objects.filter(ip=ip).count() > 0
53
54 if is_banned:
55 return redirect(you_are_banned)
56
51 data = form.cleaned_data
57 data = form.cleaned_data
52
58
53 title = data['title']
59 title = data['title']
54 text = data['text']
60 text = data['text']
55
61
56 if 'image' in data.keys():
62 if 'image' in data.keys():
57 image = data['image']
63 image = data['image']
58 else:
64 else:
59 image = None
65 image = None
60
66
61 ip = _get_client_ip(request)
62
63 tags = []
67 tags = []
64
68
65 new_thread = thread_id == boards.models.NO_PARENT
69 new_thread = thread_id == boards.models.NO_PARENT
66 if new_thread:
70 if new_thread:
67 tag_strings = data['tags']
71 tag_strings = data['tags']
68
72
69 if tag_strings:
73 if tag_strings:
70 tag_strings = tag_strings.split(' ')
74 tag_strings = tag_strings.split(' ')
71 for tag_name in tag_strings:
75 for tag_name in tag_strings:
72 tag_name = tag_name.strip()
76 tag_name = tag_name.strip()
73 if len(tag_name) > 0:
77 if len(tag_name) > 0:
74 tag, created = Tag.objects.get_or_create(name=tag_name)
78 tag, created = Tag.objects.get_or_create(name=tag_name)
75 tags.append(tag)
79 tags.append(tag)
76
80
77 # TODO Add a possibility to define a link image instead of an image file.
81 # TODO Add a possibility to define a link image instead of an image file.
78 # If a link is given, download the image automatically.
82 # If a link is given, download the image automatically.
79
83
80 post = Post.objects.create_post(title=title, text=text, ip=ip,
84 post = Post.objects.create_post(title=title, text=text, ip=ip,
81 parent_id=thread_id, image=image,
85 parent_id=thread_id, image=image,
82 tags=tags)
86 tags=tags)
83
87
84 thread_to_show = (post.id if new_thread else thread_id)
88 thread_to_show = (post.id if new_thread else thread_id)
85
89
86 if new_thread:
90 if new_thread:
87 return redirect(thread, post_id=thread_to_show)
91 return redirect(thread, post_id=thread_to_show)
88 else:
92 else:
89 return redirect(reverse(thread,
93 return redirect(reverse(thread,
90 kwargs={'post_id': thread_to_show}) + '#'
94 kwargs={'post_id': thread_to_show}) + '#'
91 + str(post.id))
95 + str(post.id))
92
96
93
97
94 def tag(request, tag_name, page=0):
98 def tag(request, tag_name, page=0):
95 """Get all tag threads (posts without a parent)."""
99 """Get all tag threads (posts without a parent)."""
96
100
97 tag = get_object_or_404(Tag, name=tag_name)
101 tag = get_object_or_404(Tag, name=tag_name)
98 threads = Post.objects.get_threads(tag=tag, page=int(page))
102 threads = Post.objects.get_threads(tag=tag, page=int(page))
99
103
100 if request.method == 'POST':
104 if request.method == 'POST':
101 form = ThreadForm(request.POST, request.FILES,
105 form = ThreadForm(request.POST, request.FILES,
102 error_class=PlainErrorList)
106 error_class=PlainErrorList)
103 if form.is_valid():
107 if form.is_valid():
104 return _new_post(request, form)
108 return _new_post(request, form)
105 else:
109 else:
106 form = forms.ThreadForm(initial={'tags': tag_name},
110 form = forms.ThreadForm(initial={'tags': tag_name},
107 error_class=PlainErrorList)
111 error_class=PlainErrorList)
108
112
109 context = _init_default_context(request)
113 context = _init_default_context(request)
110 context['threads'] = None if len(threads) == 0 else threads
114 context['threads'] = None if len(threads) == 0 else threads
111 context['tag'] = tag_name
115 context['tag'] = tag_name
112 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
116 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
113
117
114 context['form'] = form
118 context['form'] = form
115
119
116 return render(request, 'boards/posting_general.html',
120 return render(request, 'boards/posting_general.html',
117 context)
121 context)
118
122
119
123
120 def thread(request, post_id):
124 def thread(request, post_id):
121 """Get all thread posts"""
125 """Get all thread posts"""
122
126
123 if utils.need_include_captcha(request):
127 if utils.need_include_captcha(request):
124 postFormClass = PostCaptchaForm
128 postFormClass = PostCaptchaForm
125 kwargs = {'request': request}
129 kwargs = {'request': request}
126 else:
130 else:
127 postFormClass = PostForm
131 postFormClass = PostForm
128 kwargs = {}
132 kwargs = {}
129
133
130 if request.method == 'POST':
134 if request.method == 'POST':
131 form = postFormClass(request.POST, request.FILES,
135 form = postFormClass(request.POST, request.FILES,
132 error_class=PlainErrorList, **kwargs)
136 error_class=PlainErrorList, **kwargs)
133 if form.is_valid():
137 if form.is_valid():
134 return _new_post(request, form, post_id)
138 return _new_post(request, form, post_id)
135 else:
139 else:
136 form = postFormClass(error_class=PlainErrorList, **kwargs)
140 form = postFormClass(error_class=PlainErrorList, **kwargs)
137
141
138 posts = Post.objects.get_thread(post_id)
142 posts = Post.objects.get_thread(post_id)
139
143
140 context = _init_default_context(request)
144 context = _init_default_context(request)
141
145
142 context['posts'] = posts
146 context['posts'] = posts
143 context['form'] = form
147 context['form'] = form
144
148
145 return render(request, 'boards/thread.html', context)
149 return render(request, 'boards/thread.html', context)
146
150
147
151
148 def login(request):
152 def login(request):
149 """Log in as admin"""
153 """Log in as admin"""
150
154
151 if 'name' in request.POST and 'password' in request.POST:
155 if 'name' in request.POST and 'password' in request.POST:
152 request.session['admin'] = False
156 request.session['admin'] = False
153
157
154 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
158 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
155 password=request.POST[
159 password=request.POST[
156 'password'])) > 0
160 'password'])) > 0
157
161
158 if isAdmin:
162 if isAdmin:
159 request.session['admin'] = True
163 request.session['admin'] = True
160
164
161 response = HttpResponseRedirect('/')
165 response = HttpResponseRedirect('/')
162
166
163 else:
167 else:
164 response = render(request, 'boards/login.html', {'error': 'Login error'})
168 response = render(request, 'boards/login.html', {'error': 'Login error'})
165 else:
169 else:
166 response = render(request, 'boards/login.html', {})
170 response = render(request, 'boards/login.html', {})
167
171
168 return response
172 return response
169
173
170
174
171 def logout(request):
175 def logout(request):
172 request.session['admin'] = False
176 request.session['admin'] = False
173 return HttpResponseRedirect('/')
177 return HttpResponseRedirect('/')
174
178
175
179
176 def settings(request):
180 def settings(request):
177 """User's settings"""
181 """User's settings"""
178
182
179 context = _init_default_context(request)
183 context = _init_default_context(request)
180
184
181 if request.method == 'POST':
185 if request.method == 'POST':
182 form = SettingsForm(request.POST)
186 form = SettingsForm(request.POST)
183 if form.is_valid():
187 if form.is_valid():
184 selected_theme = form.cleaned_data['theme']
188 selected_theme = form.cleaned_data['theme']
185
189
186 user = _get_user(request)
190 user = _get_user(request)
187 user.save_setting('theme', selected_theme)
191 user.save_setting('theme', selected_theme)
188
192
189 return redirect(settings)
193 return redirect(settings)
190 else:
194 else:
191 selected_theme = _get_theme(request)
195 selected_theme = _get_theme(request)
192 form = SettingsForm(initial={'theme': selected_theme})
196 form = SettingsForm(initial={'theme': selected_theme})
193 context['form'] = form
197 context['form'] = form
194
198
195 return render(request, 'boards/settings.html', context)
199 return render(request, 'boards/settings.html', context)
196
200
197
201
198 def all_tags(request):
202 def all_tags(request):
199 """All tags list"""
203 """All tags list"""
200
204
201 context = _init_default_context(request)
205 context = _init_default_context(request)
202 context['all_tags'] = Tag.objects.get_not_empty_tags()
206 context['all_tags'] = Tag.objects.get_not_empty_tags()
203
207
204 return render(request, 'boards/tags.html', context)
208 return render(request, 'boards/tags.html', context)
205
209
206
210
207 def jump_to_post(request, post_id):
211 def jump_to_post(request, post_id):
208 """Determine thread in which the requested post is and open it's page"""
212 """Determine thread in which the requested post is and open it's page"""
209
213
210 post = get_object_or_404(Post, id=post_id)
214 post = get_object_or_404(Post, id=post_id)
211
215
212 if boards.models.NO_PARENT == post.parent:
216 if boards.models.NO_PARENT == post.parent:
213 return redirect(thread, post_id=post.id)
217 return redirect(thread, post_id=post.id)
214 else:
218 else:
215 parent_thread = get_object_or_404(Post, id=post.parent)
219 parent_thread = get_object_or_404(Post, id=post.parent)
216 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
220 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
217 + '#' + str(post.id))
221 + '#' + str(post.id))
218
222
219
223
220 def authors(request):
224 def authors(request):
221 context = _init_default_context(request)
225 context = _init_default_context(request)
222 context['authors'] = boards.authors.authors
226 context['authors'] = boards.authors.authors
223
227
224 return render(request, 'boards/authors.html', context)
228 return render(request, 'boards/authors.html', context)
225
229
226
230
227 def delete(request, post_id):
231 def delete(request, post_id):
228 user = _get_user(request)
232 user = _get_user(request)
229 post = get_object_or_404(Post, id=post_id)
233 post = get_object_or_404(Post, id=post_id)
230
234
231 if user.is_moderator():
235 if user.is_moderator():
232 Post.objects.delete_post(post)
236 Post.objects.delete_post(post)
233
237
234 if NO_PARENT == post.parent:
238 if NO_PARENT == post.parent:
235 return redirect(index)
239 return redirect(index)
236 else:
240 else:
237 return redirect(thread, post_id=post.parent)
241 return redirect(thread, post_id=post.parent)
238
242
243 def you_are_banned(request):
244 context = _init_default_context(request)
245 return render(request, 'boards/banned.html', context)
246
247
248 def page_404(request):
249 context = _init_default_context(request)
250 return render(request, 'boards/404.html', context)
251
239
252
240 def _get_theme(request):
253 def _get_theme(request):
241 """Get user's CSS theme"""
254 """Get user's CSS theme"""
242
255
243 user = _get_user(request)
256 user = _get_user(request)
244 theme = user.get_setting('theme')
257 theme = user.get_setting('theme')
245 if not theme:
258 if not theme:
246 theme = neboard.settings.DEFAULT_THEME
259 theme = neboard.settings.DEFAULT_THEME
247
260
248 return theme
261 return theme
249
262
250
263
251 def _get_client_ip(request):
264 def _get_client_ip(request):
252 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
265 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
253 if x_forwarded_for:
266 if x_forwarded_for:
254 ip = x_forwarded_for.split(',')[-1].strip()
267 ip = x_forwarded_for.split(',')[-1].strip()
255 else:
268 else:
256 ip = request.META.get('REMOTE_ADDR')
269 ip = request.META.get('REMOTE_ADDR')
257 return ip
270 return ip
258
271
259
272
260 def _init_default_context(request):
273 def _init_default_context(request):
261 """Create context with default values that are used in most views"""
274 """Create context with default values that are used in most views"""
262
275
263 context = RequestContext(request)
276 context = RequestContext(request)
264 context['tags'] = Tag.objects.get_popular_tags()
277 context['tags'] = Tag.objects.get_popular_tags()
265 context['theme'] = _get_theme(request)
278 context['theme'] = _get_theme(request)
266 context['user'] = _get_user(request)
279 context['user'] = _get_user(request)
267
280
268 return context
281 return context
269
282
270
283
271 def _get_user(request):
284 def _get_user(request):
272 """Get current user from the session"""
285 """Get current user from the session"""
273
286
274 session = request.session
287 session = request.session
275 if not 'user_id' in session:
288 if not 'user_id' in session:
276 request.session.save()
289 request.session.save()
277
290
278 md5 = hashlib.md5()
291 md5 = hashlib.md5()
279 md5.update(session.session_key)
292 md5.update(session.session_key)
280 new_id = md5.hexdigest()
293 new_id = md5.hexdigest()
281
294
282 user = User.objects.create(user_id=new_id, rank=RANK_USER,
295 user = User.objects.create(user_id=new_id, rank=RANK_USER,
283 registration_time=timezone.now())
296 registration_time=timezone.now())
284
297
285 session['user_id'] = user.id
298 session['user_id'] = user.id
286 else:
299 else:
287 user = User.objects.get(id=session['user_id'])
300 user = User.objects.get(id=session['user_id'])
288 user.save()
301 user.save()
289
302
290 user.last_access_time = timezone.now()
303 user.last_access_time = timezone.now()
291
304
292 return user No newline at end of file
305 return user
@@ -1,23 +1,23 b''
1 from django.conf.urls import patterns, include, url
1 from django.conf.urls import patterns, include, url
2
2
3 # Uncomment the next two lines to enable the admin:
3 # Uncomment the next two lines to enable the admin:
4 from django.conf.urls.static import static
4 from django.conf.urls.static import static
5 from django.contrib import admin
5 from django.contrib import admin
6 from neboard import settings
6 from neboard import settings
7
7
8 admin.autodiscover()
8 admin.autodiscover()
9
9
10 urlpatterns = patterns('',
10 urlpatterns = patterns('',
11 # Examples:
11 # Examples:
12 # url(r'^$', 'neboard.views.home', name='home'),
12 # url(r'^$', 'neboard.views.home', name='home'),
13 # url(r'^neboard/', include('neboard.foo.urls')),
13 # url(r'^neboard/', include('neboard.foo.urls')),
14
14
15 # Uncomment the admin/doc line below to enable admin documentation:
15 # Uncomment the admin/doc line below to enable admin documentation:
16 # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
16 # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
17
17
18 # Uncomment the next line to enable the admin:
18 # Uncomment the next line to enable the admin:
19 url(r'^admin/', include(admin.site.urls)),
19 url(r'^admin/', include(admin.site.urls)),
20 url(r'^', include('boards.urls')),
20 url(r'^', include('boards.urls')),
21 ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
21 ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
22
22
23
23 handler404 = 'boards.views.page_404'
General Comments 0
You need to be logged in to leave comments. Login now