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