##// END OF EJS Templates
Set timestamps to local ones by JS
neko259 -
r1017:67ce9645 default
parent child Browse files
Show More
@@ -1,56 +1,86 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 neko259
6 Copyright (C) 2013 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 /**
26 /**
27 * An email is a hidden file to prevent spam bots from posting. It has to be
27 * An email is a hidden file to prevent spam bots from posting. It has to be
28 * hidden.
28 * hidden.
29 */
29 */
30 function hideEmailFromForm() {
30 function hideEmailFromForm() {
31 $('.form-email').parent().parent().hide();
31 $('.form-email').parent().parent().hide();
32 }
32 }
33
33
34 /**
34 /**
35 * Highlight code blocks with code highlighter
35 * Highlight code blocks with code highlighter
36 */
36 */
37 function highlightCode(node) {
37 function highlightCode(node) {
38 node.find('pre code').each(function(i, e) {
38 node.find('pre code').each(function(i, e) {
39 hljs.highlightBlock(e);
39 hljs.highlightBlock(e);
40 });
40 });
41 }
41 }
42
42
43 /**
44 * Translate timestamps to local ones
45 */
46 function translate_time() {
47 var els = $('time');
48 if (!els.length) {
49 return;
50 }
51
52 var locale = window.navigator.language;
53 var formatter = new Intl.DateTimeFormat(
54 locale,
55 {
56 weekday: 'short', year: 'numeric', month: 'short', day: 'numeric',
57 hour: 'numeric', minute: '2-digit', second: '2-digit'
58 }
59 );
60
61 els.each(function() {
62 var el = $(this);
63 var dateAttr = el.attr('datetime');
64 if (dateAttr) {
65 var date = new Date(dateAttr);
66 el.text(formatter.format(date));
67 }
68 });
69 }
70
43 $( document ).ready(function() {
71 $( document ).ready(function() {
44 hideEmailFromForm();
72 hideEmailFromForm();
45
73
46 $("a[href='#top']").click(function() {
74 $("a[href='#top']").click(function() {
47 $("html, body").animate({ scrollTop: 0 }, "slow");
75 $("html, body").animate({ scrollTop: 0 }, "slow");
48 return false;
76 return false;
49 });
77 });
50
78
51 addImgPreview();
79 addImgPreview();
52
80
53 addRefLinkPreview();
81 addRefLinkPreview();
54
82
55 highlightCode($(document));
83 highlightCode($(document));
84
85 translate_time();
56 });
86 });
@@ -1,332 +1,334 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
110 translate_time();
109 })
111 })
110 }
112 }
111
113
112 /**
114 /**
113 * Add or update the post on html page.
115 * Add or update the post on html page.
114 */
116 */
115 function updatePost(postHtml) {
117 function updatePost(postHtml) {
116 // This needs to be set on start because the page is scrolled after posts
118 // This needs to be set on start because the page is scrolled after posts
117 // are added or updated
119 // are added or updated
118 var bottom = isPageBottom();
120 var bottom = isPageBottom();
119
121
120 var post = $(postHtml);
122 var post = $(postHtml);
121
123
122 var threadBlock = $('div.thread');
124 var threadBlock = $('div.thread');
123
125
124 var lastUpdate = '';
126 var lastUpdate = '';
125
127
126 var postId = post.attr('id');
128 var postId = post.attr('id');
127
129
128 // If the post already exists, replace it. Otherwise add as a new one.
130 // If the post already exists, replace it. Otherwise add as a new one.
129 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
131 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
130
132
131 if (existingPosts.size() > 0) {
133 if (existingPosts.size() > 0) {
132 existingPosts.replaceWith(post);
134 existingPosts.replaceWith(post);
133 } else {
135 } else {
134 var threadPosts = threadBlock.children('.post');
136 var threadPosts = threadBlock.children('.post');
135 var lastPost = threadPosts.last();
137 var lastPost = threadPosts.last();
136
138
137 post.appendTo(lastPost.parent());
139 post.appendTo(lastPost.parent());
138
140
139 updateBumplimitProgress(1);
141 updateBumplimitProgress(1);
140 showNewPostsTitle(1);
142 showNewPostsTitle(1);
141
143
142 lastUpdate = post.children('.post-info').first()
144 lastUpdate = post.children('.post-info').first()
143 .children('.pub_time').first().text();
145 .children('.pub_time').first().html();
144
146
145 if (bottom) {
147 if (bottom) {
146 scrollToBottom();
148 scrollToBottom();
147 }
149 }
148 }
150 }
149
151
150 processNewPost(post);
152 processNewPost(post);
151 updateMetadataPanel(lastUpdate)
153 updateMetadataPanel(lastUpdate)
152 }
154 }
153
155
154 /**
156 /**
155 * Initiate a blinking animation on a node to show it was updated.
157 * Initiate a blinking animation on a node to show it was updated.
156 */
158 */
157 function blink(node) {
159 function blink(node) {
158 var blinkCount = 2;
160 var blinkCount = 2;
159
161
160 var nodeToAnimate = node;
162 var nodeToAnimate = node;
161 for (var i = 0; i < blinkCount; i++) {
163 for (var i = 0; i < blinkCount; i++) {
162 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
164 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
163 }
165 }
164 }
166 }
165
167
166 function isPageBottom() {
168 function isPageBottom() {
167 var scroll = $(window).scrollTop() / ($(document).height()
169 var scroll = $(window).scrollTop() / ($(document).height()
168 - $(window).height());
170 - $(window).height());
169
171
170 return scroll == 1
172 return scroll == 1
171 }
173 }
172
174
173 function initAutoupdate() {
175 function initAutoupdate() {
174 return connectWebsocket();
176 return connectWebsocket();
175 }
177 }
176
178
177 function getReplyCount() {
179 function getReplyCount() {
178 return $('.thread').children('.post').length
180 return $('.thread').children('.post').length
179 }
181 }
180
182
181 function getImageCount() {
183 function getImageCount() {
182 return $('.thread').find('img').length
184 return $('.thread').find('img').length
183 }
185 }
184
186
185 /**
187 /**
186 * Update post count, images count and last update time in the metadata
188 * Update post count, images count and last update time in the metadata
187 * panel.
189 * panel.
188 */
190 */
189 function updateMetadataPanel(lastUpdate) {
191 function updateMetadataPanel(lastUpdate) {
190 var replyCountField = $('#reply-count');
192 var replyCountField = $('#reply-count');
191 var imageCountField = $('#image-count');
193 var imageCountField = $('#image-count');
192
194
193 replyCountField.text(getReplyCount());
195 replyCountField.text(getReplyCount());
194 imageCountField.text(getImageCount());
196 imageCountField.text(getImageCount());
195
197
196 if (lastUpdate !== '') {
198 if (lastUpdate !== '') {
197 var lastUpdateField = $('#last-update');
199 var lastUpdateField = $('#last-update');
198 lastUpdateField.text(lastUpdate);
200 lastUpdateField.html(lastUpdate);
199 blink(lastUpdateField);
201 blink(lastUpdateField);
200 }
202 }
201
203
202 blink(replyCountField);
204 blink(replyCountField);
203 blink(imageCountField);
205 blink(imageCountField);
204 }
206 }
205
207
206 /**
208 /**
207 * Update bumplimit progress bar
209 * Update bumplimit progress bar
208 */
210 */
209 function updateBumplimitProgress(postDelta) {
211 function updateBumplimitProgress(postDelta) {
210 var progressBar = $('#bumplimit_progress');
212 var progressBar = $('#bumplimit_progress');
211 if (progressBar) {
213 if (progressBar) {
212 var postsToLimitElement = $('#left_to_limit');
214 var postsToLimitElement = $('#left_to_limit');
213
215
214 var oldPostsToLimit = parseInt(postsToLimitElement.text());
216 var oldPostsToLimit = parseInt(postsToLimitElement.text());
215 var postCount = getReplyCount();
217 var postCount = getReplyCount();
216 var bumplimit = postCount - postDelta + oldPostsToLimit;
218 var bumplimit = postCount - postDelta + oldPostsToLimit;
217
219
218 var newPostsToLimit = bumplimit - postCount;
220 var newPostsToLimit = bumplimit - postCount;
219 if (newPostsToLimit <= 0) {
221 if (newPostsToLimit <= 0) {
220 $('.bar-bg').remove();
222 $('.bar-bg').remove();
221 $('.thread').children('.post').addClass('dead_post');
223 $('.thread').children('.post').addClass('dead_post');
222 } else {
224 } else {
223 postsToLimitElement.text(newPostsToLimit);
225 postsToLimitElement.text(newPostsToLimit);
224 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
226 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
225 }
227 }
226 }
228 }
227 }
229 }
228
230
229 /**
231 /**
230 * Show 'new posts' text in the title if the document is not visible to a user
232 * Show 'new posts' text in the title if the document is not visible to a user
231 */
233 */
232 function showNewPostsTitle(newPostCount) {
234 function showNewPostsTitle(newPostCount) {
233 if (document.hidden) {
235 if (document.hidden) {
234 if (documentOriginalTitle === '') {
236 if (documentOriginalTitle === '') {
235 documentOriginalTitle = document.title;
237 documentOriginalTitle = document.title;
236 }
238 }
237 unreadPosts = unreadPosts + newPostCount;
239 unreadPosts = unreadPosts + newPostCount;
238 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
240 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
239
241
240 document.addEventListener('visibilitychange', function() {
242 document.addEventListener('visibilitychange', function() {
241 if (documentOriginalTitle !== '') {
243 if (documentOriginalTitle !== '') {
242 document.title = documentOriginalTitle;
244 document.title = documentOriginalTitle;
243 documentOriginalTitle = '';
245 documentOriginalTitle = '';
244 unreadPosts = 0;
246 unreadPosts = 0;
245 }
247 }
246
248
247 document.removeEventListener('visibilitychange', null);
249 document.removeEventListener('visibilitychange', null);
248 });
250 });
249 }
251 }
250 }
252 }
251
253
252 /**
254 /**
253 * Clear all entered values in the form fields
255 * Clear all entered values in the form fields
254 */
256 */
255 function resetForm(form) {
257 function resetForm(form) {
256 form.find('input:text, input:password, input:file, select, textarea').val('');
258 form.find('input:text, input:password, input:file, select, textarea').val('');
257 form.find('input:radio, input:checkbox')
259 form.find('input:radio, input:checkbox')
258 .removeAttr('checked').removeAttr('selected');
260 .removeAttr('checked').removeAttr('selected');
259 $('.file_wrap').find('.file-thumb').remove();
261 $('.file_wrap').find('.file-thumb').remove();
260 }
262 }
261
263
262 /**
264 /**
263 * When the form is posted, this method will be run as a callback
265 * When the form is posted, this method will be run as a callback
264 */
266 */
265 function updateOnPost(response, statusText, xhr, form) {
267 function updateOnPost(response, statusText, xhr, form) {
266 var json = $.parseJSON(response);
268 var json = $.parseJSON(response);
267 var status = json.status;
269 var status = json.status;
268
270
269 showAsErrors(form, '');
271 showAsErrors(form, '');
270
272
271 if (status === 'ok') {
273 if (status === 'ok') {
272 resetForm(form);
274 resetForm(form);
273 getThreadDiff();
275 getThreadDiff();
274 } else {
276 } else {
275 var errors = json.errors;
277 var errors = json.errors;
276 for (var i = 0; i < errors.length; i++) {
278 for (var i = 0; i < errors.length; i++) {
277 var fieldErrors = errors[i];
279 var fieldErrors = errors[i];
278
280
279 var error = fieldErrors.errors;
281 var error = fieldErrors.errors;
280
282
281 showAsErrors(form, error);
283 showAsErrors(form, error);
282 }
284 }
283 }
285 }
284
286
285 scrollToBottom();
287 scrollToBottom();
286 }
288 }
287
289
288 /**
290 /**
289 * Show text in the errors row of the form.
291 * Show text in the errors row of the form.
290 * @param form
292 * @param form
291 * @param text
293 * @param text
292 */
294 */
293 function showAsErrors(form, text) {
295 function showAsErrors(form, text) {
294 form.children('.form-errors').remove();
296 form.children('.form-errors').remove();
295
297
296 if (text.length > 0) {
298 if (text.length > 0) {
297 var errorList = $('<div class="form-errors">' + text + '<div>');
299 var errorList = $('<div class="form-errors">' + text + '<div>');
298 errorList.appendTo(form);
300 errorList.appendTo(form);
299 }
301 }
300 }
302 }
301
303
302 /**
304 /**
303 * Run js methods that are usually run on the document, on the new post
305 * Run js methods that are usually run on the document, on the new post
304 */
306 */
305 function processNewPost(post) {
307 function processNewPost(post) {
306 addRefLinkPreview(post[0]);
308 addRefLinkPreview(post[0]);
307 highlightCode(post);
309 highlightCode(post);
308 blink(post);
310 blink(post);
309 }
311 }
310
312
311 $(document).ready(function(){
313 $(document).ready(function(){
312 if (initAutoupdate()) {
314 if (initAutoupdate()) {
313 // Post form data over AJAX
315 // Post form data over AJAX
314 var threadId = $('div.thread').children('.post').first().attr('id');
316 var threadId = $('div.thread').children('.post').first().attr('id');
315
317
316 var form = $('#form');
318 var form = $('#form');
317
319
318 var options = {
320 var options = {
319 beforeSubmit: function(arr, $form, options) {
321 beforeSubmit: function(arr, $form, options) {
320 showAsErrors($('form'), gettext('Sending message...'));
322 showAsErrors($('form'), gettext('Sending message...'));
321 },
323 },
322 success: updateOnPost,
324 success: updateOnPost,
323 url: '/api/add_post/' + threadId + '/'
325 url: '/api/add_post/' + threadId + '/'
324 };
326 };
325
327
326 form.ajaxForm(options);
328 form.ajaxForm(options);
327
329
328 resetForm(form);
330 resetForm(form);
329 }
331 }
330
332
331 $('#autoupdate').click(getThreadDiff);
333 $('#autoupdate').click(getThreadDiff);
332 });
334 });
@@ -1,97 +1,97 b''
1 {% load i18n %}
1 {% load i18n %}
2 {% load board %}
2 {% load board %}
3 {% load cache %}
3 {% load cache %}
4
4
5 {% get_current_language as LANGUAGE_CODE %}
5 {% get_current_language as LANGUAGE_CODE %}
6
6
7 {% if thread.archived %}
7 {% if thread.archived %}
8 <div class="post archive_post" id="{{ post.id }}">
8 <div class="post archive_post" id="{{ post.id }}">
9 {% elif bumpable %}
9 {% elif bumpable %}
10 <div class="post" id="{{ post.id }}">
10 <div class="post" id="{{ post.id }}">
11 {% else %}
11 {% else %}
12 <div class="post dead_post" id="{{ post.id }}">
12 <div class="post dead_post" id="{{ post.id }}">
13 {% endif %}
13 {% endif %}
14
14
15 <div class="post-info">
15 <div class="post-info">
16 <a class="post_id" href="{{ post.get_url }}"
16 <a class="post_id" href="{{ post.get_url }}"
17 {% if not truncated and not thread.archived %}
17 {% if not truncated and not thread.archived %}
18 onclick="javascript:addQuickReply('{{ post.id }}'); return false;"
18 onclick="javascript:addQuickReply('{{ post.id }}'); return false;"
19 title="{% trans 'Quote' %}" {% endif %}>({{ post.get_absolute_id }})</a>
19 title="{% trans 'Quote' %}" {% endif %}>({{ post.get_absolute_id }})</a>
20 <span class="title">{{ post.title }}</span>
20 <span class="title">{{ post.title }}</span>
21 <span class="pub_time">{{ post.pub_time|date:'r' }}</span>
21 <span class="pub_time"><time datetime="{{ post.pub_time|date:'c' }}">{{ post.pub_time|date:'r' }}</time></span>
22 {% comment %}
22 {% comment %}
23 Thread death time needs to be shown only if the thread is alredy archived
23 Thread death time needs to be shown only if the thread is alredy archived
24 and this is an opening post (thread death time) or a post for popup
24 and this is an opening post (thread death time) or a post for popup
25 (we don't see OP here so we show the death time in the post itself).
25 (we don't see OP here so we show the death time in the post itself).
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 β€” {{ thread.bump_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 %}
33 {% if thread.archived %}
33 {% if thread.archived %}
34 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
34 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
35 {% else %}
35 {% else %}
36 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
36 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
37 {% endif %}
37 {% endif %}
38 {% endif %}
38 {% endif %}
39
39
40 {% if moderator %}
40 {% if moderator %}
41 <span class="moderator_info">
41 <span class="moderator_info">
42 [<a href="{% url 'admin:boards_post_change' post.id %}">{% trans 'Edit' %}</a>]
42 [<a href="{% url 'admin:boards_post_change' post.id %}">{% trans 'Edit' %}</a>]
43 {% if is_opening %}
43 {% if is_opening %}
44 [<a href="{% url 'admin:boards_thread_change' thread.id %}">{% trans 'Edit thread' %}</a>]
44 [<a href="{% url 'admin:boards_thread_change' thread.id %}">{% trans 'Edit thread' %}</a>]
45 {% endif %}
45 {% endif %}
46 </span>
46 </span>
47 {% endif %}
47 {% endif %}
48 </div>
48 </div>
49 {% comment %}
49 {% comment %}
50 Post images. Currently only 1 image can be posted and shown, but post model
50 Post images. Currently only 1 image can be posted and shown, but post model
51 supports multiple.
51 supports multiple.
52 {% endcomment %}
52 {% endcomment %}
53 {% if post.images.exists %}
53 {% if post.images.exists %}
54 {% with post.images.all.0 as image %}
54 {% with post.images.all.0 as image %}
55 {% autoescape off %}
55 {% autoescape off %}
56 {{ image.get_view }}
56 {{ image.get_view }}
57 {% endautoescape %}
57 {% endautoescape %}
58 {% endwith %}
58 {% endwith %}
59 {% endif %}
59 {% endif %}
60 {% comment %}
60 {% comment %}
61 Post message (text)
61 Post message (text)
62 {% endcomment %}
62 {% endcomment %}
63 <div class="message">
63 <div class="message">
64 {% autoescape off %}
64 {% autoescape off %}
65 {% if truncated %}
65 {% if truncated %}
66 {{ post.get_text|truncatewords_html:50 }}
66 {{ post.get_text|truncatewords_html:50 }}
67 {% else %}
67 {% else %}
68 {{ post.get_text }}
68 {{ post.get_text }}
69 {% endif %}
69 {% endif %}
70 {% endautoescape %}
70 {% endautoescape %}
71 {% if post.is_referenced %}
71 {% if post.is_referenced %}
72 <div class="refmap">
72 <div class="refmap">
73 {% autoescape off %}
73 {% autoescape off %}
74 {% trans "Replies" %}: {{ post.refmap }}
74 {% trans "Replies" %}: {{ post.refmap }}
75 {% endautoescape %}
75 {% endautoescape %}
76 </div>
76 </div>
77 {% endif %}
77 {% endif %}
78 </div>
78 </div>
79 {% comment %}
79 {% comment %}
80 Thread metadata: counters, tags etc
80 Thread metadata: counters, tags etc
81 {% endcomment %}
81 {% endcomment %}
82 {% if is_opening %}
82 {% if is_opening %}
83 <div class="metadata">
83 <div class="metadata">
84 {% if is_opening and need_open_link %}
84 {% if is_opening and need_open_link %}
85 {{ thread.get_reply_count }} {% trans 'messages' %},
85 {{ thread.get_reply_count }} {% trans 'messages' %},
86 {{ thread.get_images_count }} {% trans 'images' %}.
86 {{ thread.get_images_count }} {% trans 'images' %}.
87 {% endif %}
87 {% endif %}
88 <span class="tags">
88 <span class="tags">
89 {% autoescape off %}
89 {% autoescape off %}
90 {% for tag in thread.get_tags %}
90 {% for tag in thread.get_tags %}
91 {{ tag.get_view }}{% if not forloop.last %},{% endif %}
91 {{ tag.get_view }}{% if not forloop.last %},{% endif %}
92 {% endfor %}
92 {% endfor %}
93 {% endautoescape %}
93 {% endautoescape %}
94 </span>
94 </span>
95 </div>
95 </div>
96 {% endif %}
96 {% endif %}
97 </div>
97 </div>
@@ -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>
64 <script src="{% static 'js/thread_update.js' %}"></script>
65 <script src="{% static 'js/3party/centrifuge.js' %}"></script>
65 <script src="{% static 'js/3party/centrifuge.js' %}"></script>
66 {% endif %}
66 {% endif %}
67
67
68 <script src="{% static 'js/form.js' %}"></script>
68 <script src="{% static 'js/form.js' %}"></script>
69 <script src="{% static 'js/thread.js' %}"></script>
69 <script src="{% static 'js/thread.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">{{ thread.last_edit_time|date:'r' }}</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 %}
@@ -1,66 +1,66 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>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }}
9 <title>{{ thread.get_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 {% spaceless %}
14 {% spaceless %}
15 {% get_current_language as LANGUAGE_CODE %}
15 {% get_current_language as LANGUAGE_CODE %}
16
16
17 {% cache 600 thread_gallery_view thread.id thread.last_edit_time LANGUAGE_CODE request.get_host %}
17 {% cache 600 thread_gallery_view thread.id thread.last_edit_time LANGUAGE_CODE request.get_host %}
18 <div class="image-mode-tab">
18 <div class="image-mode-tab">
19 <a href="{% url 'thread' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>,
19 <a href="{% url 'thread' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 <a class="current_mode" href="{% url 'thread_gallery' thread.get_opening_post.id %}">{% trans 'Gallery mode' %}</a>
20 <a class="current_mode" href="{% url 'thread_gallery' thread.get_opening_post.id %}">{% trans 'Gallery mode' %}</a>
21 </div>
21 </div>
22
22
23 <div id="posts-table">
23 <div id="posts-table">
24 {% for post in posts %}
24 {% for post in posts %}
25 <div class="gallery_image">
25 <div class="gallery_image">
26 {% with post.get_first_image as image %}
26 {% with post.get_first_image as image %}
27 <div>
27 <div>
28 <a
28 <a
29 class="thumb"
29 class="thumb"
30 href="{{ image.image.url }}"><img
30 href="{{ image.image.url }}"><img
31 src="{{ image.image.url_200x150 }}"
31 src="{{ image.image.url_200x150 }}"
32 alt="{{ post.id }}"
32 alt="{{ post.id }}"
33 width="{{ image.pre_width }}"
33 width="{{ image.pre_width }}"
34 height="{{ image.pre_height }}"
34 height="{{ image.pre_height }}"
35 data-width="{{ image.width }}"
35 data-width="{{ image.width }}"
36 data-height="{{ image.height }}"/>
36 data-height="{{ image.height }}"/>
37 </a>
37 </a>
38 </div>
38 </div>
39 <div class="gallery_image_metadata">
39 <div class="gallery_image_metadata">
40 {{ image.width }}x{{ image.height }}
40 {{ image.width }}x{{ image.height }}
41 {% image_actions image.image.url request.get_host %}
41 {% image_actions image.image.url request.get_host %}
42 </div>
42 </div>
43 {% endwith %}
43 {% endwith %}
44 </div>
44 </div>
45 {% endfor %}
45 {% endfor %}
46 </div>
46 </div>
47 {% endcache %}
47 {% endcache %}
48
48
49 {% endspaceless %}
49 {% endspaceless %}
50 {% endblock %}
50 {% endblock %}
51
51
52 {% block metapanel %}
52 {% block metapanel %}
53
53
54 {% get_current_language as LANGUAGE_CODE %}
54 {% get_current_language as LANGUAGE_CODE %}
55
55
56 <span class="metapanel" data-last-update="{{ last_update }}">
56 <span class="metapanel" data-last-update="{{ last_update }}">
57 {% cache 600 thread_gallery_meta thread.last_edit_time moderator LANGUAGE_CODE %}
57 {% cache 600 thread_gallery_meta thread.last_edit_time moderator LANGUAGE_CODE %}
58 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }}
58 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }}
59 {% trans 'messages' %},
59 {% trans 'messages' %},
60 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
60 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
61 {% trans 'Last update: ' %}{{ thread.last_edit_time|date:'r' }}
61 {% trans 'Last update: ' %}<time datetime="{{ thread.last_edit_time|date:'c' }}">{{ thread.last_edit_time|date:'r' }}</time>
62 [<a href="rss/">RSS</a>]
62 [<a href="rss/">RSS</a>]
63 {% endcache %}
63 {% endcache %}
64 </span>
64 </span>
65
65
66 {% endblock %}
66 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now