##// END OF EJS Templates
Post message by ctrl-enter on the text field
neko259 -
r1005:1df42bd8 default
parent child Browse files
Show More
@@ -1,22 +1,27 b''
1 var isCompact = false;
2
3 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
1 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
4
2
5 $('body').on('change', 'input[name=image]', function(event) {
3 $('body').on('change', 'input[name=image]', function(event) {
6 var file = event.target.files[0];
4 var file = event.target.files[0];
7
5
8 if(file.type.match('image.*')) {
6 if(file.type.match('image.*')) {
9 var fileReader = new FileReader();
7 var fileReader = new FileReader();
10
8
11 fileReader.addEventListener("load", function(event) {
9 fileReader.addEventListener("load", function(event) {
12 var wrapper = $('.file_wrap');
10 var wrapper = $('.file_wrap');
13
11
14 wrapper.find('.file-thumb').remove();
12 wrapper.find('.file-thumb').remove();
15 wrapper.append(
13 wrapper.append(
16 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
14 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
17 );
15 );
18 });
16 });
19
17
20 fileReader.readAsDataURL(file);
18 fileReader.readAsDataURL(file);
21 }
19 }
20 });
21
22 var form = $('#form');
23 $('textarea').keypress(function(event) {
24 if (event.which == 13) {
25 form.submit();
26 }
22 }); No newline at end of file
27 });
@@ -1,334 +1,330 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 loading = false;
29 var unreadPosts = 0;
28 var unreadPosts = 0;
30 var documentOriginalTitle = '';
29 var documentOriginalTitle = '';
31
30
32 // Thread ID does not change, can be stored one time
31 // Thread ID does not change, can be stored one time
33 var threadId = $('div.thread').children('.post').first().attr('id');
32 var threadId = $('div.thread').children('.post').first().attr('id');
34
33
35 /**
34 /**
36 * 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
37 * request a thread diff.
36 * request a thread diff.
38 *
37 *
39 * @returns {boolean} true if connected, false otherwise
38 * @returns {boolean} true if connected, false otherwise
40 */
39 */
41 function connectWebsocket() {
40 function connectWebsocket() {
42 var metapanel = $('.metapanel')[0];
41 var metapanel = $('.metapanel')[0];
43
42
44 var wsHost = metapanel.getAttribute('data-ws-host');
43 var wsHost = metapanel.getAttribute('data-ws-host');
45 var wsPort = metapanel.getAttribute('data-ws-port');
44 var wsPort = metapanel.getAttribute('data-ws-port');
46
45
47 if (wsHost.length > 0 && wsPort.length > 0)
46 if (wsHost.length > 0 && wsPort.length > 0)
48 var centrifuge = new Centrifuge({
47 var centrifuge = new Centrifuge({
49 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
48 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
50 "project": metapanel.getAttribute('data-ws-project'),
49 "project": metapanel.getAttribute('data-ws-project'),
51 "user": wsUser,
50 "user": wsUser,
52 "timestamp": metapanel.getAttribute('data-last-update'),
51 "timestamp": metapanel.getAttribute('data-last-update'),
53 "token": metapanel.getAttribute('data-ws-token'),
52 "token": metapanel.getAttribute('data-ws-token'),
54 "debug": false
53 "debug": false
55 });
54 });
56
55
57 centrifuge.on('error', function(error_message) {
56 centrifuge.on('error', function(error_message) {
58 console.log("Error connecting to websocket server.");
57 console.log("Error connecting to websocket server.");
59 return false;
58 return false;
60 });
59 });
61
60
62 centrifuge.on('connect', function() {
61 centrifuge.on('connect', function() {
63 var channelName = 'thread:' + threadId;
62 var channelName = 'thread:' + threadId;
64 centrifuge.subscribe(channelName, function(message) {
63 centrifuge.subscribe(channelName, function(message) {
65 getThreadDiff();
64 getThreadDiff();
66 });
65 });
67
66
68 // For the case we closed the browser and missed some updates
67 // For the case we closed the browser and missed some updates
69 getThreadDiff();
68 getThreadDiff();
70 $('#autoupdate').text('[+]');
69 $('#autoupdate').text('[+]');
71 });
70 });
72
71
73 centrifuge.connect();
72 centrifuge.connect();
74
73
75 return true;
74 return true;
76 }
75 }
77
76
78 /**
77 /**
79 * Get diff of the posts from the current thread timestamp.
78 * Get diff of the posts from the current thread timestamp.
80 * 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
81 * missed.
80 * missed.
82 */
81 */
83 function getThreadDiff() {
82 function getThreadDiff() {
84 var lastUpdateTime = $('.metapanel').attr('data-last-update');
83 var lastUpdateTime = $('.metapanel').attr('data-last-update');
85
84
86 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
85 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
87
86
88 $.getJSON(diffUrl)
87 $.getJSON(diffUrl)
89 .success(function(data) {
88 .success(function(data) {
90 var addedPosts = data.added;
89 var addedPosts = data.added;
91
90
92 for (var i = 0; i < addedPosts.length; i++) {
91 for (var i = 0; i < addedPosts.length; i++) {
93 var postText = addedPosts[i];
92 var postText = addedPosts[i];
94 var post = $(postText);
93 var post = $(postText);
95
94
96 updatePost(post)
95 updatePost(post)
97
98 lastPost = post;
99 }
96 }
100
97
101 var updatedPosts = data.updated;
98 var updatedPosts = data.updated;
102
99
103 for (var i = 0; i < updatedPosts.length; i++) {
100 for (var i = 0; i < updatedPosts.length; i++) {
104 var postText = updatedPosts[i];
101 var postText = updatedPosts[i];
105 var post = $(postText);
102 var post = $(postText);
106
103
107 updatePost(post)
104 updatePost(post)
108 }
105 }
109
106
110 // TODO Process removed posts if any
107 // TODO Process removed posts if any
111 $('.metapanel').attr('data-last-update', data.last_update);
108 $('.metapanel').attr('data-last-update', data.last_update);
112 })
109 })
113 }
110 }
114
111
115 /**
112 /**
116 * Add or update the post on html page.
113 * Add or update the post on html page.
117 */
114 */
118 function updatePost(postHtml) {
115 function updatePost(postHtml) {
119 // 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
120 // are added or updated
117 // are added or updated
121 var bottom = isPageBottom();
118 var bottom = isPageBottom();
122
119
123 var post = $(postHtml);
120 var post = $(postHtml);
124
121
125 var threadBlock = $('div.thread');
122 var threadBlock = $('div.thread');
126
123
127 var lastUpdate = '';
124 var lastUpdate = '';
128
125
129 var postId = post.attr('id');
126 var postId = post.attr('id');
130
127
131 // 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.
132 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
129 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
133
130
134 if (existingPosts.size() > 0) {
131 if (existingPosts.size() > 0) {
135 existingPosts.replaceWith(post);
132 existingPosts.replaceWith(post);
136 } else {
133 } else {
137 var threadPosts = threadBlock.children('.post');
134 var threadPosts = threadBlock.children('.post');
138 var lastPost = threadPosts.last();
135 var lastPost = threadPosts.last();
139
136
140 post.appendTo(lastPost.parent());
137 post.appendTo(lastPost.parent());
141
138
142 updateBumplimitProgress(1);
139 updateBumplimitProgress(1);
143 showNewPostsTitle(1);
140 showNewPostsTitle(1);
144
141
145 lastUpdate = post.children('.post-info').first()
142 lastUpdate = post.children('.post-info').first()
146 .children('.pub_time').first().text();
143 .children('.pub_time').first().text();
147
144
148 if (bottom) {
145 if (bottom) {
149 scrollToBottom();
146 scrollToBottom();
150 }
147 }
151 }
148 }
152
149
153 processNewPost(post);
150 processNewPost(post);
154 updateMetadataPanel(lastUpdate)
151 updateMetadataPanel(lastUpdate)
155 }
152 }
156
153
157 /**
154 /**
158 * 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.
159 */
156 */
160 function blink(node) {
157 function blink(node) {
161 var blinkCount = 2;
158 var blinkCount = 2;
162
159
163 var nodeToAnimate = node;
160 var nodeToAnimate = node;
164 for (var i = 0; i < blinkCount; i++) {
161 for (var i = 0; i < blinkCount; i++) {
165 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
162 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
166 }
163 }
167 }
164 }
168
165
169 function isPageBottom() {
166 function isPageBottom() {
170 var scroll = $(window).scrollTop() / ($(document).height()
167 var scroll = $(window).scrollTop() / ($(document).height()
171 - $(window).height());
168 - $(window).height());
172
169
173 return scroll == 1
170 return scroll == 1
174 }
171 }
175
172
176 function initAutoupdate() {
173 function initAutoupdate() {
177 return connectWebsocket();
174 return connectWebsocket();
178 }
175 }
179
176
180 function getReplyCount() {
177 function getReplyCount() {
181 return $('.thread').children('.post').length
178 return $('.thread').children('.post').length
182 }
179 }
183
180
184 function getImageCount() {
181 function getImageCount() {
185 return $('.thread').find('img').length
182 return $('.thread').find('img').length
186 }
183 }
187
184
188 /**
185 /**
189 * 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
190 * panel.
187 * panel.
191 */
188 */
192 function updateMetadataPanel(lastUpdate) {
189 function updateMetadataPanel(lastUpdate) {
193 var replyCountField = $('#reply-count');
190 var replyCountField = $('#reply-count');
194 var imageCountField = $('#image-count');
191 var imageCountField = $('#image-count');
195
192
196 replyCountField.text(getReplyCount());
193 replyCountField.text(getReplyCount());
197 imageCountField.text(getImageCount());
194 imageCountField.text(getImageCount());
198
195
199 if (lastUpdate !== '') {
196 if (lastUpdate !== '') {
200 var lastUpdateField = $('#last-update');
197 var lastUpdateField = $('#last-update');
201 lastUpdateField.text(lastUpdate);
198 lastUpdateField.text(lastUpdate);
202 blink(lastUpdateField);
199 blink(lastUpdateField);
203 }
200 }
204
201
205 blink(replyCountField);
202 blink(replyCountField);
206 blink(imageCountField);
203 blink(imageCountField);
207 }
204 }
208
205
209 /**
206 /**
210 * Update bumplimit progress bar
207 * Update bumplimit progress bar
211 */
208 */
212 function updateBumplimitProgress(postDelta) {
209 function updateBumplimitProgress(postDelta) {
213 var progressBar = $('#bumplimit_progress');
210 var progressBar = $('#bumplimit_progress');
214 if (progressBar) {
211 if (progressBar) {
215 var postsToLimitElement = $('#left_to_limit');
212 var postsToLimitElement = $('#left_to_limit');
216
213
217 var oldPostsToLimit = parseInt(postsToLimitElement.text());
214 var oldPostsToLimit = parseInt(postsToLimitElement.text());
218 var postCount = getReplyCount();
215 var postCount = getReplyCount();
219 var bumplimit = postCount - postDelta + oldPostsToLimit;
216 var bumplimit = postCount - postDelta + oldPostsToLimit;
220
217
221 var newPostsToLimit = bumplimit - postCount;
218 var newPostsToLimit = bumplimit - postCount;
222 if (newPostsToLimit <= 0) {
219 if (newPostsToLimit <= 0) {
223 $('.bar-bg').remove();
220 $('.bar-bg').remove();
224 $('.thread').children('.post').addClass('dead_post');
221 $('.thread').children('.post').addClass('dead_post');
225 } else {
222 } else {
226 postsToLimitElement.text(newPostsToLimit);
223 postsToLimitElement.text(newPostsToLimit);
227 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
224 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
228 }
225 }
229 }
226 }
230 }
227 }
231
228
232 /**
229 /**
233 * Show 'new posts' text in the title if the document is not visible to a user
230 * Show 'new posts' text in the title if the document is not visible to a user
234 */
231 */
235 function showNewPostsTitle(newPostCount) {
232 function showNewPostsTitle(newPostCount) {
236 if (document.hidden) {
233 if (document.hidden) {
237 if (documentOriginalTitle === '') {
234 if (documentOriginalTitle === '') {
238 documentOriginalTitle = document.title;
235 documentOriginalTitle = document.title;
239 }
236 }
240 unreadPosts = unreadPosts + newPostCount;
237 unreadPosts = unreadPosts + newPostCount;
241 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
238 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
242
239
243 document.addEventListener('visibilitychange', function() {
240 document.addEventListener('visibilitychange', function() {
244 if (documentOriginalTitle !== '') {
241 if (documentOriginalTitle !== '') {
245 document.title = documentOriginalTitle;
242 document.title = documentOriginalTitle;
246 documentOriginalTitle = '';
243 documentOriginalTitle = '';
247 unreadPosts = 0;
244 unreadPosts = 0;
248 }
245 }
249
246
250 document.removeEventListener('visibilitychange', null);
247 document.removeEventListener('visibilitychange', null);
251 });
248 });
252 }
249 }
253 }
250 }
254
251
255 /**
252 /**
256 * Clear all entered values in the form fields
253 * Clear all entered values in the form fields
257 */
254 */
258 function resetForm(form) {
255 function resetForm(form) {
259 form.find('input:text, input:password, input:file, select, textarea').val('');
256 form.find('input:text, input:password, input:file, select, textarea').val('');
260 form.find('input:radio, input:checkbox')
257 form.find('input:radio, input:checkbox')
261 .removeAttr('checked').removeAttr('selected');
258 .removeAttr('checked').removeAttr('selected');
262 $('.file_wrap').find('.file-thumb').remove();
259 $('.file_wrap').find('.file-thumb').remove();
263 }
260 }
264
261
265 /**
262 /**
266 * When the form is posted, this method will be run as a callback
263 * When the form is posted, this method will be run as a callback
267 */
264 */
268 function updateOnPost(response, statusText, xhr, form) {
265 function updateOnPost(response, statusText, xhr, form) {
269 var json = $.parseJSON(response);
266 var json = $.parseJSON(response);
270 var status = json.status;
267 var status = json.status;
271
268
272 showAsErrors(form, '');
269 showAsErrors(form, '');
273
270
274 if (status === 'ok') {
271 if (status === 'ok') {
275 resetForm(form);
272 resetForm(form);
276 getThreadDiff();
273 getThreadDiff();
277 } else {
274 } else {
278 var errors = json.errors;
275 var errors = json.errors;
279 for (var i = 0; i < errors.length; i++) {
276 for (var i = 0; i < errors.length; i++) {
280 var fieldErrors = errors[i];
277 var fieldErrors = errors[i];
281
278
282 var error = fieldErrors.errors;
279 var error = fieldErrors.errors;
283
280
284 showAsErrors(form, error);
281 showAsErrors(form, error);
285 }
282 }
286 }
283 }
287
284
288 scrollToBottom();
285 scrollToBottom();
289 }
286 }
290
287
291 /**
288 /**
292 * Show text in the errors row of the form.
289 * Show text in the errors row of the form.
293 * @param form
290 * @param form
294 * @param text
291 * @param text
295 */
292 */
296 function showAsErrors(form, text) {
293 function showAsErrors(form, text) {
297 form.children('.form-errors').remove();
294 form.children('.form-errors').remove();
298
295
299 if (text.length > 0) {
296 if (text.length > 0) {
300 var errorList = $('<div class="form-errors">' + text
297 var errorList = $('<div class="form-errors">' + text + '<div>');
301 + '<div>');
302 errorList.appendTo(form);
298 errorList.appendTo(form);
303 }
299 }
304 }
300 }
305
301
306 /**
302 /**
307 * Run js methods that are usually run on the document, on the new post
303 * Run js methods that are usually run on the document, on the new post
308 */
304 */
309 function processNewPost(post) {
305 function processNewPost(post) {
310 addRefLinkPreview(post[0]);
306 addRefLinkPreview(post[0]);
311 highlightCode(post);
307 highlightCode(post);
312 blink(post);
308 blink(post);
313 }
309 }
314
310
315 $(document).ready(function(){
311 $(document).ready(function(){
316 if (initAutoupdate()) {
312 if (initAutoupdate()) {
317 // Post form data over AJAX
313 // Post form data over AJAX
318 var threadId = $('div.thread').children('.post').first().attr('id');
314 var threadId = $('div.thread').children('.post').first().attr('id');
319
315
320 var form = $('#form');
316 var form = $('#form');
321
317
322 var options = {
318 var options = {
323 beforeSubmit: function(arr, $form, options) {
319 beforeSubmit: function(arr, $form, options) {
324 showAsErrors($('form'), gettext('Sending message...'));
320 showAsErrors($('form'), gettext('Sending message...'));
325 },
321 },
326 success: updateOnPost,
322 success: updateOnPost,
327 url: '/api/add_post/' + threadId + '/'
323 url: '/api/add_post/' + threadId + '/'
328 };
324 };
329
325
330 form.ajaxForm(options);
326 form.ajaxForm(options);
331
327
332 resetForm(form);
328 resetForm(form);
333 }
329 }
334 });
330 });
@@ -1,187 +1,188 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 board %}
5 {% load board %}
6 {% load static %}
6 {% load static %}
7
7
8 {% block head %}
8 {% block head %}
9 {% if tag %}
9 {% if tag %}
10 <title>{{ tag.name }} - {{ site_name }}</title>
10 <title>{{ tag.name }} - {{ site_name }}</title>
11 {% else %}
11 {% else %}
12 <title>{{ site_name }}</title>
12 <title>{{ site_name }}</title>
13 {% endif %}
13 {% endif %}
14
14
15 {% if current_page.has_previous %}
15 {% if current_page.has_previous %}
16 <link rel="prev" href="
16 <link rel="prev" href="
17 {% if tag %}
17 {% if tag %}
18 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
18 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
19 {% else %}
19 {% else %}
20 {% url "index" page=current_page.previous_page_number %}
20 {% url "index" page=current_page.previous_page_number %}
21 {% endif %}
21 {% endif %}
22 " />
22 " />
23 {% endif %}
23 {% endif %}
24 {% if current_page.has_next %}
24 {% if current_page.has_next %}
25 <link rel="next" href="
25 <link rel="next" href="
26 {% if tag %}
26 {% if tag %}
27 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
27 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
28 {% else %}
28 {% else %}
29 {% url "index" page=current_page.next_page_number %}
29 {% url "index" page=current_page.next_page_number %}
30 {% endif %}
30 {% endif %}
31 " />
31 " />
32 {% endif %}
32 {% endif %}
33
33
34 {% endblock %}
34 {% endblock %}
35
35
36 {% block content %}
36 {% block content %}
37
37
38 {% get_current_language as LANGUAGE_CODE %}
38 {% get_current_language as LANGUAGE_CODE %}
39
39
40 {% if tag %}
40 {% if tag %}
41 <div class="tag_info">
41 <div class="tag_info">
42 <h2>
42 <h2>
43 {% if is_favorite %}
43 {% if is_favorite %}
44 <a href="{% url 'tag' tag.name %}?method=unsubscribe&next={{ request.path }}"
44 <a href="{% url 'tag' tag.name %}?method=unsubscribe&next={{ request.path }}"
45 class="fav" rel="nofollow">β˜…</a>
45 class="fav" rel="nofollow">β˜…</a>
46 {% else %}
46 {% else %}
47 <a href="{% url 'tag' tag.name %}?method=subscribe&next={{ request.path }}"
47 <a href="{% url 'tag' tag.name %}?method=subscribe&next={{ request.path }}"
48 class="not_fav" rel="nofollow">β˜…</a>
48 class="not_fav" rel="nofollow">β˜…</a>
49 {% endif %}
49 {% endif %}
50 {% if is_hidden %}
50 {% if is_hidden %}
51 <a href="{% url 'tag' tag.name %}?method=unhide&next={{ request.path }}"
51 <a href="{% url 'tag' tag.name %}?method=unhide&next={{ request.path }}"
52 title="{% trans 'Show tag' %}"
52 title="{% trans 'Show tag' %}"
53 class="fav" rel="nofollow">H</a>
53 class="fav" rel="nofollow">H</a>
54 {% else %}
54 {% else %}
55 <a href="{% url 'tag' tag.name %}?method=hide&next={{ request.path }}"
55 <a href="{% url 'tag' tag.name %}?method=hide&next={{ request.path }}"
56 title="{% trans 'Hide tag' %}"
56 title="{% trans 'Hide tag' %}"
57 class="not_fav" rel="nofollow">H</a>
57 class="not_fav" rel="nofollow">H</a>
58 {% endif %}
58 {% endif %}
59 {% autoescape off %}
59 {% autoescape off %}
60 {{ tag.get_view }}
60 {{ tag.get_view }}
61 {% endautoescape %}
61 {% endautoescape %}
62 {% if moderator %}
62 {% if moderator %}
63 <span class="moderator_info">[<a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a>]</span>
63 <span class="moderator_info">[<a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a>]</span>
64 {% endif %}
64 {% endif %}
65 </h2>
65 </h2>
66 <p>{% blocktrans with thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ thread_count }} threads and {{ post_count }} posts.{% endblocktrans %}</p>
66 <p>{% blocktrans with thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ thread_count }} threads and {{ post_count }} posts.{% endblocktrans %}</p>
67 </div>
67 </div>
68 {% endif %}
68 {% endif %}
69
69
70 {% if threads %}
70 {% if threads %}
71 {% if current_page.has_previous %}
71 {% if current_page.has_previous %}
72 <div class="page_link">
72 <div class="page_link">
73 <a href="
73 <a href="
74 {% if tag %}
74 {% if tag %}
75 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
75 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
76 {% else %}
76 {% else %}
77 {% url "index" page=current_page.previous_page_number %}
77 {% url "index" page=current_page.previous_page_number %}
78 {% endif %}
78 {% endif %}
79 ">{% trans "Previous page" %}</a>
79 ">{% trans "Previous page" %}</a>
80 </div>
80 </div>
81 {% endif %}
81 {% endif %}
82
82
83 {% for thread in threads %}
83 {% for thread in threads %}
84 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
84 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
85 <div class="thread">
85 <div class="thread">
86 {% post_view thread.get_opening_post moderator is_opening=True thread=thread truncated=True need_open_link=True %}
86 {% post_view thread.get_opening_post moderator is_opening=True thread=thread truncated=True need_open_link=True %}
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 {% if thread.get_skipped_replies_count %}
90 {% if thread.get_skipped_replies_count %}
91 <div class="skipped_replies">
91 <div class="skipped_replies">
92 <a href="{% url 'thread' thread.get_opening_post.id %}">
92 <a href="{% url 'thread' thread.get_opening_post.id %}">
93 {% blocktrans with count=thread.get_skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
93 {% blocktrans with count=thread.get_skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
94 </a>
94 </a>
95 </div>
95 </div>
96 {% endif %}
96 {% endif %}
97 <div class="last-replies">
97 <div class="last-replies">
98 {% for post in last_replies %}
98 {% for post in last_replies %}
99 {% post_view post is_opening=False moderator=moderator truncated=True %}
99 {% post_view post is_opening=False moderator=moderator truncated=True %}
100 {% endfor %}
100 {% endfor %}
101 </div>
101 </div>
102 {% endif %}
102 {% endif %}
103 {% endwith %}
103 {% endwith %}
104 {% endif %}
104 {% endif %}
105 </div>
105 </div>
106 {% endcache %}
106 {% endcache %}
107 {% endfor %}
107 {% endfor %}
108
108
109 {% if current_page.has_next %}
109 {% if current_page.has_next %}
110 <div class="page_link">
110 <div class="page_link">
111 <a href="
111 <a href="
112 {% if tag %}
112 {% if tag %}
113 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
113 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
114 {% else %}
114 {% else %}
115 {% url "index" page=current_page.next_page_number %}
115 {% url "index" page=current_page.next_page_number %}
116 {% endif %}
116 {% endif %}
117 ">{% trans "Next page" %}</a>
117 ">{% trans "Next page" %}</a>
118 </div>
118 </div>
119 {% endif %}
119 {% endif %}
120 {% else %}
120 {% else %}
121 <div class="post">
121 <div class="post">
122 {% trans 'No threads exist. Create the first one!' %}</div>
122 {% trans 'No threads exist. Create the first one!' %}</div>
123 {% endif %}
123 {% endif %}
124
124
125 <div class="post-form-w">
125 <div class="post-form-w">
126 <script src="{% static 'js/panel.js' %}"></script>
126 <script src="{% static 'js/panel.js' %}"></script>
127 <div class="post-form">
127 <div class="post-form">
128 <div class="form-title">{% trans "Create new thread" %}</div>
128 <div class="form-title">{% trans "Create new thread" %}</div>
129 <div class="swappable-form-full">
129 <div class="swappable-form-full">
130 <form enctype="multipart/form-data" method="post">{% csrf_token %}
130 <form enctype="multipart/form-data" method="post"id="form">{% csrf_token %}
131 {{ form.as_div }}
131 {{ form.as_div }}
132 <div class="form-submit">
132 <div class="form-submit">
133 <input type="submit" value="{% trans "Post" %}"/>
133 <input type="submit" value="{% trans "Post" %}"/>
134 </div>
134 </div>
135 (ctrl-enter)
135 </form>
136 </form>
136 </div>
137 </div>
137 <div>
138 <div>
138 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
139 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
139 </div>
140 </div>
140 <div><a href="{% url "staticpage" name="help" %}">
141 <div><a href="{% url "staticpage" name="help" %}">
141 {% trans 'Text syntax' %}</a></div>
142 {% trans 'Text syntax' %}</a></div>
142 </div>
143 </div>
143 </div>
144 </div>
144
145
145 <script src="{% static 'js/form.js' %}"></script>
146 <script src="{% static 'js/form.js' %}"></script>
146
147
147 {% endblock %}
148 {% endblock %}
148
149
149 {% block metapanel %}
150 {% block metapanel %}
150
151
151 <span class="metapanel">
152 <span class="metapanel">
152 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
153 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
153 {% trans "Pages:" %}
154 {% trans "Pages:" %}
154 <a href="
155 <a href="
155 {% if tag %}
156 {% if tag %}
156 {% url "tag" tag_name=tag.name page=paginator.page_range|first %}
157 {% url "tag" tag_name=tag.name page=paginator.page_range|first %}
157 {% else %}
158 {% else %}
158 {% url "index" page=paginator.page_range|first %}
159 {% url "index" page=paginator.page_range|first %}
159 {% endif %}
160 {% endif %}
160 ">&lt;&lt;</a>
161 ">&lt;&lt;</a>
161 [
162 [
162 {% for page in paginator.center_range %}
163 {% for page in paginator.center_range %}
163 <a
164 <a
164 {% ifequal page current_page.number %}
165 {% ifequal page current_page.number %}
165 class="current_page"
166 class="current_page"
166 {% endifequal %}
167 {% endifequal %}
167 href="
168 href="
168 {% if tag %}
169 {% if tag %}
169 {% url "tag" tag_name=tag.name page=page %}
170 {% url "tag" tag_name=tag.name page=page %}
170 {% else %}
171 {% else %}
171 {% url "index" page=page %}
172 {% url "index" page=page %}
172 {% endif %}
173 {% endif %}
173 ">{{ page }}</a>
174 ">{{ page }}</a>
174 {% if not forloop.last %},{% endif %}
175 {% if not forloop.last %},{% endif %}
175 {% endfor %}
176 {% endfor %}
176 ]
177 ]
177 <a href="
178 <a href="
178 {% if tag %}
179 {% if tag %}
179 {% url "tag" tag_name=tag.name page=paginator.page_range|last %}
180 {% url "tag" tag_name=tag.name page=paginator.page_range|last %}
180 {% else %}
181 {% else %}
181 {% url "index" page=paginator.page_range|last %}
182 {% url "index" page=paginator.page_range|last %}
182 {% endif %}
183 {% endif %}
183 ">&gt;&gt;</a>
184 ">&gt;&gt;</a>
184 [<a href="rss/">RSS</a>]
185 [<a href="rss/">RSS</a>]
185 </span>
186 </span>
186
187
187 {% endblock %}
188 {% endblock %}
@@ -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" id="form">
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"
49 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
50 >{% csrf_token %}
51 <div class="compact-form-text"></div>
50 <div class="compact-form-text"></div>
52 {{ form.as_div }}
51 {{ form.as_div }}
53 <div class="form-submit">
52 <div class="form-submit">
54 <input type="submit" value="{% trans "Post" %}"/>
53 <input type="submit" value="{% trans "Post" %}"/>
55 </div>
54 </div>
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 <span id="autoupdate">[-]</span>
85 <span id="autoupdate">[-]</span>
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">{{ thread.last_edit_time|date:'r' }}</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