##// END OF EJS Templates
Updating last_update time if the thread in autoupdate
neko259 -
r536:886da067 default
parent child Browse files
Show More
@@ -1,241 +1,270 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 var THREAD_UPDATE_DELAY = 10000;
26 var THREAD_UPDATE_DELAY = 10000;
27
27
28 var loading = false;
28 var loading = false;
29 var lastUpdateTime = null;
29 var lastUpdateTime = null;
30 var unreadPosts = 0
30 var unreadPosts = 0
31
31
32 function blink(node) {
32 function blink(node) {
33 var blinkCount = 2;
33 var blinkCount = 2;
34
34
35 var nodeToAnimate = node;
35 var nodeToAnimate = node;
36 for (var i = 0; i < blinkCount; i++) {
36 for (var i = 0; i < blinkCount; i++) {
37 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
37 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
38 }
38 }
39 }
39 }
40
40
41 function updateThread() {
41 function updateThread() {
42 if (loading) {
42 if (loading) {
43 return;
43 return;
44 }
44 }
45
45
46 loading = true;
46 loading = true;
47
47
48 var threadPosts = $('div.thread').children('.post');
48 var threadPosts = $('div.thread').children('.post');
49
49
50 var lastPost = threadPosts.last();
50 var lastPost = threadPosts.last();
51 var threadId = threadPosts.first().attr('id');
51 var threadId = threadPosts.first().attr('id');
52
52
53 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
53 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
54 $.getJSON(diffUrl)
54 $.getJSON(diffUrl)
55 .success(function(data) {
55 .success(function(data) {
56 var bottom = isPageBottom();
56 var bottom = isPageBottom();
57
57
58 var lastUpdate = '';
59
58 var addedPosts = data.added;
60 var addedPosts = data.added;
59 for (var i = 0; i < addedPosts.length; i++) {
61 for (var i = 0; i < addedPosts.length; i++) {
60 var postText = addedPosts[i];
62 var postText = addedPosts[i];
61
63
62 var post = $(postText);
64 var post = $(postText);
65
66 if (lastUpdate === '') {
67 lastUpdate = post.find('.pub_time').text();
68 }
69
63 post.appendTo(lastPost.parent());
70 post.appendTo(lastPost.parent());
64 addRefLinkPreview(post[0]);
71 addRefLinkPreview(post[0]);
65
72
66 lastPost = post;
73 lastPost = post;
67 blink(post);
74 blink(post);
68 }
75 }
69
76
70 var updatedPosts = data.updated;
77 var updatedPosts = data.updated;
71 for (var i = 0; i < updatedPosts.length; i++) {
78 for (var i = 0; i < updatedPosts.length; i++) {
72 var postText = updatedPosts[i];
79 var postText = updatedPosts[i];
73
80
74 var post = $(postText);
81 var post = $(postText);
82
83 if (lastUpdate === '') {
84 lastUpdate = post.find('.pub_time').text();
85 }
86
75 var postId = post.attr('id');
87 var postId = post.attr('id');
76
88
77 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
89 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
78
90
79 oldPost.replaceWith(post);
91 oldPost.replaceWith(post);
80 addRefLinkPreview(post[0]);
92 addRefLinkPreview(post[0]);
81
93
82 blink(post);
94 blink(post);
83 }
95 }
84
96
85 // TODO Process deleted posts
97 // TODO Process deleted posts
86
98
87 lastUpdateTime = data.last_update;
99 lastUpdateTime = data.last_update;
88 loading = false;
100 loading = false;
89
101
90 if (bottom) {
102 if (bottom) {
91 var $target = $('html,body');
103 var $target = $('html,body');
92 $target.animate({scrollTop: $target.height()}, 1000);
104 $target.animate({scrollTop: $target.height()}, 1000);
93 }
105 }
94
106
95 $('#reply-count').text(getReplyCount());
107 var hasPostChanges = (updatedPosts.length > 0)
96 $('#image-count').text(getImageCount());
108 || (addedPosts.length > 0);
109 if (hasPostChanges) {
110 updateMetadataPanel(lastUpdate);
111 }
97
112
98 updateBumplimitProgress(data.added.length);
113 updateBumplimitProgress(data.added.length);
99 updatePostBumpableStatus();
114 updatePostBumpableStatus();
100
115
101 if (data.added.length + data.updated.length > 0) {
116 if (data.added.length + data.updated.length > 0) {
102 showNewPostsTitle(data.added.length);
117 showNewPostsTitle(data.added.length);
103 }
118 }
104 })
119 })
105 .error(function(data) {
120 .error(function(data) {
106 // TODO Show error message that server is unavailable?
121 // TODO Show error message that server is unavailable?
107
122
108 loading = false;
123 loading = false;
109 });
124 });
110 }
125 }
111
126
112 function isPageBottom() {
127 function isPageBottom() {
113 var scroll = $(window).scrollTop() / ($(document).height()
128 var scroll = $(window).scrollTop() / ($(document).height()
114 - $(window).height())
129 - $(window).height())
115
130
116 return scroll == 1
131 return scroll == 1
117 }
132 }
118
133
119 function initAutoupdate() {
134 function initAutoupdate() {
120 loading = false;
135 loading = false;
121
136
122 lastUpdateTime = $('.metapanel').attr('data-last-update');
137 lastUpdateTime = $('.metapanel').attr('data-last-update');
123
138
124 setInterval(updateThread, THREAD_UPDATE_DELAY);
139 setInterval(updateThread, THREAD_UPDATE_DELAY);
125 }
140 }
126
141
127 function getReplyCount() {
142 function getReplyCount() {
128 return $('.thread').children('.post').length
143 return $('.thread').children('.post').length
129 }
144 }
130
145
131 function getImageCount() {
146 function getImageCount() {
132 return $('.thread').find('img').length
147 return $('.thread').find('img').length
133 }
148 }
134
149
150 function updateMetadataPanel(lastUpdate) {
151 var replyCountField = $('#reply-count');
152 var imageCountField = $('#image-count');
153
154 replyCountField.text(getReplyCount());
155 imageCountField.text(getImageCount());
156
157 if (lastUpdate !== '') {
158 var lastUpdateField = $('#last-update');
159 lastUpdateField.text(lastUpdate);
160 blink(lastUpdateField);
161 }
162
163 blink(replyCountField);
164 blink(imageCountField);
165 }
166
135 /**
167 /**
136 * Update bumplimit progress bar
168 * Update bumplimit progress bar
137 */
169 */
138 function updateBumplimitProgress(postDelta) {
170 function updateBumplimitProgress(postDelta) {
139 var progressBar = $('#bumplimit_progress');
171 var progressBar = $('#bumplimit_progress');
140 if (progressBar) {
172 if (progressBar) {
141 var postsToLimitElement = $('#left_to_limit');
173 var postsToLimitElement = $('#left_to_limit');
142
174
143 var oldPostsToLimit = parseInt(postsToLimitElement.text());
175 var oldPostsToLimit = parseInt(postsToLimitElement.text());
144 var postCount = getReplyCount();
176 var postCount = getReplyCount();
145 var bumplimit = postCount - postDelta + oldPostsToLimit;
177 var bumplimit = postCount - postDelta + oldPostsToLimit;
146
178
147 var newPostsToLimit = bumplimit - postCount;
179 var newPostsToLimit = bumplimit - postCount;
148 if (newPostsToLimit <= 0) {
180 if (newPostsToLimit <= 0) {
149 $('.bar-bg').remove();
181 $('.bar-bg').remove();
150 } else {
182 } else {
151 postsToLimitElement.text(newPostsToLimit);
183 postsToLimitElement.text(newPostsToLimit);
152 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
184 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
153 }
185 }
154 }
186 }
155 }
187 }
156
188
157 /**
189 /**
158 * If the bumplimit is reached, add dead_post class to all posts
190 * If the bumplimit is reached, add dead_post class to all posts
159 */
191 */
160 function updatePostBumpableStatus() {
192 function updatePostBumpableStatus() {
161 var postCount = getReplyCount();
162 var postsToLimitElement = $('#left_to_limit');
193 var postsToLimitElement = $('#left_to_limit');
163 var postsToLimit = parseInt(postsToLimitElement.text());
164
194
165 if (postsToLimit <= 0) {
195 if (postsToLimitElement === null) {
166 $('.thread').find('.post').addClass('dead_post');
196 $('.thread').children('.post').addClass('dead_post');
167 }
197 }
168 }
198 }
169
199
170 var documentOriginalTitle = '';
200 var documentOriginalTitle = '';
171 /**
201 /**
172 * Show 'new posts' text in the title if the document is not visible to a user
202 * Show 'new posts' text in the title if the document is not visible to a user
173 */
203 */
174 function showNewPostsTitle(newPostCount) {
204 function showNewPostsTitle(newPostCount) {
175 if (document.hidden) {
205 if (document.hidden) {
176 if (documentOriginalTitle === '') {
206 if (documentOriginalTitle === '') {
177 documentOriginalTitle = document.title;
207 documentOriginalTitle = document.title;
178 }
208 }
179 unreadPosts = unreadPosts + newPostCount;
209 unreadPosts = unreadPosts + newPostCount;
180 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
210 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
181
211
182 document.addEventListener('visibilitychange', function() {
212 document.addEventListener('visibilitychange', function() {
183 if (documentOriginalTitle !== '') {
213 if (documentOriginalTitle !== '') {
184 document.title = documentOriginalTitle;
214 document.title = documentOriginalTitle;
185 documentOriginalTitle = '';
215 documentOriginalTitle = '';
186 unreadPosts = 0;
216 unreadPosts = 0;
187 }
217 }
188
218
189 document.removeEventListener('visibilitychange', null);
219 document.removeEventListener('visibilitychange', null);
190 });
220 });
191 }
221 }
192 }
222 }
193
223
194 /**
224 /**
195 * Clear all entered values in the form fields
225 * Clear all entered values in the form fields
196 */
226 */
197 function resetForm(form) {
227 function resetForm(form) {
198 form.find('input:text, input:password, input:file, select, textarea').val('');
228 form.find('input:text, input:password, input:file, select, textarea').val('');
199 form.find('input:radio, input:checkbox')
229 form.find('input:radio, input:checkbox')
200 .removeAttr('checked').removeAttr('selected');
230 .removeAttr('checked').removeAttr('selected');
201 }
231 }
202
232
203
233
204 $(document).ready(function(){
234 $(document).ready(function(){
205 initAutoupdate();
235 initAutoupdate();
206
236
207 // Post form data over AJAX
237 // Post form data over AJAX
208 var threadId = $('div.thread').children('.post').first().attr('id');;
238 var threadId = $('div.thread').children('.post').first().attr('id');;
209
239
210 var form = $('#form');
240 var form = $('#form');
211 var options = {
241 var options = {
212 success: updateOnPost,
242 success: updateOnPost,
213 url: '/api/add_post/' + threadId + '/',
243 url: '/api/add_post/' + threadId + '/',
214 };
244 };
215
245
216 form.ajaxForm(options);
246 form.ajaxForm(options);
217
247
218 function updateOnPost(response, statusText, xhr, $form) {
248 function updateOnPost(response, statusText, xhr, $form) {
219 var json = $.parseJSON(response);
249 var json = $.parseJSON(response);
220 var status = json.status;
250 var status = json.status;
221
251
222 form.children('.form-errors').remove();
252 form.children('.form-errors').remove();
223
253
224 if (status === 'ok') {
254 if (status === 'ok') {
225 resetForm(form);
255 resetForm(form);
226 updateThread();
256 updateThread();
227 } else {
257 } else {
228 var errors = json.errors;
258 var errors = json.errors;
229 for (var i = 0; i < errors.length; i++) {
259 for (var i = 0; i < errors.length; i++) {
230 var error = errors[i];
260 var fieldErrors = errors[i];
231
261
232 var fieldName = error.field;
262 var error = fieldErrors.errors;
233 var error = error.errors;
234
263
235 var errorList = $('<div class="form-errors">' + error
264 var errorList = $('<div class="form-errors">' + error
236 + '<div>');
265 + '<div>');
237 errorList.appendTo(form);
266 errorList.appendTo(form);
238 }
267 }
239 }
268 }
240 }
269 }
241 });
270 });
@@ -1,74 +1,74 b''
1 {% load i18n %}
1 {% load i18n %}
2 {% load board %}
2 {% load board %}
3
3
4 {% spaceless %}
4 {% spaceless %}
5 {% if post.thread_new.archived %}
5 {% if post.thread_new.archived %}
6 <div class="post archive_post" id="{{ post.id }}">
6 <div class="post archive_post" id="{{ post.id }}">
7 {% elif post.thread_new.can_bump %}
7 {% elif post.thread_new.can_bump %}
8 <div class="post" id="{{ post.id }}">
8 <div class="post" id="{{ post.id }}">
9 {% else %}
9 {% else %}
10 <div class="post dead_post" id="{{ post.id }}">
10 <div class="post dead_post" id="{{ post.id }}">
11 {% endif %}
11 {% endif %}
12
12
13 {% if post.image %}
13 {% if post.image %}
14 <div class="image">
14 <div class="image">
15 <a
15 <a
16 class="thumb"
16 class="thumb"
17 href="{{ post.image.url }}"><img
17 href="{{ post.image.url }}"><img
18 src="{{ post.image.url_200x150 }}"
18 src="{{ post.image.url_200x150 }}"
19 alt="{{ post.id }}"
19 alt="{{ post.id }}"
20 width="{{ post.image_pre_width }}"
20 width="{{ post.image_pre_width }}"
21 height="{{ post.image_pre_height }}"
21 height="{{ post.image_pre_height }}"
22 data-width="{{ post.image_width }}"
22 data-width="{{ post.image_width }}"
23 data-height="{{ post.image_height }}"/>
23 data-height="{{ post.image_height }}"/>
24 </a>
24 </a>
25 </div>
25 </div>
26 {% endif %}
26 {% endif %}
27 <div class="message">
27 <div class="message">
28 <div class="post-info">
28 <div class="post-info">
29 <span class="title">{{ post.title }}</span>
29 <span class="title">{{ post.title }}</span>
30 <a class="post_id" href="{% post_url post.id %}">
30 <a class="post_id" href="{% post_url post.id %}">
31 ({{ post.id }})</a>
31 ({{ post.id }}) </a>
32 [{{ post.pub_time }}]
32 [<span class="pub_time">{{ post.pub_time }}</span>]
33 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
33 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
34 ; return false;">&gt;&gt;</a>]
34 ; return false;">&gt;&gt;</a>]
35
35
36 {% if moderator %}
36 {% if moderator %}
37 <span class="moderator_info">
37 <span class="moderator_info">
38 [<a href="{% url 'delete' post_id=post.id %}"
38 [<a href="{% url 'delete' post_id=post.id %}"
39 >{% trans 'Delete' %}</a>]
39 >{% trans 'Delete' %}</a>]
40 ({{ post.poster_ip }})
40 ({{ post.poster_ip }})
41 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
41 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
42 >{% trans 'Ban IP' %}</a>]
42 >{% trans 'Ban IP' %}</a>]
43 </span>
43 </span>
44 {% endif %}
44 {% endif %}
45 </div>
45 </div>
46 {% autoescape off %}
46 {% autoescape off %}
47 {% if truncated %}
47 {% if truncated %}
48 {{ post.text.rendered|truncatewords_html:50 }}
48 {{ post.text.rendered|truncatewords_html:50 }}
49 {% else %}
49 {% else %}
50 {{ post.text.rendered }}
50 {{ post.text.rendered }}
51 {% endif %}
51 {% endif %}
52 {% endautoescape %}
52 {% endautoescape %}
53 {% if post.is_referenced %}
53 {% if post.is_referenced %}
54 <div class="refmap">
54 <div class="refmap">
55 {% trans "Replies" %}:
55 {% trans "Replies" %}:
56 {% for ref_post in post.get_sorted_referenced_posts %}
56 {% for ref_post in post.get_sorted_referenced_posts %}
57 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
57 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
58 >{% if not forloop.last %},{% endif %}
58 >{% if not forloop.last %},{% endif %}
59 {% endfor %}
59 {% endfor %}
60 </div>
60 </div>
61 {% endif %}
61 {% endif %}
62 </div>
62 </div>
63 {% if post.is_opening and post.thread_new.tags.exists %}
63 {% if post.is_opening and post.thread_new.tags.exists %}
64 <div class="metadata">
64 <div class="metadata">
65 <span class="tags">
65 <span class="tags">
66 {% for tag in post.thread_new.get_tags %}
66 {% for tag in post.thread_new.get_tags %}
67 <a class="tag" href="{% url 'tag' tag.name %}">
67 <a class="tag" href="{% url 'tag' tag.name %}">
68 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
68 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
69 {% endfor %}
69 {% endfor %}
70 </span>
70 </span>
71 </div>
71 </div>
72 {% endif %}
72 {% endif %}
73 </div>
73 </div>
74 {% endspaceless %}
74 {% endspaceless %}
@@ -1,145 +1,145 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 }} - Neboard</title>
9 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }} - Neboard</title>
10 {% endblock %}
10 {% endblock %}
11
11
12 {% block content %}
12 {% block content %}
13 {% spaceless %}
13 {% spaceless %}
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' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>,
19 <a class="current_mode" href="{% url 'thread' thread.get_opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 <a href="{% url 'thread_mode' thread.get_opening_post.id 'gallery' %}">{% trans 'Gallery mode' %}</a>
20 <a href="{% url 'thread_mode' thread.get_opening_post.id 'gallery' %}">{% 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 <div class="thread">
32 <div class="thread">
33 {% for post in posts %}
33 {% for post in posts %}
34 {% if bumpable %}
34 {% if bumpable %}
35 <div class="post" id="{{ post.id }}">
35 <div class="post" id="{{ post.id }}">
36 {% elif thread.archived %}
36 {% elif thread.archived %}
37 <div class="post archive_post" id="{{ post.id }}">
37 <div class="post archive_post" id="{{ post.id }}">
38 {% else %}
38 {% else %}
39 <div class="post dead_post" id="{{ post.id }}">
39 <div class="post dead_post" id="{{ post.id }}">
40 {% endif %}
40 {% endif %}
41 {% if post.image %}
41 {% if post.image %}
42 <div class="image">
42 <div class="image">
43 <a
43 <a
44 class="thumb"
44 class="thumb"
45 href="{{ post.image.url }}"><img
45 href="{{ post.image.url }}"><img
46 src="{{ post.image.url_200x150 }}"
46 src="{{ post.image.url_200x150 }}"
47 alt="{{ post.id }}"
47 alt="{{ post.id }}"
48 width="{{ post.image_pre_width }}"
48 width="{{ post.image_pre_width }}"
49 height="{{ post.image_pre_height }}"
49 height="{{ post.image_pre_height }}"
50 data-width="{{ post.image_width }}"
50 data-width="{{ post.image_width }}"
51 data-height="{{ post.image_height }}"/>
51 data-height="{{ post.image_height }}"/>
52 </a>
52 </a>
53 </div>
53 </div>
54 {% endif %}
54 {% endif %}
55 <div class="message">
55 <div class="message">
56 <div class="post-info">
56 <div class="post-info">
57 <span class="title">{{ post.title }}</span>
57 <span class="title">{{ post.title }}</span>
58 <a class="post_id" href="#{{ post.id }}">
58 <a class="post_id" href="#{{ post.id }}">
59 ({{ post.id }})</a>
59 ({{ post.id }}) </a>
60 [{{ post.pub_time }}]
60 [<span class="pub_time">{{ post.pub_time }}</span>]
61 {% if not thread.archived %}
61 {% if not thread.archived %}
62 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
62 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
63 ; return false;">&gt;&gt;</a>]
63 ; return false;">&gt;&gt;</a>]
64 {% endif %}
64 {% endif %}
65
65
66 {% if moderator %}
66 {% if moderator %}
67 <span class="moderator_info">
67 <span class="moderator_info">
68 [<a href="{% url 'delete' post_id=post.id %}"
68 [<a href="{% url 'delete' post_id=post.id %}"
69 >{% trans 'Delete' %}</a>]
69 >{% trans 'Delete' %}</a>]
70 ({{ post.poster_ip }})
70 ({{ post.poster_ip }})
71 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
71 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
72 >{% trans 'Ban IP' %}</a>]
72 >{% trans 'Ban IP' %}</a>]
73 </span>
73 </span>
74 {% endif %}
74 {% endif %}
75 </div>
75 </div>
76 {% autoescape off %}
76 {% autoescape off %}
77 {{ post.text.rendered }}
77 {{ post.text.rendered }}
78 {% endautoescape %}
78 {% endautoescape %}
79 {% if post.is_referenced %}
79 {% if post.is_referenced %}
80 <div class="refmap">
80 <div class="refmap">
81 {% trans "Replies" %}:
81 {% trans "Replies" %}:
82 {% for ref_post in post.get_sorted_referenced_posts %}
82 {% for ref_post in post.get_sorted_referenced_posts %}
83 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
83 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
84 >{% if not forloop.last %},{% endif %}
84 >{% if not forloop.last %},{% endif %}
85 {% endfor %}
85 {% endfor %}
86 </div>
86 </div>
87 {% endif %}
87 {% endif %}
88 </div>
88 </div>
89 {% if forloop.first %}
89 {% if forloop.first %}
90 <div class="metadata">
90 <div class="metadata">
91 <span class="tags">
91 <span class="tags">
92 {% for tag in thread.get_tags %}
92 {% for tag in thread.get_tags %}
93 <a class="tag" href="{% url 'tag' tag.name %}">
93 <a class="tag" href="{% url 'tag' tag.name %}">
94 #{{ tag.name }}</a
94 #{{ tag.name }}</a
95 >{% if not forloop.last %},{% endif %}
95 >{% if not forloop.last %},{% endif %}
96 {% endfor %}
96 {% endfor %}
97 </span>
97 </span>
98 </div>
98 </div>
99 {% endif %}
99 {% endif %}
100 </div>
100 </div>
101 {% endfor %}
101 {% endfor %}
102 </div>
102 </div>
103 {% endcache %}
103 {% endcache %}
104
104
105 {% if not thread.archived %}
105 {% if not thread.archived %}
106
106
107 <div class="post-form-w">
107 <div class="post-form-w">
108 <script src="{% static 'js/panel.js' %}"></script>
108 <script src="{% static 'js/panel.js' %}"></script>
109 <div class="form-title">{% trans "Reply to thread" %} #{{ thread.get_opening_post.id }}</div>
109 <div class="form-title">{% trans "Reply to thread" %} #{{ thread.get_opening_post.id }}</div>
110 <div class="post-form">
110 <div class="post-form">
111 <form id="form" enctype="multipart/form-data" method="post"
111 <form id="form" enctype="multipart/form-data" method="post"
112 >{% csrf_token %}
112 >{% csrf_token %}
113 {{ form.as_div }}
113 {{ form.as_div }}
114 <div class="form-submit">
114 <div class="form-submit">
115 <input type="submit" value="{% trans "Post" %}"/>
115 <input type="submit" value="{% trans "Post" %}"/>
116 </div>
116 </div>
117 </form>
117 </form>
118 <div><a href="{% url "staticpage" name="help" %}">
118 <div><a href="{% url "staticpage" name="help" %}">
119 {% trans 'Text syntax' %}</a></div>
119 {% trans 'Text syntax' %}</a></div>
120 </div>
120 </div>
121 </div>
121 </div>
122
122
123 <script src="{% static 'js/jquery.form.min.js' %}"></script>
123 <script src="{% static 'js/jquery.form.min.js' %}"></script>
124 <script src="{% static 'js/thread_update.js' %}"></script>
124 <script src="{% static 'js/thread_update.js' %}"></script>
125 {% endif %}
125 {% endif %}
126
126
127 <script src="{% static 'js/thread.js' %}"></script>
127 <script src="{% static 'js/thread.js' %}"></script>
128
128
129 {% endspaceless %}
129 {% endspaceless %}
130 {% endblock %}
130 {% endblock %}
131
131
132 {% block metapanel %}
132 {% block metapanel %}
133
133
134 {% get_current_language as LANGUAGE_CODE %}
134 {% get_current_language as LANGUAGE_CODE %}
135
135
136 <span class="metapanel" data-last-update="{{ last_update }}">
136 <span class="metapanel" data-last-update="{{ last_update }}">
137 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
137 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
138 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
138 <span id="reply-count">{{ thread.get_reply_count }}</span> {% trans 'replies' %},
139 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
139 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
140 {% trans 'Last update: ' %}{{ thread.last_edit_time }}
140 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
141 [<a href="rss/">RSS</a>]
141 [<a href="rss/">RSS</a>]
142 {% endcache %}
142 {% endcache %}
143 </span>
143 </span>
144
144
145 {% endblock %}
145 {% endblock %}
@@ -1,48 +1,27 b''
1 = Features =
1 = Features =
2 [DONE] Connecting tags to each other
3 [DONE] Connect posts to the replies (in messages), get rid of the JS reply map
4 [DONE] Better django admin pages to simplify admin operations
5 [DONE] Regen script to update all posts
6 [DONE] Remove jump links from refmaps
7 [DONE] Ban reasons. Split bans into 2 types "read-only" and "read
8 denied". Use second only for autoban for spam
9 [DONE] Clean up tests and make them run ALWAYS
10 [DONE] Use transactions in tests
11 [DONE] Thread autoupdate (JS + API)
12 [DONE] Split up post model into post and thread,
13 and move everything that is used only in 1st post to thread model.
14 [DONE] Show board speed in the lower panel (posts per day)
15 [DONE] Save image thumbnails size to the separate field
16
17 [NOT STARTED] Tree view (JS)
2 [NOT STARTED] Tree view (JS)
18 [NOT STARTED] Adding tags to images filename
3 [NOT STARTED] Adding tags to images filename
19 [NOT STARTED] Federative network for s2s communication
4 [NOT STARTED] Federative network for s2s communication
20 [NOT STARTED] XMPP gate
5 [NOT STARTED] XMPP gate
21 [NOT STARTED] Bitmessage gate
6 [NOT STARTED] Bitmessage gate
22 [NOT STARTED] Notification engine
7 [NOT STARTED] Notification engine
23 [NOT STARTED] Javascript disabling engine
8 [NOT STARTED] Javascript disabling engine
24 [NOT STARTED] Group tags by first letter in all tags list
9 [NOT STARTED] Group tags by first letter in all tags list
25 [NOT STARTED] Character counter in the post field
10 [NOT STARTED] Character counter in the post field
26 [NOT STARTED] Whitelist functionality. Permin autoban of an address
11 [NOT STARTED] Whitelist functionality. Permin autoban of an address
27 [NOT STARTED] Statistics module. Count views (optional, may result in bad
12 [NOT STARTED] Statistics module. Count views (optional, may result in bad
28 performance), posts per day/week/month, users (or IPs)
13 performance), posts per day/week/month, users (or IPs)
29 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
14 [NOT STARTED] Quote button next to "reply" for posts in thread to include full
30 post or its part (delimited by N characters) into quote of the new post.
15 post or its part (delimited by N characters) into quote of the new post.
31 [NOT STARTED] Ban confirmation page with reason
16 [NOT STARTED] Ban confirmation page with reason
32 [NOT STARTED] Post deletion confirmation page
17 [NOT STARTED] Post deletion confirmation page
33 [NOT STARTED] Moderating page. Tags editing and adding
18 [NOT STARTED] Moderating page. Tags editing and adding
34 [NOT STARTED] Get thread graph image using pygraphviz
19 [NOT STARTED] Get thread graph image using pygraphviz
35 [NOT STARTED] Creating post via AJAX without reloading page
36 [NOT STARTED] Subscribing to tag via AJAX
20 [NOT STARTED] Subscribing to tag via AJAX
37
21
38 = Bugs =
22 = Bugs =
39 [DONE] Fix bug with creating threads from tag view
40 [DONE] Quote characters within quote causes quote parsing to fail
41
42 [IN PROGRESS] Replies, images, last update time in bottom panel doesn't change when
43 thread updates (last update changing left)
44
23
45 = Testing =
24 = Testing =
46 [NOT STARTED] Make tests for every view
25 [NOT STARTED] Make tests for every view
47 [NOT STARTED] Make tests for every model
26 [NOT STARTED] Make tests for every model
48 [NOT STARTED] Make tests for every form
27 [NOT STARTED] Make tests for every form
General Comments 0
You need to be logged in to leave comments. Login now