##// END OF EJS Templates
Speed up loading favorite list. Add reflinks to the list to see favorite...
neko259 -
r1343:dff9bff4 default
parent child Browse files
Show More
@@ -1,129 +1,126
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 FAV_POST_UPDATE_PERIOD = 10000;
26 var FAV_POST_UPDATE_PERIOD = 10000;
27
27
28 /**
28 /**
29 * An email is a hidden file to prevent spam bots from posting. It has to be
29 * An email is a hidden file to prevent spam bots from posting. It has to be
30 * hidden.
30 * hidden.
31 */
31 */
32 function hideEmailFromForm() {
32 function hideEmailFromForm() {
33 $('.form-email').parent().parent().hide();
33 $('.form-email').parent().parent().hide();
34 }
34 }
35
35
36 /**
36 /**
37 * Highlight code blocks with code highlighter
37 * Highlight code blocks with code highlighter
38 */
38 */
39 function highlightCode(node) {
39 function highlightCode(node) {
40 node.find('pre code').each(function(i, e) {
40 node.find('pre code').each(function(i, e) {
41 hljs.highlightBlock(e);
41 hljs.highlightBlock(e);
42 });
42 });
43 }
43 }
44
44
45 function updateFavPosts() {
45 function updateFavPosts() {
46 var includePostBody = $('#fav-panel').is(":visible");
46 var includePostBody = $('#fav-panel').is(":visible");
47 var url = '/api/new_posts/';
47 var url = '/api/new_posts/';
48 if (includePostBody) {
48 if (includePostBody) {
49 url += '?include_posts'
49 url += '?include_posts'
50 }
50 }
51 $.getJSON(url,
51 $.getJSON(url,
52 function(data) {
52 function(data) {
53 var allNewPostCount = 0;
53 var allNewPostCount = 0;
54
54
55 if (includePostBody) {
55 if (includePostBody) {
56 var favoriteThreadPanel = $('#fav-panel');
56 var favoriteThreadPanel = $('#fav-panel');
57 favoriteThreadPanel.empty();
57 favoriteThreadPanel.empty();
58 }
58 }
59
59
60 $.each(data, function (_, dict) {
60 $.each(data, function (_, dict) {
61 var newPostCount = dict.new_post_count;
61 var newPostCount = dict.new_post_count;
62 allNewPostCount += newPostCount;
62 allNewPostCount += newPostCount;
63
63
64 if (includePostBody) {
64 if (includePostBody) {
65 var post = $(dict.post);
66
67 var id = post.find('.post_id');
68 var title = post.find('.title');
69
70 var favThreadNode = $('<div class="post"></div>');
65 var favThreadNode = $('<div class="post"></div>');
71 favThreadNode.append(id);
66 favThreadNode.append($(dict.post_url));
72 favThreadNode.append(' ');
67 favThreadNode.append(' ');
73 favThreadNode.append(title);
68 favThreadNode.append($('<span class="title">' + dict.title + '</span>'));
74
69
75 if (newPostCount > 0) {
70 if (newPostCount > 0) {
76 favThreadNode.append(' (<a href="' + dict.newest_post_link + '">+' + newPostCount + "</a>)");
71 favThreadNode.append(' (<a href="' + dict.newest_post_link + '">+' + newPostCount + "</a>)");
77 }
72 }
78
73
79 favoriteThreadPanel.append(favThreadNode);
74 favoriteThreadPanel.append(favThreadNode);
75
76 addRefLinkPreview(favThreadNode[0]);
80 }
77 }
81 });
78 });
82
79
83 var newPostCountNode = $('#new-fav-post-count');
80 var newPostCountNode = $('#new-fav-post-count');
84 if (allNewPostCount > 0) {
81 if (allNewPostCount > 0) {
85 newPostCountNode.text('(+' + allNewPostCount + ')');
82 newPostCountNode.text('(+' + allNewPostCount + ')');
86 newPostCountNode.show();
83 newPostCountNode.show();
87 } else {
84 } else {
88 newPostCountNode.hide();
85 newPostCountNode.hide();
89 }
86 }
90 }
87 }
91 );
88 );
92 }
89 }
93
90
94 function initFavPanel() {
91 function initFavPanel() {
95 updateFavPosts();
92 updateFavPosts();
96
93
97 if ($('#fav-panel-btn').length > 0) {
94 if ($('#fav-panel-btn').length > 0) {
98 setInterval(updateFavPosts, FAV_POST_UPDATE_PERIOD);
95 setInterval(updateFavPosts, FAV_POST_UPDATE_PERIOD);
99 $('#fav-panel-btn').click(function() {
96 $('#fav-panel-btn').click(function() {
100 $('#fav-panel').toggle();
97 $('#fav-panel').toggle();
101 updateFavPosts();
98 updateFavPosts();
102
99
103 return false;
100 return false;
104 });
101 });
105
102
106 $(document).on('keyup.removepic', function(e) {
103 $(document).on('keyup.removepic', function(e) {
107 if(e.which === 27) {
104 if(e.which === 27) {
108 $('#fav-panel').hide();
105 $('#fav-panel').hide();
109 }
106 }
110 });
107 });
111 }
108 }
112 }
109 }
113
110
114 $( document ).ready(function() {
111 $( document ).ready(function() {
115 hideEmailFromForm();
112 hideEmailFromForm();
116
113
117 $("a[href='#top']").click(function() {
114 $("a[href='#top']").click(function() {
118 $("html, body").animate({ scrollTop: 0 }, "slow");
115 $("html, body").animate({ scrollTop: 0 }, "slow");
119 return false;
116 return false;
120 });
117 });
121
118
122 addImgPreview();
119 addImgPreview();
123
120
124 addRefLinkPreview();
121 addRefLinkPreview();
125
122
126 highlightCode($(document));
123 highlightCode($(document));
127
124
128 initFavPanel();
125 initFavPanel();
129 });
126 });
@@ -1,274 +1,274
1 from collections import OrderedDict
1 from collections import OrderedDict
2 import json
2 import json
3 import logging
3 import logging
4
4
5 from django.db import transaction
5 from django.db import transaction
6 from django.http import HttpResponse
6 from django.http import HttpResponse
7 from django.shortcuts import get_object_or_404
7 from django.shortcuts import get_object_or_404
8 from django.core import serializers
8 from django.core import serializers
9 from boards.abstracts.settingsmanager import get_settings_manager
9 from boards.abstracts.settingsmanager import get_settings_manager
10
10
11 from boards.forms import PostForm, PlainErrorList
11 from boards.forms import PostForm, PlainErrorList
12 from boards.models import Post, Thread, Tag
12 from boards.models import Post, Thread, Tag
13 from boards.utils import datetime_to_epoch
13 from boards.utils import datetime_to_epoch
14 from boards.views.thread import ThreadView
14 from boards.views.thread import ThreadView
15 from boards.models.user import Notification
15 from boards.models.user import Notification
16 from boards.mdx_neboard import Parser
16 from boards.mdx_neboard import Parser
17
17
18
18
19 __author__ = 'neko259'
19 __author__ = 'neko259'
20
20
21 PARAMETER_TRUNCATED = 'truncated'
21 PARAMETER_TRUNCATED = 'truncated'
22 PARAMETER_TAG = 'tag'
22 PARAMETER_TAG = 'tag'
23 PARAMETER_OFFSET = 'offset'
23 PARAMETER_OFFSET = 'offset'
24 PARAMETER_DIFF_TYPE = 'type'
24 PARAMETER_DIFF_TYPE = 'type'
25 PARAMETER_POST = 'post'
25 PARAMETER_POST = 'post'
26 PARAMETER_UPDATED = 'updated'
26 PARAMETER_UPDATED = 'updated'
27 PARAMETER_LAST_UPDATE = 'last_update'
27 PARAMETER_LAST_UPDATE = 'last_update'
28 PARAMETER_THREAD = 'thread'
28 PARAMETER_THREAD = 'thread'
29 PARAMETER_UIDS = 'uids'
29 PARAMETER_UIDS = 'uids'
30
30
31 DIFF_TYPE_HTML = 'html'
31 DIFF_TYPE_HTML = 'html'
32 DIFF_TYPE_JSON = 'json'
32 DIFF_TYPE_JSON = 'json'
33
33
34 STATUS_OK = 'ok'
34 STATUS_OK = 'ok'
35 STATUS_ERROR = 'error'
35 STATUS_ERROR = 'error'
36
36
37 logger = logging.getLogger(__name__)
37 logger = logging.getLogger(__name__)
38
38
39
39
40 @transaction.atomic
40 @transaction.atomic
41 def api_get_threaddiff(request):
41 def api_get_threaddiff(request):
42 """
42 """
43 Gets posts that were changed or added since time
43 Gets posts that were changed or added since time
44 """
44 """
45
45
46 thread_id = request.POST.get(PARAMETER_THREAD)
46 thread_id = request.POST.get(PARAMETER_THREAD)
47 uids_str = request.POST.get(PARAMETER_UIDS).strip()
47 uids_str = request.POST.get(PARAMETER_UIDS).strip()
48 uids = uids_str.split(' ')
48 uids = uids_str.split(' ')
49
49
50 opening_post = get_object_or_404(Post, id=thread_id)
50 opening_post = get_object_or_404(Post, id=thread_id)
51 thread = opening_post.get_thread()
51 thread = opening_post.get_thread()
52
52
53 json_data = {
53 json_data = {
54 PARAMETER_UPDATED: [],
54 PARAMETER_UPDATED: [],
55 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
55 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
56 }
56 }
57 posts = Post.objects.filter(threads__in=[thread]).exclude(uid__in=uids)
57 posts = Post.objects.filter(threads__in=[thread]).exclude(uid__in=uids)
58
58
59 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
59 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
60
60
61 for post in posts:
61 for post in posts:
62 json_data[PARAMETER_UPDATED].append(post.get_post_data(
62 json_data[PARAMETER_UPDATED].append(post.get_post_data(
63 format_type=diff_type, request=request))
63 format_type=diff_type, request=request))
64 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
64 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
65
65
66 # If the tag is favorite, update the counter
66 # If the tag is favorite, update the counter
67 settings_manager = get_settings_manager(request)
67 settings_manager = get_settings_manager(request)
68 favorite = settings_manager.thread_is_fav(opening_post)
68 favorite = settings_manager.thread_is_fav(opening_post)
69 if favorite:
69 if favorite:
70 settings_manager.add_or_read_fav_thread(opening_post)
70 settings_manager.add_or_read_fav_thread(opening_post)
71
71
72 return HttpResponse(content=json.dumps(json_data))
72 return HttpResponse(content=json.dumps(json_data))
73
73
74
74
75 def api_add_post(request, opening_post_id):
75 def api_add_post(request, opening_post_id):
76 """
76 """
77 Adds a post and return the JSON response for it
77 Adds a post and return the JSON response for it
78 """
78 """
79
79
80 opening_post = get_object_or_404(Post, id=opening_post_id)
80 opening_post = get_object_or_404(Post, id=opening_post_id)
81
81
82 logger.info('Adding post via api...')
82 logger.info('Adding post via api...')
83
83
84 status = STATUS_OK
84 status = STATUS_OK
85 errors = []
85 errors = []
86
86
87 if request.method == 'POST':
87 if request.method == 'POST':
88 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
88 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
89 form.session = request.session
89 form.session = request.session
90
90
91 if form.need_to_ban:
91 if form.need_to_ban:
92 # Ban user because he is suspected to be a bot
92 # Ban user because he is suspected to be a bot
93 # _ban_current_user(request)
93 # _ban_current_user(request)
94 status = STATUS_ERROR
94 status = STATUS_ERROR
95 if form.is_valid():
95 if form.is_valid():
96 post = ThreadView().new_post(request, form, opening_post,
96 post = ThreadView().new_post(request, form, opening_post,
97 html_response=False)
97 html_response=False)
98 if not post:
98 if not post:
99 status = STATUS_ERROR
99 status = STATUS_ERROR
100 else:
100 else:
101 logger.info('Added post #%d via api.' % post.id)
101 logger.info('Added post #%d via api.' % post.id)
102 else:
102 else:
103 status = STATUS_ERROR
103 status = STATUS_ERROR
104 errors = form.as_json_errors()
104 errors = form.as_json_errors()
105
105
106 response = {
106 response = {
107 'status': status,
107 'status': status,
108 'errors': errors,
108 'errors': errors,
109 }
109 }
110
110
111 return HttpResponse(content=json.dumps(response))
111 return HttpResponse(content=json.dumps(response))
112
112
113
113
114 def get_post(request, post_id):
114 def get_post(request, post_id):
115 """
115 """
116 Gets the html of a post. Used for popups. Post can be truncated if used
116 Gets the html of a post. Used for popups. Post can be truncated if used
117 in threads list with 'truncated' get parameter.
117 in threads list with 'truncated' get parameter.
118 """
118 """
119
119
120 post = get_object_or_404(Post, id=post_id)
120 post = get_object_or_404(Post, id=post_id)
121 truncated = PARAMETER_TRUNCATED in request.GET
121 truncated = PARAMETER_TRUNCATED in request.GET
122
122
123 return HttpResponse(content=post.get_view(truncated=truncated))
123 return HttpResponse(content=post.get_view(truncated=truncated))
124
124
125
125
126 def api_get_threads(request, count):
126 def api_get_threads(request, count):
127 """
127 """
128 Gets the JSON thread opening posts list.
128 Gets the JSON thread opening posts list.
129 Parameters that can be used for filtering:
129 Parameters that can be used for filtering:
130 tag, offset (from which thread to get results)
130 tag, offset (from which thread to get results)
131 """
131 """
132
132
133 if PARAMETER_TAG in request.GET:
133 if PARAMETER_TAG in request.GET:
134 tag_name = request.GET[PARAMETER_TAG]
134 tag_name = request.GET[PARAMETER_TAG]
135 if tag_name is not None:
135 if tag_name is not None:
136 tag = get_object_or_404(Tag, name=tag_name)
136 tag = get_object_or_404(Tag, name=tag_name)
137 threads = tag.get_threads().filter(archived=False)
137 threads = tag.get_threads().filter(archived=False)
138 else:
138 else:
139 threads = Thread.objects.filter(archived=False)
139 threads = Thread.objects.filter(archived=False)
140
140
141 if PARAMETER_OFFSET in request.GET:
141 if PARAMETER_OFFSET in request.GET:
142 offset = request.GET[PARAMETER_OFFSET]
142 offset = request.GET[PARAMETER_OFFSET]
143 offset = int(offset) if offset is not None else 0
143 offset = int(offset) if offset is not None else 0
144 else:
144 else:
145 offset = 0
145 offset = 0
146
146
147 threads = threads.order_by('-bump_time')
147 threads = threads.order_by('-bump_time')
148 threads = threads[offset:offset + int(count)]
148 threads = threads[offset:offset + int(count)]
149
149
150 opening_posts = []
150 opening_posts = []
151 for thread in threads:
151 for thread in threads:
152 opening_post = thread.get_opening_post()
152 opening_post = thread.get_opening_post()
153
153
154 # TODO Add tags, replies and images count
154 # TODO Add tags, replies and images count
155 post_data = opening_post.get_post_data(include_last_update=True)
155 post_data = opening_post.get_post_data(include_last_update=True)
156 post_data['bumpable'] = thread.can_bump()
156 post_data['bumpable'] = thread.can_bump()
157 post_data['archived'] = thread.archived
157 post_data['archived'] = thread.archived
158
158
159 opening_posts.append(post_data)
159 opening_posts.append(post_data)
160
160
161 return HttpResponse(content=json.dumps(opening_posts))
161 return HttpResponse(content=json.dumps(opening_posts))
162
162
163
163
164 # TODO Test this
164 # TODO Test this
165 def api_get_tags(request):
165 def api_get_tags(request):
166 """
166 """
167 Gets all tags or user tags.
167 Gets all tags or user tags.
168 """
168 """
169
169
170 # TODO Get favorite tags for the given user ID
170 # TODO Get favorite tags for the given user ID
171
171
172 tags = Tag.objects.get_not_empty_tags()
172 tags = Tag.objects.get_not_empty_tags()
173
173
174 term = request.GET.get('term')
174 term = request.GET.get('term')
175 if term is not None:
175 if term is not None:
176 tags = tags.filter(name__contains=term)
176 tags = tags.filter(name__contains=term)
177
177
178 tag_names = [tag.name for tag in tags]
178 tag_names = [tag.name for tag in tags]
179
179
180 return HttpResponse(content=json.dumps(tag_names))
180 return HttpResponse(content=json.dumps(tag_names))
181
181
182
182
183 # TODO The result can be cached by the thread last update time
183 # TODO The result can be cached by the thread last update time
184 # TODO Test this
184 # TODO Test this
185 def api_get_thread_posts(request, opening_post_id):
185 def api_get_thread_posts(request, opening_post_id):
186 """
186 """
187 Gets the JSON array of thread posts
187 Gets the JSON array of thread posts
188 """
188 """
189
189
190 opening_post = get_object_or_404(Post, id=opening_post_id)
190 opening_post = get_object_or_404(Post, id=opening_post_id)
191 thread = opening_post.get_thread()
191 thread = opening_post.get_thread()
192 posts = thread.get_replies()
192 posts = thread.get_replies()
193
193
194 json_data = {
194 json_data = {
195 'posts': [],
195 'posts': [],
196 'last_update': None,
196 'last_update': None,
197 }
197 }
198 json_post_list = []
198 json_post_list = []
199
199
200 for post in posts:
200 for post in posts:
201 json_post_list.append(post.get_post_data())
201 json_post_list.append(post.get_post_data())
202 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
202 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
203 json_data['posts'] = json_post_list
203 json_data['posts'] = json_post_list
204
204
205 return HttpResponse(content=json.dumps(json_data))
205 return HttpResponse(content=json.dumps(json_data))
206
206
207
207
208 def api_get_notifications(request, username):
208 def api_get_notifications(request, username):
209 last_notification_id_str = request.GET.get('last', None)
209 last_notification_id_str = request.GET.get('last', None)
210 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
210 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
211
211
212 posts = Notification.objects.get_notification_posts(username=username,
212 posts = Notification.objects.get_notification_posts(username=username,
213 last=last_id)
213 last=last_id)
214
214
215 json_post_list = []
215 json_post_list = []
216 for post in posts:
216 for post in posts:
217 json_post_list.append(post.get_post_data())
217 json_post_list.append(post.get_post_data())
218 return HttpResponse(content=json.dumps(json_post_list))
218 return HttpResponse(content=json.dumps(json_post_list))
219
219
220
220
221 def api_get_post(request, post_id):
221 def api_get_post(request, post_id):
222 """
222 """
223 Gets the JSON of a post. This can be
223 Gets the JSON of a post. This can be
224 used as and API for external clients.
224 used as and API for external clients.
225 """
225 """
226
226
227 post = get_object_or_404(Post, id=post_id)
227 post = get_object_or_404(Post, id=post_id)
228
228
229 json = serializers.serialize("json", [post], fields=(
229 json = serializers.serialize("json", [post], fields=(
230 "pub_time", "_text_rendered", "title", "text", "image",
230 "pub_time", "_text_rendered", "title", "text", "image",
231 "image_width", "image_height", "replies", "tags"
231 "image_width", "image_height", "replies", "tags"
232 ))
232 ))
233
233
234 return HttpResponse(content=json)
234 return HttpResponse(content=json)
235
235
236
236
237 def api_get_preview(request):
237 def api_get_preview(request):
238 raw_text = request.POST['raw_text']
238 raw_text = request.POST['raw_text']
239
239
240 parser = Parser()
240 parser = Parser()
241 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
241 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
242
242
243
243
244 def api_get_new_posts(request):
244 def api_get_new_posts(request):
245 """
245 """
246 Gets favorite threads and unread posts count.
246 Gets favorite threads and unread posts count.
247 """
247 """
248 posts = list()
248 posts = list()
249
249
250 include_posts = 'include_posts' in request.GET
250 include_posts = 'include_posts' in request.GET
251
251
252 settings_manager = get_settings_manager(request)
252 settings_manager = get_settings_manager(request)
253 fav_threads = settings_manager.get_fav_threads()
253 fav_threads = settings_manager.get_fav_threads()
254 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\
254 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\
255 .order_by('-pub_time').prefetch_related('thread')
255 .order_by('-pub_time').prefetch_related('thread')
256
256
257 for op in fav_thread_ops:
257 for op in fav_thread_ops:
258 last_read_post = fav_threads[str(op.id)]
258 last_read_post = fav_threads[str(op.id)]
259 new_posts = op.get_thread().get_replies_newer(last_read_post)
259 new_posts = op.get_thread().get_replies_newer(last_read_post)
260 new_post_count = new_posts.count()
260 new_post_count = new_posts.count()
261 fav_thread_dict = dict()
261 fav_thread_dict = dict()
262 fav_thread_dict['id'] = op.id
262 fav_thread_dict['id'] = op.id
263 fav_thread_dict['new_post_count'] = new_post_count
263 fav_thread_dict['new_post_count'] = new_post_count
264
264
265 if include_posts:
265 if include_posts:
266 fav_thread_dict['post'] = op.get_post_data(
266 fav_thread_dict['post_url'] = op.get_link_view()
267 format_type=DIFF_TYPE_HTML)
267 fav_thread_dict['title'] = op.title
268 if new_post_count > 0:
268 if new_post_count > 0:
269 fav_thread_dict['newest_post_link'] = new_posts.first()\
269 fav_thread_dict['newest_post_link'] = new_posts.first()\
270 .get_absolute_url()
270 .get_absolute_url()
271
271
272 posts.append(fav_thread_dict)
272 posts.append(fav_thread_dict)
273
273
274 return HttpResponse(content=json.dumps(posts))
274 return HttpResponse(content=json.dumps(posts))
General Comments 0
You need to be logged in to leave comments. Login now