##// END OF EJS Templates
Don't show 'replies' and 'images' texts in the thread
neko259 -
r1770:038b4c85 default
parent child Browse files
Show More
@@ -1,402 +1,399 b''
1 /*
1 /*
2 @licstart The following is the entire license notice for the
2 @licstart The following is the entire license notice for the
3 JavaScript code in this page.
3 JavaScript code in this page.
4
4
5
5
6 Copyright (C) 2013-2014 neko259
6 Copyright (C) 2013-2014 neko259
7
7
8 The JavaScript code in this page is free software: you can
8 The JavaScript code in this page is free software: you can
9 redistribute it and/or modify it under the terms of the GNU
9 redistribute it and/or modify it under the terms of the GNU
10 General Public License (GNU GPL) as published by the Free Software
10 General Public License (GNU GPL) as published by the Free Software
11 Foundation, either version 3 of the License, or (at your option)
11 Foundation, either version 3 of the License, or (at your option)
12 any later version. The code is distributed WITHOUT ANY WARRANTY;
12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 without even the implied warranty of MERCHANTABILITY or FITNESS
13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15
15
16 As additional permission under GNU GPL version 3 section 7, you
16 As additional permission under GNU GPL version 3 section 7, you
17 may distribute non-source (e.g., minimized or compacted) forms of
17 may distribute non-source (e.g., minimized or compacted) forms of
18 that code without the copy of the GNU GPL normally required by
18 that code without the copy of the GNU GPL normally required by
19 section 4, provided you include this license notice and a URL
19 section 4, provided you include this license notice and a URL
20 through which recipients can access the Corresponding Source.
20 through which recipients can access the Corresponding Source.
21
21
22 @licend The above is the entire license notice
22 @licend The above is the entire license notice
23 for the JavaScript code in this page.
23 for the JavaScript code in this page.
24 */
24 */
25
25
26 var CLASS_POST = '.post';
26 var CLASS_POST = '.post';
27
27
28 var POST_ADDED = 0;
28 var POST_ADDED = 0;
29 var POST_UPDATED = 1;
29 var POST_UPDATED = 1;
30
30
31 // TODO These need to be syncronized with board settings.
31 // TODO These need to be syncronized with board settings.
32 var JS_AUTOUPDATE_PERIOD = 20000;
32 var JS_AUTOUPDATE_PERIOD = 20000;
33 // TODO This needs to be the same for attachment download time limit.
33 // TODO This needs to be the same for attachment download time limit.
34 var POST_AJAX_TIMEOUT = 30000;
34 var POST_AJAX_TIMEOUT = 30000;
35 var BLINK_SPEED = 500;
35 var BLINK_SPEED = 500;
36
36
37 var ALLOWED_FOR_PARTIAL_UPDATE = [
37 var ALLOWED_FOR_PARTIAL_UPDATE = [
38 'refmap',
38 'refmap',
39 'post-info'
39 'post-info'
40 ];
40 ];
41
41
42 var ATTR_CLASS = 'class';
42 var ATTR_CLASS = 'class';
43 var ATTR_UID = 'data-uid';
43 var ATTR_UID = 'data-uid';
44
44
45 var unreadPosts = 0;
45 var unreadPosts = 0;
46 var documentOriginalTitle = '';
46 var documentOriginalTitle = '';
47
47
48 // Thread ID does not change, can be stored one time
48 // Thread ID does not change, can be stored one time
49 var threadId = $('div.thread').children(CLASS_POST).first().attr('id');
49 var threadId = $('div.thread').children(CLASS_POST).first().attr('id');
50 var blinkColor = $('<div class="post-blink"></div>').css('background-color');
50 var blinkColor = $('<div class="post-blink"></div>').css('background-color');
51
51
52 /**
52 /**
53 * Get diff of the posts from the current thread timestamp.
53 * Get diff of the posts from the current thread timestamp.
54 * This is required if the browser was closed and some post updates were
54 * This is required if the browser was closed and some post updates were
55 * missed.
55 * missed.
56 */
56 */
57 function getThreadDiff() {
57 function getThreadDiff() {
58 var all_posts = $('.post');
58 var all_posts = $('.post');
59
59
60 var uids = '';
60 var uids = '';
61 var posts = all_posts;
61 var posts = all_posts;
62 for (var i = 0; i < posts.length; i++) {
62 for (var i = 0; i < posts.length; i++) {
63 uids += posts[i].getAttribute('data-uid') + ' ';
63 uids += posts[i].getAttribute('data-uid') + ' ';
64 }
64 }
65
65
66 var data = {
66 var data = {
67 uids: uids,
67 uids: uids,
68 thread: threadId
68 thread: threadId
69 };
69 };
70
70
71 var diffUrl = '/api/diff_thread/';
71 var diffUrl = '/api/diff_thread/';
72
72
73 $.post(diffUrl,
73 $.post(diffUrl,
74 data,
74 data,
75 function(data) {
75 function(data) {
76 var updatedPosts = data.updated;
76 var updatedPosts = data.updated;
77 var addedPostCount = 0;
77 var addedPostCount = 0;
78
78
79 for (var i = 0; i < updatedPosts.length; i++) {
79 for (var i = 0; i < updatedPosts.length; i++) {
80 var postText = updatedPosts[i];
80 var postText = updatedPosts[i];
81 var post = $(postText);
81 var post = $(postText);
82
82
83 if (updatePost(post) == POST_ADDED) {
83 if (updatePost(post) == POST_ADDED) {
84 addedPostCount++;
84 addedPostCount++;
85 }
85 }
86 }
86 }
87
87
88 var hasMetaUpdates = updatedPosts.length > 0;
88 var hasMetaUpdates = updatedPosts.length > 0;
89 if (hasMetaUpdates) {
89 if (hasMetaUpdates) {
90 updateMetadataPanel();
90 updateMetadataPanel();
91 }
91 }
92
92
93 if (addedPostCount > 0) {
93 if (addedPostCount > 0) {
94 updateBumplimitProgress(addedPostCount);
94 updateBumplimitProgress(addedPostCount);
95 }
95 }
96
96
97 if (updatedPosts.length > 0) {
97 if (updatedPosts.length > 0) {
98 showNewPostsTitle(addedPostCount);
98 showNewPostsTitle(addedPostCount);
99 }
99 }
100
100
101 // TODO Process removed posts if any
101 // TODO Process removed posts if any
102 $('.metapanel').attr('data-last-update', data.last_update);
102 $('.metapanel').attr('data-last-update', data.last_update);
103
103
104 if (data.subscribed == 'True') {
104 if (data.subscribed == 'True') {
105 var favButton = $('#thread-fav-button .not_fav');
105 var favButton = $('#thread-fav-button .not_fav');
106
106
107 if (favButton.length > 0) {
107 if (favButton.length > 0) {
108 favButton.attr('value', 'unsubscribe');
108 favButton.attr('value', 'unsubscribe');
109 favButton.removeClass('not_fav');
109 favButton.removeClass('not_fav');
110 favButton.addClass('fav');
110 favButton.addClass('fav');
111 }
111 }
112 }
112 }
113 },
113 },
114 'json'
114 'json'
115 )
115 )
116 }
116 }
117
117
118 /**
118 /**
119 * Add or update the post on html page.
119 * Add or update the post on html page.
120 */
120 */
121 function updatePost(postHtml) {
121 function updatePost(postHtml) {
122 // This needs to be set on start because the page is scrolled after posts
122 // This needs to be set on start because the page is scrolled after posts
123 // are added or updated
123 // are added or updated
124 var bottom = isPageBottom();
124 var bottom = isPageBottom();
125
125
126 var post = $(postHtml);
126 var post = $(postHtml);
127
127
128 var threadBlock = $('div.thread');
128 var threadBlock = $('div.thread');
129
129
130 var postId = post.attr('id');
130 var postId = post.attr('id');
131
131
132 // If the post already exists, replace it. Otherwise add as a new one.
132 // If the post already exists, replace it. Otherwise add as a new one.
133 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
133 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
134
134
135 var type;
135 var type;
136
136
137 if (existingPosts.size() > 0) {
137 if (existingPosts.size() > 0) {
138 replacePartial(existingPosts.first(), post, false);
138 replacePartial(existingPosts.first(), post, false);
139 post = existingPosts.first();
139 post = existingPosts.first();
140
140
141 type = POST_UPDATED;
141 type = POST_UPDATED;
142 } else {
142 } else {
143 post.appendTo(threadBlock);
143 post.appendTo(threadBlock);
144
144
145 if (bottom) {
145 if (bottom) {
146 scrollToBottom();
146 scrollToBottom();
147 }
147 }
148
148
149 type = POST_ADDED;
149 type = POST_ADDED;
150 }
150 }
151
151
152 processNewPost(post);
152 processNewPost(post);
153
153
154 return type;
154 return type;
155 }
155 }
156
156
157 /**
157 /**
158 * Initiate a blinking animation on a node to show it was updated.
158 * Initiate a blinking animation on a node to show it was updated.
159 */
159 */
160 function blink(node) {
160 function blink(node) {
161 node.effect('highlight', { color: blinkColor }, BLINK_SPEED);
161 node.effect('highlight', { color: blinkColor }, BLINK_SPEED);
162 }
162 }
163
163
164 function isPageBottom() {
164 function isPageBottom() {
165 var scroll = $(window).scrollTop() / ($(document).height()
165 var scroll = $(window).scrollTop() / ($(document).height()
166 - $(window).height());
166 - $(window).height());
167
167
168 return scroll == 1
168 return scroll == 1
169 }
169 }
170
170
171 function enableJsUpdate() {
171 function enableJsUpdate() {
172 setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD);
172 setInterval(getThreadDiff, JS_AUTOUPDATE_PERIOD);
173 return true;
173 return true;
174 }
174 }
175
175
176 function initAutoupdate() {
176 function initAutoupdate() {
177 return enableJsUpdate();
177 return enableJsUpdate();
178 }
178 }
179
179
180 function getReplyCount() {
180 function getReplyCount() {
181 return $('.thread').children(CLASS_POST).length
181 return $('.thread').children(CLASS_POST).length
182 }
182 }
183
183
184 function getImageCount() {
184 function getImageCount() {
185 return $('.thread').find('img').length
185 return $('.thread').find('img').length
186 }
186 }
187
187
188 /**
188 /**
189 * Update post count, images count and last update time in the metadata
189 * Update post count, images count and last update time in the metadata
190 * panel.
190 * panel.
191 */
191 */
192 function updateMetadataPanel() {
192 function updateMetadataPanel() {
193 var replyCountField = $('#reply-count');
193 var replyCountField = $('#reply-count');
194 var imageCountField = $('#image-count');
194 var imageCountField = $('#image-count');
195
195
196 var replyCount = getReplyCount();
196 var replyCount = getReplyCount();
197 replyCountField.text(replyCount);
197 replyCountField.text(replyCount);
198 var imageCount = getImageCount();
198 var imageCount = getImageCount();
199 imageCountField.text(imageCount);
199 imageCountField.text(imageCount);
200
200
201 var lastUpdate = $('.post:last').children('.post-info').first()
201 var lastUpdate = $('.post:last').children('.post-info').first()
202 .children('.pub_time').first().html();
202 .children('.pub_time').first().html();
203 if (lastUpdate !== '') {
203 if (lastUpdate !== '') {
204 var lastUpdateField = $('#last-update');
204 var lastUpdateField = $('#last-update');
205 lastUpdateField.html(lastUpdate);
205 lastUpdateField.html(lastUpdate);
206 blink(lastUpdateField);
206 blink(lastUpdateField);
207 }
207 }
208
208
209 blink(replyCountField);
209 blink(replyCountField);
210 blink(imageCountField);
210 blink(imageCountField);
211
212 $('#message-count-text').text(ngettext('message', 'messages', replyCount));
213 $('#image-count-text').text(ngettext('image', 'images', imageCount));
214 }
211 }
215
212
216 /**
213 /**
217 * Update bumplimit progress bar
214 * Update bumplimit progress bar
218 */
215 */
219 function updateBumplimitProgress(postDelta) {
216 function updateBumplimitProgress(postDelta) {
220 var progressBar = $('#bumplimit_progress');
217 var progressBar = $('#bumplimit_progress');
221 if (progressBar) {
218 if (progressBar) {
222 var postsToLimitElement = $('#left_to_limit');
219 var postsToLimitElement = $('#left_to_limit');
223
220
224 var oldPostsToLimit = parseInt(postsToLimitElement.text());
221 var oldPostsToLimit = parseInt(postsToLimitElement.text());
225 var postCount = getReplyCount();
222 var postCount = getReplyCount();
226 var bumplimit = postCount - postDelta + oldPostsToLimit;
223 var bumplimit = postCount - postDelta + oldPostsToLimit;
227
224
228 var newPostsToLimit = bumplimit - postCount;
225 var newPostsToLimit = bumplimit - postCount;
229 if (newPostsToLimit <= 0) {
226 if (newPostsToLimit <= 0) {
230 $('.bar-bg').remove();
227 $('.bar-bg').remove();
231 } else {
228 } else {
232 postsToLimitElement.text(newPostsToLimit);
229 postsToLimitElement.text(newPostsToLimit);
233 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
230 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
234 }
231 }
235 }
232 }
236 }
233 }
237
234
238 /**
235 /**
239 * Show 'new posts' text in the title if the document is not visible to a user
236 * Show 'new posts' text in the title if the document is not visible to a user
240 */
237 */
241 function showNewPostsTitle(newPostCount) {
238 function showNewPostsTitle(newPostCount) {
242 if (document.hidden) {
239 if (document.hidden) {
243 if (documentOriginalTitle === '') {
240 if (documentOriginalTitle === '') {
244 documentOriginalTitle = document.title;
241 documentOriginalTitle = document.title;
245 }
242 }
246 unreadPosts = unreadPosts + newPostCount;
243 unreadPosts = unreadPosts + newPostCount;
247
244
248 var newTitle = null;
245 var newTitle = null;
249 if (unreadPosts > 0) {
246 if (unreadPosts > 0) {
250 newTitle = '[' + unreadPosts + '] ';
247 newTitle = '[' + unreadPosts + '] ';
251 } else {
248 } else {
252 newTitle = '* ';
249 newTitle = '* ';
253 }
250 }
254 newTitle += documentOriginalTitle;
251 newTitle += documentOriginalTitle;
255
252
256 document.title = newTitle;
253 document.title = newTitle;
257
254
258 document.addEventListener('visibilitychange', function() {
255 document.addEventListener('visibilitychange', function() {
259 if (documentOriginalTitle !== '') {
256 if (documentOriginalTitle !== '') {
260 document.title = documentOriginalTitle;
257 document.title = documentOriginalTitle;
261 documentOriginalTitle = '';
258 documentOriginalTitle = '';
262 unreadPosts = 0;
259 unreadPosts = 0;
263 }
260 }
264
261
265 document.removeEventListener('visibilitychange', null);
262 document.removeEventListener('visibilitychange', null);
266 });
263 });
267 }
264 }
268 }
265 }
269
266
270 /**
267 /**
271 * Clear all entered values in the form fields
268 * Clear all entered values in the form fields
272 */
269 */
273 function resetForm(form) {
270 function resetForm(form) {
274 form.find('input:text, input:password, input:file, select, textarea').val('');
271 form.find('input:text, input:password, input:file, select, textarea').val('');
275 form.find('input:radio, input:checkbox')
272 form.find('input:radio, input:checkbox')
276 .removeAttr('checked').removeAttr('selected');
273 .removeAttr('checked').removeAttr('selected');
277 $('.file_wrap').find('.file-thumb').remove();
274 $('.file_wrap').find('.file-thumb').remove();
278 $('#preview-text').hide();
275 $('#preview-text').hide();
279 }
276 }
280
277
281 /**
278 /**
282 * When the form is posted, this method will be run as a callback
279 * When the form is posted, this method will be run as a callback
283 */
280 */
284 function updateOnPost(response, statusText, xhr, form) {
281 function updateOnPost(response, statusText, xhr, form) {
285 var json = $.parseJSON(response);
282 var json = $.parseJSON(response);
286 var status = json.status;
283 var status = json.status;
287
284
288 showAsErrors(form, '');
285 showAsErrors(form, '');
289 $('.post-form-w').unblock();
286 $('.post-form-w').unblock();
290
287
291 if (status === 'ok') {
288 if (status === 'ok') {
292 resetFormPosition();
289 resetFormPosition();
293 resetForm(form);
290 resetForm(form);
294 getThreadDiff();
291 getThreadDiff();
295 scrollToBottom();
292 scrollToBottom();
296 } else {
293 } else {
297 var errors = json.errors;
294 var errors = json.errors;
298 for (var i = 0; i < errors.length; i++) {
295 for (var i = 0; i < errors.length; i++) {
299 var fieldErrors = errors[i];
296 var fieldErrors = errors[i];
300
297
301 var error = fieldErrors.errors;
298 var error = fieldErrors.errors;
302
299
303 showAsErrors(form, error);
300 showAsErrors(form, error);
304 }
301 }
305 }
302 }
306 }
303 }
307
304
308
305
309 /**
306 /**
310 * Run js methods that are usually run on the document, on the new post
307 * Run js methods that are usually run on the document, on the new post
311 */
308 */
312 function processNewPost(post) {
309 function processNewPost(post) {
313 addScriptsToPost(post);
310 addScriptsToPost(post);
314 blink(post);
311 blink(post);
315 }
312 }
316
313
317 function replacePartial(oldNode, newNode, recursive) {
314 function replacePartial(oldNode, newNode, recursive) {
318 if (!equalNodes(oldNode, newNode)) {
315 if (!equalNodes(oldNode, newNode)) {
319 // Update parent node attributes
316 // Update parent node attributes
320 updateNodeAttr(oldNode, newNode, ATTR_CLASS);
317 updateNodeAttr(oldNode, newNode, ATTR_CLASS);
321 updateNodeAttr(oldNode, newNode, ATTR_UID);
318 updateNodeAttr(oldNode, newNode, ATTR_UID);
322
319
323 // Replace children
320 // Replace children
324 var children = oldNode.children();
321 var children = oldNode.children();
325 if (children.length == 0) {
322 if (children.length == 0) {
326 oldNode.replaceWith(newNode);
323 oldNode.replaceWith(newNode);
327 } else {
324 } else {
328 var newChildren = newNode.children();
325 var newChildren = newNode.children();
329 newChildren.each(function(i) {
326 newChildren.each(function(i) {
330 var newChild = newChildren.eq(i);
327 var newChild = newChildren.eq(i);
331 var newChildClass = newChild.attr(ATTR_CLASS);
328 var newChildClass = newChild.attr(ATTR_CLASS);
332
329
333 // Update only certain allowed blocks (e.g. not images)
330 // Update only certain allowed blocks (e.g. not images)
334 if (ALLOWED_FOR_PARTIAL_UPDATE.indexOf(newChildClass) > -1) {
331 if (ALLOWED_FOR_PARTIAL_UPDATE.indexOf(newChildClass) > -1) {
335 var oldChild = oldNode.children('.' + newChildClass);
332 var oldChild = oldNode.children('.' + newChildClass);
336
333
337 if (oldChild.length == 0) {
334 if (oldChild.length == 0) {
338 oldNode.append(newChild);
335 oldNode.append(newChild);
339 } else {
336 } else {
340 if (!equalNodes(oldChild, newChild)) {
337 if (!equalNodes(oldChild, newChild)) {
341 if (recursive) {
338 if (recursive) {
342 replacePartial(oldChild, newChild, false);
339 replacePartial(oldChild, newChild, false);
343 } else {
340 } else {
344 oldChild.replaceWith(newChild);
341 oldChild.replaceWith(newChild);
345 }
342 }
346 }
343 }
347 }
344 }
348 }
345 }
349 });
346 });
350 }
347 }
351 }
348 }
352 }
349 }
353
350
354 /**
351 /**
355 * Compare nodes by content
352 * Compare nodes by content
356 */
353 */
357 function equalNodes(node1, node2) {
354 function equalNodes(node1, node2) {
358 return node1[0].outerHTML == node2[0].outerHTML;
355 return node1[0].outerHTML == node2[0].outerHTML;
359 }
356 }
360
357
361 /**
358 /**
362 * Update attribute of a node if it has changed
359 * Update attribute of a node if it has changed
363 */
360 */
364 function updateNodeAttr(oldNode, newNode, attrName) {
361 function updateNodeAttr(oldNode, newNode, attrName) {
365 var oldAttr = oldNode.attr(attrName);
362 var oldAttr = oldNode.attr(attrName);
366 var newAttr = newNode.attr(attrName);
363 var newAttr = newNode.attr(attrName);
367 if (oldAttr != newAttr) {
364 if (oldAttr != newAttr) {
368 oldNode.attr(attrName, newAttr);
365 oldNode.attr(attrName, newAttr);
369 }
366 }
370 }
367 }
371
368
372 $(document).ready(function() {
369 $(document).ready(function() {
373 if (initAutoupdate()) {
370 if (initAutoupdate()) {
374 // Post form data over AJAX
371 // Post form data over AJAX
375 var threadId = $('div.thread').children('.post').first().attr('id');
372 var threadId = $('div.thread').children('.post').first().attr('id');
376
373
377 var form = $('#form');
374 var form = $('#form');
378
375
379 if (form.length > 0) {
376 if (form.length > 0) {
380 var options = {
377 var options = {
381 beforeSubmit: function(arr, form, options) {
378 beforeSubmit: function(arr, form, options) {
382 $('.post-form-w').block({ message: gettext('Sending message...') });
379 $('.post-form-w').block({ message: gettext('Sending message...') });
383 },
380 },
384 success: updateOnPost,
381 success: updateOnPost,
385 error: function(xhr, textStatus, errorString) {
382 error: function(xhr, textStatus, errorString) {
386 var errorText = gettext('Server error: ') + textStatus;
383 var errorText = gettext('Server error: ') + textStatus;
387 if (errorString) {
384 if (errorString) {
388 errorText += ' / ' + errorString;
385 errorText += ' / ' + errorString;
389 }
386 }
390 showAsErrors(form, errorText);
387 showAsErrors(form, errorText);
391 $('.post-form-w').unblock();
388 $('.post-form-w').unblock();
392 },
389 },
393 url: '/api/add_post/' + threadId + '/',
390 url: '/api/add_post/' + threadId + '/',
394 timeout: POST_AJAX_TIMEOUT
391 timeout: POST_AJAX_TIMEOUT
395 };
392 };
396
393
397 form.ajaxForm(options);
394 form.ajaxForm(options);
398
395
399 resetForm(form);
396 resetForm(form);
400 }
397 }
401 }
398 }
402 });
399 });
@@ -1,38 +1,38 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load static from staticfiles %}
4 {% load static from staticfiles %}
5 {% load board %}
5 {% load board %}
6 {% load tz %}
6 {% load tz %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>{{ opening_post.get_title_or_text }} - {{ site_name }}</title>
9 <title>{{ opening_post.get_title_or_text }} - {{ site_name }}</title>
10 {% endblock %}
10 {% endblock %}
11
11
12 {% block content %}
12 {% block content %}
13 <div class="image-mode-tab">
13 <div class="image-mode-tab">
14 <a {% ifequal mode 'normal' %}class="current_mode"{% endifequal %} href="{% url 'thread' opening_post.id %}">{% trans 'Normal' %}</a>,
14 <a {% ifequal mode 'normal' %}class="current_mode"{% endifequal %} href="{% url 'thread' opening_post.id %}">{% trans 'Normal' %}</a>,
15 <a {% ifequal mode 'gallery' %}class="current_mode"{% endifequal %} href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery' %}</a>,
15 <a {% ifequal mode 'gallery' %}class="current_mode"{% endifequal %} href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery' %}</a>,
16 <a {% ifequal mode 'tree' %}class="current_mode"{% endifequal %} href="{% url 'thread_tree' opening_post.id %}">{% trans 'Tree' %}</a>
16 <a {% ifequal mode 'tree' %}class="current_mode"{% endifequal %} href="{% url 'thread_tree' opening_post.id %}">{% trans 'Tree' %}</a>
17 </div>
17 </div>
18
18
19 {% block thread_content %}
19 {% block thread_content %}
20 {% endblock %}
20 {% endblock %}
21 {% endblock %}
21 {% endblock %}
22
22
23 {% block metapanel %}
23 {% block metapanel %}
24
24
25 <span class="metapanel"
25 <span class="metapanel"
26 data-last-update="{{ last_update }}"
26 data-last-update="{{ last_update }}"
27 data-ws-token-time="{{ ws_token_time }}">
27 data-ws-token-time="{{ ws_token_time }}">
28
28
29 {% with replies_count=thread.get_reply_count%}
29 {% with replies_count=thread.get_reply_count%}
30 <span id="reply-count">{{ thread.get_reply_count }}</span>{% if thread.has_post_limit %}/{{ thread.max_posts }}{% endif %}
30 <span id="reply-count">{{ thread.get_reply_count }}</span>{% if thread.has_post_limit %}/{{ thread.max_posts }}{% endif %}
31 {% endwith %}
31 {% endwith %}
32 {% with images_count=thread.get_images_count%}
32 {% with images_count=thread.get_images_count%}
33 <span id="image-count">{{ images_count }}</span> <span id="image-count-text">
33 <span id="image-count">{{ images_count }}</span>
34 {% endwith %}
34 {% endwith %}
35 {% trans 'Last update: ' %}<span id="last-update"><time datetime="{{ thread.last_edit_time|date:'c' }}">{{ thread.last_edit_time }}</time></span>
35 {% trans 'Last update: ' %}<span id="last-update"><time datetime="{{ thread.last_edit_time|date:'c' }}">{{ thread.last_edit_time }}</time></span>
36 </span>
36 </span>
37
37
38 {% endblock %}
38 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now