##// END OF EJS Templates
Merged with default
neko259 -
r1020:0e2fda16 merge decentral
parent child Browse files
Show More
@@ -1,56 +1,93 b''
1 1 /*
2 2 @licstart The following is the entire license notice for the
3 3 JavaScript code in this page.
4 4
5 5
6 6 Copyright (C) 2013 neko259
7 7
8 8 The JavaScript code in this page is free software: you can
9 9 redistribute it and/or modify it under the terms of the GNU
10 10 General Public License (GNU GPL) as published by the Free Software
11 11 Foundation, either version 3 of the License, or (at your option)
12 12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15 15
16 16 As additional permission under GNU GPL version 3 section 7, you
17 17 may distribute non-source (e.g., minimized or compacted) forms of
18 18 that code without the copy of the GNU GPL normally required by
19 19 section 4, provided you include this license notice and a URL
20 20 through which recipients can access the Corresponding Source.
21 21
22 22 @licend The above is the entire license notice
23 23 for the JavaScript code in this page.
24 24 */
25 25
26 var LOCALE = window.navigator.language;
27 var FORMATTER = new Intl.DateTimeFormat(
28 LOCALE,
29 {
30 weekday: 'short', year: 'numeric', month: 'short', day: 'numeric',
31 hour: 'numeric', minute: '2-digit', second: '2-digit'
32 }
33 );
34
26 35 /**
27 36 * An email is a hidden file to prevent spam bots from posting. It has to be
28 37 * hidden.
29 38 */
30 39 function hideEmailFromForm() {
31 40 $('.form-email').parent().parent().hide();
32 41 }
33 42
34 43 /**
35 44 * Highlight code blocks with code highlighter
36 45 */
37 46 function highlightCode(node) {
38 47 node.find('pre code').each(function(i, e) {
39 48 hljs.highlightBlock(e);
40 49 });
41 50 }
42 51
52 /**
53 * Translate timestamps to local ones for all <time> tags inside node.
54 */
55 function translate_time(node) {
56 var elements;
57
58 if (node === null) {
59 elements = $('time');
60 } else {
61 elements = node.find('time');
62 }
63
64 if (!elements.length) {
65 return;
66 }
67
68 elements.each(function() {
69 var element = $(this);
70 var dateAttr = element.attr('datetime');
71 if (dateAttr) {
72 var date = new Date(dateAttr);
73 element.text(FORMATTER.format(date));
74 }
75 });
76 }
77
43 78 $( document ).ready(function() {
44 79 hideEmailFromForm();
45 80
46 81 $("a[href='#top']").click(function() {
47 82 $("html, body").animate({ scrollTop: 0 }, "slow");
48 83 return false;
49 84 });
50 85
51 86 addImgPreview();
52 87
53 88 addRefLinkPreview();
54 89
55 90 highlightCode($(document));
91
92 translate_time(null);
56 93 });
@@ -1,100 +1,101 b''
1 1 function $X(path, root) {
2 2 return document.evaluate(path, root || document, null, 6, null);
3 3 }
4 4 function $x(path, root) {
5 5 return document.evaluate(path, root || document, null, 8, null).singleNodeValue;
6 6 }
7 7
8 8 function $del(el) {
9 9 if(el) el.parentNode.removeChild(el);
10 10 }
11 11
12 12 function $each(list, fn) {
13 13 if(!list) return;
14 14 var i = list.snapshotLength;
15 15 if(i > 0) while(i--) fn(list.snapshotItem(i), i);
16 16 }
17 17
18 18 function addRefLinkPreview(node) {
19 19 $each($X('.//a[starts-with(text(),">>")]', node || document), function(link) {
20 20 link.addEventListener('mouseover', showPostPreview, false);
21 21 link.addEventListener('mouseout', delPostPreview, false);
22 22 });
23 23 }
24 24
25 25 function showPostPreview(e) {
26 26 var doc = document;
27 27 //ref id
28 28 var pNum = $(this).text().match(/\d+/);
29 29
30 30 if (pNum == null || pNum.length == 0) {
31 31 return;
32 32 }
33 33
34 34 //position
35 35 //var x = e.clientX + (doc.documentElement.scrollLeft || doc.body.scrollLeft) - doc.documentElement.clientLeft + 1;
36 36 //var y = e.clientY + (doc.documentElement.scrollTop || doc.body.scrollTop) - doc.documentElement.clientTop;
37 37
38 38 var x = e.clientX + (doc.documentElement.scrollLeft || doc.body.scrollLeft) + 2;
39 39 var y = e.clientY + (doc.documentElement.scrollTop || doc.body.scrollTop);
40 40
41 41 var cln = doc.createElement('div');
42 42 cln.id = 'pstprev_' + pNum;
43 43 cln.className = 'post_preview';
44 44
45 45 cln.style.cssText = 'top:' + y + 'px;' + (x < doc.body.clientWidth/2 ? 'left:' + x + 'px' : 'right:' + parseInt(doc.body.clientWidth - x + 1) + 'px');
46 46
47 47 cln.addEventListener('mouseout', delPostPreview, false);
48 48
49 49
50 50 var mkPreview = function(cln, html) {
51 51 cln.innerHTML = html;
52 52
53 53 highlightCode($(cln));
54 54 addRefLinkPreview(cln);
55 translate_time($(cln));
55 56 };
56 57
57 58
58 59 cln.innerHTML = "<div class=\"post\">" + gettext('Loading...') + "</div>";
59 60
60 61 if($('div[id='+pNum+']').length > 0) {
61 62 var postdata = $('div[id='+pNum+']').clone().wrap("<div/>").parent().html();
62 63
63 64 mkPreview(cln, postdata);
64 65 } else {
65 66 $.ajax({
66 67 url: '/api/post/' + pNum + '/?truncated'
67 68 })
68 69 .success(function(data) {
69 70 var postdata = $(data).wrap("<div/>").parent().html();
70 71
71 72 //make preview
72 73 mkPreview(cln, postdata);
73 74
74 75 })
75 76 .error(function() {
76 77 cln.innerHTML = "<div class=\"post\">"
77 78 + gettext('Post not found') + "</div>";
78 79 });
79 80 }
80 81
81 82 $del(doc.getElementById(cln.id));
82 83
83 84 //add preview
84 85 $(cln).fadeIn(200);
85 86 $('body').append(cln);
86 87 }
87 88
88 89 function delPostPreview(e) {
89 90 var el = $x('ancestor-or-self::*[starts-with(@id,"pstprev")]', e.relatedTarget);
90 91 if(!el) $each($X('.//div[starts-with(@id,"pstprev")]'), function(clone) {
91 92 $del(clone)
92 93 });
93 94 else while(el.nextSibling) $del(el.nextSibling);
94 95 }
95 96
96 97 function addPreview() {
97 98 $('.post').find('a').each(function() {
98 99 showPostPreview($(this));
99 100 });
100 101 }
@@ -1,332 +1,334 b''
1 1 /*
2 2 @licstart The following is the entire license notice for the
3 3 JavaScript code in this page.
4 4
5 5
6 6 Copyright (C) 2013-2014 neko259
7 7
8 8 The JavaScript code in this page is free software: you can
9 9 redistribute it and/or modify it under the terms of the GNU
10 10 General Public License (GNU GPL) as published by the Free Software
11 11 Foundation, either version 3 of the License, or (at your option)
12 12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15 15
16 16 As additional permission under GNU GPL version 3 section 7, you
17 17 may distribute non-source (e.g., minimized or compacted) forms of
18 18 that code without the copy of the GNU GPL normally required by
19 19 section 4, provided you include this license notice and a URL
20 20 through which recipients can access the Corresponding Source.
21 21
22 22 @licend The above is the entire license notice
23 23 for the JavaScript code in this page.
24 24 */
25 25
26 26 var wsUser = '';
27 27
28 28 var unreadPosts = 0;
29 29 var documentOriginalTitle = '';
30 30
31 31 // Thread ID does not change, can be stored one time
32 32 var threadId = $('div.thread').children('.post').first().attr('id');
33 33
34 34 /**
35 35 * Connect to websocket server and subscribe to thread updates. On any update we
36 36 * request a thread diff.
37 37 *
38 38 * @returns {boolean} true if connected, false otherwise
39 39 */
40 40 function connectWebsocket() {
41 41 var metapanel = $('.metapanel')[0];
42 42
43 43 var wsHost = metapanel.getAttribute('data-ws-host');
44 44 var wsPort = metapanel.getAttribute('data-ws-port');
45 45
46 46 if (wsHost.length > 0 && wsPort.length > 0)
47 47 var centrifuge = new Centrifuge({
48 48 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
49 49 "project": metapanel.getAttribute('data-ws-project'),
50 50 "user": wsUser,
51 51 "timestamp": metapanel.getAttribute('data-last-update'),
52 52 "token": metapanel.getAttribute('data-ws-token'),
53 53 "debug": false
54 54 });
55 55
56 56 centrifuge.on('error', function(error_message) {
57 57 console.log("Error connecting to websocket server.");
58 58 return false;
59 59 });
60 60
61 61 centrifuge.on('connect', function() {
62 62 var channelName = 'thread:' + threadId;
63 63 centrifuge.subscribe(channelName, function(message) {
64 64 getThreadDiff();
65 65 });
66 66
67 67 // For the case we closed the browser and missed some updates
68 68 getThreadDiff();
69 69 $('#autoupdate').hide();
70 70 });
71 71
72 72 centrifuge.connect();
73 73
74 74 return true;
75 75 }
76 76
77 77 /**
78 78 * Get diff of the posts from the current thread timestamp.
79 79 * This is required if the browser was closed and some post updates were
80 80 * missed.
81 81 */
82 82 function getThreadDiff() {
83 83 var lastUpdateTime = $('.metapanel').attr('data-last-update');
84 84
85 85 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
86 86
87 87 $.getJSON(diffUrl)
88 88 .success(function(data) {
89 89 var addedPosts = data.added;
90 90
91 91 for (var i = 0; i < addedPosts.length; i++) {
92 92 var postText = addedPosts[i];
93 93 var post = $(postText);
94 94
95 95 updatePost(post)
96 96 }
97 97
98 98 var updatedPosts = data.updated;
99 99
100 100 for (var i = 0; i < updatedPosts.length; i++) {
101 101 var postText = updatedPosts[i];
102 102 var post = $(postText);
103 103
104 104 updatePost(post)
105 105 }
106 106
107 107 // TODO Process removed posts if any
108 108 $('.metapanel').attr('data-last-update', data.last_update);
109 109 })
110 110 }
111 111
112 112 /**
113 113 * Add or update the post on html page.
114 114 */
115 115 function updatePost(postHtml) {
116 116 // This needs to be set on start because the page is scrolled after posts
117 117 // are added or updated
118 118 var bottom = isPageBottom();
119 119
120 120 var post = $(postHtml);
121 121
122 122 var threadBlock = $('div.thread');
123 123
124 124 var lastUpdate = '';
125 125
126 126 var postId = post.attr('id');
127 127
128 128 // If the post already exists, replace it. Otherwise add as a new one.
129 129 var existingPosts = threadBlock.children('.post[id=' + postId + ']');
130 130
131 131 if (existingPosts.size() > 0) {
132 132 existingPosts.replaceWith(post);
133 133 } else {
134 134 var threadPosts = threadBlock.children('.post');
135 135 var lastPost = threadPosts.last();
136 136
137 137 post.appendTo(lastPost.parent());
138 138
139 139 updateBumplimitProgress(1);
140 140 showNewPostsTitle(1);
141 141
142 142 lastUpdate = post.children('.post-info').first()
143 .children('.pub_time').first().text();
143 .children('.pub_time').first().html();
144 144
145 145 if (bottom) {
146 146 scrollToBottom();
147 147 }
148 148 }
149 149
150 150 processNewPost(post);
151 151 updateMetadataPanel(lastUpdate)
152 152 }
153 153
154 154 /**
155 155 * Initiate a blinking animation on a node to show it was updated.
156 156 */
157 157 function blink(node) {
158 158 var blinkCount = 2;
159 159
160 160 var nodeToAnimate = node;
161 161 for (var i = 0; i < blinkCount; i++) {
162 162 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
163 163 }
164 164 }
165 165
166 166 function isPageBottom() {
167 167 var scroll = $(window).scrollTop() / ($(document).height()
168 168 - $(window).height());
169 169
170 170 return scroll == 1
171 171 }
172 172
173 173 function initAutoupdate() {
174 174 return connectWebsocket();
175 175 }
176 176
177 177 function getReplyCount() {
178 178 return $('.thread').children('.post').length
179 179 }
180 180
181 181 function getImageCount() {
182 182 return $('.thread').find('img').length
183 183 }
184 184
185 185 /**
186 186 * Update post count, images count and last update time in the metadata
187 187 * panel.
188 188 */
189 189 function updateMetadataPanel(lastUpdate) {
190 190 var replyCountField = $('#reply-count');
191 191 var imageCountField = $('#image-count');
192 192
193 193 replyCountField.text(getReplyCount());
194 194 imageCountField.text(getImageCount());
195 195
196 196 if (lastUpdate !== '') {
197 197 var lastUpdateField = $('#last-update');
198 lastUpdateField.text(lastUpdate);
198 lastUpdateField.html(lastUpdate);
199 translate_time(lastUpdateField);
199 200 blink(lastUpdateField);
200 201 }
201 202
202 203 blink(replyCountField);
203 204 blink(imageCountField);
204 205 }
205 206
206 207 /**
207 208 * Update bumplimit progress bar
208 209 */
209 210 function updateBumplimitProgress(postDelta) {
210 211 var progressBar = $('#bumplimit_progress');
211 212 if (progressBar) {
212 213 var postsToLimitElement = $('#left_to_limit');
213 214
214 215 var oldPostsToLimit = parseInt(postsToLimitElement.text());
215 216 var postCount = getReplyCount();
216 217 var bumplimit = postCount - postDelta + oldPostsToLimit;
217 218
218 219 var newPostsToLimit = bumplimit - postCount;
219 220 if (newPostsToLimit <= 0) {
220 221 $('.bar-bg').remove();
221 222 $('.thread').children('.post').addClass('dead_post');
222 223 } else {
223 224 postsToLimitElement.text(newPostsToLimit);
224 225 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
225 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 232 */
232 233 function showNewPostsTitle(newPostCount) {
233 234 if (document.hidden) {
234 235 if (documentOriginalTitle === '') {
235 236 documentOriginalTitle = document.title;
236 237 }
237 238 unreadPosts = unreadPosts + newPostCount;
238 239 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
239 240
240 241 document.addEventListener('visibilitychange', function() {
241 242 if (documentOriginalTitle !== '') {
242 243 document.title = documentOriginalTitle;
243 244 documentOriginalTitle = '';
244 245 unreadPosts = 0;
245 246 }
246 247
247 248 document.removeEventListener('visibilitychange', null);
248 249 });
249 250 }
250 251 }
251 252
252 253 /**
253 254 * Clear all entered values in the form fields
254 255 */
255 256 function resetForm(form) {
256 257 form.find('input:text, input:password, input:file, select, textarea').val('');
257 258 form.find('input:radio, input:checkbox')
258 259 .removeAttr('checked').removeAttr('selected');
259 260 $('.file_wrap').find('.file-thumb').remove();
260 261 }
261 262
262 263 /**
263 264 * When the form is posted, this method will be run as a callback
264 265 */
265 266 function updateOnPost(response, statusText, xhr, form) {
266 267 var json = $.parseJSON(response);
267 268 var status = json.status;
268 269
269 270 showAsErrors(form, '');
270 271
271 272 if (status === 'ok') {
272 273 resetForm(form);
273 274 getThreadDiff();
274 275 } else {
275 276 var errors = json.errors;
276 277 for (var i = 0; i < errors.length; i++) {
277 278 var fieldErrors = errors[i];
278 279
279 280 var error = fieldErrors.errors;
280 281
281 282 showAsErrors(form, error);
282 283 }
283 284 }
284 285
285 286 scrollToBottom();
286 287 }
287 288
288 289 /**
289 290 * Show text in the errors row of the form.
290 291 * @param form
291 292 * @param text
292 293 */
293 294 function showAsErrors(form, text) {
294 295 form.children('.form-errors').remove();
295 296
296 297 if (text.length > 0) {
297 298 var errorList = $('<div class="form-errors">' + text + '<div>');
298 299 errorList.appendTo(form);
299 300 }
300 301 }
301 302
302 303 /**
303 304 * Run js methods that are usually run on the document, on the new post
304 305 */
305 306 function processNewPost(post) {
306 307 addRefLinkPreview(post[0]);
307 308 highlightCode(post);
309 translate_time(post);
308 310 blink(post);
309 311 }
310 312
311 313 $(document).ready(function(){
312 314 if (initAutoupdate()) {
313 315 // Post form data over AJAX
314 316 var threadId = $('div.thread').children('.post').first().attr('id');
315 317
316 318 var form = $('#form');
317 319
318 320 var options = {
319 321 beforeSubmit: function(arr, $form, options) {
320 322 showAsErrors($('form'), gettext('Sending message...'));
321 323 },
322 324 success: updateOnPost,
323 325 url: '/api/add_post/' + threadId + '/'
324 326 };
325 327
326 328 form.ajaxForm(options);
327 329
328 330 resetForm(form);
329 331 }
330 332
331 333 $('#autoupdate').click(getThreadDiff);
332 334 });
@@ -1,102 +1,102 b''
1 1 {% load i18n %}
2 2 {% load board %}
3 3 {% load cache %}
4 4
5 5 {% get_current_language as LANGUAGE_CODE %}
6 6
7 7 {% if thread.archived %}
8 8 <div class="post archive_post" id="{{ post.id }}">
9 9 {% elif bumpable %}
10 10 <div class="post" id="{{ post.id }}">
11 11 {% else %}
12 12 <div class="post dead_post" id="{{ post.id }}">
13 13 {% endif %}
14 14
15 15 <div class="post-info">
16 16 <a class="post_id" href="{{ post.get_url }}"
17 17 {% if not truncated and not thread.archived %}
18 18 onclick="javascript:addQuickReply('{{ post.id }}'); return false;"
19 19 title="{% trans 'Quote' %}" {% endif %}>({{ post.get_absolute_id }})</a>
20 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 22 {% comment %}
23 23 Thread death time needs to be shown only if the thread is alredy archived
24 24 and this is an opening post (thread death time) or a post for popup
25 25 (we don't see OP here so we show the death time in the post itself).
26 26 {% endcomment %}
27 27 {% if thread.archived %}
28 28 {% if is_opening %}
29 29 β€” {{ thread.bump_time }}
30 30 {% endif %}
31 31 {% endif %}
32 32 {% if is_opening and need_open_link %}
33 33 {% if thread.archived %}
34 34 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
35 35 {% else %}
36 36 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
37 37 {% endif %}
38 38 {% endif %}
39 39
40 40 {% if post.global_id %}
41 41 <a class="global-id" href="
42 42 {% url 'post_sync_data' post.id %}"> [RAW] </a>
43 43 {% endif %}
44 44
45 45 {% if moderator %}
46 46 <span class="moderator_info">
47 47 [<a href="{% url 'admin:boards_post_change' post.id %}">{% trans 'Edit' %}</a>]
48 48 {% if is_opening %}
49 49 [<a href="{% url 'admin:boards_thread_change' thread.id %}">{% trans 'Edit thread' %}</a>]
50 50 {% endif %}
51 51 </span>
52 52 {% endif %}
53 53 </div>
54 54 {% comment %}
55 55 Post images. Currently only 1 image can be posted and shown, but post model
56 56 supports multiple.
57 57 {% endcomment %}
58 58 {% if post.images.exists %}
59 59 {% with post.images.all.0 as image %}
60 60 {% autoescape off %}
61 61 {{ image.get_view }}
62 62 {% endautoescape %}
63 63 {% endwith %}
64 64 {% endif %}
65 65 {% comment %}
66 66 Post message (text)
67 67 {% endcomment %}
68 68 <div class="message">
69 69 {% autoescape off %}
70 70 {% if truncated %}
71 {{ post.get_text|truncatewords_html:50|truncate_lines:3 }}
71 {{ post.get_text|truncatewords_html:50 }}
72 72 {% else %}
73 73 {{ post.get_text }}
74 74 {% endif %}
75 75 {% endautoescape %}
76 76 {% if post.is_referenced %}
77 77 <div class="refmap">
78 78 {% autoescape off %}
79 79 {% trans "Replies" %}: {{ post.refmap }}
80 80 {% endautoescape %}
81 81 </div>
82 82 {% endif %}
83 83 </div>
84 84 {% comment %}
85 85 Thread metadata: counters, tags etc
86 86 {% endcomment %}
87 87 {% if is_opening %}
88 88 <div class="metadata">
89 89 {% if is_opening and need_open_link %}
90 90 {{ thread.get_reply_count }} {% trans 'messages' %},
91 91 {{ thread.get_images_count }} {% trans 'images' %}.
92 92 {% endif %}
93 93 <span class="tags">
94 94 {% autoescape off %}
95 95 {% for tag in thread.get_tags %}
96 96 {{ tag.get_view }}{% if not forloop.last %},{% endif %}
97 97 {% endfor %}
98 98 {% endautoescape %}
99 99 </span>
100 100 </div>
101 101 {% endif %}
102 102 </div>
@@ -1,188 +1,188 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load board %}
6 6 {% load static %}
7 7
8 8 {% block head %}
9 9 {% if tag %}
10 10 <title>{{ tag.name }} - {{ site_name }}</title>
11 11 {% else %}
12 12 <title>{{ site_name }}</title>
13 13 {% endif %}
14 14
15 15 {% if current_page.has_previous %}
16 16 <link rel="prev" href="
17 17 {% if tag %}
18 18 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
19 19 {% else %}
20 20 {% url "index" page=current_page.previous_page_number %}
21 21 {% endif %}
22 22 " />
23 23 {% endif %}
24 24 {% if current_page.has_next %}
25 25 <link rel="next" href="
26 26 {% if tag %}
27 27 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
28 28 {% else %}
29 29 {% url "index" page=current_page.next_page_number %}
30 30 {% endif %}
31 31 " />
32 32 {% endif %}
33 33
34 34 {% endblock %}
35 35
36 36 {% block content %}
37 37
38 38 {% get_current_language as LANGUAGE_CODE %}
39 39
40 40 {% if tag %}
41 41 <div class="tag_info">
42 42 <h2>
43 43 {% if is_favorite %}
44 44 <a href="{% url 'tag' tag.name %}?method=unsubscribe&next={{ request.path }}"
45 45 class="fav" rel="nofollow">β˜…</a>
46 46 {% else %}
47 47 <a href="{% url 'tag' tag.name %}?method=subscribe&next={{ request.path }}"
48 48 class="not_fav" rel="nofollow">β˜…</a>
49 49 {% endif %}
50 50 {% if is_hidden %}
51 51 <a href="{% url 'tag' tag.name %}?method=unhide&next={{ request.path }}"
52 52 title="{% trans 'Show tag' %}"
53 53 class="fav" rel="nofollow">H</a>
54 54 {% else %}
55 55 <a href="{% url 'tag' tag.name %}?method=hide&next={{ request.path }}"
56 56 title="{% trans 'Hide tag' %}"
57 57 class="not_fav" rel="nofollow">H</a>
58 58 {% endif %}
59 59 {% autoescape off %}
60 60 {{ tag.get_view }}
61 61 {% endautoescape %}
62 62 {% if moderator %}
63 63 <span class="moderator_info">[<a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a>]</span>
64 64 {% endif %}
65 65 </h2>
66 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 67 </div>
68 68 {% endif %}
69 69
70 70 {% if threads %}
71 71 {% if current_page.has_previous %}
72 72 <div class="page_link">
73 73 <a href="
74 74 {% if tag %}
75 75 {% url "tag" tag_name=tag.name page=current_page.previous_page_number %}
76 76 {% else %}
77 77 {% url "index" page=current_page.previous_page_number %}
78 78 {% endif %}
79 79 ">{% trans "Previous page" %}</a>
80 80 </div>
81 81 {% endif %}
82 82
83 83 {% for thread in threads %}
84 84 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
85 85 <div class="thread">
86 86 {% post_view thread.get_opening_post moderator is_opening=True thread=thread truncated=True need_open_link=True %}
87 87 {% if not thread.archived %}
88 88 {% with last_replies=thread.get_last_replies %}
89 89 {% if last_replies %}
90 90 {% if thread.get_skipped_replies_count %}
91 91 <div class="skipped_replies">
92 92 <a href="{% url 'thread' thread.get_opening_post.id %}">
93 93 {% blocktrans with count=thread.get_skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
94 94 </a>
95 95 </div>
96 96 {% endif %}
97 97 <div class="last-replies">
98 98 {% for post in last_replies %}
99 99 {% post_view post is_opening=False moderator=moderator truncated=True %}
100 100 {% endfor %}
101 101 </div>
102 102 {% endif %}
103 103 {% endwith %}
104 104 {% endif %}
105 105 </div>
106 106 {% endcache %}
107 107 {% endfor %}
108 108
109 109 {% if current_page.has_next %}
110 110 <div class="page_link">
111 111 <a href="
112 112 {% if tag %}
113 113 {% url "tag" tag_name=tag.name page=current_page.next_page_number %}
114 114 {% else %}
115 115 {% url "index" page=current_page.next_page_number %}
116 116 {% endif %}
117 117 ">{% trans "Next page" %}</a>
118 118 </div>
119 119 {% endif %}
120 120 {% else %}
121 121 <div class="post">
122 122 {% trans 'No threads exist. Create the first one!' %}</div>
123 123 {% endif %}
124 124
125 125 <div class="post-form-w">
126 126 <script src="{% static 'js/panel.js' %}"></script>
127 127 <div class="post-form">
128 128 <div class="form-title">{% trans "Create new thread" %}</div>
129 129 <div class="swappable-form-full">
130 <form enctype="multipart/form-data" method="post"id="form">{% csrf_token %}
130 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
131 131 {{ form.as_div }}
132 132 <div class="form-submit">
133 133 <input type="submit" value="{% trans "Post" %}"/>
134 134 </div>
135 135 (ctrl-enter)
136 136 </form>
137 137 </div>
138 138 <div>
139 139 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
140 140 </div>
141 141 <div><a href="{% url "staticpage" name="help" %}">
142 142 {% trans 'Text syntax' %}</a></div>
143 143 </div>
144 144 </div>
145 145
146 146 <script src="{% static 'js/form.js' %}"></script>
147 147
148 148 {% endblock %}
149 149
150 150 {% block metapanel %}
151 151
152 152 <span class="metapanel">
153 153 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
154 154 {% trans "Pages:" %}
155 155 <a href="
156 156 {% if tag %}
157 157 {% url "tag" tag_name=tag.name page=paginator.page_range|first %}
158 158 {% else %}
159 159 {% url "index" page=paginator.page_range|first %}
160 160 {% endif %}
161 161 ">&lt;&lt;</a>
162 162 [
163 163 {% for page in paginator.center_range %}
164 164 <a
165 165 {% ifequal page current_page.number %}
166 166 class="current_page"
167 167 {% endifequal %}
168 168 href="
169 169 {% if tag %}
170 170 {% url "tag" tag_name=tag.name page=page %}
171 171 {% else %}
172 172 {% url "index" page=page %}
173 173 {% endif %}
174 174 ">{{ page }}</a>
175 175 {% if not forloop.last %},{% endif %}
176 176 {% endfor %}
177 177 ]
178 178 <a href="
179 179 {% if tag %}
180 180 {% url "tag" tag_name=tag.name page=paginator.page_range|last %}
181 181 {% else %}
182 182 {% url "index" page=paginator.page_range|last %}
183 183 {% endif %}
184 184 ">&gt;&gt;</a>
185 185 [<a href="rss/">RSS</a>]
186 186 </span>
187 187
188 188 {% endblock %}
@@ -1,93 +1,93 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load static from staticfiles %}
6 6 {% load board %}
7 7
8 8 {% block head %}
9 9 <title>{{ opening_post.get_title|striptags|truncatewords:10 }}
10 10 - {{ site_name }}</title>
11 11 {% endblock %}
12 12
13 13 {% block content %}
14 14 {% get_current_language as LANGUAGE_CODE %}
15 15
16 16 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
17 17
18 18 <div class="image-mode-tab">
19 19 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 20 <a href="{% url 'thread_gallery' opening_post.id %}">{% trans 'Gallery mode' %}</a>
21 21 </div>
22 22
23 23 {% if bumpable %}
24 24 <div class="bar-bg">
25 25 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
26 26 </div>
27 27 <div class="bar-text">
28 28 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
29 29 </div>
30 30 </div>
31 31 {% endif %}
32 32
33 33 <div class="thread">
34 34 {% with can_bump=thread.can_bump %}
35 35 {% for post in thread.get_replies %}
36 36 {% with is_opening=forloop.first %}
37 37 {% post_view post moderator=moderator is_opening=is_opening bumpable=can_bump opening_post_id=opening_post.id %}
38 38 {% endwith %}
39 39 {% endfor %}
40 40 {% endwith %}
41 41 </div>
42 42
43 43 {% if not thread.archived %}
44 44 <div class="post-form-w">
45 45 <script src="{% static 'js/panel.js' %}"></script>
46 46 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
47 47 <div class="post-form" id="compact-form">
48 48 <div class="swappable-form-full">
49 49 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
50 50 <div class="compact-form-text"></div>
51 51 {{ form.as_div }}
52 52 <div class="form-submit">
53 53 <input type="submit" value="{% trans "Post" %}"/>
54 54 </div>
55 55 (ctrl-enter)
56 56 </form>
57 57 </div>
58 58 <div><a href="{% url "staticpage" name="help" %}">
59 59 {% trans 'Text syntax' %}</a></div>
60 60 </div>
61 61 </div>
62 62
63 63 <script src="{% static 'js/jquery.form.min.js' %}"></script>
64 64 <script src="{% static 'js/thread_update.js' %}"></script>
65 65 <script src="{% static 'js/3party/centrifuge.js' %}"></script>
66 66 {% endif %}
67 67
68 68 <script src="{% static 'js/form.js' %}"></script>
69 69 <script src="{% static 'js/thread.js' %}"></script>
70 70
71 71 {% endcache %}
72 72 {% endblock %}
73 73
74 74 {% block metapanel %}
75 75
76 76 {% get_current_language as LANGUAGE_CODE %}
77 77
78 78 <span class="metapanel"
79 79 data-last-update="{{ last_update }}"
80 80 data-ws-token="{{ ws_token }}"
81 81 data-ws-project="{{ ws_project }}"
82 82 data-ws-host="{{ ws_host }}"
83 83 data-ws-port="{{ ws_port }}">
84 84 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
85 85 <button id="autoupdate">{% trans 'Update' %}</button>
86 86 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'messages' %},
87 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 89 [<a href="rss/">RSS</a>]
90 90 {% endcache %}
91 91 </span>
92 92
93 93 {% endblock %}
@@ -1,66 +1,66 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load static from staticfiles %}
6 6 {% load board %}
7 7
8 8 {% block head %}
9 9 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }}
10 10 - {{ site_name }}</title>
11 11 {% endblock %}
12 12
13 13 {% block content %}
14 14 {% spaceless %}
15 15 {% get_current_language as LANGUAGE_CODE %}
16 16
17 17 {% cache 600 thread_gallery_view thread.id thread.last_edit_time LANGUAGE_CODE request.get_host %}
18 18 <div class="image-mode-tab">
19 19 <a href="{% url 'thread' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 20 <a class="current_mode" href="{% url 'thread_gallery' thread.get_opening_post.id %}">{% trans 'Gallery mode' %}</a>
21 21 </div>
22 22
23 23 <div id="posts-table">
24 24 {% for post in posts %}
25 25 <div class="gallery_image">
26 26 {% with post.get_first_image as image %}
27 27 <div>
28 28 <a
29 29 class="thumb"
30 30 href="{{ image.image.url }}"><img
31 31 src="{{ image.image.url_200x150 }}"
32 32 alt="{{ post.id }}"
33 33 width="{{ image.pre_width }}"
34 34 height="{{ image.pre_height }}"
35 35 data-width="{{ image.width }}"
36 36 data-height="{{ image.height }}"/>
37 37 </a>
38 38 </div>
39 39 <div class="gallery_image_metadata">
40 40 {{ image.width }}x{{ image.height }}
41 41 {% image_actions image.image.url request.get_host %}
42 42 </div>
43 43 {% endwith %}
44 44 </div>
45 45 {% endfor %}
46 46 </div>
47 47 {% endcache %}
48 48
49 49 {% endspaceless %}
50 50 {% endblock %}
51 51
52 52 {% block metapanel %}
53 53
54 54 {% get_current_language as LANGUAGE_CODE %}
55 55
56 56 <span class="metapanel" data-last-update="{{ last_update }}">
57 57 {% cache 600 thread_gallery_meta thread.last_edit_time moderator LANGUAGE_CODE %}
58 58 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }}
59 59 {% trans 'messages' %},
60 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 62 [<a href="rss/">RSS</a>]
63 63 {% endcache %}
64 64 </span>
65 65
66 66 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now