Show More
@@ -25,3 +25,4 b' 957e2fec91468f739b0fc2b9936d564505048c68' | |||||
25 | bb91141c6ea5c822ccbe2d46c3c48bdab683b77d 2.4.0 |
|
25 | bb91141c6ea5c822ccbe2d46c3c48bdab683b77d 2.4.0 | |
26 | 97eb184637e5691b288eaf6b03e8971f3364c239 2.5.0 |
|
26 | 97eb184637e5691b288eaf6b03e8971f3364c239 2.5.0 | |
27 | 119fafc5381b933bf30d97be0b278349f6135075 2.5.1 |
|
27 | 119fafc5381b933bf30d97be0b278349f6135075 2.5.1 | |
|
28 | d528d76d3242cced614fa11bb63f3d342e4e1d09 2.5.2 |
@@ -5,7 +5,7 b' from boards.models.user import Notificat' | |||||
5 | __author__ = 'neko259' |
|
5 | __author__ = 'neko259' | |
6 |
|
6 | |||
7 | from boards import settings |
|
7 | from boards import settings | |
8 | from boards.models import Post |
|
8 | from boards.models import Post, Tag | |
9 |
|
9 | |||
10 | CONTEXT_SITE_NAME = 'site_name' |
|
10 | CONTEXT_SITE_NAME = 'site_name' | |
11 | CONTEXT_VERSION = 'version' |
|
11 | CONTEXT_VERSION = 'version' | |
@@ -17,6 +17,7 b" CONTEXT_TAGS = 'tags'" | |||||
17 | CONTEXT_USER = 'user' |
|
17 | CONTEXT_USER = 'user' | |
18 | CONTEXT_NEW_NOTIFICATIONS_COUNT = 'new_notifications_count' |
|
18 | CONTEXT_NEW_NOTIFICATIONS_COUNT = 'new_notifications_count' | |
19 | CONTEXT_USERNAME = 'username' |
|
19 | CONTEXT_USERNAME = 'username' | |
|
20 | CONTEXT_TAGS_STR = 'tags_str' | |||
20 |
|
21 | |||
21 | PERMISSION_MODERATE = 'moderation' |
|
22 | PERMISSION_MODERATE = 'moderation' | |
22 |
|
23 | |||
@@ -49,7 +50,9 b' def user_and_ui_processor(request):' | |||||
49 | context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day()) |
|
50 | context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day()) | |
50 |
|
51 | |||
51 | settings_manager = get_settings_manager(request) |
|
52 | settings_manager = get_settings_manager(request) | |
52 |
|
|
53 | fav_tags = settings_manager.get_fav_tags() | |
|
54 | context[CONTEXT_TAGS] = fav_tags | |||
|
55 | context[CONTEXT_TAGS_STR] = Tag.objects.get_tag_url_list(fav_tags) | |||
53 | theme = settings_manager.get_theme() |
|
56 | theme = settings_manager.get_theme() | |
54 | context[CONTEXT_THEME] = theme |
|
57 | context[CONTEXT_THEME] = theme | |
55 | context[CONTEXT_THEME_CSS] = 'css/' + theme + '/base_page.css' |
|
58 | context[CONTEXT_THEME_CSS] = 'css/' + theme + '/base_page.css' |
@@ -1,4 +1,4 b'' | |||||
1 |
VERSION = '2.5. |
|
1 | VERSION = '2.5.2 Yasako' | |
2 | SITE_NAME = 'Neboard' |
|
2 | SITE_NAME = 'Neboard' | |
3 |
|
3 | |||
4 | CACHE_TIMEOUT = 600 # Timeout for caching, if cache is used |
|
4 | CACHE_TIMEOUT = 600 # Timeout for caching, if cache is used |
@@ -166,7 +166,8 b' class PostForm(NeboardForm):' | |||||
166 | def clean_image(self): |
|
166 | def clean_image(self): | |
167 | image = self.cleaned_data['image'] |
|
167 | image = self.cleaned_data['image'] | |
168 |
|
168 | |||
169 | self._validate_image(image) |
|
169 | if image: | |
|
170 | self.validate_image_size(image.size) | |||
170 |
|
171 | |||
171 | return image |
|
172 | return image | |
172 |
|
173 | |||
@@ -179,8 +180,8 b' class PostForm(NeboardForm):' | |||||
179 |
|
180 | |||
180 | if not image: |
|
181 | if not image: | |
181 | raise forms.ValidationError(_('Invalid URL')) |
|
182 | raise forms.ValidationError(_('Invalid URL')) | |
182 |
|
183 | else: | ||
183 |
self. |
|
184 | self.validate_image_size(image.size) | |
184 |
|
185 | |||
185 | return image |
|
186 | return image | |
186 |
|
187 | |||
@@ -218,13 +219,6 b' class PostForm(NeboardForm):' | |||||
218 | error_message = _('Either text or image must be entered.') |
|
219 | error_message = _('Either text or image must be entered.') | |
219 | self._errors['text'] = self.error_class([error_message]) |
|
220 | self._errors['text'] = self.error_class([error_message]) | |
220 |
|
221 | |||
221 | def _validate_image(self, image): |
|
|||
222 | if image: |
|
|||
223 | if image.size > board_settings.MAX_IMAGE_SIZE: |
|
|||
224 | raise forms.ValidationError( |
|
|||
225 | _('Image must be less than %s bytes') |
|
|||
226 | % str(board_settings.MAX_IMAGE_SIZE)) |
|
|||
227 |
|
||||
228 | def _validate_posting_speed(self): |
|
222 | def _validate_posting_speed(self): | |
229 | can_post = True |
|
223 | can_post = True | |
230 |
|
224 | |||
@@ -247,6 +241,12 b' class PostForm(NeboardForm):' | |||||
247 | if can_post: |
|
241 | if can_post: | |
248 | self.session[LAST_POST_TIME] = time.time() |
|
242 | self.session[LAST_POST_TIME] = time.time() | |
249 |
|
243 | |||
|
244 | def validate_image_size(self, size: int): | |||
|
245 | if size > board_settings.MAX_IMAGE_SIZE: | |||
|
246 | raise forms.ValidationError( | |||
|
247 | _('Image must be less than %s bytes') | |||
|
248 | % str(board_settings.MAX_IMAGE_SIZE)) | |||
|
249 | ||||
250 | def _get_image_from_url(self, url: str) -> SimpleUploadedFile: |
|
250 | def _get_image_from_url(self, url: str) -> SimpleUploadedFile: | |
251 | """ |
|
251 | """ | |
252 | Gets an image file from URL. |
|
252 | Gets an image file from URL. | |
@@ -262,11 +262,7 b' class PostForm(NeboardForm):' | |||||
262 | length_header = response_head.headers.get('content-length') |
|
262 | length_header = response_head.headers.get('content-length') | |
263 | if length_header: |
|
263 | if length_header: | |
264 | length = int(length_header) |
|
264 | length = int(length_header) | |
265 | if length > board_settings.MAX_IMAGE_SIZE: |
|
265 | self.validate_image_size(length) | |
266 | raise forms.ValidationError( |
|
|||
267 | _('Image must be less than %s bytes') |
|
|||
268 | % str(board_settings.MAX_IMAGE_SIZE)) |
|
|||
269 |
|
||||
270 | # Get the actual content into memory |
|
266 | # Get the actual content into memory | |
271 | response = requests.get(url, verify=False, stream=True) |
|
267 | response = requests.get(url, verify=False, stream=True) | |
272 |
|
268 | |||
@@ -275,11 +271,7 b' class PostForm(NeboardForm):' | |||||
275 | content = b'' |
|
271 | content = b'' | |
276 | for chunk in response.iter_content(IMAGE_DOWNLOAD_CHUNK_BYTES): |
|
272 | for chunk in response.iter_content(IMAGE_DOWNLOAD_CHUNK_BYTES): | |
277 | size += len(chunk) |
|
273 | size += len(chunk) | |
278 | if size > board_settings.MAX_IMAGE_SIZE: |
|
274 | self.validate_image_size(size) | |
279 | # TODO Dedup this code into a method |
|
|||
280 | raise forms.ValidationError( |
|
|||
281 | _('Image must be less than %s bytes') |
|
|||
282 | % str(board_settings.MAX_IMAGE_SIZE)) |
|
|||
283 | content += chunk |
|
275 | content += chunk | |
284 |
|
276 | |||
285 | if response.status_code == HTTP_RESULT_OK and content: |
|
277 | if response.status_code == HTTP_RESULT_OK and content: |
@@ -18,7 +18,30 b" CSS_CLASS_IMAGE = 'image'" | |||||
18 | CSS_CLASS_THUMB = 'thumb' |
|
18 | CSS_CLASS_THUMB = 'thumb' | |
19 |
|
19 | |||
20 |
|
20 | |||
|
21 | class PostImageManager(models.Manager): | |||
|
22 | def create_with_hash(self, image): | |||
|
23 | image_hash = self.get_hash(image) | |||
|
24 | existing = self.filter(hash=image_hash) | |||
|
25 | if len(existing) > 0: | |||
|
26 | post_image = existing[0] | |||
|
27 | else: | |||
|
28 | post_image = PostImage.objects.create(image=image) | |||
|
29 | ||||
|
30 | return post_image | |||
|
31 | ||||
|
32 | def get_hash(self, image): | |||
|
33 | """ | |||
|
34 | Gets hash of an image. | |||
|
35 | """ | |||
|
36 | md5 = hashlib.md5() | |||
|
37 | for chunk in image.chunks(): | |||
|
38 | md5.update(chunk) | |||
|
39 | return md5.hexdigest() | |||
|
40 | ||||
|
41 | ||||
21 | class PostImage(models.Model, Viewable): |
|
42 | class PostImage(models.Model, Viewable): | |
|
43 | objects = PostImageManager() | |||
|
44 | ||||
22 | class Meta: |
|
45 | class Meta: | |
23 | app_label = 'boards' |
|
46 | app_label = 'boards' | |
24 | ordering = ('id',) |
|
47 | ordering = ('id',) | |
@@ -29,10 +52,12 b' class PostImage(models.Model, Viewable):' | |||||
29 | """ |
|
52 | """ | |
30 |
|
53 | |||
31 | path = IMAGES_DIRECTORY |
|
54 | path = IMAGES_DIRECTORY | |
32 | new_name = str(int(time.mktime(time.gmtime()))) |
|
55 | ||
33 | new_name += str(int(random() * 1000)) |
|
56 | # TODO Use something other than random number in file name | |
34 | new_name += FILE_EXTENSION_DELIMITER |
|
57 | new_name = '{}{}.{}'.format( | |
35 | new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0] |
|
58 | str(int(time.mktime(time.gmtime()))), | |
|
59 | str(int(random() * 1000)), | |||
|
60 | filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]) | |||
36 |
|
61 | |||
37 | return os.path.join(path, new_name) |
|
62 | return os.path.join(path, new_name) | |
38 |
|
63 | |||
@@ -56,7 +81,7 b' class PostImage(models.Model, Viewable):' | |||||
56 | """ |
|
81 | """ | |
57 |
|
82 | |||
58 | if not self.pk and self.image: |
|
83 | if not self.pk and self.image: | |
59 | self.hash = PostImage.get_hash(self.image) |
|
84 | self.hash = PostImage.objects.get_hash(self.image) | |
60 | super(PostImage, self).save(*args, **kwargs) |
|
85 | super(PostImage, self).save(*args, **kwargs) | |
61 |
|
86 | |||
62 | def __str__(self): |
|
87 | def __str__(self): | |
@@ -78,13 +103,3 b' class PostImage(models.Model, Viewable):' | |||||
78 | self.image.url_200x150, |
|
103 | self.image.url_200x150, | |
79 | str(self.hash), str(self.pre_width), |
|
104 | str(self.hash), str(self.pre_width), | |
80 | str(self.pre_height), str(self.width), str(self.height)) |
|
105 | str(self.pre_height), str(self.width), str(self.height)) | |
81 |
|
||||
82 | @staticmethod |
|
|||
83 | def get_hash(image): |
|
|||
84 | """ |
|
|||
85 | Gets hash of an image. |
|
|||
86 | """ |
|
|||
87 | md5 = hashlib.md5() |
|
|||
88 | for chunk in image.chunks(): |
|
|||
89 | md5.update(chunk) |
|
|||
90 | return md5.hexdigest() |
|
@@ -137,25 +137,15 b' class PostManager(models.Manager):' | |||||
137 | post, post.poster_ip)) |
|
137 | post, post.poster_ip)) | |
138 |
|
138 | |||
139 | if image: |
|
139 | if image: | |
140 | # Try to find existing image. If it exists, assign it to the post |
|
140 | post.images.add(PostImage.objects.create_with_hash(image)) | |
141 | # instead of createing the new one |
|
|||
142 | image_hash = PostImage.get_hash(image) |
|
|||
143 | existing = PostImage.objects.filter(hash=image_hash) |
|
|||
144 | if len(existing) > 0: |
|
|||
145 | post_image = existing[0] |
|
|||
146 | else: |
|
|||
147 | post_image = PostImage.objects.create(image=image) |
|
|||
148 | logger.info('Created new image #{} for post #{}'.format( |
|
|||
149 | post_image.id, post.id)) |
|
|||
150 | post.images.add(post_image) |
|
|||
151 |
|
141 | |||
152 | list(map(thread.add_tag, tags)) |
|
142 | list(map(thread.add_tag, tags)) | |
153 |
|
143 | |||
154 | if new_thread: |
|
144 | if new_thread: | |
155 | boards.models.thread.Thread.objects.process_oldest_threads() |
|
145 | boards.models.thread.Thread.objects.process_oldest_threads() | |
156 | else: |
|
146 | else: | |
|
147 | thread.last_edit_time = posting_time | |||
157 | thread.bump() |
|
148 | thread.bump() | |
158 | thread.last_edit_time = posting_time |
|
|||
159 | thread.save() |
|
149 | thread.save() | |
160 |
|
150 | |||
161 | post.connect_replies() |
|
151 | post.connect_replies() |
@@ -21,6 +21,13 b' class TagManager(models.Manager):' | |||||
21 | .annotate(num_threads=Count('thread')).filter(num_threads__gt=0)\ |
|
21 | .annotate(num_threads=Count('thread')).filter(num_threads__gt=0)\ | |
22 | .order_by('-required', 'name') |
|
22 | .order_by('-required', 'name') | |
23 |
|
23 | |||
|
24 | def get_tag_url_list(self, tags: list) -> str: | |||
|
25 | """ | |||
|
26 | Gets a comma-separated list of tag links. | |||
|
27 | """ | |||
|
28 | ||||
|
29 | return ', '.join([tag.get_view() for tag in tags]) | |||
|
30 | ||||
24 |
|
31 | |||
25 | class Tag(models.Model, Viewable): |
|
32 | class Tag(models.Model, Viewable): | |
26 | """ |
|
33 | """ |
@@ -5,6 +5,7 b' from django.utils import timezone' | |||||
5 | from django.db import models |
|
5 | from django.db import models | |
6 |
|
6 | |||
7 | from boards import settings |
|
7 | from boards import settings | |
|
8 | import boards | |||
8 | from boards.utils import cached_result |
|
9 | from boards.utils import cached_result | |
9 | from boards.models.post import Post |
|
10 | from boards.models.post import Post | |
10 |
|
11 | |||
@@ -41,6 +42,7 b' class ThreadManager(models.Manager):' | |||||
41 | thread.archived = True |
|
42 | thread.archived = True | |
42 | thread.bumpable = False |
|
43 | thread.bumpable = False | |
43 | thread.last_edit_time = timezone.now() |
|
44 | thread.last_edit_time = timezone.now() | |
|
45 | thread.update_posts_time() | |||
44 | thread.save(update_fields=['archived', 'last_edit_time', 'bumpable']) |
|
46 | thread.save(update_fields=['archived', 'last_edit_time', 'bumpable']) | |
45 |
|
47 | |||
46 |
|
48 | |||
@@ -69,10 +71,11 b' class Thread(models.Model):' | |||||
69 | """ |
|
71 | """ | |
70 |
|
72 | |||
71 | if self.can_bump(): |
|
73 | if self.can_bump(): | |
72 |
self.bump_time = |
|
74 | self.bump_time = self.last_edit_time | |
73 |
|
75 | |||
74 | if self.get_reply_count() >= settings.MAX_POSTS_PER_THREAD: |
|
76 | if self.get_reply_count() >= settings.MAX_POSTS_PER_THREAD: | |
75 | self.bumpable = False |
|
77 | self.bumpable = False | |
|
78 | self.update_posts_time() | |||
76 |
|
79 | |||
77 | logger.info('Bumped thread %d' % self.id) |
|
80 | logger.info('Bumped thread %d' % self.id) | |
78 |
|
81 | |||
@@ -88,7 +91,7 b' class Thread(models.Model):' | |||||
88 | Checks if the thread can be bumped by replying to it. |
|
91 | Checks if the thread can be bumped by replying to it. | |
89 | """ |
|
92 | """ | |
90 |
|
93 | |||
91 | return self.bumpable |
|
94 | return self.bumpable and not self.archived | |
92 |
|
95 | |||
93 | def get_last_replies(self): |
|
96 | def get_last_replies(self): | |
94 | """ |
|
97 | """ | |
@@ -161,9 +164,6 b' class Thread(models.Model):' | |||||
161 |
|
164 | |||
162 | return self.get_opening_post(only_id=True).id |
|
165 | return self.get_opening_post(only_id=True).id | |
163 |
|
166 | |||
164 | def __unicode__(self): |
|
|||
165 | return str(self.id) |
|
|||
166 |
|
||||
167 | def get_pub_time(self): |
|
167 | def get_pub_time(self): | |
168 | """ |
|
168 | """ | |
169 | Gets opening post's pub time because thread does not have its own one. |
|
169 | Gets opening post's pub time because thread does not have its own one. | |
@@ -183,3 +183,9 b' class Thread(models.Model):' | |||||
183 |
|
183 | |||
184 | def __str__(self): |
|
184 | def __str__(self): | |
185 | return 'T#{}/{}'.format(self.id, self.get_opening_post_id()) |
|
185 | return 'T#{}/{}'.format(self.id, self.get_opening_post_id()) | |
|
186 | ||||
|
187 | def get_tag_url_list(self): | |||
|
188 | return boards.models.Tag.objects.get_tag_url_list(self.get_tags()) | |||
|
189 | ||||
|
190 | def update_posts_time(self): | |||
|
191 | self.post_set.update(last_edit_time=self.last_edit_time) |
@@ -23,14 +23,16 b'' | |||||
23 | for the JavaScript code in this page. |
|
23 | for the JavaScript code in this page. | |
24 | */ |
|
24 | */ | |
25 |
|
25 | |||
26 | var LOCALE = window.navigator.language; |
|
26 | if (window.Intl) { | |
27 | var FORMATTER = new Intl.DateTimeFormat( |
|
27 | var LOCALE = window.navigator.language; | |
28 | LOCALE, |
|
28 | var FORMATTER = new Intl.DateTimeFormat( | |
29 | { |
|
29 | LOCALE, | |
30 | weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', |
|
30 | { | |
31 | hour: 'numeric', minute: '2-digit', second: '2-digit' |
|
31 | weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', | |
32 | } |
|
32 | hour: 'numeric', minute: '2-digit', second: '2-digit' | |
33 | ); |
|
33 | } | |
|
34 | ); | |||
|
35 | } | |||
34 |
|
36 | |||
35 | /** |
|
37 | /** | |
36 | * An email is a hidden file to prevent spam bots from posting. It has to be |
|
38 | * An email is a hidden file to prevent spam bots from posting. It has to be | |
@@ -53,6 +55,10 b' function highlightCode(node) {' | |||||
53 | * Translate timestamps to local ones for all <time> tags inside node. |
|
55 | * Translate timestamps to local ones for all <time> tags inside node. | |
54 | */ |
|
56 | */ | |
55 | function translate_time(node) { |
|
57 | function translate_time(node) { | |
|
58 | if (window.Intl === null) { | |||
|
59 | return; | |||
|
60 | } | |||
|
61 | ||||
56 | var elements; |
|
62 | var elements; | |
57 |
|
63 | |||
58 | if (node === null) { |
|
64 | if (node === null) { |
@@ -219,7 +219,6 b' function updateBumplimitProgress(postDel' | |||||
219 | var newPostsToLimit = bumplimit - postCount; |
|
219 | var newPostsToLimit = bumplimit - postCount; | |
220 | if (newPostsToLimit <= 0) { |
|
220 | if (newPostsToLimit <= 0) { | |
221 | $('.bar-bg').remove(); |
|
221 | $('.bar-bg').remove(); | |
222 | $('.thread').children('.post').addClass('dead_post'); |
|
|||
223 | } else { |
|
222 | } else { | |
224 | postsToLimitElement.text(newPostsToLimit); |
|
223 | postsToLimitElement.text(newPostsToLimit); | |
225 | progressBar.width((100 - postCount / bumplimit * 100.0) + '%'); |
|
224 | progressBar.width((100 - postCount / bumplimit * 100.0) + '%'); | |
@@ -317,17 +316,19 b' function processNewPost(post) {' | |||||
317 |
|
316 | |||
318 | var form = $('#form'); |
|
317 | var form = $('#form'); | |
319 |
|
318 | |||
320 | var options = { |
|
319 | if (form.length > 0) { | |
321 | beforeSubmit: function(arr, $form, options) { |
|
320 | var options = { | |
322 | showAsErrors($('form'), gettext('Sending message...')); |
|
321 | beforeSubmit: function(arr, $form, options) { | |
323 | }, |
|
322 | showAsErrors($('form'), gettext('Sending message...')); | |
324 | success: updateOnPost, |
|
323 | }, | |
325 | url: '/api/add_post/' + threadId + '/' |
|
324 | success: updateOnPost, | |
326 | }; |
|
325 | url: '/api/add_post/' + threadId + '/' | |
|
326 | }; | |||
327 |
|
327 | |||
328 | form.ajaxForm(options); |
|
328 | form.ajaxForm(options); | |
329 |
|
329 | |||
330 | resetForm(form); |
|
330 | resetForm(form); | |
|
331 | } | |||
331 | } |
|
332 | } | |
332 |
|
333 | |||
333 | $('#autoupdate').click(getThreadDiff); |
|
334 | $('#autoupdate').click(getThreadDiff); |
@@ -29,9 +29,7 b'' | |||||
29 | <div class="navigation_panel header"> |
|
29 | <div class="navigation_panel header"> | |
30 | <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a> |
|
30 | <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a> | |
31 | {% autoescape off %} |
|
31 | {% autoescape off %} | |
32 |
{ |
|
32 | {{ tags_str }}, | |
33 | {{ tag.get_view }}, |
|
|||
34 | {% endfor %} |
|
|||
35 | {% endautoescape %} |
|
33 | {% endautoescape %} | |
36 | <a href="{% url 'tags' %}" title="{% trans 'Tag management' %}" |
|
34 | <a href="{% url 'tags' %}" title="{% trans 'Tag management' %}" | |
37 | >[...]</a>, |
|
35 | >[...]</a>, |
@@ -26,7 +26,7 b'' | |||||
26 | {% endcomment %} |
|
26 | {% endcomment %} | |
27 | {% if thread.archived %} |
|
27 | {% if thread.archived %} | |
28 | {% if is_opening %} |
|
28 | {% if is_opening %} | |
29 | β {{ thread.bump_time }} |
|
29 | β <time datetime="{{ thread.bump_time|date:'c' }}">{{ thread.bump_time|date:'r' }}</time> | |
30 | {% endif %} |
|
30 | {% endif %} | |
31 | {% endif %} |
|
31 | {% endif %} | |
32 | {% if is_opening and need_open_link %} |
|
32 | {% if is_opening and need_open_link %} | |
@@ -92,9 +92,7 b'' | |||||
92 | {% endif %} |
|
92 | {% endif %} | |
93 | <span class="tags"> |
|
93 | <span class="tags"> | |
94 | {% autoescape off %} |
|
94 | {% autoescape off %} | |
95 |
{ |
|
95 | {{ thread.get_tag_url_list }} | |
96 | {{ tag.get_view }}{% if not forloop.last %},{% endif %} |
|
|||
97 | {% endfor %} |
|
|||
98 | {% endautoescape %} |
|
96 | {% endautoescape %} | |
99 | </span> |
|
97 | </span> | |
100 | </div> |
|
98 | </div> |
@@ -87,13 +87,15 b'' | |||||
87 | {% if not thread.archived %} |
|
87 | {% if not thread.archived %} | |
88 | {% with last_replies=thread.get_last_replies %} |
|
88 | {% with last_replies=thread.get_last_replies %} | |
89 | {% if last_replies %} |
|
89 | {% if last_replies %} | |
90 |
{% |
|
90 | {% with skipped_replies_count=thread.get_skipped_replies_count %} | |
91 |
|
|
91 | {% if skipped_replies_count %} | |
92 | <a href="{% url 'thread' thread.get_opening_post.id %}"> |
|
92 | <div class="skipped_replies"> | |
93 | {% blocktrans with count=thread.get_skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %} |
|
93 | <a href="{% url 'thread' thread.get_opening_post_id %}"> | |
94 | </a> |
|
94 | {% blocktrans with count=skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %} | |
95 |
</ |
|
95 | </a> | |
96 |
|
|
96 | </div> | |
|
97 | {% endif %} | |||
|
98 | {% endwith %} | |||
97 | <div class="last-replies"> |
|
99 | <div class="last-replies"> | |
98 | {% for post in last_replies %} |
|
100 | {% for post in last_replies %} | |
99 | {% post_view post is_opening=False moderator=moderator truncated=True %} |
|
101 | {% post_view post is_opening=False moderator=moderator truncated=True %} |
@@ -61,12 +61,12 b'' | |||||
61 | </div> |
|
61 | </div> | |
62 |
|
62 | |||
63 | <script src="{% static 'js/jquery.form.min.js' %}"></script> |
|
63 | <script src="{% static 'js/jquery.form.min.js' %}"></script> | |
64 | <script src="{% static 'js/thread_update.js' %}"></script> |
|
|||
65 | <script src="{% static 'js/3party/centrifuge.js' %}"></script> |
|
|||
66 | {% endif %} |
|
64 | {% endif %} | |
67 |
|
65 | |||
68 | <script src="{% static 'js/form.js' %}"></script> |
|
66 | <script src="{% static 'js/form.js' %}"></script> | |
69 | <script src="{% static 'js/thread.js' %}"></script> |
|
67 | <script src="{% static 'js/thread.js' %}"></script> | |
|
68 | <script src="{% static 'js/thread_update.js' %}"></script> | |||
|
69 | <script src="{% static 'js/3party/centrifuge.js' %}"></script> | |||
70 |
|
70 | |||
71 | {% endcache %} |
|
71 | {% endcache %} | |
72 | {% endblock %} |
|
72 | {% endblock %} |
@@ -7,6 +7,8 b" ELLIPSIZER = '...'" | |||||
7 | REGEX_LINES = re.compile(r'(<div class="br"></div>)', re.U | re.S) |
|
7 | REGEX_LINES = re.compile(r'(<div class="br"></div>)', re.U | re.S) | |
8 | REGEX_TAG = re.compile(r'<(/)?([^ ]+?)(?:(\s*/)| .*?)?>', re.S) |
|
8 | REGEX_TAG = re.compile(r'<(/)?([^ ]+?)(?:(\s*/)| .*?)?>', re.S) | |
9 |
|
9 | |||
|
10 | IMG_ACTION_URL = '[<a href="{}">{}</a>]' | |||
|
11 | ||||
10 |
|
12 | |||
11 | register = template.Library() |
|
13 | register = template.Library() | |
12 |
|
14 | |||
@@ -35,15 +37,10 b' def post_url(*args, **kwargs):' | |||||
35 | def image_actions(*args, **kwargs): |
|
37 | def image_actions(*args, **kwargs): | |
36 | image_link = args[0] |
|
38 | image_link = args[0] | |
37 | if len(args) > 1: |
|
39 | if len(args) > 1: | |
38 | image_link = 'http://' + args[1] + image_link # TODO https? |
|
40 | image_link = 'http://' + args[1] + image_link # TODO https? | |
39 |
|
||||
40 | result = '' |
|
|||
41 |
|
41 | |||
42 | for action in actions: |
|
42 | return ', '.join([IMG_ACTION_URL.format( | |
43 | result += '[<a href="' + action['link'] % image_link + '">' + \ |
|
43 | action['link'] % image_link, action['name'])for action in actions]) | |
44 | action['name'] + '</a>]' |
|
|||
45 |
|
||||
46 | return result |
|
|||
47 |
|
44 | |||
48 |
|
45 | |||
49 | # TODO Use get_view of a post instead of this |
|
46 | # TODO Use get_view of a post instead of this | |
@@ -80,6 +77,7 b' def post_view(post, moderator=False, nee' | |||||
80 | } |
|
77 | } | |
81 |
|
78 | |||
82 |
|
79 | |||
|
80 | # TODO Fix or remove this method | |||
83 | @register.filter(is_safe=True) |
|
81 | @register.filter(is_safe=True) | |
84 | def truncate_lines(text, length): |
|
82 | def truncate_lines(text, length): | |
85 | if length <= 0: |
|
83 | if length <= 0: |
General Comments 0
You need to be logged in to leave comments.
Login now