From 3d3ab23d270bfc6a409ac815b6302db9b65e5021 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 21 Mar 2022 16:09:16 -0500 Subject: [PATCH] New home UI --- dmoj/urls.py | 6 +- judge/models/comment.py | 6 +- judge/views/blog.py | 149 +++++++++----- judge/views/problem.py | 67 ++++-- locale/vi/LC_MESSAGES/django.po | 347 ++++++++++++++++++-------------- resources/base.scss | 4 +- resources/blog.scss | 100 ++++++++- templates/blog/content.html | 22 +- templates/blog/list.html | 201 +++++++++--------- templates/chat/chat_css.html | 2 +- templates/comments/feed.html | 18 ++ templates/problem/comments.html | 28 --- templates/problem/feed.html | 28 +++ templates/problem/problem.html | 51 ++++- templates/ticket/feed.html | 25 +++ 15 files changed, 676 insertions(+), 378 deletions(-) create mode 100644 templates/comments/feed.html delete mode 100644 templates/problem/comments.html create mode 100644 templates/problem/feed.html create mode 100644 templates/ticket/feed.html diff --git a/dmoj/urls.py b/dmoj/urls.py index 4869ab6..c691074 100644 --- a/dmoj/urls.py +++ b/dmoj/urls.py @@ -110,13 +110,17 @@ urlpatterns = [ url(r'^accounts/', include(register_patterns)), url(r'^', include('social_django.urls')), + url(r'^feed/', include([ + url(r'^problems/$', problem.ProblemFeed.as_view(), name='problem_feed'), + url(r'^tickets/$', blog.TicketFeed.as_view(), name='ticket_feed'), + url(r'^comments/$', blog.CommentFeed.as_view(), name='comment_feed'), + ])), url(r'^problems/$', problem.ProblemList.as_view(), name='problem_list'), url(r'^problems/random/$', problem.RandomProblem.as_view(), name='problem_random'), url(r'^problem/(?P[^/]+)', include([ url(r'^$', problem.ProblemDetail.as_view(), name='problem_detail'), url(r'^/editorial$', problem.ProblemSolution.as_view(), name='problem_editorial'), - url(r'^/comments$', problem.ProblemComments.as_view(), name='problem_comments'), url(r'^/raw$', problem.ProblemRaw.as_view(), name='problem_raw'), url(r'^/pdf$', problem.ProblemPdfView.as_view(), name='problem_pdf'), url(r'^/pdf/(?P[a-z-]+)$', problem.ProblemPdfView.as_view(), name='problem_pdf'), diff --git a/judge/models/comment.py b/judge/models/comment.py index a58ab1d..5d26500 100644 --- a/judge/models/comment.py +++ b/judge/models/comment.py @@ -65,7 +65,9 @@ class Comment(MPTTModel): problem_access = CacheDict(lambda code: Problem.objects.get(code=code).is_accessible_by(user)) contest_access = CacheDict(lambda key: Contest.objects.get(key=key).is_accessible_by(user)) blog_access = CacheDict(lambda id: BlogPost.objects.get(id=id).can_see(user)) - + + if n == -1: + n = len(queryset) if user.is_superuser: return queryset[:n] if batch is None: @@ -105,7 +107,7 @@ class Comment(MPTTModel): try: link = None if self.page.startswith('p:'): - link = reverse('problem_comments', args=(self.page[2:],)) + link = reverse('problem_detail', args=(self.page[2:],)) elif self.page.startswith('c:'): link = reverse('contest_view', args=(self.page[2:],)) elif self.page.startswith('b:'): diff --git a/judge/views/blog.py b/judge/views/blog.py index c16aa6e..b7855dd 100644 --- a/judge/views/blog.py +++ b/judge/views/blog.py @@ -17,10 +17,8 @@ from judge.utils.tickets import filter_visible_tickets from judge.utils.views import TitleMixin -class PostList(ListView): - model = BlogPost - paginate_by = 10 - context_object_name = 'posts' +# General view for all content list on home feed +class FeedView(ListView): template_name = 'blog/list.html' title = None @@ -29,6 +27,56 @@ class PostList(ListView): return DiggPaginator(queryset, per_page, body=6, padding=2, orphans=orphans, allow_empty_first_page=allow_empty_first_page, **kwargs) + def get_context_data(self, **kwargs): + context = super(FeedView, self).get_context_data(**kwargs) + context['has_clarifications'] = False + if self.request.user.is_authenticated: + participation = self.request.profile.current_contest + if participation: + clarifications = ProblemClarification.objects.filter(problem__in=participation.contest.problems.all()) + context['has_clarifications'] = clarifications.count() > 0 + context['clarifications'] = clarifications.order_by('-date') + if participation.contest.is_editable_by(self.request.user): + context['can_edit_contest'] = True + + context['page_titles'] = CacheDict(lambda page: Comment.get_page_title(page)) + + context['user_count'] = lazy(Profile.objects.count, int, int) + context['problem_count'] = lazy(Problem.objects.filter(is_public=True).count, int, int) + context['submission_count'] = lazy(Submission.objects.count, int, int) + context['language_count'] = lazy(Language.objects.count, int, int) + + now = timezone.now() + + # Dashboard stuff + # if self.request.user.is_authenticated: + # user = self.request.profile + # context['recently_attempted_problems'] = (Submission.objects.filter(user=user) + # .exclude(problem__in=user_completed_ids(user)) + # .values_list('problem__code', 'problem__name', 'problem__points') + # .annotate(points=Max('points'), latest=Max('date')) + # .order_by('-latest') + # [:settings.DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT]) + + visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True) \ + .order_by('start_time') + + context['current_contests'] = visible_contests.filter(start_time__lte=now, end_time__gt=now) + context['future_contests'] = visible_contests.filter(start_time__gt=now) + + visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True) + + context['top_rated'] = Profile.objects.order_by('-rating')[:10] + context['top_scorer'] = Profile.objects.order_by('-performance_points')[:10] + + return context + + +class PostList(FeedView): + model = BlogPost + paginate_by = 10 + context_object_name = 'posts' + def get_queryset(self): queryset = BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now()) \ .order_by('-sticky', '-publish_on') \ @@ -45,25 +93,7 @@ class PostList(ListView): context['title'] = self.title or _('Page %d of Posts') % context['page_obj'].number context['first_page_href'] = reverse('home') context['page_prefix'] = reverse('blog_post_list') - context['comments'] = Comment.most_recent(self.request.user, 25) - context['new_problems'] = Problem.objects.filter(is_public=True, is_organization_private=False) \ - .order_by('-date', '-id')[:settings.DMOJ_BLOG_NEW_PROBLEM_COUNT] - context['page_titles'] = CacheDict(lambda page: Comment.get_page_title(page)) - - context['has_clarifications'] = False - if self.request.user.is_authenticated: - participation = self.request.profile.current_contest - if participation: - clarifications = ProblemClarification.objects.filter(problem__in=participation.contest.problems.all()) - context['has_clarifications'] = clarifications.count() > 0 - context['clarifications'] = clarifications.order_by('-date') - if participation.contest.is_editable_by(self.request.user): - context['can_edit_contest'] = True - context['user_count'] = lazy(Profile.objects.count, int, int) - context['problem_count'] = lazy(Problem.objects.filter(is_public=True).count, int, int) - context['submission_count'] = lazy(Submission.objects.count, int, int) - context['language_count'] = lazy(Language.objects.count, int, int) - + context['feed_type'] = 'blog' context['post_comment_counts'] = { int(page[2:]): count for page, count in Comment.objects @@ -71,40 +101,55 @@ class PostList(ListView): .values_list('page').annotate(count=Count('page')).order_by() } - now = timezone.now() + return context - # Dashboard stuff - if self.request.user.is_authenticated: - user = self.request.profile - context['recently_attempted_problems'] = (Submission.objects.filter(user=user) - .exclude(problem__in=user_completed_ids(user)) - .values_list('problem__code', 'problem__name', 'problem__points') - .annotate(points=Max('points'), latest=Max('date')) - .order_by('-latest') - [:settings.DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT]) - - visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True) \ - .order_by('start_time') - context['current_contests'] = visible_contests.filter(start_time__lte=now, end_time__gt=now) - context['future_contests'] = visible_contests.filter(start_time__gt=now) +class TicketFeed(FeedView): + model = Ticket + context_object_name = 'tickets' + paginate_by = 30 - visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True) - if self.request.user.is_authenticated: - profile = self.request.profile - context['own_open_tickets'] = (Ticket.objects.filter(Q(user=profile) | Q(assignees__in=[profile]), is_open=True).order_by('-id') - .prefetch_related('linked_item').select_related('user__user')) + def get_queryset(self, is_own=True): + profile = self.request.profile + if is_own: + if self.request.user.is_authenticated: + return (Ticket.objects.filter(Q(user=profile) | Q(assignees__in=[profile]), is_open=True).order_by('-id') + .prefetch_related('linked_item').select_related('user__user')) + else: + return [] else: - profile = None - context['own_open_tickets'] = [] + # Superusers better be staffs, not the spell-casting kind either. + if self.request.user.is_staff: + tickets = (Ticket.objects.order_by('-id').filter(is_open=True).prefetch_related('linked_item') + .select_related('user__user')) + return filter_visible_tickets(tickets, self.request.user, profile) + else: + return [] - # Superusers better be staffs, not the spell-casting kind either. - if self.request.user.is_staff: - tickets = (Ticket.objects.order_by('-id').filter(is_open=True).prefetch_related('linked_item') - .select_related('user__user')) - context['open_tickets'] = filter_visible_tickets(tickets, self.request.user, profile)[:10] - else: - context['open_tickets'] = [] + def get_context_data(self, **kwargs): + context = super(TicketFeed, self).get_context_data(**kwargs) + context['feed_type'] = 'ticket' + context['first_page_href'] = self.request.path + context['page_prefix'] = '?page=' + context['title'] = _('Ticket feed') + + return context + + +class CommentFeed(FeedView): + model = Comment + context_object_name = 'comments' + paginate_by = 50 + + def get_queryset(self): + return Comment.most_recent(self.request.user, 1000) + + def get_context_data(self, **kwargs): + context = super(CommentFeed, self).get_context_data(**kwargs) + context['feed_type'] = 'comment' + context['first_page_href'] = self.request.path + context['page_prefix'] = '?page=' + context['title'] = _('Comment feed') return context diff --git a/judge/views/problem.py b/judge/views/problem.py index 9149511..995c14e 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -21,7 +21,7 @@ from django.utils.functional import cached_property from django.utils.html import escape, format_html from django.utils.safestring import mark_safe from django.utils.translation import gettext as _, gettext_lazy -from django.views.generic import DetailView, ListView, View +from django.views.generic import ListView, View from django.views.generic.base import TemplateResponseMixin from django.views.generic.detail import SingleObjectMixin @@ -38,6 +38,7 @@ from judge.utils.problems import contest_attempted_ids, contest_completed_ids, h from judge.utils.strings import safe_float_or_none, safe_int_or_none from judge.utils.tickets import own_ticket_filter from judge.utils.views import QueryStringSortMixin, SingleObjectFormView, TitleMixin, generic_message +from judge.views.blog import FeedView def get_contest_problem(problem, profile): @@ -155,10 +156,13 @@ class ProblemRaw(ProblemMixin, TitleMixin, TemplateResponseMixin, SingleObjectMi )) -class ProblemDetail(ProblemMixin, SolvedProblemMixin, DetailView): +class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView): context_object_name = 'problem' template_name = 'problem/problem.html' + def get_comment_page(self): + return 'p:%s' % self.object.code + def get_context_data(self, **kwargs): context = super(ProblemDetail, self).get_context_data(**kwargs) user = self.request.user @@ -235,6 +239,7 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, DetailView): context['min_possible_vote'] = 100 return context + class DeleteVote(ProblemMixin, SingleObjectMixin, View): def get(self, request, *args, **kwargs): return HttpResponseForbidden(status=405, content_type='text/plain') @@ -273,21 +278,6 @@ class Vote(ProblemMixin, SingleObjectMixin, View): return JsonResponse(form.errors, status=400) -class ProblemComments(ProblemMixin, TitleMixin, CommentedDetailView): - context_object_name = 'problem' - template_name = 'problem/comments.html' - - def get_title(self): - return _('Disscuss {0}').format(self.object.name) - - def get_content_title(self): - return format_html(_(u'Discuss {0}'), self.object.name, - reverse('problem_detail', args=[self.object.code])) - - def get_comment_page(self): - return 'p:%s' % self.object.code - - class LatexError(Exception): pass @@ -592,6 +582,49 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView return HttpResponseRedirect(request.get_full_path()) +class ProblemFeed(FeedView): + model = Problem + context_object_name = 'problems' + paginate_by = 50 + title = _('Problem feed') + + @cached_property + def profile(self): + if not self.request.user.is_authenticated: + return None + return self.request.profile + + def get_unsolved_queryset(self): + filter = Q(is_public=True) + if self.profile is not None: + filter |= Q(authors=self.profile) + filter |= Q(curators=self.profile) + filter |= Q(testers=self.profile) + queryset = Problem.objects.filter(filter).select_related('group').defer('description') + if not self.request.user.has_perm('see_organization_problem'): + filter = Q(is_organization_private=False) + if self.profile is not None: + filter |= Q(organizations__in=self.profile.organizations.all()) + queryset = queryset.filter(filter) + if self.profile is not None: + queryset = queryset.exclude(id__in=Submission.objects.filter(user=self.profile, points=F('problem__points')) + .values_list('problem__id', flat=True)) + return queryset.distinct() + + def get_queryset(self): + queryset = self.get_unsolved_queryset() + return queryset.order_by('?') + + def get_context_data(self, **kwargs): + context = super(ProblemFeed, self).get_context_data(**kwargs) + context['first_page_href'] = self.request.path + context['page_prefix'] = '?page=' + context['feed_type'] = 'problem' + context['title'] = self.title + + return context + + class LanguageTemplateAjax(View): def get(self, request, *args, **kwargs): try: diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index 36f657d..c77acf1 100644 --- a/locale/vi/LC_MESSAGES/django.po +++ b/locale/vi/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: lqdoj2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-03-10 12:30+0700\n" +"POT-Creation-Date: 2022-03-22 04:37+0700\n" "PO-Revision-Date: 2021-07-20 03:44\n" "Last-Translator: Icyene\n" "Language-Team: Vietnamese\n" @@ -24,7 +24,7 @@ msgstr "" msgid "user" msgstr "người dùng" -#: chat_box/models.py:24 judge/models/comment.py:43 judge/models/comment.py:191 +#: chat_box/models.py:24 judge/models/comment.py:43 judge/models/comment.py:193 msgid "posted time" msgstr "thời gian đăng" @@ -225,7 +225,7 @@ msgstr "ảo" msgid "link path" msgstr "đường dẫn" -#: judge/admin/interface.py:65 +#: judge/admin/interface.py:65 templates/blog/list.html:114 msgid "Content" msgstr "Nội dung" @@ -254,11 +254,11 @@ msgstr "Mạng Xã Hội" msgid "Taxonomy" msgstr "" -#: judge/admin/problem.py:128 templates/contest/contest.html:84 -#: templates/problem/data.html:469 templates/problem/list.html:222 -#: templates/problem/list.html:248 templates/user/base-users-table.html:10 -#: templates/user/user-about.html:36 templates/user/user-about.html:52 -#: templates/user/user-problems.html:58 +#: judge/admin/problem.py:128 judge/admin/problem.py:258 +#: templates/contest/contest.html:84 templates/problem/data.html:469 +#: templates/problem/list.html:222 templates/problem/list.html:248 +#: templates/user/base-users-table.html:10 templates/user/user-about.html:36 +#: templates/user/user-about.html:52 templates/user/user-problems.html:58 msgid "Points" msgstr "Điểm" @@ -299,6 +299,19 @@ msgstr[0] "%d bài tập đã được đánh dấu riêng tư." msgid "Mark problems as private" msgstr "Đánh dấu các bài tập là riêng tư" +#: judge/admin/problem.py:253 judge/admin/submission.py:200 +#: templates/problem/list.html:216 templates/problem/list.html:236 +msgid "Problem code" +msgstr "Mã bài" + +#: judge/admin/problem.py:263 judge/admin/submission.py:205 +msgid "Problem name" +msgstr "Tên bài" + +#: judge/admin/problem.py:268 +msgid "Vote" +msgstr "" + #: judge/admin/profile.py:34 msgid "timezone" msgstr "múi giờ" @@ -401,15 +414,6 @@ msgstr[0] "%d bài nộp đã được tính điểm lại." msgid "Rescore the selected submissions" msgstr "Tính điểm lại cái bài nộp" -#: judge/admin/submission.py:200 templates/problem/list.html:216 -#: templates/problem/list.html:236 -msgid "Problem code" -msgstr "Mã bài" - -#: judge/admin/submission.py:205 -msgid "Problem name" -msgstr "Tên bài" - #: judge/admin/submission.py:215 templates/notification/list.html:15 #: templates/organization/requests/log.html:10 #: templates/organization/requests/pending.html:13 @@ -550,7 +554,7 @@ msgstr "g:i a j b, Y" msgid "{time}" msgstr "{time}" -#: judge/jinja2/datetime.py:26 templates/blog/content.html:13 +#: judge/jinja2/datetime.py:26 templates/blog/content.html:10 #, python-brace-format msgid "on {time}" msgstr "vào {time}" @@ -583,11 +587,11 @@ msgstr "Mã trang phải có dạng ^[pcs]:[a-z0-9]+$|^b:\\d+$" msgid "commenter" msgstr "người bình luận" -#: judge/models/comment.py:44 judge/models/comment.py:177 +#: judge/models/comment.py:44 judge/models/comment.py:179 msgid "associated page" msgstr "trang tương ứng" -#: judge/models/comment.py:46 judge/models/problem.py:492 +#: judge/models/comment.py:46 judge/models/problem.py:493 msgid "votes" msgstr "bình chọn" @@ -599,7 +603,7 @@ msgstr "ẩn bình luận" msgid "parent" msgstr "" -#: judge/models/comment.py:54 judge/models/comment.py:192 +#: judge/models/comment.py:54 judge/models/comment.py:194 msgid "comment" msgstr "bình luận" @@ -607,24 +611,24 @@ msgstr "bình luận" msgid "comments" msgstr "" -#: judge/models/comment.py:137 judge/models/problem.py:462 +#: judge/models/comment.py:139 judge/models/problem.py:463 #, python-format msgid "Editorial for %s" msgstr "" -#: judge/models/comment.py:172 +#: judge/models/comment.py:174 msgid "comment vote" msgstr "" -#: judge/models/comment.py:173 +#: judge/models/comment.py:175 msgid "comment votes" msgstr "" -#: judge/models/comment.py:182 +#: judge/models/comment.py:184 msgid "Override comment lock" msgstr "" -#: judge/models/comment.py:190 +#: judge/models/comment.py:192 #: src/dmoj-wpadmin/test_project/apps/books/admin.py:24 #: src/dmoj-wpadmin/test_project/apps/books/models.py:30 #: src/dmoj-wpadmin/test_project/apps/cds/models.py:30 @@ -632,22 +636,22 @@ msgstr "" msgid "owner" msgstr "" -#: judge/models/comment.py:193 judge/models/message.py:16 +#: judge/models/comment.py:195 judge/models/message.py:16 msgid "read" msgstr "" -#: judge/models/comment.py:194 +#: judge/models/comment.py:196 #: src/dmoj-wpadmin/test_project/apps/books/models.py:28 #: src/dmoj-wpadmin/test_project/apps/cds/models.py:28 #: src/dmoj-wpadmin/test_project/apps/dvds/models.py:28 msgid "category" msgstr "" -#: judge/models/comment.py:195 +#: judge/models/comment.py:197 msgid "html link to comments, used for non-comments" msgstr "" -#: judge/models/comment.py:196 +#: judge/models/comment.py:198 msgid "who trigger, used for non-comment" msgstr "" @@ -725,7 +729,7 @@ msgstr "" msgid "description" msgstr "mô tả" -#: judge/models/contest.py:72 judge/models/problem.py:409 +#: judge/models/contest.py:72 judge/models/problem.py:410 #: judge/models/runtime.py:138 msgid "problems" msgstr "bài tập" @@ -739,7 +743,7 @@ msgid "end time" msgstr "thời gian kết thúc" #: judge/models/contest.py:75 judge/models/problem.py:118 -#: judge/models/problem.py:433 +#: judge/models/problem.py:434 msgid "time limit" msgstr "giới hạn thời gian" @@ -1037,8 +1041,8 @@ msgid "contest participations" msgstr "lần tham gia kỳ thi" #: judge/models/contest.py:491 judge/models/contest.py:513 -#: judge/models/contest.py:554 judge/models/problem.py:408 -#: judge/models/problem.py:413 judge/models/problem.py:431 +#: judge/models/contest.py:554 judge/models/problem.py:409 +#: judge/models/problem.py:414 judge/models/problem.py:432 #: judge/models/problem_data.py:40 msgid "problem" msgstr "bài tập" @@ -1176,7 +1180,7 @@ msgstr "mục cha" msgid "post title" msgstr "tiêu đề bài đăng" -#: judge/models/interface.py:67 judge/models/problem.py:451 +#: judge/models/interface.py:67 judge/models/problem.py:452 msgid "authors" msgstr "tác giả" @@ -1184,7 +1188,7 @@ msgstr "tác giả" msgid "slug" msgstr "slug" -#: judge/models/interface.py:69 judge/models/problem.py:449 +#: judge/models/interface.py:69 judge/models/problem.py:450 msgid "public visibility" msgstr "khả năng hiển thị công khai" @@ -1380,7 +1384,7 @@ msgid "" "are supported." msgstr "" -#: judge/models/problem.py:123 judge/models/problem.py:436 +#: judge/models/problem.py:123 judge/models/problem.py:437 msgid "memory limit" msgstr "" @@ -1453,82 +1457,82 @@ msgstr "" msgid "If private, only these organizations may see the problem." msgstr "" -#: judge/models/problem.py:414 judge/models/problem.py:432 +#: judge/models/problem.py:415 judge/models/problem.py:433 #: judge/models/runtime.py:111 msgid "language" msgstr "" -#: judge/models/problem.py:415 +#: judge/models/problem.py:416 msgid "translated name" msgstr "" -#: judge/models/problem.py:416 +#: judge/models/problem.py:417 msgid "translated description" msgstr "" -#: judge/models/problem.py:420 +#: judge/models/problem.py:421 msgid "problem translation" msgstr "" -#: judge/models/problem.py:421 +#: judge/models/problem.py:422 msgid "problem translations" msgstr "" -#: judge/models/problem.py:425 +#: judge/models/problem.py:426 msgid "clarified problem" msgstr "" -#: judge/models/problem.py:426 +#: judge/models/problem.py:427 msgid "clarification body" msgstr "" -#: judge/models/problem.py:427 +#: judge/models/problem.py:428 msgid "clarification timestamp" msgstr "" -#: judge/models/problem.py:442 +#: judge/models/problem.py:443 msgid "language-specific resource limit" msgstr "" -#: judge/models/problem.py:443 +#: judge/models/problem.py:444 msgid "language-specific resource limits" msgstr "" -#: judge/models/problem.py:447 +#: judge/models/problem.py:448 msgid "associated problem" msgstr "" -#: judge/models/problem.py:450 +#: judge/models/problem.py:451 msgid "publish date" msgstr "" -#: judge/models/problem.py:452 +#: judge/models/problem.py:453 msgid "editorial content" msgstr "nội dung lời giải" -#: judge/models/problem.py:468 +#: judge/models/problem.py:469 msgid "solution" msgstr "lời giải" -#: judge/models/problem.py:469 +#: judge/models/problem.py:470 msgid "solutions" msgstr "lời giải" -#: judge/models/problem.py:474 +#: judge/models/problem.py:475 #, fuzzy #| msgid "point value" msgid "proposed point value" msgstr "điểm" -#: judge/models/problem.py:475 +#: judge/models/problem.py:476 msgid "The amount of points you think this problem deserves." msgstr "" -#: judge/models/problem.py:485 +#: judge/models/problem.py:486 msgid "The time this vote was cast" msgstr "" -#: judge/models/problem.py:491 +#: judge/models/problem.py:492 msgid "vote" msgstr "" @@ -2376,11 +2380,23 @@ msgstr "Giới thiệu" msgid "Custom Checker Sample" msgstr "Hướng dẫn viết trình chấm" -#: judge/views/blog.py:45 +#: judge/views/blog.py:93 #, python-format msgid "Page %d of Posts" msgstr "Trang %d" +#: judge/views/blog.py:134 +#, fuzzy +#| msgid "Ticket title" +msgid "Ticket feed" +msgstr "Tiêu đề báo cáo" + +#: judge/views/blog.py:152 +#, fuzzy +#| msgid "Comment body" +msgid "Comment feed" +msgstr "Nội dung bình luận" + #: judge/views/comment.py:28 msgid "Messing around, are we?" msgstr "Messing around, are we?" @@ -2675,65 +2691,61 @@ msgstr "" msgid "The user you are trying to kick is not in organization: %s." msgstr "" -#: judge/views/problem.py:68 +#: judge/views/problem.py:69 msgid "No such problem" msgstr "Không có bài nào như vậy" -#: judge/views/problem.py:69 +#: judge/views/problem.py:70 #, python-format msgid "Could not find a problem with the code \"%s\"." msgstr "Không tìm thấy bài tập với mã bài \"%s\"." -#: judge/views/problem.py:113 +#: judge/views/problem.py:114 #, python-brace-format msgid "Editorial for {0}" msgstr "Hướng dẫn cho {0}" -#: judge/views/problem.py:116 +#: judge/views/problem.py:117 #, python-brace-format msgid "Editorial for {0}" msgstr "Hướng dẫn cho {0}" -#: judge/views/problem.py:284 -#, python-brace-format -msgid "Disscuss {0}" -msgstr "" - -#: judge/views/problem.py:287 -#, python-brace-format -msgid "Discuss {0}" -msgstr "Thảo luận {0}" - -#: judge/views/problem.py:355 templates/contest/contest.html:79 -#: templates/user/user-about.html:28 templates/user/user-tabs.html:5 -#: templates/user/users-table.html:29 +#: judge/views/problem.py:342 templates/blog/list.html:121 +#: templates/contest/contest.html:79 templates/user/user-about.html:28 +#: templates/user/user-tabs.html:5 templates/user/users-table.html:29 msgid "Problems" msgstr "Bài tập" -#: judge/views/problem.py:655 +#: judge/views/problem.py:589 +#, fuzzy +#| msgid "Problem code" +msgid "Problem feed" +msgstr "Mã bài" + +#: judge/views/problem.py:685 msgid "Banned from submitting" msgstr "Bị cấm nộp bài" -#: judge/views/problem.py:656 +#: judge/views/problem.py:686 msgid "" "You have been declared persona non grata for this problem. You are " "permanently barred from submitting this problem." msgstr "Bạn đã bị cấm nộp bài này." -#: judge/views/problem.py:670 +#: judge/views/problem.py:700 msgid "Too many submissions" msgstr "Quá nhiều lần nộp" -#: judge/views/problem.py:671 +#: judge/views/problem.py:701 msgid "You have exceeded the submission limit for this problem." msgstr "Bạn đã vượt quá số lần nộp cho bài này." -#: judge/views/problem.py:731 judge/views/problem.py:734 +#: judge/views/problem.py:761 judge/views/problem.py:764 #, python-format msgid "Submit to %(problem)s" msgstr "Nộp bài cho %(problem)s" -#: judge/views/problem.py:749 +#: judge/views/problem.py:779 msgid "Clone Problem" msgstr "Nhân bản bài tập" @@ -2881,7 +2893,7 @@ msgid "Submission of %(problem)s by %(user)s" msgstr "Bài nộp của %(user)s cho bài %(problem)s" #: judge/views/submission.py:244 judge/views/submission.py:245 -#: templates/problem/problem.html:165 +#: templates/problem/problem.html:194 msgid "All submissions" msgstr "Tất cả bài nộp" @@ -3299,10 +3311,6 @@ msgstr "Chỉnh sửa" msgid " posted on %(time)s" msgstr "đã đăng vào %(time)s" -#: templates/blog/content.html:13 -msgid "posted" -msgstr "đã đăng" - #: templates/blog/dashboard.html:21 #, python-format msgid "" @@ -3314,59 +3322,63 @@ msgstr "" " vào %(time)s\n" " " -#: templates/blog/list.html:93 -msgid "Blog" +#: templates/blog/list.html:107 +msgid "Feed" msgstr "" -#: templates/blog/list.html:95 +#: templates/blog/list.html:109 msgid "Events" msgstr "Sự kiện" -#: templates/blog/list.html:100 +#: templates/blog/list.html:117 msgid "News" msgstr "Tin tức" -#: templates/blog/list.html:115 templates/problem/list.html:347 -#: templates/problem/problem.html:370 +#: templates/blog/list.html:125 templates/comments/list.html:2 +msgid "Comments" +msgstr "Bình luận" + +#: templates/blog/list.html:129 +msgid "Tickets" +msgstr "Báo cáo" + +#: templates/blog/list.html:148 +msgid "You have no ticket" +msgstr "Bạn không có báo cáo" + +#: templates/blog/list.html:163 templates/problem/list.html:347 +#: templates/problem/problem.html:407 msgid "Clarifications" msgstr "Thông báo" -#: templates/blog/list.html:121 +#: templates/blog/list.html:169 msgid "Add" msgstr "Thêm mới" -#: templates/blog/list.html:140 templates/problem/list.html:369 -#: templates/problem/problem.html:381 +#: templates/blog/list.html:188 templates/problem/list.html:369 +#: templates/problem/problem.html:418 msgid "No clarifications have been made at this time." msgstr "Không có thông báo nào." -#: templates/blog/list.html:148 +#: templates/blog/list.html:196 msgid "Ongoing contests" msgstr "Kỳ thi đang diễn ra" -#: templates/blog/list.html:156 +#: templates/blog/list.html:204 msgid "Ends in" msgstr "Còn" -#: templates/blog/list.html:166 +#: templates/blog/list.html:214 msgid "Upcoming contests" msgstr "Kỳ thi sắp diễn ra" -#: templates/blog/list.html:184 -msgid "My open tickets" -msgstr "Báo cáo dành cho tôi" +#: templates/blog/list.html:230 +msgid "Top Rating" +msgstr "Top Rating" -#: templates/blog/list.html:206 -msgid "New tickets" -msgstr "Báo cáo mới" - -#: templates/blog/list.html:227 -msgid "New problems" -msgstr "Bài tập mới" - -#: templates/blog/list.html:244 -msgid "Comment stream" -msgstr "Dòng bình luận" +#: templates/blog/list.html:246 +msgid "Top Score" +msgstr "Top Score" #: templates/chat/chat.html:18 msgid "Recent" @@ -3435,10 +3447,6 @@ msgstr "Tắt thông báo" msgid "users are online" msgstr "người đang trực tuyến" -#: templates/comments/list.html:2 -msgid "Comments" -msgstr "Bình luận" - #: templates/comments/list.html:18 templates/comments/list.html:27 msgid "Please login to vote" msgstr "Đăng nhập để vote" @@ -4347,122 +4355,126 @@ msgstr "Bạn có chắc muốn tính điểm lại %(count)d bài nộp?" msgid "Rescore all submissions" msgstr "Tính điểm lại các bài nộp" -#: templates/problem/problem.html:130 +#: templates/problem/problem.html:159 msgid "View as PDF" msgstr "Xem PDF" -#: templates/problem/problem.html:139 templates/problem/problem.html:149 -#: templates/problem/problem.html:154 +#: templates/problem/problem.html:168 templates/problem/problem.html:178 +#: templates/problem/problem.html:183 msgid "Submit solution" msgstr "Nộp bài" -#: templates/problem/problem.html:142 +#: templates/problem/problem.html:171 #, python-format msgid "%(counter)s submission left" msgid_plural "%(counter)s submissions left" msgstr[0] "Còn %(counter)s lần nộp" -#: templates/problem/problem.html:150 +#: templates/problem/problem.html:179 msgid "0 submissions left" msgstr "Còn 0 lần nộp" -#: templates/problem/problem.html:162 +#: templates/problem/problem.html:191 msgid "My submissions" msgstr "Bài nộp của tôi" -#: templates/problem/problem.html:166 +#: templates/problem/problem.html:195 msgid "Best submissions" msgstr "Các bài nộp tốt nhất" -#: templates/problem/problem.html:170 -msgid "Discuss" -msgstr "Thảo luận" - -#: templates/problem/problem.html:174 +#: templates/problem/problem.html:199 msgid "Read editorial" msgstr "Xem hướng dẫn" -#: templates/problem/problem.html:179 +#: templates/problem/problem.html:204 msgid "Manage tickets" msgstr "Xử lý báo cáo" -#: templates/problem/problem.html:183 +#: templates/problem/problem.html:208 msgid "Edit problem" msgstr "Chỉnh sửa bài" -#: templates/problem/problem.html:185 +#: templates/problem/problem.html:210 msgid "Edit test data" msgstr "Chỉnh sửa test" -#: templates/problem/problem.html:190 +#: templates/problem/problem.html:215 msgid "My tickets" msgstr "Báo cáo của tôi" -#: templates/problem/problem.html:198 +#: templates/problem/problem.html:223 msgid "Manage submissions" msgstr "Quản lý bài nộp" -#: templates/problem/problem.html:204 +#: templates/problem/problem.html:229 msgid "Clone problem" msgstr "Nhân bản bài" -#: templates/problem/problem.html:211 +#: templates/problem/problem.html:236 msgid "Points:" msgstr "Điểm:" -#: templates/problem/problem.html:214 templates/problem/problem.html:216 +#: templates/problem/problem.html:239 templates/problem/problem.html:241 msgid "(partial)" msgstr "(thành phần)" -#: templates/problem/problem.html:221 +#: templates/problem/problem.html:246 msgid "Time limit:" msgstr "Thời gian:" -#: templates/problem/problem.html:233 +#: templates/problem/problem.html:258 msgid "Memory limit:" msgstr "Bộ nhớ:" -#: templates/problem/problem.html:252 +#: templates/problem/problem.html:277 msgid "Author:" msgid_plural "Authors:" msgstr[0] "Tác giả:" -#: templates/problem/problem.html:267 +#: templates/problem/problem.html:292 msgid "Problem type" msgid_plural "Problem types" msgstr[0] "Dạng bài" -#: templates/problem/problem.html:280 +#: templates/problem/problem.html:305 msgid "Allowed languages" msgstr "Ngôn ngữ cho phép" -#: templates/problem/problem.html:288 +#: templates/problem/problem.html:313 #, python-format msgid "No %(lang)s judge online" msgstr "Không có máy chấm cho %(lang)s" -#: templates/problem/problem.html:299 +#: templates/problem/problem.html:324 msgid "Judge:" msgid_plural "Judges:" msgstr[0] "Máy chấm:" -#: templates/problem/problem.html:316 +#: templates/problem/problem.html:341 msgid "none available" msgstr "không có sẵn" -#: templates/problem/problem.html:331 +#: templates/problem/problem.html:356 #, python-format msgid "This problem has %(length)s clarification(s)" msgstr "Bài này có %(length)s thông báo" -#: templates/problem/problem.html:359 +#: templates/problem/problem.html:384 msgid "Request clarification" msgstr "Yêu cầu làm rõ đề" -#: templates/problem/problem.html:361 +#: templates/problem/problem.html:386 msgid "Report an issue" msgstr "Báo cáo một vấn đề" +#: templates/problem/problem.html:395 +msgid "View comments" +msgstr "Xem bình luận" + +#: templates/problem/problem.html:397 +msgid "Be the first to comment" +msgstr "Bình luận đầu tiên" + #: templates/problem/raw.html:64 msgid "Time Limit:" msgstr "Giới hạn thời gian:" @@ -4557,11 +4569,11 @@ msgstr "Không có máy chấm có thể chấm bài này." msgid "Submit!" msgstr "Nộp bài!" -#: templates/problem/voting-controls.html:53 +#: templates/problem/voting-controls.html:55 msgid "Edit difficulty" msgstr "Thay đổi độ khó" -#: templates/problem/voting-controls.html:61 +#: templates/problem/voting-controls.html:63 msgid "Vote difficulty" msgstr "Bình chọn độ khó" @@ -4573,14 +4585,6 @@ msgstr "Bạn thấy độ khó bài này thế nào?" msgid "This helps us improve the site" msgstr "Bình chọn giúp admin cải thiện bài tập." -#: templates/problem/voting-form.html:38 -msgid "Easy" -msgstr "Dễ" - -#: templates/problem/voting-form.html:39 -msgid "Hard" -msgstr "Khó" - #: templates/problem/voting-stats.html:29 msgid "Voting Statistics" msgstr "Thống kê" @@ -4992,6 +4996,10 @@ msgstr "Tốt nhất" msgid "%(user)s's" msgstr "" +#: templates/ticket/feed.html:20 +msgid " replied" +msgstr "" + #: templates/ticket/list.html:135 templates/ticket/ticket.html:273 msgid "Reopened: " msgstr "Mở lại: " @@ -5331,3 +5339,30 @@ msgstr "Thông tin" #: templates/widgets/select_all.html:8 msgid "Check all" msgstr "Chọn tất cả" + +#~ msgid "Discuss {0}" +#~ msgstr "Thảo luận {0}" + +#~ msgid "posted" +#~ msgstr "đã đăng" + +#~ msgid "My open tickets" +#~ msgstr "Báo cáo dành cho tôi" + +#~ msgid "New tickets" +#~ msgstr "Báo cáo mới" + +#~ msgid "New problems" +#~ msgstr "Bài tập mới" + +#~ msgid "Comment stream" +#~ msgstr "Dòng bình luận" + +#~ msgid "Discuss" +#~ msgstr "Thảo luận" + +#~ msgid "Easy" +#~ msgstr "Dễ" + +#~ msgid "Hard" +#~ msgstr "Khó" diff --git a/resources/base.scss b/resources/base.scss index 369b91b..9e21b4c 100644 --- a/resources/base.scss +++ b/resources/base.scss @@ -232,7 +232,7 @@ header { } #navigation { - position: relative; + position: fixed; top: 0; left: 0; right: 0; @@ -382,7 +382,7 @@ hr { } #content { - margin: 1em auto auto; + margin: 4.5em auto 1em auto; // Header width: 90%; diff --git a/resources/blog.scss b/resources/blog.scss index cd6aeb2..8cd8693 100644 --- a/resources/blog.scss +++ b/resources/blog.scss @@ -2,9 +2,9 @@ .blog-content { padding-right: 0em; - flex: 73.5%; vertical-align: top; margin-right: 0; + width: 100%; .post { border: 1px dotted grey; @@ -33,7 +33,8 @@ } .blog-sidebar { - flex: 26.5%; + width: 100%; + margin-left: auto; } .blog-sidebox { @@ -88,6 +89,19 @@ color: #555; } +@media (max-width: 799px) { + .left-sidebar-header { + display: none; + } + .left-sidebar-item { + display: inline-block; + } + .blog-left-sidebar { + text-align: right; + padding-right: 1em; + margin-bottom: 1em; + } +} @media (min-width: 800px) { .blog-content, .blog-sidebar { display: block !important; @@ -104,6 +118,28 @@ #blog-container { display: flex; } + + .blog-content { + max-width: 71.5%; + margin-left: 10%; + } + + .blog-sidebar { + width: 18%; + } + + .blog-left-sidebar { + width: 8%; + margin-right: 1em; + position: fixed; + height: 100%; + margin-top: -4em; + padding-top: 4em; + } + + .feed-table { + font-size: small; + } } #mobile.tabs { @@ -135,3 +171,63 @@ } } } + +.blog-box { + border-bottom: 1px solid black; + width: 90%; + margin-bottom: 2.5em; + padding: 0.5em 1.25em; + background-color: white; + margin-left: auto; + margin-right: auto; +} + +.blog-description { + max-height: 20em; + overflow: hidden; + overflow-wrap: anywhere; + padding-bottom: 1em; +} +.problem-feed-name { + display: inline; + font-weight: bold; +} +.problem-feed-name a { + color: #0645ad; +} +.problem-feed-info-entry { + display: inline; + float: right; +} +.problem-feed-types { + color: gray; +} + +.blog-left-sidebar { + background-color: #f0f1f3; + color: #616161; +} + +.left-sidebar-item { + padding: 1em 0.5em; + text-align: center; +} +.left-sidebar-item:hover { + background-color: lightgray; + cursor: pointer; +} +.sidebar-icon { + font-size: x-large; + margin-bottom: 0.1em; + color: black; +} +.left-sidebar-header { + text-align: center; + padding-bottom: 1em; + border-bottom: 1px solid black; + color: black; + border-radius: 0; +} +.feed-table { + margin: 0; +} \ No newline at end of file diff --git a/templates/blog/content.html b/templates/blog/content.html index fd8804d..753e36e 100644 --- a/templates/blog/content.html +++ b/templates/blog/content.html @@ -1,18 +1,16 @@ -
-

- {{ post.title }} -

- +
+
- {%- if post.sticky %}{% endif -%} {% with authors=post.authors.all() %} {%- if authors -%} + {%- endif -%} {% endwith %} - {{_('posted')}} {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}} + • {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}} + {%- if post.sticky %} • {% endif -%} - + @@ -20,8 +18,10 @@ - -
+
+

+ {{ post.title }} +

{% if post.is_organization_private and show_organization_private_icon %}
{% for org in post.organizations.all() %} @@ -33,7 +33,7 @@ {% endfor %}
{% endif %} -
+
{% cache 86400 'post_summary' post.id %} {{ post.summary|default(post.content, true)|markdown('blog', 'svg', lazy_load=True)|reference|str|safe }} {% endcache %} diff --git a/templates/blog/list.html b/templates/blog/list.html index cd5b65d..1419703 100644 --- a/templates/blog/list.html +++ b/templates/blog/list.html @@ -16,30 +16,10 @@ clear: both; } } - .post { - margin: 0 2%; - } .time { margin-left: 0; } - .post:first-child { - margin-top: 0.6em; - } - - .own-open-tickets .title a, .open-tickets .title a { - display: block; - } - - .own-open-tickets .object, .open-tickets .object { - margin-left: 1em; - font-style: italic; - } - - .open-tickets .user { - margin-left: 1em; - } - .no-clarifications-message { font-style: italic; text-align: center; @@ -56,6 +36,11 @@ #add-clarification:hover { color: cyan; } + + #content { + width: 99%; + margin-left: 0; + } {% endblock %} @@ -81,6 +66,35 @@ $('.blog-content').hide(); $('.blog-sidebar').show(); }); + $('.blog-description').on('click', function() { + var max_height = $(this).css('max-height'); + if (max_height !== 'fit-content') { + $(this).css('max-height', 'fit-content'); + $(this).parent().css('background-image', 'inherit') + .css('padding-bottom', '0.5em'); + $(this).css('cursor', 'auto'); + } + }) + $('.blog-description').each(function() { + if ($(this).prop('scrollHeight') > $(this).height() ) { + $(this).parent().css('background-image', '-webkit-linear-gradient(bottom, lightgray, lightgray 3%, transparent 8%, transparent 100%)'); + $(this).parent().css('padding-bottom', '0'); + $(this).css('cursor', 'pointer'); + } + }); + $('.left-sidebar-item').on('click', function() { + var url = $(this).attr('data-href'); + window.location.replace(url); + }); + {% if feed_type == 'blog' %} + $('#news-icon').css('color', 'green'); + {% elif feed_type == 'problem' %} + $('#problems-icon').css('color', 'green'); + {% elif feed_type == 'ticket' %} + $('#tickets-icon').css('color', 'green'); + {% elif feed_type == 'comment' %} + $('#comments-icon').css('color', 'green'); + {% endif %} }); {% endblock %} @@ -90,20 +104,54 @@
- {% endif %} - - {% if own_open_tickets %} - - {% endif %} - - {% if open_tickets %} - - {% endif %} - + {% else %} +
+ {% include "comments/list.html" %} +
{% endif %} {% endblock %} diff --git a/templates/ticket/feed.html b/templates/ticket/feed.html new file mode 100644 index 0000000..89a2db5 --- /dev/null +++ b/templates/ticket/feed.html @@ -0,0 +1,25 @@ +
+

+ + {{ ticket.title }} + + - + + {{ ticket.linked_item|item_title }} +

+ {% with author=ticket.user %} + {% if author %} +
+ + {{ link_user(author) }} +
+ {% endif %} + {% endwith %} +
+ + {{link_user(ticket.messages.last().user)}} {{_(' replied')}} +
+
+ {{ ticket.messages.last().body |markdown("ticket", MATH_ENGINE)|reference|str|safe }} +
+
\ No newline at end of file