##// END OF EJS Templates
Init autoupdate in archived threads
neko259 -
r1024:26e889af default
parent child Browse files
Show More
@@ -1,334 +1,336 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 wsUser = '';
26 var wsUser = '';
27
27
28 var unreadPosts = 0;
28 var unreadPosts = 0;
29 var documentOriginalTitle = '';
29 var documentOriginalTitle = '';
30
30
31 // Thread ID does not change, can be stored one time
31 // Thread ID does not change, can be stored one time
32 var threadId = $('div.thread').children('.post').first().attr('id');
32 var threadId = $('div.thread').children('.post').first().attr('id');
33
33
34 /**
34 /**
35 * Connect to websocket server and subscribe to thread updates. On any update we
35 * Connect to websocket server and subscribe to thread updates. On any update we
36 * request a thread diff.
36 * request a thread diff.
37 *
37 *
38 * @returns {boolean} true if connected, false otherwise
38 * @returns {boolean} true if connected, false otherwise
39 */
39 */
40 function connectWebsocket() {
40 function connectWebsocket() {
41 var metapanel = $('.metapanel')[0];
41 var metapanel = $('.metapanel')[0];
42
42
43 var wsHost = metapanel.getAttribute('data-ws-host');
43 var wsHost = metapanel.getAttribute('data-ws-host');
44 var wsPort = metapanel.getAttribute('data-ws-port');
44 var wsPort = metapanel.getAttribute('data-ws-port');
45
45
46 if (wsHost.length > 0 && wsPort.length > 0)
46 if (wsHost.length > 0 && wsPort.length > 0)
47 var centrifuge = new Centrifuge({
47 var centrifuge = new Centrifuge({
48 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
48 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
49 "project": metapanel.getAttribute('data-ws-project'),
49 "project": metapanel.getAttribute('data-ws-project'),
50 "user": wsUser,
50 "user": wsUser,
51 "timestamp": metapanel.getAttribute('data-last-update'),
51 "timestamp": metapanel.getAttribute('data-last-update'),
52 "token": metapanel.getAttribute('data-ws-token'),
52 "token": metapanel.getAttribute('data-ws-token'),
53 "debug": false
53 "debug": false
54 });
54 });
55
55
56 centrifuge.on('error', function(error_message) {
56 centrifuge.on('error', function(error_message) {
57 console.log("Error connecting to websocket server.");
57 console.log("Error connecting to websocket server.");
58 return false;
58 return false;
59 });
59 });
60
60
61 centrifuge.on('connect', function() {
61 centrifuge.on('connect', function() {
62 var channelName = 'thread:' + threadId;
62 var channelName = 'thread:' + threadId;
63 centrifuge.subscribe(channelName, function(message) {
63 centrifuge.subscribe(channelName, function(message) {
64 getThreadDiff();
64 getThreadDiff();
65 });
65 });
66
66
67 // For the case we closed the browser and missed some updates
67 // For the case we closed the browser and missed some updates
68 getThreadDiff();
68 getThreadDiff();
69 $('#autoupdate').hide();
69 $('#autoupdate').hide();
70 });
70 });
71
71
72 centrifuge.connect();
72 centrifuge.connect();
73
73
74 return true;
74 return true;
75 }
75 }
76
76
77 /**
77 /**
78 * Get diff of the posts from the current thread timestamp.
78 * Get diff of the posts from the current thread timestamp.
79 * This is required if the browser was closed and some post updates were
79 * This is required if the browser was closed and some post updates were
80 * missed.
80 * missed.
81 */
81 */
82 function getThreadDiff() {
82 function getThreadDiff() {
83 var lastUpdateTime = $('.metapanel').attr('data-last-update');
83 var lastUpdateTime = $('.metapanel').attr('data-last-update');
84
84
85 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
85 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
86
86
87 $.getJSON(diffUrl)
87 $.getJSON(diffUrl)
88 .success(function(data) {
88 .success(function(data) {
89 var addedPosts = data.added;
89 var addedPosts = data.added;
90
90
91 for (var i = 0; i < addedPosts.length; i++) {
91 for (var i = 0; i < addedPosts.length; i++) {
92 var postText = addedPosts[i];
92 var postText = addedPosts[i];
93 var post = $(postText);
93 var post = $(postText);
94
94
95 updatePost(post)
95 updatePost(post)
96 }
96 }
97
97
98 var updatedPosts = data.updated;
98 var updatedPosts = data.updated;
99
99
100 for (var i = 0; i < updatedPosts.length; i++) {
100 for (var i = 0; i < updatedPosts.length; i++) {
101 var postText = updatedPosts[i];
101 var postText = updatedPosts[i];
102 var post = $(postText);
102 var post = $(postText);
103
103
104 updatePost(post)
104 updatePost(post)
105 }
105 }
106
106
107 // TODO Process removed posts if any
107 // TODO Process removed posts if any
108 $('.metapanel').attr('data-last-update', data.last_update);
108 $('.metapanel').attr('data-last-update', data.last_update);
109 })
109 })
110 }
110 }
111
111
112 /**
112 /**
113 * Add or update the post on html page.
113 * Add or update the post on html page.
114 */
114 */
115 function updatePost(postHtml) {
115 function updatePost(postHtml) {
116 // This needs to be set on start because the page is scrolled after posts
116 // This needs to be set on start because the page is scrolled after posts
117 // are added or updated
117 // are added or updated
118 var bottom = isPageBottom();
118 var bottom = isPageBottom();
119
119
120 var post = $(postHtml);
120 var post = $(postHtml);
121
121
122 var threadBlock = $('div.thread');
122 var threadBlock = $('div.thread');
123
123
124 var lastUpdate = '';
124 var lastUpdate = '';
125
125
126 var postId = post.attr('id');
126 var postId = post.attr('id');
127
127
128 // If the post already exists, replace it. Otherwise add as a new one.
128 // If the post already exists, replace it. Otherwise add as a new one.
129 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
129 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
130
130
131 if (existingPosts.size() > 0) {
131 if (existingPosts.size() > 0) {
132 existingPosts.replaceWith(post);
132 existingPosts.replaceWith(post);
133 } else {
133 } else {
134 var threadPosts = threadBlock.children('.post');
134 var threadPosts = threadBlock.children('.post');
135 var lastPost = threadPosts.last();
135 var lastPost = threadPosts.last();
136
136
137 post.appendTo(lastPost.parent());
137 post.appendTo(lastPost.parent());
138
138
139 updateBumplimitProgress(1);
139 updateBumplimitProgress(1);
140 showNewPostsTitle(1);
140 showNewPostsTitle(1);
141
141
142 lastUpdate = post.children('.post-info').first()
142 lastUpdate = post.children('.post-info').first()
143 .children('.pub_time').first().html();
143 .children('.pub_time').first().html();
144
144
145 if (bottom) {
145 if (bottom) {
146 scrollToBottom();
146 scrollToBottom();
147 }
147 }
148 }
148 }
149
149
150 processNewPost(post);
150 processNewPost(post);
151 updateMetadataPanel(lastUpdate)
151 updateMetadataPanel(lastUpdate)
152 }
152 }
153
153
154 /**
154 /**
155 * Initiate a blinking animation on a node to show it was updated.
155 * Initiate a blinking animation on a node to show it was updated.
156 */
156 */
157 function blink(node) {
157 function blink(node) {
158 var blinkCount = 2;
158 var blinkCount = 2;
159
159
160 var nodeToAnimate = node;
160 var nodeToAnimate = node;
161 for (var i = 0; i < blinkCount; i++) {
161 for (var i = 0; i < blinkCount; i++) {
162 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
162 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
163 }
163 }
164 }
164 }
165
165
166 function isPageBottom() {
166 function isPageBottom() {
167 var scroll = $(window).scrollTop() / ($(document).height()
167 var scroll = $(window).scrollTop() / ($(document).height()
168 - $(window).height());
168 - $(window).height());
169
169
170 return scroll == 1
170 return scroll == 1
171 }
171 }
172
172
173 function initAutoupdate() {
173 function initAutoupdate() {
174 return connectWebsocket();
174 return connectWebsocket();
175 }
175 }
176
176
177 function getReplyCount() {
177 function getReplyCount() {
178 return $('.thread').children('.post').length
178 return $('.thread').children('.post').length
179 }
179 }
180
180
181 function getImageCount() {
181 function getImageCount() {
182 return $('.thread').find('img').length
182 return $('.thread').find('img').length
183 }
183 }
184
184
185 /**
185 /**
186 * Update post count, images count and last update time in the metadata
186 * Update post count, images count and last update time in the metadata
187 * panel.
187 * panel.
188 */
188 */
189 function updateMetadataPanel(lastUpdate) {
189 function updateMetadataPanel(lastUpdate) {
190 var replyCountField = $('#reply-count');
190 var replyCountField = $('#reply-count');
191 var imageCountField = $('#image-count');
191 var imageCountField = $('#image-count');
192
192
193 replyCountField.text(getReplyCount());
193 replyCountField.text(getReplyCount());
194 imageCountField.text(getImageCount());
194 imageCountField.text(getImageCount());
195
195
196 if (lastUpdate !== '') {
196 if (lastUpdate !== '') {
197 var lastUpdateField = $('#last-update');
197 var lastUpdateField = $('#last-update');
198 lastUpdateField.html(lastUpdate);
198 lastUpdateField.html(lastUpdate);
199 translate_time(lastUpdateField);
199 translate_time(lastUpdateField);
200 blink(lastUpdateField);
200 blink(lastUpdateField);
201 }
201 }
202
202
203 blink(replyCountField);
203 blink(replyCountField);
204 blink(imageCountField);
204 blink(imageCountField);
205 }
205 }
206
206
207 /**
207 /**
208 * Update bumplimit progress bar
208 * Update bumplimit progress bar
209 */
209 */
210 function updateBumplimitProgress(postDelta) {
210 function updateBumplimitProgress(postDelta) {
211 var progressBar = $('#bumplimit_progress');
211 var progressBar = $('#bumplimit_progress');
212 if (progressBar) {
212 if (progressBar) {
213 var postsToLimitElement = $('#left_to_limit');
213 var postsToLimitElement = $('#left_to_limit');
214
214
215 var oldPostsToLimit = parseInt(postsToLimitElement.text());
215 var oldPostsToLimit = parseInt(postsToLimitElement.text());
216 var postCount = getReplyCount();
216 var postCount = getReplyCount();
217 var bumplimit = postCount - postDelta + oldPostsToLimit;
217 var bumplimit = postCount - postDelta + oldPostsToLimit;
218
218
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');
222 $('.thread').children('.post').addClass('dead_post');
223 } else {
223 } else {
224 postsToLimitElement.text(newPostsToLimit);
224 postsToLimitElement.text(newPostsToLimit);
225 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
225 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
226 }
226 }
227 }
227 }
228 }
228 }
229
229
230 /**
230 /**
231 * Show 'new posts' text in the title if the document is not visible to a user
231 * Show 'new posts' text in the title if the document is not visible to a user
232 */
232 */
233 function showNewPostsTitle(newPostCount) {
233 function showNewPostsTitle(newPostCount) {
234 if (document.hidden) {
234 if (document.hidden) {
235 if (documentOriginalTitle === '') {
235 if (documentOriginalTitle === '') {
236 documentOriginalTitle = document.title;
236 documentOriginalTitle = document.title;
237 }
237 }
238 unreadPosts = unreadPosts + newPostCount;
238 unreadPosts = unreadPosts + newPostCount;
239 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
239 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
240
240
241 document.addEventListener('visibilitychange', function() {
241 document.addEventListener('visibilitychange', function() {
242 if (documentOriginalTitle !== '') {
242 if (documentOriginalTitle !== '') {
243 document.title = documentOriginalTitle;
243 document.title = documentOriginalTitle;
244 documentOriginalTitle = '';
244 documentOriginalTitle = '';
245 unreadPosts = 0;
245 unreadPosts = 0;
246 }
246 }
247
247
248 document.removeEventListener('visibilitychange', null);
248 document.removeEventListener('visibilitychange', null);
249 });
249 });
250 }
250 }
251 }
251 }
252
252
253 /**
253 /**
254 * Clear all entered values in the form fields
254 * Clear all entered values in the form fields
255 */
255 */
256 function resetForm(form) {
256 function resetForm(form) {
257 form.find('input:text, input:password, input:file, select, textarea').val('');
257 form.find('input:text, input:password, input:file, select, textarea').val('');
258 form.find('input:radio, input:checkbox')
258 form.find('input:radio, input:checkbox')
259 .removeAttr('checked').removeAttr('selected');
259 .removeAttr('checked').removeAttr('selected');
260 $('.file_wrap').find('.file-thumb').remove();
260 $('.file_wrap').find('.file-thumb').remove();
261 }
261 }
262
262
263 /**
263 /**
264 * When the form is posted, this method will be run as a callback
264 * When the form is posted, this method will be run as a callback
265 */
265 */
266 function updateOnPost(response, statusText, xhr, form) {
266 function updateOnPost(response, statusText, xhr, form) {
267 var json = $.parseJSON(response);
267 var json = $.parseJSON(response);
268 var status = json.status;
268 var status = json.status;
269
269
270 showAsErrors(form, '');
270 showAsErrors(form, '');
271
271
272 if (status === 'ok') {
272 if (status === 'ok') {
273 resetForm(form);
273 resetForm(form);
274 getThreadDiff();
274 getThreadDiff();
275 } else {
275 } else {
276 var errors = json.errors;
276 var errors = json.errors;
277 for (var i = 0; i < errors.length; i++) {
277 for (var i = 0; i < errors.length; i++) {
278 var fieldErrors = errors[i];
278 var fieldErrors = errors[i];
279
279
280 var error = fieldErrors.errors;
280 var error = fieldErrors.errors;
281
281
282 showAsErrors(form, error);
282 showAsErrors(form, error);
283 }
283 }
284 }
284 }
285
285
286 scrollToBottom();
286 scrollToBottom();
287 }
287 }
288
288
289 /**
289 /**
290 * Show text in the errors row of the form.
290 * Show text in the errors row of the form.
291 * @param form
291 * @param form
292 * @param text
292 * @param text
293 */
293 */
294 function showAsErrors(form, text) {
294 function showAsErrors(form, text) {
295 form.children('.form-errors').remove();
295 form.children('.form-errors').remove();
296
296
297 if (text.length > 0) {
297 if (text.length > 0) {
298 var errorList = $('<div class="form-errors">' + text + '<div>');
298 var errorList = $('<div class="form-errors">' + text + '<div>');
299 errorList.appendTo(form);
299 errorList.appendTo(form);
300 }
300 }
301 }
301 }
302
302
303 /**
303 /**
304 * Run js methods that are usually run on the document, on the new post
304 * Run js methods that are usually run on the document, on the new post
305 */
305 */
306 function processNewPost(post) {
306 function processNewPost(post) {
307 addRefLinkPreview(post[0]);
307 addRefLinkPreview(post[0]);
308 highlightCode(post);
308 highlightCode(post);
309 translate_time(post);
309 translate_time(post);
310 blink(post);
310 blink(post);
311 }
311 }
312
312
313 $(document).ready(function(){
313 $(document).ready(function(){
314 if (initAutoupdate()) {
314 if (initAutoupdate()) {
315 // Post form data over AJAX
315 // Post form data over AJAX
316 var threadId = $('div.thread').children('.post').first().attr('id');
316 var threadId = $('div.thread').children('.post').first().attr('id');
317
317
318 var form = $('#form');
318 var form = $('#form');
319
319
320 var options = {
320 if (form.length > 0) {
321 beforeSubmit: function(arr, $form, options) {
321 var options = {
322 showAsErrors($('form'), gettext('Sending message...'));
322 beforeSubmit: function(arr, $form, options) {
323 },
323 showAsErrors($('form'), gettext('Sending message...'));
324 success: updateOnPost,
324 },
325 url: '/api/add_post/' + threadId + '/'
325 success: updateOnPost,
326 };
326 url: '/api/add_post/' + threadId + '/'
327 };
327
328
328 form.ajaxForm(options);
329 form.ajaxForm(options);
329
330
330 resetForm(form);
331 resetForm(form);
332 }
331 }
333 }
332
334
333 $('#autoupdate').click(getThreadDiff);
335 $('#autoupdate').click(getThreadDiff);
334 });
336 });
@@ -1,93 +1,93 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load cache %}
4 {% load cache %}
5 {% load static from staticfiles %}
5 {% load static from staticfiles %}
6 {% load board %}
6 {% load board %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>{{ opening_post.get_title|striptags|truncatewords:10 }}
9 <title>{{ opening_post.get_title|striptags|truncatewords:10 }}
10 - {{ site_name }}</title>
10 - {{ site_name }}</title>
11 {% endblock %}
11 {% endblock %}
12
12
13 {% block content %}
13 {% block content %}
14 {% get_current_language as LANGUAGE_CODE %}
14 {% get_current_language as LANGUAGE_CODE %}
15
15
16 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
16 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
17
17
18 <div class="image-mode-tab">
18 <div class="image-mode-tab">
19 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
19 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 <a href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery mode' %}</a>
20 <a href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery mode' %}</a>
21 </div>
21 </div>
22
22
23 {% if bumpable %}
23 {% if bumpable %}
24 <div class="bar-bg">
24 <div class="bar-bg">
25 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
25 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
26 </div>
26 </div>
27 <div class="bar-text">
27 <div class="bar-text">
28 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
28 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
29 </div>
29 </div>
30 </div>
30 </div>
31 {% endif %}
31 {% endif %}
32
32
33 <div class="thread">
33 <div class="thread">
34 {% with can_bump=thread.can_bump %}
34 {% with can_bump=thread.can_bump %}
35 {% for post in thread.get_replies %}
35 {% for post in thread.get_replies %}
36 {% with is_opening=forloop.first %}
36 {% with is_opening=forloop.first %}
37 {% post_view post moderator=moderator is_opening=is_opening bumpable=can_bump opening_post_id=opening_post.id %}
37 {% post_view post moderator=moderator is_opening=is_opening bumpable=can_bump opening_post_id=opening_post.id %}
38 {% endwith %}
38 {% endwith %}
39 {% endfor %}
39 {% endfor %}
40 {% endwith %}
40 {% endwith %}
41 </div>
41 </div>
42
42
43 {% if not thread.archived %}
43 {% if not thread.archived %}
44 <div class="post-form-w">
44 <div class="post-form-w">
45 <script src="{% static 'js/panel.js' %}"></script>
45 <script src="{% static 'js/panel.js' %}"></script>
46 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
46 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
47 <div class="post-form" id="compact-form">
47 <div class="post-form" id="compact-form">
48 <div class="swappable-form-full">
48 <div class="swappable-form-full">
49 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
49 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
50 <div class="compact-form-text"></div>
50 <div class="compact-form-text"></div>
51 {{ form.as_div }}
51 {{ form.as_div }}
52 <div class="form-submit">
52 <div class="form-submit">
53 <input type="submit" value="{% trans "Post" %}"/>
53 <input type="submit" value="{% trans "Post" %}"/>
54 </div>
54 </div>
55 (ctrl-enter)
55 (ctrl-enter)
56 </form>
56 </form>
57 </div>
57 </div>
58 <div><a href="{% url "staticpage" name="help" %}">
58 <div><a href="{% url "staticpage" name="help" %}">
59 {% trans 'Text syntax' %}</a></div>
59 {% trans 'Text syntax' %}</a></div>
60 </div>
60 </div>
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 %}
73
73
74 {% block metapanel %}
74 {% block metapanel %}
75
75
76 {% get_current_language as LANGUAGE_CODE %}
76 {% get_current_language as LANGUAGE_CODE %}
77
77
78 <span class="metapanel"
78 <span class="metapanel"
79 data-last-update="{{ last_update }}"
79 data-last-update="{{ last_update }}"
80 data-ws-token="{{ ws_token }}"
80 data-ws-token="{{ ws_token }}"
81 data-ws-project="{{ ws_project }}"
81 data-ws-project="{{ ws_project }}"
82 data-ws-host="{{ ws_host }}"
82 data-ws-host="{{ ws_host }}"
83 data-ws-port="{{ ws_port }}">
83 data-ws-port="{{ ws_port }}">
84 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
84 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
85 <button id="autoupdate">{% trans 'Update' %}</button>
85 <button id="autoupdate">{% trans 'Update' %}</button>
86 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'messages' %},
86 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'messages' %},
87 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
87 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
88 {% trans 'Last update: ' %}<span id="last-update"><time datetime="{{ thread.last_edit_time|date:'c' }}">{{ thread.last_edit_time|date:'r' }}</time></span>
88 {% trans 'Last update: ' %}<span id="last-update"><time datetime="{{ thread.last_edit_time|date:'c' }}">{{ thread.last_edit_time|date:'r' }}</time></span>
89 [<a href="rss/">RSS</a>]
89 [<a href="rss/">RSS</a>]
90 {% endcache %}
90 {% endcache %}
91 </span>
91 </span>
92
92
93 {% endblock %}
93 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now