##// END OF EJS Templates
Scroll to bottom when the form mode changes
neko259 -
r686:64d75fce default
parent child Browse files
Show More
@@ -1,43 +1,45 b''
1 var isCompact = true;
1 var isCompact = true;
2
2
3 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
3 $('input[name=image]').wrap($('<div class="file_wrap"></div>'));
4
4
5 $('body').on('change', 'input[name=image]', function(event) {
5 $('body').on('change', 'input[name=image]', function(event) {
6 var file = event.target.files[0];
6 var file = event.target.files[0];
7
7
8 if(file.type.match('image.*')) {
8 if(file.type.match('image.*')) {
9 var fileReader = new FileReader();
9 var fileReader = new FileReader();
10
10
11 fileReader.addEventListener("load", function(event) {
11 fileReader.addEventListener("load", function(event) {
12 var wrapper = $('.file_wrap');
12 var wrapper = $('.file_wrap');
13
13
14 wrapper.find('.file-thumb').remove();
14 wrapper.find('.file-thumb').remove();
15 wrapper.append(
15 wrapper.append(
16 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
16 $('<div class="file-thumb" style="background-image: url('+event.target.result+')"></div>')
17 );
17 );
18 });
18 });
19
19
20 fileReader.readAsDataURL(file);
20 fileReader.readAsDataURL(file);
21 }
21 }
22 });
22 });
23
23
24 var compactForm = $('.swappable-form-compact');
24 var compactForm = $('.swappable-form-compact');
25 var fullForm = $('.swappable-form-full');
25 var fullForm = $('.swappable-form-full');
26
26
27 function swapForm() {
27 function swapForm() {
28 compactForm.toggle();
28 compactForm.toggle();
29 fullForm.toggle();
29 fullForm.toggle();
30
30
31 if (isCompact) {
31 if (isCompact) {
32 var oldText = compactForm.find('textarea')[0].value;
32 var oldText = compactForm.find('textarea')[0].value;
33 fullForm.find('textarea')[0].value = oldText;
33 fullForm.find('textarea')[0].value = oldText;
34 } else {
34 } else {
35 var oldText = fullForm.find('textarea')[0].value;
35 var oldText = fullForm.find('textarea')[0].value;
36 compactForm.find('textarea')[0].value = oldText;
36 compactForm.find('textarea')[0].value = oldText;
37 }
37 }
38 isCompact = !isCompact;
38 isCompact = !isCompact;
39
40 scrollToBottom();
39 }
41 }
40
42
41 if (compactForm.length > 0) {
43 if (compactForm.length > 0) {
42 fullForm.toggle();
44 fullForm.toggle();
43 } No newline at end of file
45 }
@@ -1,54 +1,59 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 function moveCaretToEnd(el) {
26 function moveCaretToEnd(el) {
27 if (typeof el.selectionStart == "number") {
27 if (typeof el.selectionStart == "number") {
28 el.selectionStart = el.selectionEnd = el.value.length;
28 el.selectionStart = el.selectionEnd = el.value.length;
29 } else if (typeof el.createTextRange != "undefined") {
29 } else if (typeof el.createTextRange != "undefined") {
30 el.focus();
30 el.focus();
31 var range = el.createTextRange();
31 var range = el.createTextRange();
32 range.collapse(false);
32 range.collapse(false);
33 range.select();
33 range.select();
34 }
34 }
35 }
35 }
36
36
37 function addQuickReply(postId) {
37 function addQuickReply(postId) {
38 var textToAdd = '>>' + postId + '\n\n';
38 var textToAdd = '>>' + postId + '\n\n';
39 var selection = window.getSelection().toString();
39 var selection = window.getSelection().toString();
40 if (selection.length > 0) {
40 if (selection.length > 0) {
41 textToAdd += '> ' + selection + '\n\n';
41 textToAdd += '> ' + selection + '\n\n';
42 }
42 }
43
43
44 var textAreaId = 'textarea';
44 var textAreaId = 'textarea';
45 $(textAreaId).val($(textAreaId).val()+ textToAdd);
45 $(textAreaId).val($(textAreaId).val()+ textToAdd);
46
46
47 var textarea = document.getElementsByTagName('textarea')[0];
47 var textarea = document.getElementsByTagName('textarea')[0];
48 $(textAreaId).focus();
48 $(textAreaId).focus();
49 moveCaretToEnd(textarea);
49 moveCaretToEnd(textarea);
50
50
51 $("html, body").animate({ scrollTop: $(textAreaId).offset().top }, "slow");
51 $("html, body").animate({ scrollTop: $(textAreaId).offset().top }, "slow");
52 }
52 }
53
53
54 function scrollToBottom() {
55 var $target = $('html,body');
56 $target.animate({scrollTop: $target.height()}, 1000);
57 }
58
54 $('#full-form').toggle();
59 $('#full-form').toggle();
@@ -1,265 +1,264 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 = '';
58 var lastUpdate = '';
59
59
60 var addedPosts = data.added;
60 var addedPosts = data.added;
61 for (var i = 0; i < addedPosts.length; i++) {
61 for (var i = 0; i < addedPosts.length; i++) {
62 var postText = addedPosts[i];
62 var postText = addedPosts[i];
63
63
64 var post = $(postText);
64 var post = $(postText);
65
65
66 if (lastUpdate === '') {
66 if (lastUpdate === '') {
67 lastUpdate = post.find('.pub_time').text();
67 lastUpdate = post.find('.pub_time').text();
68 }
68 }
69
69
70 post.appendTo(lastPost.parent());
70 post.appendTo(lastPost.parent());
71 addRefLinkPreview(post[0]);
71 addRefLinkPreview(post[0]);
72
72
73 lastPost = post;
73 lastPost = post;
74 blink(post);
74 blink(post);
75 }
75 }
76
76
77 var updatedPosts = data.updated;
77 var updatedPosts = data.updated;
78 for (var i = 0; i < updatedPosts.length; i++) {
78 for (var i = 0; i < updatedPosts.length; i++) {
79 var postText = updatedPosts[i];
79 var postText = updatedPosts[i];
80
80
81 var post = $(postText);
81 var post = $(postText);
82
82
83 if (lastUpdate === '') {
83 if (lastUpdate === '') {
84 lastUpdate = post.find('.pub_time').text();
84 lastUpdate = post.find('.pub_time').text();
85 }
85 }
86
86
87 var postId = post.attr('id');
87 var postId = post.attr('id');
88
88
89 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
89 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
90
90
91 oldPost.replaceWith(post);
91 oldPost.replaceWith(post);
92 addRefLinkPreview(post[0]);
92 addRefLinkPreview(post[0]);
93
93
94 blink(post);
94 blink(post);
95 }
95 }
96
96
97 // TODO Process deleted posts
97 // TODO Process deleted posts
98
98
99 lastUpdateTime = data.last_update;
99 lastUpdateTime = data.last_update;
100 loading = false;
100 loading = false;
101
101
102 if (bottom) {
102 if (bottom) {
103 var $target = $('html,body');
103 scrollToBottom();
104 $target.animate({scrollTop: $target.height()}, 1000);
105 }
104 }
106
105
107 var hasPostChanges = (updatedPosts.length > 0)
106 var hasPostChanges = (updatedPosts.length > 0)
108 || (addedPosts.length > 0);
107 || (addedPosts.length > 0);
109 if (hasPostChanges) {
108 if (hasPostChanges) {
110 updateMetadataPanel(lastUpdate);
109 updateMetadataPanel(lastUpdate);
111 }
110 }
112
111
113 updateBumplimitProgress(data.added.length);
112 updateBumplimitProgress(data.added.length);
114
113
115 if (data.added.length + data.updated.length > 0) {
114 if (data.added.length + data.updated.length > 0) {
116 showNewPostsTitle(data.added.length);
115 showNewPostsTitle(data.added.length);
117 }
116 }
118 })
117 })
119 .error(function(data) {
118 .error(function(data) {
120 // TODO Show error message that server is unavailable?
119 // TODO Show error message that server is unavailable?
121
120
122 loading = false;
121 loading = false;
123 });
122 });
124 }
123 }
125
124
126 function isPageBottom() {
125 function isPageBottom() {
127 var scroll = $(window).scrollTop() / ($(document).height()
126 var scroll = $(window).scrollTop() / ($(document).height()
128 - $(window).height())
127 - $(window).height())
129
128
130 return scroll == 1
129 return scroll == 1
131 }
130 }
132
131
133 function initAutoupdate() {
132 function initAutoupdate() {
134 loading = false;
133 loading = false;
135
134
136 lastUpdateTime = $('.metapanel').attr('data-last-update');
135 lastUpdateTime = $('.metapanel').attr('data-last-update');
137
136
138 setInterval(updateThread, THREAD_UPDATE_DELAY);
137 setInterval(updateThread, THREAD_UPDATE_DELAY);
139 }
138 }
140
139
141 function getReplyCount() {
140 function getReplyCount() {
142 return $('.thread').children('.post').length
141 return $('.thread').children('.post').length
143 }
142 }
144
143
145 function getImageCount() {
144 function getImageCount() {
146 return $('.thread').find('img').length
145 return $('.thread').find('img').length
147 }
146 }
148
147
149 function updateMetadataPanel(lastUpdate) {
148 function updateMetadataPanel(lastUpdate) {
150 var replyCountField = $('#reply-count');
149 var replyCountField = $('#reply-count');
151 var imageCountField = $('#image-count');
150 var imageCountField = $('#image-count');
152
151
153 replyCountField.text(getReplyCount());
152 replyCountField.text(getReplyCount());
154 imageCountField.text(getImageCount());
153 imageCountField.text(getImageCount());
155
154
156 if (lastUpdate !== '') {
155 if (lastUpdate !== '') {
157 var lastUpdateField = $('#last-update');
156 var lastUpdateField = $('#last-update');
158 lastUpdateField.text(lastUpdate);
157 lastUpdateField.text(lastUpdate);
159 blink(lastUpdateField);
158 blink(lastUpdateField);
160 }
159 }
161
160
162 blink(replyCountField);
161 blink(replyCountField);
163 blink(imageCountField);
162 blink(imageCountField);
164 }
163 }
165
164
166 /**
165 /**
167 * Update bumplimit progress bar
166 * Update bumplimit progress bar
168 */
167 */
169 function updateBumplimitProgress(postDelta) {
168 function updateBumplimitProgress(postDelta) {
170 var progressBar = $('#bumplimit_progress');
169 var progressBar = $('#bumplimit_progress');
171 if (progressBar) {
170 if (progressBar) {
172 var postsToLimitElement = $('#left_to_limit');
171 var postsToLimitElement = $('#left_to_limit');
173
172
174 var oldPostsToLimit = parseInt(postsToLimitElement.text());
173 var oldPostsToLimit = parseInt(postsToLimitElement.text());
175 var postCount = getReplyCount();
174 var postCount = getReplyCount();
176 var bumplimit = postCount - postDelta + oldPostsToLimit;
175 var bumplimit = postCount - postDelta + oldPostsToLimit;
177
176
178 var newPostsToLimit = bumplimit - postCount;
177 var newPostsToLimit = bumplimit - postCount;
179 if (newPostsToLimit <= 0) {
178 if (newPostsToLimit <= 0) {
180 $('.bar-bg').remove();
179 $('.bar-bg').remove();
181 $('.thread').children('.post').addClass('dead_post');
180 $('.thread').children('.post').addClass('dead_post');
182 } else {
181 } else {
183 postsToLimitElement.text(newPostsToLimit);
182 postsToLimitElement.text(newPostsToLimit);
184 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
183 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
185 }
184 }
186 }
185 }
187 }
186 }
188
187
189 var documentOriginalTitle = '';
188 var documentOriginalTitle = '';
190 /**
189 /**
191 * Show 'new posts' text in the title if the document is not visible to a user
190 * Show 'new posts' text in the title if the document is not visible to a user
192 */
191 */
193 function showNewPostsTitle(newPostCount) {
192 function showNewPostsTitle(newPostCount) {
194 if (document.hidden) {
193 if (document.hidden) {
195 if (documentOriginalTitle === '') {
194 if (documentOriginalTitle === '') {
196 documentOriginalTitle = document.title;
195 documentOriginalTitle = document.title;
197 }
196 }
198 unreadPosts = unreadPosts + newPostCount;
197 unreadPosts = unreadPosts + newPostCount;
199 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
198 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
200
199
201 document.addEventListener('visibilitychange', function() {
200 document.addEventListener('visibilitychange', function() {
202 if (documentOriginalTitle !== '') {
201 if (documentOriginalTitle !== '') {
203 document.title = documentOriginalTitle;
202 document.title = documentOriginalTitle;
204 documentOriginalTitle = '';
203 documentOriginalTitle = '';
205 unreadPosts = 0;
204 unreadPosts = 0;
206 }
205 }
207
206
208 document.removeEventListener('visibilitychange', null);
207 document.removeEventListener('visibilitychange', null);
209 });
208 });
210 }
209 }
211 }
210 }
212
211
213 /**
212 /**
214 * Clear all entered values in the form fields
213 * Clear all entered values in the form fields
215 */
214 */
216 function resetForm(form) {
215 function resetForm(form) {
217 form.find('input:text, input:password, input:file, select, textarea').val('');
216 form.find('input:text, input:password, input:file, select, textarea').val('');
218 form.find('input:radio, input:checkbox')
217 form.find('input:radio, input:checkbox')
219 .removeAttr('checked').removeAttr('selected');
218 .removeAttr('checked').removeAttr('selected');
220 $('.file_wrap').find('.file-thumb').remove();
219 $('.file_wrap').find('.file-thumb').remove();
221 }
220 }
222
221
223 /**
222 /**
224 * When the form is posted, this method will be run as a callback
223 * When the form is posted, this method will be run as a callback
225 */
224 */
226 function updateOnPost(response, statusText, xhr, form) {
225 function updateOnPost(response, statusText, xhr, form) {
227 var json = $.parseJSON(response);
226 var json = $.parseJSON(response);
228 var status = json.status;
227 var status = json.status;
229
228
230 form.children('.form-errors').remove();
229 form.children('.form-errors').remove();
231
230
232 if (status === 'ok') {
231 if (status === 'ok') {
233 resetForm(form);
232 resetForm(form);
234 updateThread();
233 updateThread();
235 } else {
234 } else {
236 var errors = json.errors;
235 var errors = json.errors;
237 for (var i = 0; i < errors.length; i++) {
236 for (var i = 0; i < errors.length; i++) {
238 var fieldErrors = errors[i];
237 var fieldErrors = errors[i];
239
238
240 var error = fieldErrors.errors;
239 var error = fieldErrors.errors;
241
240
242 var errorList = $('<div class="form-errors">' + error
241 var errorList = $('<div class="form-errors">' + error
243 + '<div>');
242 + '<div>');
244 errorList.appendTo(form);
243 errorList.appendTo(form);
245 }
244 }
246 }
245 }
247 }
246 }
248
247
249 $(document).ready(function(){
248 $(document).ready(function(){
250 initAutoupdate();
249 initAutoupdate();
251
250
252 // Post form data over AJAX
251 // Post form data over AJAX
253 var threadId = $('div.thread').children('.post').first().attr('id');;
252 var threadId = $('div.thread').children('.post').first().attr('id');;
254
253
255 var form = $('#form');
254 var form = $('#form');
256
255
257 var options = {
256 var options = {
258 success: updateOnPost,
257 success: updateOnPost,
259 url: '/api/add_post/' + threadId + '/'
258 url: '/api/add_post/' + threadId + '/'
260 };
259 };
261
260
262 form.ajaxForm(options);
261 form.ajaxForm(options);
263
262
264 resetForm(form);
263 resetForm(form);
265 });
264 });
General Comments 0
You need to be logged in to leave comments. Login now