From 4e7b8daada74af2eca2269ef977f5e1d1dc1d60f Mon Sep 17 00:00:00 2001 From: zhaospei Date: Mon, 6 Feb 2023 23:25:50 +0700 Subject: [PATCH 001/478] fix show-more display --- resources/blog.scss | 3 ++- templates/blog/content.html | 2 +- templates/comments/feed.html | 1 + templates/problem/feed.html | 2 +- templates/three-column-content.html | 8 ++++---- templates/ticket/feed.html | 1 + 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/resources/blog.scss b/resources/blog.scss index b422b82..5091076 100644 --- a/resources/blog.scss +++ b/resources/blog.scss @@ -162,6 +162,7 @@ overflow-wrap: anywhere; padding-bottom: 1em; clear: both; + position: relative; } .problem-feed-name { @@ -234,7 +235,7 @@ padding: 0px 12px; margin-top: 5px; position: absolute; - inset: 50% 0px 52px; + inset: 50% 0px 0px; background: linear-gradient(transparent, white); display: flex; -webkit-box-pack: end; diff --git a/templates/blog/content.html b/templates/blog/content.html index 7f77438..2b20b2a 100644 --- a/templates/blog/content.html +++ b/templates/blog/content.html @@ -42,8 +42,8 @@ {{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }} {% endcache %} +
{{_("...More")}}
-
{{_("...More")}}
{% set pagevote = post.pagevote %} {% set bookmark = post.bookmark %} diff --git a/templates/comments/feed.html b/templates/comments/feed.html index c5fb439..05a35ed 100644 --- a/templates/comments/feed.html +++ b/templates/comments/feed.html @@ -14,5 +14,6 @@ {% endwith %}
{{ comment.body|markdown(lazy_load=True)|reference|str|safe }} +
\ No newline at end of file diff --git a/templates/problem/feed.html b/templates/problem/feed.html index b64dfe7..d28868f 100644 --- a/templates/problem/feed.html +++ b/templates/problem/feed.html @@ -132,8 +132,8 @@ {% endif %} +
{{_("...More")}}
-
{{_("...More")}}
{% set pagevote = problem.pagevote %} {% set bookmark = problem.bookmark %} diff --git a/templates/three-column-content.html b/templates/three-column-content.html index 4a7a84c..fa72d48 100644 --- a/templates/three-column-content.html +++ b/templates/three-column-content.html @@ -33,13 +33,13 @@ + {% endblock %} {% macro make_tab_item(name, fa, url, text) %} - + {% endmacro %} {% block body %} -{% block before_posts %}{% endblock %} -
- {% block left_sidebar %}{% endblock %} -
- {% block three_col_media %}{% endblock %} - {% block three_col_js %}{% endblock %} -
- {% block middle_title %}{% endblock %} - {% block middle_content %}{% endblock %} + {% block before_posts %}{% endblock %} +
+ {% block left_sidebar %}{% endblock %} +
+ {% block three_col_media %}{% endblock %} + {% block three_col_js %}{% endblock %} +
+ {% block middle_title %}{% endblock %} + {% block middle_content %}{% endblock %} +
+ {% block right_sidebar %}{% endblock %}
- {% block right_sidebar %}{% endblock %}
-
-{% block after_posts %}{% endblock %} + {% block after_posts %}{% endblock %} {% endblock %} {% block bodyend %} -{{ super() }} -{% if REQUIRE_JAX %} -{% include "mathjax-load.html" %} -{% endif %} -{% include "comments/math.html" %} + {{ super() }} + {% if REQUIRE_JAX %} + {% include "mathjax-load.html" %} + {% endif %} + {% include "comments/math.html" %} {% endblock %} \ No newline at end of file diff --git a/templates/two-column-content.html b/templates/two-column-content.html index cebf64b..82899a7 100644 --- a/templates/two-column-content.html +++ b/templates/two-column-content.html @@ -1,3 +1,4 @@ +{% set is_two_column = true %} {% extends "three-column-content.html" %} {% block three_col_js %} From 8e6bcd90afc7aff5e822b36f09d80fbc85318009 Mon Sep 17 00:00:00 2001 From: zhaospei Date: Wed, 8 Feb 2023 17:22:43 +0700 Subject: [PATCH 007/478] fix superuser contest search --- judge/views/contests.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/judge/views/contests.py b/judge/views/contests.py index 9fb4273..f944e11 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -153,12 +153,13 @@ class ContestList( if "orgs" in self.request.GET and self.request.profile: try: self.org_query = list(map(int, request.GET.getlist("orgs"))) - self.org_query = [ - i - for i in self.org_query - if i - in self.request.profile.organizations.values_list("id", flat=True) - ] + if not self.request.user.is_superuser: + self.org_query = [ + i + for i in self.org_query + if i + in self.request.profile.organizations.values_list("id", flat=True) + ] except ValueError: pass @@ -228,7 +229,10 @@ class ContestList( context["contest_query"] = self.contest_query context["org_query"] = self.org_query if self.request.profile: - context["organizations"] = self.request.profile.organizations.all() + if self.request.user.is_superuser: + context["organizations"] = Organization.objects.all() + else: + context["organizations"] = self.request.profile.organizations.all() context["page_type"] = "list" context.update(self.get_sort_context()) context.update(self.get_sort_paginate_context()) From e2067d4d18823aac1f7db9c89aabebea9e4f0002 Mon Sep 17 00:00:00 2001 From: zhaospei Date: Thu, 9 Feb 2023 02:12:23 +0700 Subject: [PATCH 008/478] add hide organization contests list --- judge/views/contests.py | 6 ++++++ templates/contest/list.html | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/judge/views/contests.py b/judge/views/contests.py index f944e11..414a650 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -149,6 +149,9 @@ class ContestList( def get(self, request, *args, **kwargs): self.contest_query = None self.org_query = [] + self.show_orgs = 0 + if request.GET.get("show_orgs"): + self.show_orgs = 1 if "orgs" in self.request.GET and self.request.profile: try: @@ -182,6 +185,8 @@ class ContestList( ) if not self.org_query and self.request.organization: self.org_query = [self.request.organization.id] + if self.show_orgs: + queryset = queryset.filter(organizations=None) if self.org_query: queryset = queryset.filter(organizations__in=self.org_query) @@ -228,6 +233,7 @@ class ContestList( context["first_page_href"] = "." context["contest_query"] = self.contest_query context["org_query"] = self.org_query + context["show_orgs"] = int(self.show_orgs) if self.request.profile: if self.request.user.is_superuser: context["organizations"] = Organization.objects.all() diff --git a/templates/contest/list.html b/templates/contest/list.html index 94ec439..7d406d4 100644 --- a/templates/contest/list.html +++ b/templates/contest/list.html @@ -18,6 +18,10 @@ padding-bottom: 1em; } + #show-btn { + margin-top: 0.5em; + } + {% if page_obj and page_obj.number > 1%} #ongoing-table { display: none; @@ -71,6 +75,10 @@ \ No newline at end of file diff --git a/templates/feed/has_next.html b/templates/feed/has_next.html new file mode 100644 index 0000000..18415b5 --- /dev/null +++ b/templates/feed/has_next.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/notification/list.html b/templates/notification/list.html index 175c696..cd50e32 100644 --- a/templates/notification/list.html +++ b/templates/notification/list.html @@ -29,7 +29,7 @@ {% if notification.comment %} - {{ page_titles[notification.comment.page] }} + {{ notification.comment.page_title }} {% else %} {% autoescape off %} {{notification.html_link}} diff --git a/templates/organization/home.html b/templates/organization/home.html index bebfc73..80ad6dc 100644 --- a/templates/organization/home.html +++ b/templates/organization/home.html @@ -4,6 +4,7 @@ {% block org_js %} {% include "actionbar/media-js.html" %} + {% include "feed/feed_js.html" %} {% endblock %} {% block middle_title %} @@ -40,12 +41,7 @@ {% block middle_content %} {% block before_posts %}{% endblock %} {% if is_member or can_edit %} - {% for post in posts %} - {% include "blog/content.html" %} - {% endfor %} - {% if posts.paginator.num_pages > 1 %} -
{% include "list-pages.html" %}
- {% endif %} + {% include "blog/content.html" %} {% else %} {% endif %} - {% if has_comments or comment_all_list %} + {% if has_comments %}
    - {% set logged_in = request.user.is_authenticated %} - {% set profile = request.profile if logged_in else None %} - {% if comment_all_list %} - {% for node in mptt_tree(comment_all_list) recursive %} -
  • -
    -
    -
    - {% if logged_in %} - - {% else %} - - {% endif %} -
    -
    {{ node.score }}
    - {% if logged_in %} - - {% else %} - - {% endif %} -
    -
    -
    -
    - {% with author=node.author, user=node.author.user %} - - - - {% endwith %} - {{ link_user(node.author) }},  - {{ relative_time(node.time, abs=_('{time}'), rel=_('{time}')) }} - - - {% if node.revisions > 1 %} - - - - {% if node.revisions > 2 %} - {% trans edits=node.revisions - 1 %}edit {{ edits }}{% endtrans %} - {% else %} - {{ _('edited') }} - {% endif %} - - - - {% else %} - - {% endif %} - - - - {% if logged_in and not comment_lock %} - {% set can_edit = node.author.id == profile.id and not profile.mute %} - {% if can_edit %} - - - - {% else %} - - - - {% endif %} - {% if perms.judge.change_comment %} - {% if can_edit %} - - {% else %} - - {% endif %} - - - {% endif %} - {% endif %} - -
    -
    - - {% if node.score <= vote_hide_threshold %} -
    -

    - {% trans id=node.id %}This comment is hidden due to too much negative feedback. Click here to view it.{% endtrans %} -

    -
    - {% endif %} -
    -
    -
    -
  • - - {% with children=node.get_children() %} - {% if children %} -
      {{ loop(children) }}
    - {% endif %} - {% endwith %} - {% endfor %} - {% set comment_more = comment_count - offset %} - {% if comment_more == 1 %} - - - - - - {{ comment_count - offset }} {{ _('comment more') }} - - {% elif comment_more > 1 %} - - - - - - {{ comment_count - offset }} {{ _('comments more') }} - - {% endif %} - {% else %} {% include "comments/content-list.html" %} - {% endif %} -
{% elif not comment_lock %}

{{ _('There are no comments at the moment.') }}

diff --git a/templates/comments/media-js.html b/templates/comments/media-js.html index 131d040..31a7862 100644 --- a/templates/comments/media-js.html +++ b/templates/comments/media-js.html @@ -121,24 +121,23 @@ } const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); - const comment_remove = urlParams.get('comment-id'); - console.log(comment_remove); + const target_comment = urlParams.get('comment-id'); - window.comment_get_replies = function (id, parrent_none) { + window.comment_get_replies = function (id, parent_none) { var $comment_show_btn = $("#comment-" + id + " .show_more_reply"); $comment_show_btn.hide(); var $comment = $("#comment-" + id + "-children"); $comment.append("

Loading...

"); - ajax_get_reply('{{ url('comment_get_replies') }}', id, parrent_none); + ajax_get_reply('{{ url('comment_get_replies') }}', id, parent_none); } - function ajax_get_reply(url, id, parrent_none) { + function ajax_get_reply(url, id, parent_none) { return $.ajax({ url: url, type: 'GET', data: { id: id, - parrent_none: parrent_none, + parent_none: parent_none, }, success: function(data) { var $comment_loading = $("#comment-" + id + "-children .loading"); @@ -149,9 +148,8 @@ }) } - window.comment_show_more = function (id, parrent_none, offset, comment_remove) { - console.log(parrent_none) - if (parrent_none == 1) { + window.comment_show_more = function (id, parent_none, offset, target_comment) { + if (parent_none == 1) { var $comment_show_btn = $("#comment-0" + " .show_more_comment"); $comment_show_btn.hide(); var $comment = $("#comment-0"); @@ -162,21 +160,21 @@ var $comment = $("#comment-" + id + "-children"); $comment.append("

Loading...

"); } - ajax_comment_show_more('{{ url('comment_show_more') }}', id, parrent_none, offset, comment_remove); + ajax_comment_show_more('{{ url('comment_show_more') }}', id, parent_none, offset, target_comment); } - function ajax_comment_show_more(url, id, parrent_none, offset, comment_remove) { + function ajax_comment_show_more(url, id, parent_none, offset, target_comment) { return $.ajax({ url: url, type: 'GET', data: { id: id, - parrent_none: parrent_none, + parent_none: parent_none, offset: offset, - comment_remove: comment_remove, + target_comment: target_comment, }, success: function(data) { - if (parrent_none == 1) { + if (parent_none == 1) { var $comment_loading = $("#comment-0" + " .loading"); $comment_loading.hide(); var $comment = $("#comment-0"); From 1cbd4dee49add0c675e15ba717f81d902ae8e648 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 22 May 2023 23:27:04 +0700 Subject: [PATCH 105/478] Fix bug --- judge/comments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/judge/comments.py b/judge/comments.py index 5a38380..88f65e6 100644 --- a/judge/comments.py +++ b/judge/comments.py @@ -217,7 +217,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): if self.request.user.is_authenticated: context["is_new_user"] = ( not self.request.user.is_staff - and not profile.submission_set.filter( + and not self.request.profile.submission_set.filter( points=F("problem__points") ).exists() ) From f65238ba420149972a062d759f7fb9496ca8fee7 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 24 May 2023 18:36:15 +0700 Subject: [PATCH 106/478] Remove sleep --- judge/tasks/submission.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/judge/tasks/submission.py b/judge/tasks/submission.py index 1c39b1c..aa474f8 100644 --- a/judge/tasks/submission.py +++ b/judge/tasks/submission.py @@ -1,7 +1,6 @@ from celery import shared_task from django.core.cache import cache from django.utils.translation import gettext as _ -from time import sleep from judge.models import Problem, Profile, Submission from judge.utils.celery import Progress @@ -35,7 +34,6 @@ def rejudge_problem_filter( for submission in queryset.iterator(): submission.judge(rejudge=True, batch_rejudge=True) rejudged += 1 - sleep(0.1) if rejudged % 10 == 0: p.done = rejudged return rejudged From fbd1d865faad69118fa3e23646352eb8740ced95 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Sat, 27 May 2023 08:33:19 +0700 Subject: [PATCH 107/478] Try optimizing batch rejudge --- judge/judgeapi.py | 5 +++-- judge/tasks/submission.py | 12 +++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/judge/judgeapi.py b/judge/judgeapi.py index 96a2153..dd02898 100644 --- a/judge/judgeapi.py +++ b/judge/judgeapi.py @@ -56,7 +56,7 @@ def judge_request(packet, reply=True): return result -def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=None): +def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=None, delete_testcases=True): from .models import ContestSubmission, Submission, SubmissionTestCase CONTEST_SUBMISSION_PRIORITY = 0 @@ -101,7 +101,8 @@ def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=No ): return False - SubmissionTestCase.objects.filter(submission_id=submission.id).delete() + if delete_testcases: + SubmissionTestCase.objects.filter(submission_id=submission.id).delete() try: response = judge_request( diff --git a/judge/tasks/submission.py b/judge/tasks/submission.py index aa474f8..3cc32ad 100644 --- a/judge/tasks/submission.py +++ b/judge/tasks/submission.py @@ -2,7 +2,7 @@ from celery import shared_task from django.core.cache import cache from django.utils.translation import gettext as _ -from judge.models import Problem, Profile, Submission +from judge.models import Problem, Profile, Submission, SubmissionTestCase from judge.utils.celery import Progress __all__ = ("apply_submission_filter", "rejudge_problem_filter", "rescore_problem") @@ -30,9 +30,15 @@ def rejudge_problem_filter( queryset = apply_submission_filter(queryset, id_range, languages, results, contest) rejudged = 0 - with Progress(self, queryset.count()) as p: + with Progress(self, queryset.count() * 2) as p: + for submission in queryset: + SubmissionTestCase.objects.filter(submission_id=submission.id).delete() + rejudged += 1 + if rejudged % 10 == 0: + p.done = rejudged + for submission in queryset.iterator(): - submission.judge(rejudge=True, batch_rejudge=True) + submission.judge(rejudge=True, batch_rejudge=True, delete_testcases=False) rejudged += 1 if rejudged % 10 == 0: p.done = rejudged From 2291d6bbb8f6c081b7b8b34fe3dbeb1a5be92466 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Sat, 27 May 2023 09:00:42 +0700 Subject: [PATCH 108/478] Revert "Try optimizing batch rejudge" This reverts commit fbd1d865faad69118fa3e23646352eb8740ced95. --- judge/judgeapi.py | 5 ++--- judge/tasks/submission.py | 12 +++--------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/judge/judgeapi.py b/judge/judgeapi.py index dd02898..96a2153 100644 --- a/judge/judgeapi.py +++ b/judge/judgeapi.py @@ -56,7 +56,7 @@ def judge_request(packet, reply=True): return result -def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=None, delete_testcases=True): +def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=None): from .models import ContestSubmission, Submission, SubmissionTestCase CONTEST_SUBMISSION_PRIORITY = 0 @@ -101,8 +101,7 @@ def judge_submission(submission, rejudge=False, batch_rejudge=False, judge_id=No ): return False - if delete_testcases: - SubmissionTestCase.objects.filter(submission_id=submission.id).delete() + SubmissionTestCase.objects.filter(submission_id=submission.id).delete() try: response = judge_request( diff --git a/judge/tasks/submission.py b/judge/tasks/submission.py index 3cc32ad..aa474f8 100644 --- a/judge/tasks/submission.py +++ b/judge/tasks/submission.py @@ -2,7 +2,7 @@ from celery import shared_task from django.core.cache import cache from django.utils.translation import gettext as _ -from judge.models import Problem, Profile, Submission, SubmissionTestCase +from judge.models import Problem, Profile, Submission from judge.utils.celery import Progress __all__ = ("apply_submission_filter", "rejudge_problem_filter", "rescore_problem") @@ -30,15 +30,9 @@ def rejudge_problem_filter( queryset = apply_submission_filter(queryset, id_range, languages, results, contest) rejudged = 0 - with Progress(self, queryset.count() * 2) as p: - for submission in queryset: - SubmissionTestCase.objects.filter(submission_id=submission.id).delete() - rejudged += 1 - if rejudged % 10 == 0: - p.done = rejudged - + with Progress(self, queryset.count()) as p: for submission in queryset.iterator(): - submission.judge(rejudge=True, batch_rejudge=True, delete_testcases=False) + submission.judge(rejudge=True, batch_rejudge=True) rejudged += 1 if rejudged % 10 == 0: p.done = rejudged From 90700369788a797806cc22c5d795265f8816a1d2 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Sat, 27 May 2023 11:06:40 +0700 Subject: [PATCH 109/478] Fix actionbar style --- templates/actionbar/list.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/actionbar/list.html b/templates/actionbar/list.html index bbed0bc..6b8e1d6 100644 --- a/templates/actionbar/list.html +++ b/templates/actionbar/list.html @@ -43,7 +43,7 @@ {{_("Bookmark")}} - + From 8cfc58ad91d1efd1ac42ce4c0dbd2f81030b39b8 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 31 May 2023 16:04:47 +0700 Subject: [PATCH 110/478] Fix show types bug for problem feed --- judge/views/problem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/judge/views/problem.py b/judge/views/problem.py index 7fec6fb..5697bd3 100644 --- a/judge/views/problem.py +++ b/judge/views/problem.py @@ -946,6 +946,7 @@ class ProblemFeed(ProblemList, FeedView): return { "completed_problem_ids": self.get_completed_problems(), "attempted_problems": self.get_attempted_problems(), + "show_types": self.show_types, } def get_context_data(self, **kwargs): From a02814621e5a42c3a6c9f0abcb173c16ff96eb3f Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 2 Jun 2023 12:45:30 +0700 Subject: [PATCH 111/478] Fix chat input --- resources/chatbox.scss | 2 +- templates/chat/chat.html | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/resources/chatbox.scss b/resources/chatbox.scss index ae2be84..c433ce1 100644 --- a/resources/chatbox.scss +++ b/resources/chatbox.scss @@ -63,7 +63,7 @@ #chat-input { width: 100%; - padding: 0.4em 4em 0.6em 1.2em; + padding: 0.4em 4em 1em 1.2em; border: 0; color: black; border-top-left-radius: 0; diff --git a/templates/chat/chat.html b/templates/chat/chat.html index 4993436..beb38b4 100644 --- a/templates/chat/chat.html +++ b/templates/chat/chat.html @@ -560,6 +560,10 @@ return !in_user_redirect; }); + $("#chat-input").on("keyup", function() { + $("#chat-input").scrollTop($("#chat-input")[0].scrollHeight); + }); + // https://stackoverflow.com/questions/42121565/detecting-class-change-without-setinterval if (typeof(MutationObserver) !== undefined) { var observer = new MutationObserver(function (event) { From 15950634633e9bdda458ce702b6645bee1d828d5 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Thu, 6 Jul 2023 22:37:43 +0700 Subject: [PATCH 112/478] Fix organization submissions --- judge/views/organization.py | 4 ++-- judge/views/submission.py | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/judge/views/organization.py b/judge/views/organization.py index 05524b8..d83d2bb 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -441,7 +441,7 @@ class OrganizationSubmissions( LoginRequiredMixin, MemberOrganizationMixin, SubmissionsListBase ): template_name = "organization/submissions.html" - + @cached_property def in_contest(self): return False @@ -453,7 +453,7 @@ class OrganizationSubmissions( def get_queryset(self): return ( super() - ._get_entire_queryset() + .get_queryset() .filter(contest_object__organizations=self.organization) ) diff --git a/judge/views/submission.py b/judge/views/submission.py index a846327..a7f3eb0 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -322,6 +322,7 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): context_object_name = "submissions" first_page_href = None include_frozen = False + organization = None def get_result_data(self): result = self._get_result_data() @@ -348,7 +349,11 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): return self.request.profile.current_contest.contest def _get_entire_queryset(self): - queryset = Submission.objects.all() + organization = self.organization or self.request.organization + if organization: + queryset = Submission.objects.filter(contest_object__organizations=organization) + else: + queryset = Submission.objects.all() use_straight_join(queryset) queryset = submission_related(queryset.order_by("-id")) if self.show_problem: @@ -412,10 +417,6 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): def get_queryset(self): queryset = self._get_entire_queryset() if not self.in_contest: - if self.request.organization: - queryset = queryset.filter( - contest_object__organizations=self.request.organization - ) join_sql_subquery( queryset, subquery=str( From 1ca0d51f67618767ac470d19aa2cff2b248a830e Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Thu, 6 Jul 2023 22:39:16 +0700 Subject: [PATCH 113/478] Format --- judge/comments.py | 16 +++++++--------- judge/models/comment.py | 4 ++-- judge/models/course.py | 14 +++++++------- judge/views/comment.py | 30 ++++++++++++++---------------- judge/views/course.py | 12 +++++++----- judge/views/organization.py | 2 +- judge/views/submission.py | 4 +++- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/judge/comments.py b/judge/comments.py index 88f65e6..bb8f32b 100644 --- a/judge/comments.py +++ b/judge/comments.py @@ -156,7 +156,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): def get(self, request, *args, **kwargs): target_comment = None - if "comment-id" in request.GET: + if "comment-id" in request.GET: comment_id = int(request.GET["comment-id"]) try: comment_obj = Comment.objects.get(id=comment_id) @@ -179,9 +179,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): queryset.select_related("author__user") .filter(hidden=False) .defer("author__about") - .annotate( - revisions=Count("versions", distinct=True) - ) + .annotate(revisions=Count("versions", distinct=True)) ) else: queryset = self.object.comments @@ -203,7 +201,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): "votes", condition=Q(votes__voter_id=profile.id) ), ).annotate(vote_score=Coalesce(F("my_vote__score"), Value(0))) - + return queryset def get_context_data(self, target_comment=None, **kwargs): @@ -211,9 +209,9 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): queryset = self._get_queryset(target_comment) comment_count = self.object.comments.filter(parent=None, hidden=False).count() context["target_comment"] = -1 - if (target_comment != None): + if target_comment != None: context["target_comment"] = target_comment.id - + if self.request.user.is_authenticated: context["is_new_user"] = ( not self.request.user.is_staff @@ -229,7 +227,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): context["vote_hide_threshold"] = settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD if queryset.exists(): context["comment_root_id"] = queryset[0].id - else: + else: context["comment_root_id"] = 0 context["comment_parent_none"] = 1 if target_comment != None: @@ -238,7 +236,7 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View): else: context["offset"] = DEFAULT_OFFSET context["comment_more"] = comment_count - DEFAULT_OFFSET - + context["limit"] = DEFAULT_OFFSET context["comment_count"] = comment_count return context diff --git a/judge/models/comment.py b/judge/models/comment.py index 335d567..6058cf8 100644 --- a/judge/models/comment.py +++ b/judge/models/comment.py @@ -56,7 +56,7 @@ class Comment(MPTTModel): related_name="replies", on_delete=CASCADE, ) - + versions = VersionRelation() class Meta: @@ -112,7 +112,7 @@ class Comment(MPTTModel): if len(output) >= n: return output return output - + @cached_property def get_replies(self): query = Comment.filter(parent=self) diff --git a/judge/models/course.py b/judge/models/course.py index 457e0ea..e4a155a 100644 --- a/judge/models/course.py +++ b/judge/models/course.py @@ -68,7 +68,7 @@ class Course(models.Model): return False @classmethod - def is_accessible_by(cls,course, profile): + def is_accessible_by(cls, course, profile): userqueryset = CourseRole.objects.filter(course=course, user=profile) if userqueryset.exists(): return True @@ -76,29 +76,29 @@ class Course(models.Model): return False @classmethod - def get_students(cls,course): + def get_students(cls, course): return CourseRole.objects.filter(course=course, role="ST").values("user") @classmethod - def get_assistants(cls,course): + def get_assistants(cls, course): return CourseRole.objects.filter(course=course, role="AS").values("user") @classmethod - def get_teachers(cls,course): + def get_teachers(cls, course): return CourseRole.objects.filter(course=course, role="TE").values("user") @classmethod - def add_student(cls,course, profiles): + def add_student(cls, course, profiles): for profile in profiles: CourseRole.make_role(course=course, user=profile, role="ST") @classmethod - def add_teachers(cls,course, profiles): + def add_teachers(cls, course, profiles): for profile in profiles: CourseRole.make_role(course=course, user=profile, role="TE") @classmethod - def add_assistants(cls,course, profiles): + def add_assistants(cls, course, profiles): for profile in profiles: CourseRole.make_role(course=course, user=profile, role="AS") diff --git a/judge/views/comment.py b/judge/views/comment.py index 3c88923..03285ad 100644 --- a/judge/views/comment.py +++ b/judge/views/comment.py @@ -128,19 +128,19 @@ def get_comments(request, limit=10): offset = 0 if "offset" in request.GET: offset = int(request.GET["offset"]) - + target_comment = -1 if "target_comment" in request.GET: target_comment = int(request.GET["target_comment"]) - + comment_root_id = 0 - + if comment_id: comment_obj = Comment.objects.get(pk=comment_id) comment_root_id = comment_obj.id else: comment_obj = None - + queryset = comment_obj.linked_object.comments if parent_none: queryset = queryset.filter(parent=None, hidden=False) @@ -152,35 +152,33 @@ def get_comments(request, limit=10): queryset.select_related("author__user") .defer("author__about") .annotate( - count_replies=Count("replies", distinct=True), + count_replies=Count("replies", distinct=True), revisions=Count("versions", distinct=True), - )[offset:offset+limit] + )[offset : offset + limit] ) if request.user.is_authenticated: profile = request.profile queryset = queryset.annotate( - my_vote=FilteredRelation( - "votes", condition=Q(votes__voter_id=profile.id) - ), + my_vote=FilteredRelation("votes", condition=Q(votes__voter_id=profile.id)), ).annotate(vote_score=Coalesce(F("my_vote__score"), Value(0))) new_offset = offset + min(len(queryset), limit) comment_html = loader.render_to_string( - "comments/content-list.html", + "comments/content-list.html", { "request": request, - "comment_root_id": comment_root_id, - "comment_list": queryset, - "vote_hide_threshold" : settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD, + "comment_root_id": comment_root_id, + "comment_list": queryset, + "vote_hide_threshold": settings.DMOJ_COMMENT_VOTE_HIDE_THRESHOLD, "perms": PermWrapper(request.user), - "offset": new_offset, + "offset": new_offset, "limit": limit, "comment_count": comment_count, "comment_parent_none": parent_none, "target_comment": target_comment, - "comment_more": comment_count - new_offset - } + "comment_more": comment_count - new_offset, + }, ) return HttpResponse(comment_html) diff --git a/judge/views/course.py b/judge/views/course.py index f24155b..d8582af 100644 --- a/judge/views/course.py +++ b/judge/views/course.py @@ -10,22 +10,25 @@ __all__ = [ "CourseStudentResults", "CourseEdit", "CourseResourceDetailEdit", - "CourseResourceEdit", + "CourseResourceEdit", ] course_directory_file = "" + class CourseListMixin(object): def get_queryset(self): - return Course.objects.filter(is_open = "true").values() + return Course.objects.filter(is_open="true").values() + class CourseList(ListView): model = Course template_name = "course/list.html" queryset = Course.objects.filter(is_public=True).filter(is_open=True) + def get_context_data(self, **kwargs): - context = super(CourseList,self).get_context_data(**kwargs) - available , enrolling = [] , [] + context = super(CourseList, self).get_context_data(**kwargs) + available, enrolling = [], [] for course in Course.objects.filter(is_public=True).filter(is_open=True): if Course.is_accessible_by(course, self.request.profile): enrolling.append(course) @@ -34,4 +37,3 @@ class CourseList(ListView): context["available"] = available context["enrolling"] = enrolling return context - diff --git a/judge/views/organization.py b/judge/views/organization.py index d83d2bb..799b734 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -441,7 +441,7 @@ class OrganizationSubmissions( LoginRequiredMixin, MemberOrganizationMixin, SubmissionsListBase ): template_name = "organization/submissions.html" - + @cached_property def in_contest(self): return False diff --git a/judge/views/submission.py b/judge/views/submission.py index a7f3eb0..5a2a695 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -351,7 +351,9 @@ class SubmissionsListBase(DiggPaginatorMixin, TitleMixin, ListView): def _get_entire_queryset(self): organization = self.organization or self.request.organization if organization: - queryset = Submission.objects.filter(contest_object__organizations=organization) + queryset = Submission.objects.filter( + contest_object__organizations=organization + ) else: queryset = Submission.objects.all() use_straight_join(queryset) From 0b27c9da239acb596dce1babd538759ec3ef959e Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 7 Jul 2023 00:54:52 +0700 Subject: [PATCH 114/478] Fix some css --- judge/views/organization.py | 7 ------- templates/blog/list.html | 5 +++-- templates/problem/list-base.html | 2 +- templates/three-column-content.html | 16 ++++++---------- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/judge/views/organization.py b/judge/views/organization.py index 799b734..58ac765 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -450,13 +450,6 @@ class OrganizationSubmissions( def contest(self): return None - def get_queryset(self): - return ( - super() - .get_queryset() - .filter(contest_object__organizations=self.organization) - ) - def get_context_data(self, **kwargs): context = super(OrganizationSubmissions, self).get_context_data(**kwargs) # context["dynamic_update"] = context["page_obj"].number == 1 diff --git a/templates/blog/list.html b/templates/blog/list.html index 0535def..6677bf8 100644 --- a/templates/blog/list.html +++ b/templates/blog/list.html @@ -67,14 +67,15 @@ '),s=e('
'),i.append(s),u.addClass("xdsoft_scroller_box").append(i),D=function(e){var t=d(e).y-c+p;t<0&&(t=0),t+s[0].offsetHeight>h&&(t=h-s[0].offsetHeight),u.trigger("scroll_element.xdsoft_scroller",[l?t/l:0])},s.on("touchstart.xdsoft_scroller mousedown.xdsoft_scroller",function(r){n||u.trigger("resize_scroll.xdsoft_scroller",[a]),c=d(r).y,p=parseInt(s.css("margin-top"),10),h=i[0].offsetHeight,"mousedown"===r.type||"touchstart"===r.type?(t.ownerDocument&&e(t.ownerDocument.body).addClass("xdsoft_noselect"),e([t.ownerDocument.body,t.contentWindow]).on("touchend mouseup.xdsoft_scroller",function a(){e([t.ownerDocument.body,t.contentWindow]).off("touchend mouseup.xdsoft_scroller",a).off("mousemove.xdsoft_scroller",D).removeClass("xdsoft_noselect")}),e(t.ownerDocument.body).on("mousemove.xdsoft_scroller",D)):(g=!0,r.stopPropagation(),r.preventDefault())}).on("touchmove",function(e){g&&(e.preventDefault(),D(e))}).on("touchend touchcancel",function(){g=!1,p=0}),u.on("scroll_element.xdsoft_scroller",function(e,t){n||u.trigger("resize_scroll.xdsoft_scroller",[t,!0]),t=t>1?1:t<0||isNaN(t)?0:t,s.css("margin-top",l*t),setTimeout(function(){r.css("marginTop",-parseInt((r[0].offsetHeight-n)*t,10))},10)}).on("resize_scroll.xdsoft_scroller",function(e,t,a){var d,f;n=u[0].clientHeight,o=r[0].offsetHeight,f=(d=n/o)*i[0].offsetHeight,d>1?s.hide():(s.show(),s.css("height",parseInt(f>10?f:10,10)),l=i[0].offsetHeight-s[0].offsetHeight,!0!==a&&u.trigger("scroll_element.xdsoft_scroller",[t||Math.abs(parseInt(r.css("marginTop"),10))/(o-n)]))}),u.on("mousewheel",function(e){var t=Math.abs(parseInt(r.css("marginTop"),10));return(t-=20*e.deltaY)<0&&(t=0),u.trigger("scroll_element.xdsoft_scroller",[t/(o-n)]),e.stopPropagation(),!1}),u.on("touchstart",function(e){f=d(e),m=Math.abs(parseInt(r.css("marginTop"),10))}),u.on("touchmove",function(e){if(f){e.preventDefault();var t=d(e);u.trigger("scroll_element.xdsoft_scroller",[(m-(t.y-f.y))/(o-n)])}}),u.on("touchend touchcancel",function(){f=!1,m=0})),u.trigger("resize_scroll.xdsoft_scroller",[a])):u.find(".xdsoft_scrollbar").hide()})},e.fn.datetimepicker=function(n,i){var s,u,d=this,l=48,f=57,c=96,m=105,h=17,g=46,p=13,D=27,v=8,y=37,b=38,k=39,x=40,T=9,S=116,M=65,w=67,O=86,W=90,_=89,F=!1,C=e.isPlainObject(n)||!n?e.extend(!0,{},a,n):e.extend(!0,{},a),P=0,Y=function(e){e.on("open.xdsoft focusin.xdsoft mousedown.xdsoft touchstart",function t(){e.is(":disabled")||e.data("xdsoft_datetimepicker")||(clearTimeout(P),P=setTimeout(function(){e.data("xdsoft_datetimepicker")||s(e),e.off("open.xdsoft focusin.xdsoft mousedown.xdsoft touchstart",t).trigger("open.xdsoft")},100))})};return s=function(a){function i(){var e,t=!1;return C.startDate?t=A.strToDate(C.startDate):(t=C.value||(a&&a.val&&a.val()?a.val():""))?(t=A.strToDateTime(t),C.yearOffset&&(t=new Date(t.getFullYear()-C.yearOffset,t.getMonth(),t.getDate(),t.getHours(),t.getMinutes(),t.getSeconds(),t.getMilliseconds()))):C.defaultDate&&(t=A.strToDateTime(C.defaultDate),C.defaultTime&&(e=A.strtotime(C.defaultTime),t.setHours(e.getHours()),t.setMinutes(e.getMinutes()))),t&&A.isValidDate(t)?j.data("changed",!0):t="",t||0}function s(t){var n=function(e,t){var a=e.replace(/([\[\]\/\{\}\(\)\-\.\+]{1})/g,"\\$1").replace(/_/g,"{digit+}").replace(/([0-9]{1})/g,"{digit$1}").replace(/\{digit([0-9]{1})\}/g,"[0-$1_]{1}").replace(/\{digit[\+]\}/g,"[0-9_]{1}");return new RegExp(a).test(t)},o=function(e,a){if(!(e="string"==typeof e||e instanceof String?t.ownerDocument.getElementById(e):e))return!1;if(e.createTextRange){var r=e.createTextRange();return r.collapse(!0),r.moveEnd("character",a),r.moveStart("character",a),r.select(),!0}return!!e.setSelectionRange&&(e.setSelectionRange(a,a),!0)};t.mask&&a.off("keydown.xdsoft"),!0===t.mask&&(r.formatMask?t.mask=r.formatMask(t.format):t.mask=t.format.replace(/Y/g,"9999").replace(/F/g,"9999").replace(/m/g,"19").replace(/d/g,"39").replace(/H/g,"29").replace(/i/g,"59").replace(/s/g,"59")),"string"===e.type(t.mask)&&(n(t.mask,a.val())||(a.val(t.mask.replace(/[0-9]/g,"_")),o(a[0],0)),a.on("paste.xdsoft",function(r){var i=(r.clipboardData||r.originalEvent.clipboardData||window.clipboardData).getData("text"),s=this.value,u=this.selectionStart;return s=s.substr(0,u)+i+s.substr(u+i.length),u+=i.length,n(t.mask,s)?(this.value=s,o(this,u)):""===e.trim(s)?this.value=t.mask.replace(/[0-9]/g,"_"):a.trigger("error_input.xdsoft"),r.preventDefault(),!1}),a.on("keydown.xdsoft",function(r){var i,s=this.value,u=r.which,d=this.selectionStart,C=this.selectionEnd,P=d!==C;if(u>=l&&u<=f||u>=c&&u<=m||u===v||u===g){for(i=u===v||u===g?"_":String.fromCharCode(c<=u&&u<=m?u-l:u),u===v&&d&&!P&&(d-=1);;){var Y=t.mask.substr(d,1),A=d0;if(!(/[^0-9_]/.test(Y)&&A&&H))break;d+=u!==v||P?1:-1}if(P){var j=C-d,J=t.mask.replace(/[0-9]/g,"_"),z=J.substr(d,j).substr(1);s=s.substr(0,d)+(i+z)+s.substr(d+j)}else s=s.substr(0,d)+i+s.substr(d+1);if(""===e.trim(s))s=J;else if(d===t.mask.length)return r.preventDefault(),!1;for(d+=u===v?0:1;/[^0-9_]/.test(t.mask.substr(d,1))&&d0;)d+=u===v?0:1;n(t.mask,s)?(this.value=s,o(this,d)):""===e.trim(s)?this.value=t.mask.replace(/[0-9]/g,"_"):a.trigger("error_input.xdsoft")}else if(-1!==[M,w,O,W,_].indexOf(u)&&F||-1!==[D,b,x,y,k,S,h,T,p].indexOf(u))return!0;return r.preventDefault(),!1}))}var u,d,P,Y,A,H,j=e('
'),J=e(''),z=e('
'),I=e('
'),N=e('
'),L=e('
'),E=L.find(".xdsoft_time_box").eq(0),R=e('
'),V=e(''),B=e('
'),G=e('
'),U=!1,q=0;C.id&&j.attr("id",C.id),C.style&&j.attr("style",C.style),C.weeks&&j.addClass("xdsoft_showweeks"),C.rtl&&j.addClass("xdsoft_rtl"),j.addClass("xdsoft_"+C.theme),j.addClass(C.className),I.find(".xdsoft_month span").after(B),I.find(".xdsoft_year span").after(G),I.find(".xdsoft_month,.xdsoft_year").on("touchstart mousedown.xdsoft",function(t){var a,r,n=e(this).find(".xdsoft_select").eq(0),o=0,i=0,s=n.is(":visible");for(I.find(".xdsoft_select").hide(),A.currentTime&&(o=A.currentTime[e(this).hasClass("xdsoft_month")?"getMonth":"getFullYear"]()),n[s?"hide":"show"](),a=n.find("div.xdsoft_option"),r=0;rC.touchMovedThreshold&&(this.touchMoved=!0)};I.find(".xdsoft_select").xdsoftScroller(C).on("touchstart mousedown.xdsoft",function(e){var t=e.originalEvent;this.touchMoved=!1,this.touchStartPosition=t.touches?t.touches[0]:t,e.stopPropagation(),e.preventDefault()}).on("touchmove",".xdsoft_option",X).on("touchend mousedown.xdsoft",".xdsoft_option",function(){if(!this.touchMoved){void 0!==A.currentTime&&null!==A.currentTime||(A.currentTime=A.now());var t=A.currentTime.getFullYear();A&&A.currentTime&&A.currentTime[e(this).parent().parent().hasClass("xdsoft_monthselect")?"setMonth":"setFullYear"](e(this).data("value")),e(this).parent().parent().hide(),j.trigger("xchange.xdsoft"),C.onChangeMonth&&e.isFunction(C.onChangeMonth)&&C.onChangeMonth.call(j,A.currentTime,j.data("input")),t!==A.currentTime.getFullYear()&&e.isFunction(C.onChangeYear)&&C.onChangeYear.call(j,A.currentTime,j.data("input"))}}),j.getValue=function(){return A.getCurrentTime()},j.setOptions=function(n){var o={};C=e.extend(!0,{},C,n),n.allowTimes&&e.isArray(n.allowTimes)&&n.allowTimes.length&&(C.allowTimes=e.extend(!0,[],n.allowTimes)),n.weekends&&e.isArray(n.weekends)&&n.weekends.length&&(C.weekends=e.extend(!0,[],n.weekends)),n.allowDates&&e.isArray(n.allowDates)&&n.allowDates.length&&(C.allowDates=e.extend(!0,[],n.allowDates)),n.allowDateRe&&"[object String]"===Object.prototype.toString.call(n.allowDateRe)&&(C.allowDateRe=new RegExp(n.allowDateRe)),n.highlightedDates&&e.isArray(n.highlightedDates)&&n.highlightedDates.length&&(e.each(n.highlightedDates,function(a,n){var i,s=e.map(n.split(","),e.trim),u=new t(r.parseDate(s[0],C.formatDate),s[1],s[2]),d=r.formatDate(u.date,C.formatDate);void 0!==o[d]?(i=o[d].desc)&&i.length&&u.desc&&u.desc.length&&(o[d].desc=i+"\n"+u.desc):o[d]=u}),C.highlightedDates=e.extend(!0,[],o)),n.highlightedPeriods&&e.isArray(n.highlightedPeriods)&&n.highlightedPeriods.length&&(o=e.extend(!0,[],C.highlightedDates),e.each(n.highlightedPeriods,function(a,n){var i,s,u,d,l,f,c;if(e.isArray(n))i=n[0],s=n[1],u=n[2],c=n[3];else{var m=e.map(n.split(","),e.trim);i=r.parseDate(m[0],C.formatDate),s=r.parseDate(m[1],C.formatDate),u=m[2],c=m[3]}for(;i<=s;)d=new t(i,u,c),l=r.formatDate(i,C.formatDate),i.setDate(i.getDate()+1),void 0!==o[l]?(f=o[l].desc)&&f.length&&d.desc&&d.desc.length&&(o[l].desc=f+"\n"+d.desc):o[l]=d}),C.highlightedDates=e.extend(!0,[],o)),n.disabledDates&&e.isArray(n.disabledDates)&&n.disabledDates.length&&(C.disabledDates=e.extend(!0,[],n.disabledDates)),n.disabledWeekDays&&e.isArray(n.disabledWeekDays)&&n.disabledWeekDays.length&&(C.disabledWeekDays=e.extend(!0,[],n.disabledWeekDays)),!C.open&&!C.opened||C.inline||a.trigger("open.xdsoft"),C.inline&&(U=!0,j.addClass("xdsoft_inline"),a.after(j).hide()),C.inverseButton&&(C.next="xdsoft_prev",C.prev="xdsoft_next"),C.datepicker?z.addClass("active"):z.removeClass("active"),C.timepicker?L.addClass("active"):L.removeClass("active"),C.value&&(A.setCurrentTime(C.value),a&&a.val&&a.val(A.str)),isNaN(C.dayOfWeekStart)?C.dayOfWeekStart=0:C.dayOfWeekStart=parseInt(C.dayOfWeekStart,10)%7,C.timepickerScrollbar||E.xdsoftScroller(C,"hide"),C.minDate&&/^[\+\-](.*)$/.test(C.minDate)&&(C.minDate=r.formatDate(A.strToDateTime(C.minDate),C.formatDate)),C.maxDate&&/^[\+\-](.*)$/.test(C.maxDate)&&(C.maxDate=r.formatDate(A.strToDateTime(C.maxDate),C.formatDate)),C.minDateTime&&/^\+(.*)$/.test(C.minDateTime)&&(C.minDateTime=A.strToDateTime(C.minDateTime).dateFormat(C.formatDate)),C.maxDateTime&&/^\+(.*)$/.test(C.maxDateTime)&&(C.maxDateTime=A.strToDateTime(C.maxDateTime).dateFormat(C.formatDate)),V.toggle(C.showApplyButton),I.find(".xdsoft_today_button").css("visibility",C.todayButton?"visible":"hidden"),I.find("."+C.prev).css("visibility",C.prevButton?"visible":"hidden"),I.find("."+C.next).css("visibility",C.nextButton?"visible":"hidden"),s(C),C.validateOnBlur&&a.off("blur.xdsoft").on("blur.xdsoft",function(){if(C.allowBlank&&(!e.trim(e(this).val()).length||"string"==typeof C.mask&&e.trim(e(this).val())===C.mask.replace(/[0-9]/g,"_")))e(this).val(null),j.data("xdsoft_datetime").empty();else{var t=r.parseDate(e(this).val(),C.format);if(t)e(this).val(r.formatDate(t,C.format));else{var a=+[e(this).val()[0],e(this).val()[1]].join(""),n=+[e(this).val()[2],e(this).val()[3]].join("");!C.datepicker&&C.timepicker&&a>=0&&a<24&&n>=0&&n<60?e(this).val([a,n].map(function(e){return e>9?e:"0"+e}).join(":")):e(this).val(r.formatDate(A.now(),C.format))}j.data("xdsoft_datetime").setCurrentTime(e(this).val())}j.trigger("changedatetime.xdsoft"),j.trigger("close.xdsoft")}),C.dayOfWeekStartPrev=0===C.dayOfWeekStart?6:C.dayOfWeekStart-1,j.trigger("xchange.xdsoft").trigger("afterOpen.xdsoft")},j.data("options",C).on("touchstart mousedown.xdsoft",function(e){return e.stopPropagation(),e.preventDefault(),G.hide(),B.hide(),!1}),E.append(R),E.xdsoftScroller(C),j.on("afterOpen.xdsoft",function(){E.xdsoftScroller(C)}),j.append(z).append(L),!0!==C.withoutCopyright&&j.append(J),z.append(I).append(N).append(V),e(C.parentID).append(j),A=new function(){var t=this;t.now=function(e){var a,r,n=new Date;return!e&&C.defaultDate&&(a=t.strToDateTime(C.defaultDate),n.setFullYear(a.getFullYear()),n.setMonth(a.getMonth()),n.setDate(a.getDate())),n.setFullYear(n.getFullYear()),!e&&C.defaultTime&&(r=t.strtotime(C.defaultTime),n.setHours(r.getHours()),n.setMinutes(r.getMinutes()),n.setSeconds(r.getSeconds()),n.setMilliseconds(r.getMilliseconds())),n},t.isValidDate=function(e){return"[object Date]"===Object.prototype.toString.call(e)&&!isNaN(e.getTime())},t.setCurrentTime=function(e,a){"string"==typeof e?t.currentTime=t.strToDateTime(e):t.isValidDate(e)?t.currentTime=e:e||a||!C.allowBlank||C.inline?t.currentTime=t.now():t.currentTime=null,j.trigger("xchange.xdsoft")},t.empty=function(){t.currentTime=null},t.getCurrentTime=function(){return t.currentTime},t.nextMonth=function(){void 0!==t.currentTime&&null!==t.currentTime||(t.currentTime=t.now());var a,r=t.currentTime.getMonth()+1;return 12===r&&(t.currentTime.setFullYear(t.currentTime.getFullYear()+1),r=0),a=t.currentTime.getFullYear(),t.currentTime.setDate(Math.min(new Date(t.currentTime.getFullYear(),r+1,0).getDate(),t.currentTime.getDate())),t.currentTime.setMonth(r),C.onChangeMonth&&e.isFunction(C.onChangeMonth)&&C.onChangeMonth.call(j,A.currentTime,j.data("input")),a!==t.currentTime.getFullYear()&&e.isFunction(C.onChangeYear)&&C.onChangeYear.call(j,A.currentTime,j.data("input")),j.trigger("xchange.xdsoft"),r},t.prevMonth=function(){void 0!==t.currentTime&&null!==t.currentTime||(t.currentTime=t.now());var a=t.currentTime.getMonth()-1;return-1===a&&(t.currentTime.setFullYear(t.currentTime.getFullYear()-1),a=11),t.currentTime.setDate(Math.min(new Date(t.currentTime.getFullYear(),a+1,0).getDate(),t.currentTime.getDate())),t.currentTime.setMonth(a),C.onChangeMonth&&e.isFunction(C.onChangeMonth)&&C.onChangeMonth.call(j,A.currentTime,j.data("input")),j.trigger("xchange.xdsoft"),a},t.getWeekOfYear=function(t){if(C.onGetWeekOfYear&&e.isFunction(C.onGetWeekOfYear)){var a=C.onGetWeekOfYear.call(j,t);if(void 0!==a)return a}var r=new Date(t.getFullYear(),0,1);return 4!==r.getDay()&&r.setMonth(0,1+(4-r.getDay()+7)%7),Math.ceil(((t-r)/864e5+r.getDay()+1)/7)},t.strToDateTime=function(e){var a,n,o=[];return e&&e instanceof Date&&t.isValidDate(e)?e:((o=/^([+-]{1})(.*)$/.exec(e))&&(o[2]=r.parseDate(o[2],C.formatDate)),o&&o[2]?(a=o[2].getTime()-6e4*o[2].getTimezoneOffset(),n=new Date(t.now(!0).getTime()+parseInt(o[1]+"1",10)*a)):n=e?r.parseDate(e,C.format):t.now(),t.isValidDate(n)||(n=t.now()),n)},t.strToDate=function(e){if(e&&e instanceof Date&&t.isValidDate(e))return e;var a=e?r.parseDate(e,C.formatDate):t.now(!0);return t.isValidDate(a)||(a=t.now(!0)),a},t.strtotime=function(e){if(e&&e instanceof Date&&t.isValidDate(e))return e;var a=e?r.parseDate(e,C.formatTime):t.now(!0);return t.isValidDate(a)||(a=t.now(!0)),a},t.str=function(){var e=C.format;return C.yearOffset&&(e=(e=e.replace("Y",t.currentTime.getFullYear()+C.yearOffset)).replace("y",String(t.currentTime.getFullYear()+C.yearOffset).substring(2,4))),r.formatDate(t.currentTime,e)},t.currentTime=this.now()},V.on("touchend click",function(e){e.preventDefault(),j.data("changed",!0),A.setCurrentTime(i()),a.val(A.str()),j.trigger("close.xdsoft")}),I.find(".xdsoft_today_button").on("touchend mousedown.xdsoft",function(){j.data("changed",!0),A.setCurrentTime(0,!0),j.trigger("afterOpen.xdsoft")}).on("dblclick.xdsoft",function(){var e,t,r=A.getCurrentTime();r=new Date(r.getFullYear(),r.getMonth(),r.getDate()),e=A.strToDate(C.minDate),r<(e=new Date(e.getFullYear(),e.getMonth(),e.getDate()))||(t=A.strToDate(C.maxDate),r>(t=new Date(t.getFullYear(),t.getMonth(),t.getDate()))||(a.val(A.str()),a.trigger("change"),j.trigger("close.xdsoft")))}),I.find(".xdsoft_prev,.xdsoft_next").on("touchend mousedown.xdsoft",function(){var t=e(this),a=0,r=!1;!function e(n){t.hasClass(C.next)?A.nextMonth():t.hasClass(C.prev)&&A.prevMonth(),C.monthChangeSpinner&&(r||(a=setTimeout(e,n||100)))}(500),e([C.ownerDocument.body,C.contentWindow]).on("touchend mouseup.xdsoft",function t(){clearTimeout(a),r=!0,e([C.ownerDocument.body,C.contentWindow]).off("touchend mouseup.xdsoft",t)})}),L.find(".xdsoft_prev,.xdsoft_next").on("touchend mousedown.xdsoft",function(){var t=e(this),a=0,r=!1,n=110;!function e(o){var i=E[0].clientHeight,s=R[0].offsetHeight,u=Math.abs(parseInt(R.css("marginTop"),10));t.hasClass(C.next)&&s-i-C.timeHeightInTimePicker>=u?R.css("marginTop","-"+(u+C.timeHeightInTimePicker)+"px"):t.hasClass(C.prev)&&u-C.timeHeightInTimePicker>=0&&R.css("marginTop","-"+(u-C.timeHeightInTimePicker)+"px"),E.trigger("scroll_element.xdsoft_scroller",[Math.abs(parseInt(R[0].style.marginTop,10)/(s-i))]),n=n>10?10:n-10,r||(a=setTimeout(e,o||n))}(500),e([C.ownerDocument.body,C.contentWindow]).on("touchend mouseup.xdsoft",function t(){clearTimeout(a),r=!0,e([C.ownerDocument.body,C.contentWindow]).off("touchend mouseup.xdsoft",t)})}),u=0,j.on("xchange.xdsoft",function(t){clearTimeout(u),u=setTimeout(function(){void 0!==A.currentTime&&null!==A.currentTime||(A.currentTime=A.now());for(var t,i,s,u,d,l,f,c,m,h,g="",p=new Date(A.currentTime.getFullYear(),A.currentTime.getMonth(),1,12,0,0),D=0,v=A.now(),y=!1,b=!1,k=!1,x=!1,T=[],S=!0,M="";p.getDay()!==C.dayOfWeekStart;)p.setDate(p.getDate()-1);for(g+="",C.weeks&&(g+=""),t=0;t<7;t+=1)g+="";g+="",g+="",!1!==C.maxDate&&(y=A.strToDate(C.maxDate),y=new Date(y.getFullYear(),y.getMonth(),y.getDate(),23,59,59,999)),!1!==C.minDate&&(b=A.strToDate(C.minDate),b=new Date(b.getFullYear(),b.getMonth(),b.getDate())),!1!==C.minDateTime&&(k=A.strToDate(C.minDateTime),k=new Date(k.getFullYear(),k.getMonth(),k.getDate(),k.getHours(),k.getMinutes(),k.getSeconds())),!1!==C.maxDateTime&&(x=A.strToDate(C.maxDateTime),x=new Date(x.getFullYear(),x.getMonth(),x.getDate(),x.getHours(),x.getMinutes(),x.getSeconds()));var w;for(!1!==x&&(w=31*(12*x.getFullYear()+x.getMonth())+x.getDate());D0&&-1===C.allowDates.indexOf(r.formatDate(p,C.formatDate))&&T.push("xdsoft_disabled");var O=31*(12*p.getFullYear()+p.getMonth())+p.getDate();(!1!==y&&p>y||!1!==k&&pw||c&&!1===c[0])&&T.push("xdsoft_disabled"),-1!==C.disabledDates.indexOf(r.formatDate(p,C.formatDate))&&T.push("xdsoft_disabled"),-1!==C.disabledWeekDays.indexOf(s)&&T.push("xdsoft_disabled"),a.is("[disabled]")&&T.push("xdsoft_disabled"),c&&""!==c[1]&&T.push(c[1]),A.currentTime.getMonth()!==l&&T.push("xdsoft_other_month"),(C.defaultSelect||j.data("changed"))&&r.formatDate(A.currentTime,C.formatDate)===r.formatDate(p,C.formatDate)&&T.push("xdsoft_current"),r.formatDate(v,C.formatDate)===r.formatDate(p,C.formatDate)&&T.push("xdsoft_today"),0!==p.getDay()&&6!==p.getDay()&&-1===C.weekends.indexOf(r.formatDate(p,C.formatDate))||T.push("xdsoft_weekend"),void 0!==C.highlightedDates[r.formatDate(p,C.formatDate)]&&(i=C.highlightedDates[r.formatDate(p,C.formatDate)],T.push(void 0===i.style?"xdsoft_highlighted_default":i.style),h=void 0===i.desc?"":i.desc),C.beforeShowDay&&e.isFunction(C.beforeShowDay)&&T.push(C.beforeShowDay(p)),S&&(g+="",S=!1,C.weeks&&(g+="")),g+='",p.getDay()===C.dayOfWeekStartPrev&&(g+="",S=!0),p.setDate(u+1)}g+="
"+C.i18n[o].dayOfWeekShort[(t+C.dayOfWeekStart)%7]+"
"+f+"
'+u+"
",N.html(g),I.find(".xdsoft_label span").eq(0).text(C.i18n[o].months[A.currentTime.getMonth()]),I.find(".xdsoft_label span").eq(1).text(A.currentTime.getFullYear()+C.yearOffset),M="",l="";var W=0;if(!1!==C.minTime){F=A.strtotime(C.minTime);W=60*F.getHours()+F.getMinutes()}var _=1440;if(!1!==C.maxTime){F=A.strtotime(C.maxTime);_=60*F.getHours()+F.getMinutes()}if(!1!==C.minDateTime){F=A.strToDateTime(C.minDateTime);r.formatDate(A.currentTime,C.formatDate)===r.formatDate(F,C.formatDate)&&(l=60*F.getHours()+F.getMinutes())>W&&(W=l)}if(!1!==C.maxDateTime){var F=A.strToDateTime(C.maxDateTime);r.formatDate(A.currentTime,C.formatDate)===r.formatDate(F,C.formatDate)&&(l=60*F.getHours()+F.getMinutes())<_&&(_=l)}if(m=function(t,n){var o,i=A.now(),s=C.allowTimes&&e.isArray(C.allowTimes)&&C.allowTimes.length;i.setHours(t),t=parseInt(i.getHours(),10),i.setMinutes(n),n=parseInt(i.getMinutes(),10),T=[];var u=60*t+n;(a.is("[disabled]")||u>=_||u59||o.getMinutes()===parseInt(n,10))&&(C.defaultSelect||j.data("changed")?T.push("xdsoft_current"):C.initTime&&T.push("xdsoft_init_time")),parseInt(v.getHours(),10)===parseInt(t,10)&&parseInt(v.getMinutes(),10)===parseInt(n,10)&&T.push("xdsoft_today"),M+='
'+r.formatDate(i,C.formatTime)+"
"},C.allowTimes&&e.isArray(C.allowTimes)&&C.allowTimes.length)for(D=0;D=_||m((D<10?"0":"")+D,l=(t<10?"0":"")+t))}for(R.html(M),n="",D=parseInt(C.yearStart,10);D<=parseInt(C.yearEnd,10);D+=1)n+='
'+(D+C.yearOffset)+"
";for(G.children().eq(0).html(n),D=parseInt(C.monthStart,10),n="";D<=parseInt(C.monthEnd,10);D+=1)n+='
'+C.i18n[o].months[D]+"
";B.children().eq(0).html(n),e(j).trigger("generate.xdsoft")},10),t.stopPropagation()}).on("afterOpen.xdsoft",function(){if(C.timepicker){var e,t,a,r;R.find(".xdsoft_current").length?e=".xdsoft_current":R.find(".xdsoft_init_time").length&&(e=".xdsoft_init_time"),e?(t=E[0].clientHeight,(a=R[0].offsetHeight)-t<(r=R.find(e).index()*C.timeHeightInTimePicker+1)&&(r=a-t),E.trigger("scroll_element.xdsoft_scroller",[parseInt(r,10)/(a-t)])):E.trigger("scroll_element.xdsoft_scroller",[0])}}),d=0,N.on("touchend click.xdsoft","td",function(t){t.stopPropagation(),d+=1;var r=e(this),n=A.currentTime;if(void 0!==n&&null!==n||(A.currentTime=A.now(),n=A.currentTime),r.hasClass("xdsoft_disabled"))return!1;n.setDate(1),n.setFullYear(r.data("year")),n.setMonth(r.data("month")),n.setDate(r.data("date")),j.trigger("select.xdsoft",[n]),a.val(A.str()),C.onSelectDate&&e.isFunction(C.onSelectDate)&&C.onSelectDate.call(j,A.currentTime,j.data("input"),t),j.data("changed",!0),j.trigger("xchange.xdsoft"),j.trigger("changedatetime.xdsoft"),(d>1||!0===C.closeOnDateSelect||!1===C.closeOnDateSelect&&!C.timepicker)&&!C.inline&&j.trigger("close.xdsoft"),setTimeout(function(){d=0},200)}),R.on("touchstart","div",function(e){this.touchMoved=!1}).on("touchmove","div",X).on("touchend click.xdsoft","div",function(t){if(!this.touchMoved){t.stopPropagation();var a=e(this),r=A.currentTime;if(void 0!==r&&null!==r||(A.currentTime=A.now(),r=A.currentTime),a.hasClass("xdsoft_disabled"))return!1;r.setHours(a.data("hour")),r.setMinutes(a.data("minute")),j.trigger("select.xdsoft",[r]),j.data("input").val(A.str()),C.onSelectTime&&e.isFunction(C.onSelectTime)&&C.onSelectTime.call(j,A.currentTime,j.data("input"),t),j.data("changed",!0),j.trigger("xchange.xdsoft"),j.trigger("changedatetime.xdsoft"),!0!==C.inline&&!0===C.closeOnTimeSelect&&j.trigger("close.xdsoft")}}),z.on("mousewheel.xdsoft",function(e){return!C.scrollMonth||(e.deltaY<0?A.nextMonth():A.prevMonth(),!1)}),a.on("mousewheel.xdsoft",function(e){return!C.scrollInput||(!C.datepicker&&C.timepicker?((P=R.find(".xdsoft_current").length?R.find(".xdsoft_current").eq(0).index():0)+e.deltaY>=0&&P+e.deltaYc+m?(l="bottom",r=c+m-t.top):r-=m):r+j[0].offsetHeight>c+m&&(r=t.top-j[0].offsetHeight+1),r<0&&(r=0),n+a.offsetWidth>d&&(n=d-a.offsetWidth)),i=j[0],H(i,function(e){if("relative"===C.contentWindow.getComputedStyle(e).getPropertyValue("position")&&d>=e.offsetWidth)return n-=(d-e.offsetWidth)/2,!1}),(f={position:o,left:n,top:"",bottom:""})[l]=r,j.css(f)},j.on("open.xdsoft",function(t){var a=!0;C.onShow&&e.isFunction(C.onShow)&&(a=C.onShow.call(j,A.currentTime,j.data("input"),t)),!1!==a&&(j.show(),Y(),e(C.contentWindow).off("resize.xdsoft",Y).on("resize.xdsoft",Y),C.closeOnWithoutClick&&e([C.ownerDocument.body,C.contentWindow]).on("touchstart mousedown.xdsoft",function t(){j.trigger("close.xdsoft"),e([C.ownerDocument.body,C.contentWindow]).off("touchstart mousedown.xdsoft",t)}))}).on("close.xdsoft",function(t){var a=!0;I.find(".xdsoft_month,.xdsoft_year").find(".xdsoft_select").hide(),C.onClose&&e.isFunction(C.onClose)&&(a=C.onClose.call(j,A.currentTime,j.data("input"),t)),!1===a||C.opened||C.inline||j.hide(),t.stopPropagation()}).on("toggle.xdsoft",function(){j.is(":visible")?j.trigger("close.xdsoft"):j.trigger("open.xdsoft")}).data("input",a),q=0,j.data("xdsoft_datetime",A),j.setOptions(C),A.setCurrentTime(i()),a.data("xdsoft_datetimepicker",j).on("open.xdsoft focusin.xdsoft mousedown.xdsoft touchstart",function(){a.is(":disabled")||a.data("xdsoft_datetimepicker").is(":visible")&&C.closeOnInputClick||C.openOnFocus&&(clearTimeout(q),q=setTimeout(function(){a.is(":disabled")||(U=!0,A.setCurrentTime(i(),!0),C.mask&&s(C),j.trigger("open.xdsoft"))},100))}).on("keydown.xdsoft",function(t){var a,r=t.which;return-1!==[p].indexOf(r)&&C.enterLikeTab?(a=e("input:visible,textarea:visible,button:visible,a:visible"),j.trigger("close.xdsoft"),a.eq(a.index(this)+1).focus(),!1):-1!==[T].indexOf(r)?(j.trigger("close.xdsoft"),!0):void 0}).on("blur.xdsoft",function(){j.trigger("close.xdsoft")})},u=function(t){var a=t.data("xdsoft_datetimepicker");a&&(a.data("xdsoft_datetime",null),a.remove(),t.data("xdsoft_datetimepicker",null).off(".xdsoft"),e(C.contentWindow).off("resize.xdsoft"),e([C.contentWindow,C.ownerDocument.body]).off("mousedown.xdsoft touchstart"),t.unmousewheel&&t.unmousewheel())},e(C.ownerDocument).off("keydown.xdsoftctrl keyup.xdsoftctrl").on("keydown.xdsoftctrl",function(e){e.keyCode===h&&(F=!0)}).on("keyup.xdsoftctrl",function(e){e.keyCode===h&&(F=!1)}),this.each(function(){var t=e(this).data("xdsoft_datetimepicker");if(t){if("string"===e.type(n))switch(n){case"show":e(this).select().focus(),t.trigger("open.xdsoft");break;case"hide":t.trigger("close.xdsoft");break;case"toggle":t.trigger("toggle.xdsoft");break;case"destroy":u(e(this));break;case"reset":this.value=this.defaultValue,this.value&&t.data("xdsoft_datetime").isValidDate(r.parseDate(this.value,C.format))||t.data("changed",!1),t.data("xdsoft_datetime").setCurrentTime(this.value);break;case"validate":t.data("input").trigger("blur.xdsoft");break;default:t[n]&&e.isFunction(t[n])&&(d=t[n](i))}else t.setOptions(n);return 0}"string"!==e.type(n)&&(!C.lazyInit||C.open||C.inline?s(e(this)):Y(e(this)))}),d},e.fn.datetimepicker.defaults=a};!function(e){"function"==typeof define&&define.amd?define(["jquery","jquery-mousewheel"],e):"object"==typeof exports?module.exports=e(require("jquery")):e(jQuery)}(datetimepickerFactory),function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e:e(jQuery)}(function(e){function t(t){var i=t||window.event,s=u.call(arguments,1),d=0,f=0,c=0,m=0,h=0,g=0;if(t=e.event.fix(i),t.type="mousewheel","detail"in i&&(c=-1*i.detail),"wheelDelta"in i&&(c=i.wheelDelta),"wheelDeltaY"in i&&(c=i.wheelDeltaY),"wheelDeltaX"in i&&(f=-1*i.wheelDeltaX),"axis"in i&&i.axis===i.HORIZONTAL_AXIS&&(f=-1*c,c=0),d=0===c?f:c,"deltaY"in i&&(d=c=-1*i.deltaY),"deltaX"in i&&(f=i.deltaX,0===c&&(d=-1*f)),0!==c||0!==f){if(1===i.deltaMode){var p=e.data(this,"mousewheel-line-height");d*=p,c*=p,f*=p}else if(2===i.deltaMode){var D=e.data(this,"mousewheel-page-height");d*=D,c*=D,f*=D}if(m=Math.max(Math.abs(c),Math.abs(f)),(!o||m=1?"floor":"ceil"](d/o),f=Math[f>=1?"floor":"ceil"](f/o),c=Math[c>=1?"floor":"ceil"](c/o),l.settings.normalizeOffset&&this.getBoundingClientRect){var v=this.getBoundingClientRect();h=t.clientX-v.left,g=t.clientY-v.top}return t.deltaX=f,t.deltaY=c,t.deltaFactor=o,t.offsetX=h,t.offsetY=g,t.deltaMode=0,s.unshift(t,d,f,c),n&&clearTimeout(n),n=setTimeout(a,200),(e.event.dispatch||e.event.handle).apply(this,s)}}function a(){o=null}function r(e,t){return l.settings.adjustOldDeltas&&"mousewheel"===e.type&&t%120==0}var n,o,i=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],s="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],u=Array.prototype.slice;if(e.event.fixHooks)for(var d=i.length;d;)e.event.fixHooks[i[--d]]=e.event.mouseHooks;var l=e.event.special.mousewheel={version:"3.1.12",setup:function(){if(this.addEventListener)for(var a=s.length;a;)this.addEventListener(s[--a],t,!1);else this.onmousewheel=t;e.data(this,"mousewheel-line-height",l.getLineHeight(this)),e.data(this,"mousewheel-page-height",l.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var a=s.length;a;)this.removeEventListener(s[--a],t,!1);else this.onmousewheel=null;e.removeData(this,"mousewheel-line-height"),e.removeData(this,"mousewheel-page-height")},getLineHeight:function(t){var a=e(t),r=a["offsetParent"in e.fn?"offsetParent":"parent"]();return r.length||(r=e("body")),parseInt(r.css("fontSize"),10)||parseInt(a.css("fontSize"),10)||16},getPageHeight:function(t){return e(t).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};e.fn.extend({mousewheel:function(e){return e?this.bind("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.unbind("mousewheel",e)}})}); \ No newline at end of file diff --git a/resources/datetime-picker/datetimepicker.min.css b/resources/datetime-picker/datetimepicker.min.css new file mode 100644 index 0000000..14a08a1 --- /dev/null +++ b/resources/datetime-picker/datetimepicker.min.css @@ -0,0 +1 @@ +.xdsoft_datetimepicker{box-shadow:0 5px 15px -5px rgba(0,0,0,0.506);background:#fff;border-bottom:1px solid #bbb;border-left:1px solid #ccc;border-right:1px solid #ccc;border-top:1px solid #ccc;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;padding:8px;padding-left:0;padding-top:2px;position:absolute;z-index:9999;-moz-box-sizing:border-box;box-sizing:border-box;display:none}.xdsoft_datetimepicker.xdsoft_rtl{padding:8px 0 8px 8px}.xdsoft_datetimepicker iframe{position:absolute;left:0;top:0;width:75px;height:210px;background:transparent;border:0}.xdsoft_datetimepicker button{border:none !important}.xdsoft_noselect{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.xdsoft_noselect::selection{background:transparent}.xdsoft_noselect::-moz-selection{background:transparent}.xdsoft_datetimepicker.xdsoft_inline{display:inline-block;position:static;box-shadow:none}.xdsoft_datetimepicker *{-moz-box-sizing:border-box;box-sizing:border-box;padding:0;margin:0}.xdsoft_datetimepicker .xdsoft_datepicker,.xdsoft_datetimepicker .xdsoft_timepicker{display:none}.xdsoft_datetimepicker .xdsoft_datepicker.active,.xdsoft_datetimepicker .xdsoft_timepicker.active{display:block}.xdsoft_datetimepicker .xdsoft_datepicker{width:224px;float:left;margin-left:8px}.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_datepicker{float:right;margin-right:8px;margin-left:0}.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_datepicker{width:256px}.xdsoft_datetimepicker .xdsoft_timepicker{width:58px;float:left;text-align:center;margin-left:8px;margin-top:0}.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker{float:right;margin-right:8px;margin-left:0}.xdsoft_datetimepicker .xdsoft_datepicker.active+.xdsoft_timepicker{margin-top:8px;margin-bottom:3px}.xdsoft_datetimepicker .xdsoft_monthpicker{position:relative;text-align:center}.xdsoft_datetimepicker .xdsoft_label i,.xdsoft_datetimepicker .xdsoft_prev,.xdsoft_datetimepicker .xdsoft_next,.xdsoft_datetimepicker .xdsoft_today_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAeCAYAAADaW7vzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Q0NBRjI1NjM0M0UwMTFFNDk4NkFGMzJFQkQzQjEwRUIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Q0NBRjI1NjQ0M0UwMTFFNDk4NkFGMzJFQkQzQjEwRUIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDQ0FGMjU2MTQzRTAxMUU0OTg2QUYzMkVCRDNCMTBFQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDQ0FGMjU2MjQzRTAxMUU0OTg2QUYzMkVCRDNCMTBFQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoNEP54AAAIOSURBVHja7Jq9TsMwEMcxrZD4WpBYeKUCe+kTMCACHZh4BFfHO/AAIHZGFhYkBBsSEqxsLCAgXKhbXYOTxh9pfJVP+qutnZ5s/5Lz2Y5I03QhWji2GIcgAokWgfCxNvcOCCGKqiSqhUp0laHOne05vdEyGMfkdxJDVjgwDlEQgYQBgx+ULJaWSXXS6r/ER5FBVR8VfGftTKcITNs+a1XpcFoExREIDF14AVIFxgQUS+h520cdud6wNkC0UBw6BCO/HoCYwBhD8QCkQ/x1mwDyD4plh4D6DDV0TAGyo4HcawLIBBSLDkHeH0Mg2yVP3l4TQMZQDDsEOl/MgHQqhMNuE0D+oBh0CIr8MAKyazBH9WyBuKxDWgbXfjNf32TZ1KWm/Ap1oSk/R53UtQ5xTh3LUlMmT8gt6g51Q9p+SobxgJQ/qmsfZhWywGFSl0yBjCLJCMgXail3b7+rumdVJ2YRss4cN+r6qAHDkPWjPjdJCF4n9RmAD/V9A/Wp4NQassDjwlB6XBiCxcJQWmZZb8THFilfy/lfrTvLghq2TqTHrRMTKNJ0sIhdo15RT+RpyWwFdY96UZ/LdQKBGjcXpcc1AlSFEfLmouD+1knuxBDUVrvOBmoOC/rEcN7OQxKVeJTCiAdUzUJhA2Oez9QTkp72OTVcxDcXY8iKNkxGAJXmJCOQwOa6dhyXsOa6XwEGAKdeb5ET3rQdAAAAAElFTkSuQmCC)}.xdsoft_datetimepicker .xdsoft_label i{opacity:.5;background-position:-92px -19px;display:inline-block;width:9px;height:20px;vertical-align:middle}.xdsoft_datetimepicker .xdsoft_prev{float:left;background-position:-20px 0}.xdsoft_datetimepicker .xdsoft_today_button{float:left;background-position:-70px 0;margin-left:5px}.xdsoft_datetimepicker .xdsoft_next{float:right;background-position:0 0}.xdsoft_datetimepicker .xdsoft_next,.xdsoft_datetimepicker .xdsoft_prev,.xdsoft_datetimepicker .xdsoft_today_button{background-color:transparent;background-repeat:no-repeat;border:0 none;cursor:pointer;display:block;height:30px;opacity:.5;-ms-filter:"alpha(opacity=50)";outline:medium none;overflow:hidden;padding:0;position:relative;text-indent:100%;white-space:nowrap;width:20px;min-width:0}.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev,.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_next{float:none;background-position:-40px -15px;height:15px;width:30px;display:block;margin-left:14px;margin-top:7px}.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_prev,.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_timepicker .xdsoft_next{float:none;margin-left:0;margin-right:14px}.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_prev{background-position:-40px 0;margin-bottom:7px;margin-top:0}.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box{height:151px;overflow:hidden;border-bottom:1px solid #ddd}.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div{background:#f5f5f5;border-top:1px solid #ddd;color:#666;font-size:12px;text-align:center;border-collapse:collapse;cursor:pointer;border-bottom-width:0;height:25px;line-height:25px}.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div:first-child{border-top-width:0}.xdsoft_datetimepicker .xdsoft_today_button:hover,.xdsoft_datetimepicker .xdsoft_next:hover,.xdsoft_datetimepicker .xdsoft_prev:hover{opacity:1;-ms-filter:"alpha(opacity=100)"}.xdsoft_datetimepicker .xdsoft_label{display:inline;position:relative;z-index:9999;margin:0;padding:5px 3px;font-size:14px;line-height:20px;font-weight:bold;background-color:#fff;float:left;width:182px;text-align:center;cursor:pointer}.xdsoft_datetimepicker .xdsoft_label:hover>span{text-decoration:underline}.xdsoft_datetimepicker .xdsoft_label:hover i{opacity:1.0}.xdsoft_datetimepicker .xdsoft_label>.xdsoft_select{border:1px solid #ccc;position:absolute;right:0;top:30px;z-index:101;display:none;background:#fff;max-height:160px;overflow-y:hidden}.xdsoft_datetimepicker .xdsoft_label>.xdsoft_select.xdsoft_monthselect{right:-7px}.xdsoft_datetimepicker .xdsoft_label>.xdsoft_select.xdsoft_yearselect{right:2px}.xdsoft_datetimepicker .xdsoft_label>.xdsoft_select>div>.xdsoft_option:hover{color:#fff;background:#ff8000}.xdsoft_datetimepicker .xdsoft_label>.xdsoft_select>div>.xdsoft_option{padding:2px 10px 2px 5px;text-decoration:none !important}.xdsoft_datetimepicker .xdsoft_label>.xdsoft_select>div>.xdsoft_option.xdsoft_current{background:#3af;box-shadow:#178fe5 0 1px 3px 0 inset;color:#fff;font-weight:700}.xdsoft_datetimepicker .xdsoft_month{width:100px;text-align:right}.xdsoft_datetimepicker .xdsoft_calendar{clear:both}.xdsoft_datetimepicker .xdsoft_year{width:48px;margin-left:5px}.xdsoft_datetimepicker .xdsoft_calendar table{border-collapse:collapse;width:100%}.xdsoft_datetimepicker .xdsoft_calendar td>div{padding-right:5px}.xdsoft_datetimepicker .xdsoft_calendar th{height:25px}.xdsoft_datetimepicker .xdsoft_calendar td,.xdsoft_datetimepicker .xdsoft_calendar th{width:14.2857142%;background:#f5f5f5;border:1px solid #ddd;color:#666;font-size:12px;text-align:right;vertical-align:middle;padding:0;border-collapse:collapse;cursor:pointer;height:25px}.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar td,.xdsoft_datetimepicker.xdsoft_showweeks .xdsoft_calendar th{width:12.5%}.xdsoft_datetimepicker .xdsoft_calendar th{background:#f1f1f1}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_today{color:#3af}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_default{background:#ffe9d2;box-shadow:#ffb871 0 1px 4px 0 inset;color:#000}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_highlighted_mint{background:#c1ffc9;box-shadow:#00dd1c 0 1px 4px 0 inset;color:#000}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_default,.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current,.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div.xdsoft_current{background:#3af;box-shadow:#178fe5 0 1px 3px 0 inset;color:#fff;font-weight:700}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month,.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled,.xdsoft_datetimepicker .xdsoft_time_box>div>div.xdsoft_disabled{opacity:.5;-ms-filter:"alpha(opacity=50)";cursor:default}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_other_month.xdsoft_disabled{opacity:.2;-ms-filter:"alpha(opacity=20)"}.xdsoft_datetimepicker .xdsoft_calendar td:hover,.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div:hover{color:#fff !important;background:#ff8000 !important;box-shadow:none !important}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_current.xdsoft_disabled:hover,.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div.xdsoft_current.xdsoft_disabled:hover{background:#3af !important;box-shadow:#178fe5 0 1px 3px 0 inset !important;color:#fff !important}.xdsoft_datetimepicker .xdsoft_calendar td.xdsoft_disabled:hover,.xdsoft_datetimepicker .xdsoft_timepicker .xdsoft_time_box>div>div.xdsoft_disabled:hover{color:inherit !important;background:inherit !important;box-shadow:inherit !important}.xdsoft_datetimepicker .xdsoft_calendar th{font-weight:700;text-align:center;color:#999;cursor:default}.xdsoft_datetimepicker .xdsoft_copyright{color:#ccc !important;font-size:10px;clear:both;float:none;margin-left:8px}.xdsoft_datetimepicker .xdsoft_copyright a{color:#eee !important}.xdsoft_datetimepicker .xdsoft_copyright a:hover{color:#aaa !important}.xdsoft_time_box{position:relative;border:1px solid #ccc}.xdsoft_scrollbar>.xdsoft_scroller{background:#ccc !important;height:20px;border-radius:3px}.xdsoft_scrollbar{position:absolute;width:7px;right:0;top:0;bottom:0;cursor:pointer}.xdsoft_datetimepicker.xdsoft_rtl .xdsoft_scrollbar{left:0;right:auto}.xdsoft_scroller_box{position:relative}.xdsoft_datetimepicker.xdsoft_dark{box-shadow:0 5px 15px -5px rgba(255,255,255,0.506);background:#000;border-bottom:1px solid #444;border-left:1px solid #333;border-right:1px solid #333;border-top:1px solid #333;color:#ccc}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box{border-bottom:1px solid #222}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box>div>div{background:#0a0a0a;border-top:1px solid #222;color:#999}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label{background-color:#000}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label>.xdsoft_select{border:1px solid #333;background:#000}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label>.xdsoft_select>div>.xdsoft_option:hover{color:#000;background:#007fff}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label>.xdsoft_select>div>.xdsoft_option.xdsoft_current{background:#c50;box-shadow:#b03e00 0 1px 3px 0 inset;color:#000}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_label i,.xdsoft_datetimepicker.xdsoft_dark .xdsoft_prev,.xdsoft_datetimepicker.xdsoft_dark .xdsoft_next,.xdsoft_datetimepicker.xdsoft_dark .xdsoft_today_button{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAeCAYAAADaW7vzAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUExQUUzOTA0M0UyMTFFNDlBM0FFQTJENTExRDVBODYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUExQUUzOTE0M0UyMTFFNDlBM0FFQTJENTExRDVBODYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBQTFBRTM4RTQzRTIxMUU0OUEzQUVBMkQ1MTFENUE4NiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBQTFBRTM4RjQzRTIxMUU0OUEzQUVBMkQ1MTFENUE4NiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pp0VxGEAAAIASURBVHja7JrNSgMxEMebtgh+3MSLr1T1Xn2CHoSKB08+QmR8Bx9A8e7RixdB9CKCoNdexIugxFlJa7rNZneTbLIpM/CnNLsdMvNjM8l0mRCiQ9Ye61IKCAgZAUnH+mU3MMZaHYChBnJUDzWOFZdVfc5+ZFLbrWDeXPwbxIqrLLfaeS0hEBVGIRQCEiZoHQwtlGSByCCdYBl8g8egTTAWoKQMRBRBcZxYlhzhKegqMOageErsCHVkk3hXIFooDgHB1KkHIHVgzKB4ADJQ/A1jAFmAYhkQqA5TOBtocrKrgXwQA8gcFIuAIO8sQSA7hidvPwaQGZSaAYHOUWJABhWWw2EMIH9QagQERU4SArJXo0ZZL18uvaxejXt/Em8xjVBXmvFr1KVm/AJ10tRe2XnraNqaJvKE3KHuUbfK1E+VHB0q40/y3sdQSxY4FHWeKJCunP8UyDdqJZenT3ntVV5jIYCAh20vT7ioP8tpf6E2lfEMwERe+whV1MHjwZB7PBiCxcGQWwKZKD62lfGNnP/1poFAA60T7rF1UgcKd2id3KDeUS+oLWV8DfWAepOfq00CgQabi9zjcgJVYVD7PVzQUAUGAQkbNJTBICDhgwYTjDYD6XeW08ZKh+A4pYkzenOxXUbvZcWz7E8ykRMnIHGX1XPl+1m2vPYpL+2qdb8CDAARlKFEz/ZVkAAAAABJRU5ErkJggg==)}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td,.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th{background:#0a0a0a;border:1px solid #222;color:#999}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th{background:#0e0e0e}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_today{color:#c50}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_highlighted_default{background:#ffe9d2;box-shadow:#ffb871 0 1px 4px 0 inset;color:#000}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_highlighted_mint{background:#c1ffc9;box-shadow:#00dd1c 0 1px 4px 0 inset;color:#000}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_default,.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td.xdsoft_current,.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box>div>div.xdsoft_current{background:#c50;box-shadow:#b03e00 0 1px 3px 0 inset;color:#000}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar td:hover,.xdsoft_datetimepicker.xdsoft_dark .xdsoft_timepicker .xdsoft_time_box>div>div:hover{color:#000 !important;background:#007fff !important}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_calendar th{color:#666}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright{color:#333 !important}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright a{color:#111 !important}.xdsoft_datetimepicker.xdsoft_dark .xdsoft_copyright a:hover{color:#555 !important}.xdsoft_dark .xdsoft_time_box{border:1px solid #333}.xdsoft_dark .xdsoft_scrollbar>.xdsoft_scroller{background:#333 !important}.xdsoft_datetimepicker .xdsoft_save_selected{display:block;border:1px solid #ddd !important;margin-top:5px;width:100%;color:#454551;font-size:13px}.xdsoft_datetimepicker .blue-gradient-button{font-family:"museo-sans","Book Antiqua",sans-serif;font-size:12px;font-weight:300;color:#82878c;height:28px;position:relative;padding:4px 17px 4px 33px;border:1px solid #d7d8da;background:-moz-linear-gradient(top,#fff 0,#f4f8fa 73%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#fff),color-stop(73%,#f4f8fa));background:-webkit-linear-gradient(top,#fff 0,#f4f8fa 73%);background:-o-linear-gradient(top,#fff 0,#f4f8fa 73%);background:-ms-linear-gradient(top,#fff 0,#f4f8fa 73%);background:linear-gradient(to bottom,#fff 0,#f4f8fa 73%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff',endColorstr='#f4f8fa',GradientType=0)}.xdsoft_datetimepicker .blue-gradient-button:hover,.xdsoft_datetimepicker .blue-gradient-button:focus,.xdsoft_datetimepicker .blue-gradient-button:hover span,.xdsoft_datetimepicker .blue-gradient-button:focus span{color:#454551;background:-moz-linear-gradient(top,#f4f8fa 0,#FFF 73%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0,#f4f8fa),color-stop(73%,#FFF));background:-webkit-linear-gradient(top,#f4f8fa 0,#FFF 73%);background:-o-linear-gradient(top,#f4f8fa 0,#FFF 73%);background:-ms-linear-gradient(top,#f4f8fa 0,#FFF 73%);background:linear-gradient(to bottom,#f4f8fa 0,#FFF 73%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#f4f8fa',endColorstr='#FFF',GradientType=0)} \ No newline at end of file From 97d02399630d8feacbd57695539ce18141214bc0 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 25 Aug 2023 17:34:33 -0500 Subject: [PATCH 156/478] Add duplication check for contest edit in group --- judge/forms.py | 30 +++++++++++++++++++++++- judge/views/organization.py | 8 +++++-- locale/vi/LC_MESSAGES/django.po | 29 ++++++++++++++--------- resources/widgets.scss | 14 ++++++----- templates/organization/contest/edit.html | 5 ++-- templates/organization/form.html | 3 +-- 6 files changed, 64 insertions(+), 25 deletions(-) diff --git a/judge/forms.py b/judge/forms.py index 5bc43a9..32fc75e 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -538,9 +538,37 @@ class ContestProblemForm(ModelForm): } +class ContestProblemModelFormSet(BaseModelFormSet): + def is_valid(self): + valid = super().is_valid() + + if not valid: + return valid + + problems = set() + duplicates = [] + + for form in self.forms: + if form.cleaned_data and not form.cleaned_data.get("DELETE", False): + problem = form.cleaned_data.get("problem") + if problem in problems: + duplicates.append(problem) + else: + problems.add(problem) + + if duplicates: + for form in self.forms: + problem = form.cleaned_data.get("problem") + if problem in duplicates: + form.add_error("problem", _("This problem is duplicated.")) + return False + + return True + + class ContestProblemFormSet( formset_factory( - ContestProblemForm, formset=BaseModelFormSet, extra=6, can_delete=True + ContestProblemForm, formset=ContestProblemModelFormSet, extra=6, can_delete=True ) ): model = ContestProblem diff --git a/judge/views/organization.py b/judge/views/organization.py index 962be71..0576afe 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -926,8 +926,12 @@ class EditOrganizationContest( super().post(request, *args, **kwargs) return HttpResponseRedirect( reverse( - "organization_contests", - args=(self.organization_id, self.organization.slug), + "organization_contest_edit", + args=( + self.organization_id, + self.organization.slug, + self.contest.key, + ), ) ) diff --git a/locale/vi/LC_MESSAGES/django.po b/locale/vi/LC_MESSAGES/django.po index d1e8a78..0cb9282 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: 2023-08-26 03:42+0700\n" +"POT-Creation-Date: 2023-08-26 05:31+0700\n" "PO-Revision-Date: 2021-07-20 03:44\n" "Last-Translator: Icyene\n" "Language-Team: Vietnamese\n" @@ -555,6 +555,10 @@ msgstr "" msgid "You don't have permission in this group." msgstr "Bạn không có quyền chấm lại bài." +#: judge/forms.py:563 +msgid "This problem is duplicated." +msgstr "Bài này bị lặp" + #: judge/jinja2/datetime.py:26 templates/blog/blog.html:28 #: templates/blog/dashboard.html:21 msgid "N j, Y, g:i a" @@ -2883,19 +2887,19 @@ msgstr "Thay đổi Email" msgid "Change Email" msgstr "Thay đổi Email" -#: judge/views/email.py:65 templates/user/edit-profile.html:120 +#: judge/views/email.py:71 templates/user/edit-profile.html:120 msgid "Change email" msgstr "Thay đổi email" -#: judge/views/email.py:89 +#: judge/views/email.py:94 msgid "Success" msgstr "Thành công" -#: judge/views/email.py:93 +#: judge/views/email.py:98 msgid "Invalid" msgstr "Không hợp lệ" -#: judge/views/email.py:102 +#: judge/views/email.py:107 msgid "Email change pending" msgstr "Yêu cầu thay đổi email đang đợi xác thực." @@ -4426,8 +4430,8 @@ msgstr "Hoạt động" #: templates/organization/blog/edit.html:36 #: templates/organization/contest/add.html:36 -#: templates/organization/contest/edit.html:87 -#: templates/organization/form.html:24 +#: templates/organization/contest/edit.html:86 +#: templates/organization/form.html:23 msgid "Save" msgstr "Lưu" @@ -4441,12 +4445,15 @@ msgid "Author" msgstr "Tác giả" #: templates/organization/blog/pending.html:14 -#, fuzzy -#| msgid "posted time" msgid "Post time" -msgstr "thời gian đăng" +msgstr "Thời gian đăng" -#: templates/organization/contest/edit.html:61 +#: templates/organization/contest/edit.html:40 +#: templates/organization/form.html:6 +msgid "Please fix below errors" +msgstr "Vui lòng sửa các lỗi bên dưới" + +#: templates/organization/contest/edit.html:60 msgid "If you run out of rows, click Save" msgstr "Ấn nút lưu lại nếu cần thêm hàng" diff --git a/resources/widgets.scss b/resources/widgets.scss index f33882d..085d508 100644 --- a/resources/widgets.scss +++ b/resources/widgets.scss @@ -606,6 +606,14 @@ ul.select2-selection__rendered { background: gray; } +ul.errorlist { + margin: 0px; + text-align: right; + list-style: none; + padding: 0px; + color: red; +} + .registration-form { .sortedm2m-container, .sortedm2m-container p.selector-filter { width: 300px; @@ -682,12 +690,6 @@ ul.select2-selection__rendered { width: 450px; } - ul.errorlist { - margin: 0px; - text-align: right; - list-style: none; - } - .full-textfield { padding-top: 0.5em; } diff --git a/templates/organization/contest/edit.html b/templates/organization/contest/edit.html index 101bfbf..5682504 100644 --- a/templates/organization/contest/edit.html +++ b/templates/organization/contest/edit.html @@ -34,11 +34,10 @@ {% block middle_content %}
{% csrf_token %} - {% if form.errors %} + {% if form.errors or problems_form.errors %}
x - {{ form.non_field_errors() }} - {{ form.errors }} + {{_("Please fix below errors")}}
{% endif %} {% for field in form %} diff --git a/templates/organization/form.html b/templates/organization/form.html index 5201ace..85c86d9 100644 --- a/templates/organization/form.html +++ b/templates/organization/form.html @@ -3,8 +3,7 @@ {% if form.errors %}
x - {{ form.non_field_errors() }} - {{ form.errors }} + {{ _("Please fix below errors") }}
{% endif %} {% for field in form %} From 8f046c59c172ede5c4d2a9f2540a7e5989ad0f8a Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 25 Aug 2023 18:12:53 -0500 Subject: [PATCH 157/478] Drop output_prefix_override and use show_testcases --- judge/admin/contest.py | 3 +- judge/forms.py | 2 +- judge/migrations/0164_show_testcase.py | 30 +++++++++++++++++++ .../0165_drop_output_prefix_override.py | 17 +++++++++++ judge/models/contest.py | 7 ++--- judge/models/profile.py | 7 +++++ judge/views/submission.py | 6 ++-- templates/submission/status-testcases.html | 5 ---- 8 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 judge/migrations/0164_show_testcase.py create mode 100644 judge/migrations/0165_drop_output_prefix_override.py diff --git a/judge/admin/contest.py b/judge/admin/contest.py index f183880..9c8b6fd 100644 --- a/judge/admin/contest.py +++ b/judge/admin/contest.py @@ -71,7 +71,6 @@ class ContestProblemInlineForm(ModelForm): "hidden_subtasks": TextInput(attrs={"size": "3"}), "points": TextInput(attrs={"size": "1"}), "order": TextInput(attrs={"size": "1"}), - "output_prefix_override": TextInput(attrs={"size": "1"}), } @@ -86,7 +85,7 @@ class ContestProblemInline(admin.TabularInline): "is_pretested", "max_submissions", "hidden_subtasks", - "output_prefix_override", + "show_testcases", "order", "rejudge_column", ) diff --git a/judge/forms.py b/judge/forms.py index 32fc75e..d71ffbc 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -528,7 +528,7 @@ class ContestProblemForm(ModelForm): "problem", "points", "partial", - "output_prefix_override", + "show_testcases", "max_submissions", ) widgets = { diff --git a/judge/migrations/0164_show_testcase.py b/judge/migrations/0164_show_testcase.py new file mode 100644 index 0000000..33cbdf8 --- /dev/null +++ b/judge/migrations/0164_show_testcase.py @@ -0,0 +1,30 @@ +# Generated by Django 3.2.18 on 2023-08-25 23:03 + +from django.db import migrations, models + + +def migrate_show_testcases(apps, schema_editor): + ContestProblem = apps.get_model("judge", "ContestProblem") + + for c in ContestProblem.objects.all(): + if c.output_prefix_override == 1: + c.show_testcases = True + c.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0163_email_change"), + ] + + operations = [ + migrations.AddField( + model_name="contestproblem", + name="show_testcases", + field=models.BooleanField(default=False, verbose_name="visible testcases"), + ), + migrations.RunPython( + migrate_show_testcases, migrations.RunPython.noop, atomic=True + ), + ] diff --git a/judge/migrations/0165_drop_output_prefix_override.py b/judge/migrations/0165_drop_output_prefix_override.py new file mode 100644 index 0000000..ea9b17a --- /dev/null +++ b/judge/migrations/0165_drop_output_prefix_override.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.18 on 2023-08-25 23:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0164_show_testcase"), + ] + + operations = [ + migrations.RemoveField( + model_name="contestproblem", + name="output_prefix_override", + ), + ] diff --git a/judge/models/contest.py b/judge/models/contest.py index 1129076..8bb3d5b 100644 --- a/judge/models/contest.py +++ b/judge/models/contest.py @@ -772,12 +772,9 @@ class ContestProblem(models.Model): partial = models.BooleanField(default=True, verbose_name=_("partial")) is_pretested = models.BooleanField(default=False, verbose_name=_("is pretested")) order = models.PositiveIntegerField(db_index=True, verbose_name=_("order")) - output_prefix_override = models.IntegerField( - help_text=_("0 to not show testcases, 1 to show"), + show_testcases = models.BooleanField( verbose_name=_("visible testcases"), - null=True, - blank=True, - default=0, + default=False, ) max_submissions = models.IntegerField( help_text=_( diff --git a/judge/models/profile.py b/judge/models/profile.py index eb3572a..ccb0e87 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -109,6 +109,13 @@ class Organization(models.Model): "Organization membership test must be Profile or primany key" ) + def delete(self, *args, **kwargs): + contests = self.contest_set + for contest in contests.all(): + if contest.organizations.count() == 1: + contest.delete() + super().delete(*args, **kwargs) + def __str__(self): return self.name diff --git a/judge/views/submission.py b/judge/views/submission.py index 147a2cc..1556dd9 100644 --- a/judge/views/submission.py +++ b/judge/views/submission.py @@ -259,13 +259,13 @@ class SubmissionStatus(SubmissionDetailBase): ) contest = submission.contest_or_none - prefix_length = 0 + show_testcases = False can_see_testcases = self.access_testcases_in_contest() if contest is not None: - prefix_length = contest.problem.output_prefix_override or 0 + show_testcases = contest.problem.show_testcases or False - if contest is None or prefix_length > 0 or can_see_testcases: + if contest is None or show_testcases or can_see_testcases: context["cases_data"] = get_cases_data(submission) context["can_see_testcases"] = True try: diff --git a/templates/submission/status-testcases.html b/templates/submission/status-testcases.html index 4d4b435..2d2547d 100644 --- a/templates/submission/status-testcases.html +++ b/templates/submission/status-testcases.html @@ -1,8 +1,3 @@ -{% if submission.contest_or_none %} - {% set prefix_length = submission.contest_or_none.problem.output_prefix_override %} -{% else %} - {% set prefix_length = None %} -{% endif %} {% set is_pretest = submission.is_pretested %} {% if submission.status != 'IE' %} From 9a825225dd6c622b4c21dfe2d3fa3e7ef2d2dfa4 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 25 Aug 2023 18:38:19 -0500 Subject: [PATCH 158/478] Small UI improvement --- judge/forms.py | 2 +- templates/organization/contest/edit.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/judge/forms.py b/judge/forms.py index d71ffbc..fc73b31 100644 --- a/judge/forms.py +++ b/judge/forms.py @@ -533,7 +533,7 @@ class ContestProblemForm(ModelForm): ) widgets = { "problem": HeavySelect2Widget( - data_view="problem_select2", attrs={"style": "width:100%"} + data_view="problem_select2", attrs={"style": "width: 100%"} ), } diff --git a/templates/organization/contest/edit.html b/templates/organization/contest/edit.html index 5682504..d5d9228 100644 --- a/templates/organization/contest/edit.html +++ b/templates/organization/contest/edit.html @@ -17,7 +17,7 @@ display: inline-flex; } .problems-problem { - width: 40%; + max-width: 60vh; } input[type=number] { width: 5em; @@ -63,7 +63,7 @@ {% for field in problems_form[0] %} {% if not field.is_hidden %} - + {{field.label}} {% endif %} From 3ff608e4ffb1220c4866e7a94a649b61bc6f06b3 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 25 Aug 2023 23:50:03 -0500 Subject: [PATCH 159/478] Add password to email change form --- judge/views/email.py | 14 +++++++++++++- templates/email_change/email_change.html | 4 ---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/judge/views/email.py b/judge/views/email.py index 56c43b9..5544daf 100644 --- a/judge/views/email.py +++ b/judge/views/email.py @@ -9,6 +9,7 @@ from django.utils.translation import gettext_lazy as _ from django.urls import reverse from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User +from django.contrib.auth.hashers import check_password from urllib.parse import urlencode, urlunparse, urlparse @@ -18,6 +19,11 @@ from judge.utils.email_render import render_email_message class EmailChangeForm(forms.Form): new_email = forms.EmailField(label=_("New Email")) + password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) + + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user", None) + super().__init__(*args, **kwargs) def clean_new_email(self): new_email = self.cleaned_data.get("new_email") @@ -25,10 +31,16 @@ class EmailChangeForm(forms.Form): raise forms.ValidationError(_("An account with this email already exists.")) return new_email + def clean_password(self): + password = self.cleaned_data.get("password") + if not self.user.check_password(password): + raise forms.ValidationError("Invalid password") + return password + @login_required def email_change_view(request): - form = EmailChangeForm(request.POST or None) + form = EmailChangeForm(request.POST or None, user=request.user) if request.method == "POST" and form.is_valid(): new_email = request.POST.get("new_email") diff --git a/templates/email_change/email_change.html b/templates/email_change/email_change.html index 6033d18..edfbfee 100644 --- a/templates/email_change/email_change.html +++ b/templates/email_change/email_change.html @@ -3,10 +3,6 @@ {% block media %} From f11d9b4b53cb13a651e797532e88f5c92b111b31 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Sat, 26 Aug 2023 12:38:50 -0500 Subject: [PATCH 160/478] Fix ticket ui on mobile --- resources/ticket.scss | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/resources/ticket.scss b/resources/ticket.scss index d7f9143..092c1a1 100644 --- a/resources/ticket.scss +++ b/resources/ticket.scss @@ -150,14 +150,6 @@ padding: 7px; } - .message .content :first-child { - margin-top: 0; - } - - .message .content :last-child { - margin-bottom: 0; - } - .new-message .detail { padding: 8px 10px; } @@ -174,4 +166,10 @@ padding-left: 0.5em; padding-top: 1.65em; } +} + +@media (max-width: 799px) { + .ticket-container { + flex-direction: column-reverse; + } } \ No newline at end of file From 2854ac97e930ed112d5ac126409b4a17db032714 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 28 Aug 2023 14:20:35 -0500 Subject: [PATCH 161/478] Make chat faster --- .../migrations/0013_alter_message_time.py | 20 + .../migrations/0014_userroom_unread_count.py | 38 ++ chat_box/models.py | 26 +- chat_box/utils.py | 35 +- chat_box/views.py | 176 +++--- dmoj/settings.py | 3 + judge/migrations/0166_display_rank_index.py | 28 + judge/models/profile.py | 1 + templates/chat/chat.html | 562 +----------------- templates/chat/chat_css.html | 262 ++++---- templates/chat/chat_js.html | 553 +++++++++++++++++ 11 files changed, 903 insertions(+), 801 deletions(-) create mode 100644 chat_box/migrations/0013_alter_message_time.py create mode 100644 chat_box/migrations/0014_userroom_unread_count.py create mode 100644 judge/migrations/0166_display_rank_index.py create mode 100644 templates/chat/chat_js.html diff --git a/chat_box/migrations/0013_alter_message_time.py b/chat_box/migrations/0013_alter_message_time.py new file mode 100644 index 0000000..0f4ddc3 --- /dev/null +++ b/chat_box/migrations/0013_alter_message_time.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.18 on 2023-08-28 01:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("chat_box", "0012_auto_20230308_1417"), + ] + + operations = [ + migrations.AlterField( + model_name="message", + name="time", + field=models.DateTimeField( + auto_now_add=True, db_index=True, verbose_name="posted time" + ), + ), + ] diff --git a/chat_box/migrations/0014_userroom_unread_count.py b/chat_box/migrations/0014_userroom_unread_count.py new file mode 100644 index 0000000..8f407b3 --- /dev/null +++ b/chat_box/migrations/0014_userroom_unread_count.py @@ -0,0 +1,38 @@ +# Generated by Django 3.2.18 on 2023-08-28 06:02 + +from django.db import migrations, models + + +def migrate(apps, schema_editor): + UserRoom = apps.get_model("chat_box", "UserRoom") + Message = apps.get_model("chat_box", "Message") + + for ur in UserRoom.objects.all(): + if not ur.room: + continue + messages = ur.room.message_set + last_msg = messages.first() + try: + if last_msg and last_msg.author != ur.user: + ur.unread_count = messages.filter(time__gte=ur.last_seen).count() + else: + ur.unread_count = 0 + ur.save() + except: + continue + + +class Migration(migrations.Migration): + + dependencies = [ + ("chat_box", "0013_alter_message_time"), + ] + + operations = [ + migrations.AddField( + model_name="userroom", + name="unread_count", + field=models.IntegerField(db_index=True, default=0), + ), + migrations.RunPython(migrate, migrations.RunPython.noop, atomic=True), + ] diff --git a/chat_box/models.py b/chat_box/models.py index 61e39a6..b6d7220 100644 --- a/chat_box/models.py +++ b/chat_box/models.py @@ -1,9 +1,10 @@ from django.db import models -from django.db.models import CASCADE +from django.db.models import CASCADE, Q from django.utils.translation import gettext_lazy as _ from judge.models.profile import Profile +from judge.caching import cache_wrapper __all__ = ["Message", "Room", "UserRoom", "Ignore"] @@ -29,7 +30,9 @@ class Room(models.Model): class Message(models.Model): author = models.ForeignKey(Profile, verbose_name=_("user"), on_delete=CASCADE) - time = models.DateTimeField(verbose_name=_("posted time"), auto_now_add=True) + time = models.DateTimeField( + verbose_name=_("posted time"), auto_now_add=True, db_index=True + ) body = models.TextField(verbose_name=_("body of comment"), max_length=8192) hidden = models.BooleanField(verbose_name="is hidden", default=False) room = models.ForeignKey( @@ -56,6 +59,7 @@ class UserRoom(models.Model): Room, verbose_name="room id", on_delete=CASCADE, default=None, null=True ) last_seen = models.DateTimeField(verbose_name=_("last seen"), auto_now_add=True) + unread_count = models.IntegerField(default=0, db_index=True) class Meta: unique_together = ("user", "room") @@ -74,11 +78,9 @@ class Ignore(models.Model): @classmethod def is_ignored(self, current_user, new_friend): try: - return ( - current_user.ignored_chat_users.get() - .ignored_users.filter(id=new_friend.id) - .exists() - ) + return current_user.ignored_chat_users.ignored_users.filter( + id=new_friend.id + ).exists() except: return False @@ -89,6 +91,16 @@ class Ignore(models.Model): except: return Profile.objects.none() + @classmethod + def get_ignored_rooms(self, user): + try: + ignored_users = self.objects.get(user=user).ignored_users.all() + return Room.objects.filter(Q(user_one=user) | Q(user_two=user)).filter( + Q(user_one__in=ignored_users) | Q(user_two__in=ignored_users) + ) + except: + return Room.objects.none() + @classmethod def add_ignore(self, current_user, friend): ignore, created = self.objects.get_or_create(user=current_user) diff --git a/chat_box/utils.py b/chat_box/utils.py index e25e861..dd59d98 100644 --- a/chat_box/utils.py +++ b/chat_box/utils.py @@ -1,10 +1,12 @@ from cryptography.fernet import Fernet +import hmac +import hashlib from django.conf import settings -from django.db.models import OuterRef, Count, Subquery, IntegerField +from django.db.models import OuterRef, Count, Subquery, IntegerField, Q from django.db.models.functions import Coalesce -from chat_box.models import Ignore, Message, UserRoom +from chat_box.models import Ignore, Message, UserRoom, Room secret_key = settings.CHAT_SECRET_KEY fernet = Fernet(secret_key) @@ -24,25 +26,22 @@ def decrypt_url(message_encrypted): return None, None -def get_unread_boxes(profile): - ignored_users = Ignore.get_ignored_users(profile) - - mess = ( - Message.objects.filter(room=OuterRef("room"), time__gte=OuterRef("last_seen")) - .exclude(author=profile) - .exclude(author__in=ignored_users) - .order_by() - .values("room") - .annotate(unread_count=Count("pk")) - .values("unread_count") +def encrypt_channel(channel): + return ( + hmac.new( + settings.CHAT_SECRET_KEY.encode(), + channel.encode(), + hashlib.sha512, + ).hexdigest()[:16] + + "%s" % channel ) + +def get_unread_boxes(profile): + ignored_rooms = Ignore.get_ignored_rooms(profile) unread_boxes = ( - UserRoom.objects.filter(user=profile, room__isnull=False) - .annotate( - unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), - ) - .filter(unread_count__gte=1) + UserRoom.objects.filter(user=profile, unread_count__gt=0) + .exclude(room__in=ignored_rooms) .count() ) diff --git a/chat_box/views.py b/chat_box/views.py index cea58e1..879dfcf 100644 --- a/chat_box/views.py +++ b/chat_box/views.py @@ -21,6 +21,7 @@ from django.db.models import ( Exists, Count, IntegerField, + F, ) from django.db.models.functions import Coalesce from django.utils import timezone @@ -34,7 +35,7 @@ from judge.jinja2.gravatar import gravatar from judge.models import Friend from chat_box.models import Message, Profile, Room, UserRoom, Ignore -from chat_box.utils import encrypt_url, decrypt_url +from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel import json @@ -49,7 +50,8 @@ class ChatView(ListView): self.room_id = None self.room = None self.messages = None - self.page_size = 20 + self.first_page_size = 20 # only for first request + self.follow_up_page_size = 50 def get_queryset(self): return self.messages @@ -63,10 +65,12 @@ class ChatView(ListView): def get(self, request, *args, **kwargs): request_room = kwargs["room_id"] + page_size = self.follow_up_page_size try: last_id = int(request.GET.get("last_id")) except Exception: last_id = 1e15 + page_size = self.first_page_size only_messages = request.GET.get("only_messages") if request_room: @@ -80,11 +84,12 @@ class ChatView(ListView): request_room = None self.room_id = request_room - self.messages = Message.objects.filter( - hidden=False, room=self.room_id, id__lt=last_id - )[: self.page_size] + self.messages = ( + Message.objects.filter(hidden=False, room=self.room_id, id__lt=last_id) + .select_related("author", "author__user") + .defer("author__about", "author__user_script")[:page_size] + ) if not only_messages: - update_last_seen(request, **kwargs) return super().get(request, *args, **kwargs) return render( @@ -101,10 +106,14 @@ class ChatView(ListView): context["title"] = self.title context["last_msg"] = event.last() - context["status_sections"] = get_status_context(self.request) + context["status_sections"] = get_status_context(self.request.profile) context["room"] = self.room_id context["has_next"] = self.has_next() context["unread_count_lobby"] = get_unread_count(None, self.request.profile) + context["chat_channel"] = encrypt_channel( + "chat_" + str(self.request.profile.id) + ) + context["chat_lobby_channel"] = encrypt_channel("chat_lobby") if self.room: users_room = [self.room.user_one, self.room.user_two] users_room.remove(self.request.profile) @@ -187,7 +196,7 @@ def post_message(request): if not room: event.post( - "chat_lobby", + encrypt_channel("chat_lobby"), { "type": "lobby", "author_id": request.profile.id, @@ -199,7 +208,7 @@ def post_message(request): else: for user in room.users(): event.post( - "chat_" + str(user.id), + encrypt_channel("chat_" + str(user.id)), { "type": "private", "author_id": request.profile.id, @@ -208,6 +217,10 @@ def post_message(request): "tmp_id": request.POST.get("tmp_id"), }, ) + if user != request.profile: + UserRoom.objects.filter(user=user, room=room).update( + unread_count=F("unread_count") + 1 + ) return JsonResponse(ret) @@ -254,35 +267,33 @@ def update_last_seen(request, **kwargs): room_id = request.POST.get("room") else: return HttpResponseBadRequest() - try: profile = request.profile room = None if room_id: - room = Room.objects.get(id=int(room_id)) + room = Room.objects.filter(id=int(room_id)).first() except Room.DoesNotExist: return HttpResponseBadRequest() - except Exception as e: - return HttpResponseBadRequest() if room and not room.contain(profile): return HttpResponseBadRequest() user_room, _ = UserRoom.objects.get_or_create(user=profile, room=room) user_room.last_seen = timezone.now() + user_room.unread_count = 0 user_room.save() return JsonResponse({"msg": "updated"}) def get_online_count(): - last_two_minutes = timezone.now() - timezone.timedelta(minutes=2) - return Profile.objects.filter(last_access__gte=last_two_minutes).count() + last_5_minutes = timezone.now() - timezone.timedelta(minutes=5) + return Profile.objects.filter(last_access__gte=last_5_minutes).count() def get_user_online_status(user): time_diff = timezone.now() - user.last_access - is_online = time_diff <= timezone.timedelta(minutes=2) + is_online = time_diff <= timezone.timedelta(minutes=5) return is_online @@ -319,47 +330,51 @@ def user_online_status_ajax(request): ) -def get_online_status(request_user, queryset, rooms=None): - if not queryset: +def get_online_status(profile, other_profile_ids, rooms=None): + if not other_profile_ids: return None - last_two_minutes = timezone.now() - timezone.timedelta(minutes=2) + joined_ids = ",".join([str(id) for id in other_profile_ids]) + other_profiles = Profile.objects.raw( + f"SELECT * from judge_profile where id in ({joined_ids}) order by field(id,{joined_ids})" + ) + last_5_minutes = timezone.now() - timezone.timedelta(minutes=5) ret = [] - if rooms: - unread_count = get_unread_count(rooms, request_user) + unread_count = get_unread_count(rooms, profile) count = {} for i in unread_count: count[i["other_user"]] = i["unread_count"] - - for user in queryset: + for other_profile in other_profiles: is_online = False - if user.last_access >= last_two_minutes: + if other_profile.last_access >= last_5_minutes: is_online = True - user_dict = {"user": user, "is_online": is_online} - if rooms and user.id in count: - user_dict["unread_count"] = count[user.id] - user_dict["url"] = encrypt_url(request_user.id, user.id) + user_dict = {"user": other_profile, "is_online": is_online} + if rooms and other_profile.id in count: + user_dict["unread_count"] = count[other_profile.id] + user_dict["url"] = encrypt_url(profile.id, other_profile.id) ret.append(user_dict) return ret -def get_status_context(request, include_ignored=False): +def get_status_context(profile, include_ignored=False): if include_ignored: - ignored_users = Profile.objects.none() + ignored_users = [] queryset = Profile.objects else: - ignored_users = Ignore.get_ignored_users(request.profile) + ignored_users = list( + Ignore.get_ignored_users(profile).values_list("id", flat=True) + ) queryset = Profile.objects.exclude(id__in=ignored_users) - last_two_minutes = timezone.now() - timezone.timedelta(minutes=2) + last_5_minutes = timezone.now() - timezone.timedelta(minutes=5) recent_profile = ( - Room.objects.filter(Q(user_one=request.profile) | Q(user_two=request.profile)) + Room.objects.filter(Q(user_one=profile) | Q(user_two=profile)) .annotate( last_msg_time=Subquery( Message.objects.filter(room=OuterRef("pk")).values("time")[:1] ), other_user=Case( - When(user_one=request.profile, then="user_two"), + When(user_one=profile, then="user_two"), default="user_one", ), ) @@ -369,50 +384,49 @@ def get_status_context(request, include_ignored=False): .values("other_user", "id")[:20] ) - recent_profile_id = [str(i["other_user"]) for i in recent_profile] - joined_id = ",".join(recent_profile_id) + recent_profile_ids = [str(i["other_user"]) for i in recent_profile] recent_rooms = [int(i["id"]) for i in recent_profile] - recent_list = None - if joined_id: - recent_list = Profile.objects.raw( - f"SELECT * from judge_profile where id in ({joined_id}) order by field(id,{joined_id})" - ) friend_list = ( - Friend.get_friend_profiles(request.profile) - .exclude(id__in=recent_profile_id) + Friend.get_friend_profiles(profile) + .exclude(id__in=recent_profile_ids) .exclude(id__in=ignored_users) .order_by("-last_access") + .values_list("id", flat=True) ) + admin_list = ( queryset.filter(display_rank="admin") .exclude(id__in=friend_list) - .exclude(id__in=recent_profile_id) + .exclude(id__in=recent_profile_ids) + .values_list("id", flat=True) ) + all_user_status = ( - queryset.filter(display_rank="user", last_access__gte=last_two_minutes) + queryset.filter(last_access__gte=last_5_minutes) .annotate(is_online=Case(default=True, output_field=BooleanField())) .order_by("-rating") .exclude(id__in=friend_list) .exclude(id__in=admin_list) - .exclude(id__in=recent_profile_id)[:30] + .exclude(id__in=recent_profile_ids) + .values_list("id", flat=True)[:30] ) return [ { "title": "Recent", - "user_list": get_online_status(request.profile, recent_list, recent_rooms), + "user_list": get_online_status(profile, recent_profile_ids, recent_rooms), }, { "title": "Following", - "user_list": get_online_status(request.profile, friend_list), + "user_list": get_online_status(profile, friend_list), }, { "title": "Admin", - "user_list": get_online_status(request.profile, admin_list), + "user_list": get_online_status(profile, admin_list), }, { "title": "Other", - "user_list": get_online_status(request.profile, all_user_status), + "user_list": get_online_status(profile, all_user_status), }, ] @@ -423,7 +437,7 @@ def online_status_ajax(request): request, "chat/online_status.html", { - "status_sections": get_status_context(request), + "status_sections": get_status_context(request.profile), "unread_count_lobby": get_unread_count(None, request.profile), }, ) @@ -447,7 +461,6 @@ def get_or_create_room(request): return HttpResponseBadRequest() request_id, other_id = decrypt_url(decrypted_other_id) - if not other_id or not request_id or request_id != request.profile.id: return HttpResponseBadRequest() @@ -475,48 +488,31 @@ def get_or_create_room(request): def get_unread_count(rooms, user): if rooms: - mess = ( - Message.objects.filter( - room=OuterRef("room"), time__gte=OuterRef("last_seen") - ) - .exclude(author=user) - .order_by() - .values("room") - .annotate(unread_count=Count("pk")) - .values("unread_count") - ) - - return ( - UserRoom.objects.filter(user=user, room__in=rooms) - .annotate( - unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), - other_user=Case( - When(room__user_one=user, then="room__user_two"), - default="room__user_one", - ), - ) - .filter(unread_count__gte=1) - .values("other_user", "unread_count") - ) - else: # lobby - mess = ( - Message.objects.filter(room__isnull=True, time__gte=OuterRef("last_seen")) - .exclude(author=user) - .order_by() - .values("room") - .annotate(unread_count=Count("pk")) - .values("unread_count") - ) - res = ( - UserRoom.objects.filter(user=user, room__isnull=True) - .annotate( - unread_count=Coalesce(Subquery(mess, output_field=IntegerField()), 0), + UserRoom.objects.filter(user=user, room__in=rooms, unread_count__gt=0) + .select_related("room__user_one", "room__user_two") + .values("unread_count", "room__user_one", "room__user_two") + ) + for ur in res: + ur["other_user"] = ( + ur["room__user_one"] + if ur["room__user_two"] == user.id + else ur["room__user_two"] ) - .values_list("unread_count", flat=True) + return res + else: # lobby + user_room = UserRoom.objects.filter(user=user, room__isnull=True).first() + if not user_room: + return 0 + last_seen = user_room.last_seen + res = ( + Message.objects.filter(room__isnull=True, time__gte=last_seen) + .exclude(author=user) + .exclude(hidden=True) + .count() ) - return res[0] if len(res) else 0 + return res @login_required diff --git a/dmoj/settings.py b/dmoj/settings.py index e205e60..d03dc27 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -484,3 +484,6 @@ except IOError: pass DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +# Chat +CHAT_SECRET_KEY = "QUdVFsxk6f5-Hd8g9BXv81xMqvIZFRqMl-KbRzztW-U=" diff --git a/judge/migrations/0166_display_rank_index.py b/judge/migrations/0166_display_rank_index.py new file mode 100644 index 0000000..ffea311 --- /dev/null +++ b/judge/migrations/0166_display_rank_index.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.18 on 2023-08-28 01:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0165_drop_output_prefix_override"), + ] + + operations = [ + migrations.AlterField( + model_name="profile", + name="display_rank", + field=models.CharField( + choices=[ + ("user", "Normal User"), + ("setter", "Problem Setter"), + ("admin", "Admin"), + ], + db_index=True, + default="user", + max_length=10, + verbose_name="display rank", + ), + ), + ] diff --git a/judge/models/profile.py b/judge/models/profile.py index ccb0e87..3f6e430 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -183,6 +183,7 @@ class Profile(models.Model): ("setter", "Problem Setter"), ("admin", "Admin"), ), + db_index=True, ) mute = models.BooleanField( verbose_name=_("comment mute"), diff --git a/templates/chat/chat.html b/templates/chat/chat.html index 9cde791..857aaf9 100644 --- a/templates/chat/chat.html +++ b/templates/chat/chat.html @@ -7,21 +7,11 @@ - - + {% compress js %} + {% include "chat/chat_js.html" %} + {% endcompress %} + @@ -638,8 +89,7 @@ {% include 'chat/user_online_status.html' %}
- - + diff --git a/templates/chat/chat_css.html b/templates/chat/chat_css.html index f5bf8cd..d79a22b 100644 --- a/templates/chat/chat_css.html +++ b/templates/chat/chat_css.html @@ -1,132 +1,134 @@ - + + #content { + padding-top: 0; + } + + ::-webkit-scrollbar { + width: 20px; + } + + ::-webkit-scrollbar-track { + background-color: transparent; + } + + ::-webkit-scrollbar-thumb { + background-color: #d6dee1; + border-radius: 20px; + border: 6px solid transparent; + background-clip: content-box; + } + + ::-webkit-scrollbar-thumb:hover { + background-color: #a8bbbf; + } + + #page-container { + width: 100%; + } + + .body-message img{ + max-height: 12em; + } + + .tooltip:not(.shown) { + display: none; + } + + textarea { + resize: none; + } + + .tooltip { + position: absolute; + z-index: 1000; + } + + #loader { + display: block; + margin-left: auto; + margin-right: auto; + width: 80px; + } + .profile-pic { + height: 2.6em; + width: 2.6em; + border-radius: 0.3em; + margin-top: 0.1em; + float: left; + } + .body-message { + padding-left: 3em; + padding-bottom: 0.5em; + border-bottom: 1px dotted lightgray; + } + .user-time { + margin-bottom: 0.3em; + } + .time { + margin-left: 0.5em; + } + .clear { + clear: both; + } + .content-message { + word-wrap: break-word; + } + .content-message p { + margin: 0; + } + #content { + width: 100%; + } + #content-body { + padding-bottom: 0; + } + #page-container { + min-height: 0; + } + .sidebox h3 { + border-radius: 0; + } + .body-block { + border-radius: 4px; + padding: 0.05em 0.6em; + width: 100%; + } + #search-form { + float: inherit; + } + #search-container { + margin-bottom: 0.4em; + } + #setting { + position: relative; + } + + + @media (min-width: 800px) { + #page-container { + position:fixed; + overflow:hidden; + } + } + @media (max-width: 799px) { + html, body { + max-width: 100%; + overflow-x: hidden; + } + #mobile ul { + width: 100%; + } + .info-pic { + margin-left: 0.5em; + } + .active-span { + display: none; + } + } + +{% endcompress %} \ No newline at end of file diff --git a/templates/chat/chat_js.html b/templates/chat/chat_js.html new file mode 100644 index 0000000..116d949 --- /dev/null +++ b/templates/chat/chat_js.html @@ -0,0 +1,553 @@ + \ No newline at end of file From 9f0213865de5242cfe72d1f2db62e89935229899 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Mon, 28 Aug 2023 14:35:44 -0500 Subject: [PATCH 162/478] Move chat template out of compress --- templates/chat/chat.html | 5 +++++ templates/chat/chat_js.html | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/chat/chat.html b/templates/chat/chat.html index 857aaf9..bf65e17 100644 --- a/templates/chat/chat.html +++ b/templates/chat/chat.html @@ -42,6 +42,11 @@ return receiver; } + let message_template = ` +{% with message=message_template %} + {% include "chat/message.html" %} +{% endwith %} + `; $(function() { load_dynamic_update({{last_msg}}); }); diff --git a/templates/chat/chat_js.html b/templates/chat/chat_js.html index 116d949..95ca7df 100644 --- a/templates/chat/chat_js.html +++ b/templates/chat/chat_js.html @@ -1,9 +1,4 @@ \ No newline at end of file diff --git a/templates/chat/online_status.html b/templates/chat/online_status.html index edd257b..d82159d 100644 --- a/templates/chat/online_status.html +++ b/templates/chat/online_status.html @@ -27,11 +27,18 @@ fill="{{'green' if user.is_online else 'red'}}"/>
- - {{ user.user.username }} - - - {{user.unread_count if user.unread_count}} +
+ + {{ user.user.username }} + + {% if user.last_msg %} + + {{ user.last_msg }} + + {% endif %} +
+ + {{user.unread_count if user.unread_count}} {% endfor %} From accf5864137aeb18ace364c2b93c5ac8b6aed8eb Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Tue, 29 Aug 2023 18:52:24 -0500 Subject: [PATCH 165/478] Add search to internal problem --- judge/views/internal.py | 21 ++++++++++++++------- templates/internal/problem.html | 5 +++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/judge/views/internal.py b/judge/views/internal.py index 4467fc3..eff02a4 100644 --- a/judge/views/internal.py +++ b/judge/views/internal.py @@ -3,7 +3,7 @@ import json from django.views.generic import ListView from django.utils.translation import gettext as _, gettext_lazy -from django.db.models import Count +from django.db.models import Count, Q from django.http import HttpResponseForbidden from django.urls import reverse @@ -38,13 +38,19 @@ class InternalProblem(InternalView, ListView): **kwargs ) + def get_search_query(self): + return self.request.GET.get("q") or self.request.POST.get("q") + def get_queryset(self): - queryset = ( - Problem.objects.annotate(vote_count=Count("volunteer_user_votes")) - .filter(vote_count__gte=1) - .order_by("-vote_count") - ) - return queryset + queryset = Problem.objects.annotate( + vote_count=Count("volunteer_user_votes") + ).filter(vote_count__gte=1) + query = self.get_search_query() + if query: + queryset = queryset.filter( + Q(code__icontains=query) | Q(name__icontains=query) + ) + return queryset.order_by("-vote_count") def get_context_data(self, **kwargs): context = super(InternalProblem, self).get_context_data(**kwargs) @@ -52,6 +58,7 @@ class InternalProblem(InternalView, ListView): context["title"] = self.title context["page_prefix"] = self.request.path + "?page=" context["first_page_href"] = self.request.path + context["query"] = self.get_search_query() return context diff --git a/templates/internal/problem.html b/templates/internal/problem.html index eebb02c..d1147e8 100644 --- a/templates/internal/problem.html +++ b/templates/internal/problem.html @@ -29,6 +29,11 @@ {% endblock %} {% block middle_content %} + + + + +
From 944d3a733ed825a1a843ed44d1708ee9249cb1e5 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Tue, 29 Aug 2023 21:50:33 -0500 Subject: [PATCH 166/478] More chat ui --- resources/chatbox.scss | 48 ++++++-------------- resources/darkmode.css | 40 +++++++---------- templates/chat/chat.html | 12 +++-- templates/chat/chat_css.html | 61 +++++++++++++++++++++++++- templates/chat/chat_js.html | 8 +++- templates/chat/user_online_status.html | 2 +- 6 files changed, 107 insertions(+), 64 deletions(-) diff --git a/resources/chatbox.scss b/resources/chatbox.scss index fa37b15..fac7090 100644 --- a/resources/chatbox.scss +++ b/resources/chatbox.scss @@ -11,15 +11,6 @@ float: right; margin-right: 1em; } -#emoji-button { - position: absolute; - right: 1em; - font-size: 2em; - color: lightgray; -} -#emoji-button:hover { - color: gray; -} #chat-log { padding: 0; padding-top: 2em; @@ -58,18 +49,12 @@ overflow-y: scroll; border-bottom-left-radius: 0; border-bottom-right-radius: 0; - height: 75%; + flex-grow: 1; } #chat-input { - width: 100%; - padding: 0.4em 4em 1em 1.2em; - border: 0; color: black; - border-top-left-radius: 0; - border-top-right-radius: 0; - height: 100%; - font-size: 16px; + border: 2px solid #e4a81c; } #chat-online-content { padding: 0; @@ -87,7 +72,7 @@ #chat-container { display: flex; width: 100%; - height: calc(100vh - 3em);; + height: calc(100vh - 3em); border: 1px solid #ccc; /*border-radius: 0 4px 0 0;*/ border-bottom: 0; @@ -99,9 +84,6 @@ #chat-area { flex-grow: 1; } - .chat-left-panel, .chat-right-panel { - display: block !important; - } } #chat-input, #chat-log .content-message { font-family: "Noto Sans", Arial, "Lucida Grande", sans-serif; @@ -109,14 +91,7 @@ .info-pic { height: 100%; } -.info-circle { - position: absolute; - cx: 12%; - cy: 12%; - r: 12%; - stroke: white; - stroke-width: 1; -} + .info-name { margin-left: 10px; font-size: 2em; @@ -173,6 +148,7 @@ display: flex; padding: 15px; gap: 0.5em; + border-radius: 6px; } .status-row:hover { background: lightgray; @@ -191,6 +167,8 @@ border-radius: 15px; max-width: 70%; width: fit-content; + font-size: 1.05rem; + line-height: 1.3; } .message-text-other { background: #eeeeee; @@ -200,7 +178,12 @@ background: rgb(0, 132, 255); color: white; } - +.chat-input-icon { + color: #045343; +} +.chat-input-icon:hover { + background: lightgray; +} .chat { .active-span { color: #636363; @@ -239,9 +222,6 @@ @media (max-width: 799px) { #chat-area { - height: calc(100vh - 50px); - } - #emoji-button { - display: none; + height: calc(100vh - 120px); } } diff --git a/resources/darkmode.css b/resources/darkmode.css index fc2d96d..b0562f9 100644 --- a/resources/darkmode.css +++ b/resources/darkmode.css @@ -3008,19 +3008,13 @@ a.voted { background-image: initial; background-color: rgb(20, 22, 22); } -#emoji-button { - color: rgb(169, 166, 160); -} -#emoji-button:hover { - color: rgb(126, 119, 107); -} #chat-online { border-right-color: rgb(51, 56, 58); border-bottom-color: initial; } #chat-input { - border-color: initial; color: rgb(193, 191, 188); + border-color: rgb(132, 97, 16); } .selected-status-row { background-color: rgb(41, 44, 46); @@ -3036,9 +3030,6 @@ a.voted { border-bottom-color: initial; } } -.info-circle { - stroke: rgb(193, 191, 188); -} #chat-info { box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 3px; } @@ -3068,6 +3059,13 @@ a.voted { background-color: rgb(0, 88, 169); color: rgb(193, 191, 188); } +.chat-input-icon { + color: rgb(170, 166, 161); +} +.chat-input-icon:hover { + background-image: initial; + background-color: rgb(41, 44, 46); +} .chat .active-span { color: rgb(140, 134, 125); } @@ -3728,19 +3726,6 @@ code .il { .sr-only { border-color: initial; } -::-webkit-scrollbar-track { - background-color: transparent; -} -::-webkit-scrollbar-thumb { - background-color: rgb(33, 41, 45); - border-color: transparent; -} -::-webkit-scrollbar-thumb:hover { - background-color: rgb(50, 65, 68); -} -.body-message { - border-bottom-color: rgb(50, 54, 56); -} .CtxtMenu_InfoContent { border-color: initial; background-color: rgb(28, 30, 32); @@ -3838,6 +3823,15 @@ mjx-merror { mjx-assistive-mml { border-color: initial !important; } +mjx-stretchy-v > mjx-ext { + border-color: transparent; +} +.recently-attempted ul { + list-style-image: initial; +} +.organization-row:last-child { + border-bottom-color: initial; +} /* Override Style */ .vimvixen-hint { diff --git a/templates/chat/chat.html b/templates/chat/chat.html index ea1365e..1991402 100644 --- a/templates/chat/chat.html +++ b/templates/chat/chat.html @@ -84,18 +84,22 @@
-
+
{% include 'chat/user_online_status.html' %}
-
-
- +
+
+
+
+ +
@@ -319,6 +319,11 @@

+ {% else %} +
+ {{ _('There is no ongoing contest at this time.') }} +
+
{% endif %}

@@ -354,15 +359,17 @@

{% else %} - {{ _('There are no scheduled contests at this time.') }} -
+
+ {{ _('There is no scheduled contest at this time.') }} +
+
{% endif %}
+

+ {{ _('Past Contests') }} +

{% if past_contests %} -

- {{ _('Past Contests') }} -

{% if page_obj and page_obj.num_pages > 1 %}
{% include "list-pages.html" %} @@ -411,6 +418,11 @@ {% include "list-pages.html" %}
{% endif %} + {% else %} +
+ {{ _('There is no past contest.') }} +
+
{% endif %} {% endblock %} From 1749e6480297c9cf183be17c793d67daf69a5434 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 30 Aug 2023 13:07:57 -0500 Subject: [PATCH 168/478] Fix chat css --- judge/views/contests.py | 4 +--- templates/chat/chat_css.html | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/judge/views/contests.py b/judge/views/contests.py index ff4b320..c183435 100644 --- a/judge/views/contests.py +++ b/judge/views/contests.py @@ -1357,9 +1357,7 @@ class ContestClarificationAjax(ContestMixin, DetailView): raise Http404() polling_time = 1 # minute - last_one_minute = last_five_minutes = timezone.now() - timezone.timedelta( - minutes=polling_time - ) + last_one_minute = timezone.now() - timezone.timedelta(minutes=polling_time) queryset = ContestProblemClarification.objects.filter( problem__in=self.object.contest_problems.all(), date__gte=last_one_minute diff --git a/templates/chat/chat_css.html b/templates/chat/chat_css.html index 5e333bf..ccc2feb 100644 --- a/templates/chat/chat_css.html +++ b/templates/chat/chat_css.html @@ -164,9 +164,10 @@ .info-pic { border-radius: 50%; margin-left: 1em; + width: 3em; } #chat-info { - height: 10%; + height: 3em; } @media (min-width: 800px) { @@ -195,9 +196,6 @@ .active-span { display: none; } - #chat-info { - height: 5%; - } } {% endcompress %} \ No newline at end of file From 1473118c5a316d250087530dc70727f3c719a5e8 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 30 Aug 2023 13:19:51 -0500 Subject: [PATCH 169/478] More css fix for chat --- templates/chat/chat_css.html | 1 - 1 file changed, 1 deletion(-) diff --git a/templates/chat/chat_css.html b/templates/chat/chat_css.html index ccc2feb..1ae5790 100644 --- a/templates/chat/chat_css.html +++ b/templates/chat/chat_css.html @@ -164,7 +164,6 @@ .info-pic { border-radius: 50%; margin-left: 1em; - width: 3em; } #chat-info { height: 3em; From abbe5f15e1451c3910a2074c6bcdd929f3ce0b6e Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 30 Aug 2023 18:46:47 -0500 Subject: [PATCH 170/478] Add meta address key to setting --- dmoj/settings.py | 3 +++ judge/user_log.py | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dmoj/settings.py b/dmoj/settings.py index d03dc27..713e31c 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -487,3 +487,6 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField" # Chat CHAT_SECRET_KEY = "QUdVFsxk6f5-Hd8g9BXv81xMqvIZFRqMl-KbRzztW-U=" + +# Nginx +META_REMOTE_ADDRESS_KEY = "REMOTE_ADDR" diff --git a/judge/user_log.py b/judge/user_log.py index 91a4c67..b8aec43 100644 --- a/judge/user_log.py +++ b/judge/user_log.py @@ -1,4 +1,5 @@ from django.utils.timezone import now +from django.conf import settings from judge.models import Profile @@ -17,8 +18,8 @@ class LogUserAccessMiddleware(object): ): updates = {"last_access": now()} # Decided on using REMOTE_ADDR as nginx will translate it to the external IP that hits it. - if request.META.get("REMOTE_ADDR"): - updates["ip"] = request.META.get("REMOTE_ADDR") + if request.META.get(settings.META_REMOTE_ADDRESS_KEY): + updates["ip"] = request.META.get(settings.META_REMOTE_ADDRESS_KEY) Profile.objects.filter(user_id=request.user.pk).update(**updates) return response From b03836715f1ade44393066c0bc61b1c096274923 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Wed, 30 Aug 2023 18:48:04 -0500 Subject: [PATCH 171/478] Fix setting --- dmoj/settings.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/dmoj/settings.py b/dmoj/settings.py index 713e31c..d5cfa38 100644 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -477,16 +477,16 @@ ML_OUTPUT_PATH = None # Use subdomain for organizations USE_SUBDOMAIN = False -try: - with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f: - exec(f.read(), globals()) -except IOError: - pass - -DEFAULT_AUTO_FIELD = "django.db.models.AutoField" - # Chat CHAT_SECRET_KEY = "QUdVFsxk6f5-Hd8g9BXv81xMqvIZFRqMl-KbRzztW-U=" # Nginx META_REMOTE_ADDRESS_KEY = "REMOTE_ADDR" + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +try: + with open(os.path.join(os.path.dirname(__file__), "local_settings.py")) as f: + exec(f.read(), globals()) +except IOError: + pass From 1f03106766d8c281adc906fd399ebbdce4b26f4d Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Thu, 31 Aug 2023 19:35:56 -0500 Subject: [PATCH 172/478] Add ultimate format --- judge/contest_format/__init__.py | 1 + judge/contest_format/ultimate.py | 55 +++++++++++++++++++ .../0167_ultimate_contest_format.py | 32 +++++++++++ 3 files changed, 88 insertions(+) create mode 100644 judge/contest_format/ultimate.py create mode 100644 judge/migrations/0167_ultimate_contest_format.py diff --git a/judge/contest_format/__init__.py b/judge/contest_format/__init__.py index 8d2ccc9..9f6d628 100644 --- a/judge/contest_format/__init__.py +++ b/judge/contest_format/__init__.py @@ -4,4 +4,5 @@ from judge.contest_format.ecoo import ECOOContestFormat from judge.contest_format.icpc import ICPCContestFormat from judge.contest_format.ioi import IOIContestFormat from judge.contest_format.new_ioi import NewIOIContestFormat +from judge.contest_format.ultimate import UltimateContestFormat from judge.contest_format.registry import choices, formats diff --git a/judge/contest_format/ultimate.py b/judge/contest_format/ultimate.py new file mode 100644 index 0000000..7960d02 --- /dev/null +++ b/judge/contest_format/ultimate.py @@ -0,0 +1,55 @@ +from django.utils.translation import gettext_lazy + +from judge.contest_format.ioi import IOIContestFormat +from judge.contest_format.registry import register_contest_format +from django.db.models import Min, OuterRef, Subquery + +# This contest format only counts last submission for each problem. + + +@register_contest_format("ultimate") +class UltimateContestFormat(IOIContestFormat): + name = gettext_lazy("Ultimate") + + def update_participation(self, participation): + cumtime = 0 + score = 0 + format_data = {} + + queryset = participation.submissions + if self.contest.freeze_after: + queryset = queryset.filter( + submission__date__lt=participation.start + self.contest.freeze_after + ) + + queryset = ( + queryset.values("problem_id") + .filter( + id=Subquery( + queryset.filter(problem_id=OuterRef("problem_id")) + .order_by("-id") + .values("id")[:1] + ) + ) + .values_list("problem_id", "submission__date", "points") + ) + + for problem_id, time, points in queryset: + if self.config["cumtime"]: + dt = (time - participation.start).total_seconds() + if points: + cumtime += dt + else: + dt = 0 + format_data[str(problem_id)] = { + "time": dt, + "points": points, + } + score += points + + self.handle_frozen_state(participation, format_data) + participation.cumtime = max(cumtime, 0) + participation.score = round(score, self.contest.points_precision) + participation.tiebreaker = 0 + participation.format_data = format_data + participation.save() diff --git a/judge/migrations/0167_ultimate_contest_format.py b/judge/migrations/0167_ultimate_contest_format.py new file mode 100644 index 0000000..636e882 --- /dev/null +++ b/judge/migrations/0167_ultimate_contest_format.py @@ -0,0 +1,32 @@ +# Generated by Django 3.2.18 on 2023-09-01 00:09 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("judge", "0166_display_rank_index"), + ] + + operations = [ + migrations.AlterField( + model_name="contest", + name="format_name", + field=models.CharField( + choices=[ + ("atcoder", "AtCoder"), + ("default", "Default"), + ("ecoo", "ECOO"), + ("icpc", "ICPC"), + ("ioi", "IOI"), + ("ioi16", "New IOI"), + ("ultimate", "Ultimate"), + ], + default="default", + help_text="The contest format module to use.", + max_length=32, + verbose_name="contest format", + ), + ), + ] From 120cc3c06d5b3710a1a7dc1c13aade4e452780fe Mon Sep 17 00:00:00 2001 From: Tran Trong Nghia <80335335+emladevops@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:31:24 +0700 Subject: [PATCH 173/478] Update README.md Profile images nginx guide update. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 416ae9c..a30ddcf 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,14 @@ python3 manage.py runserver 0.0.0.0:8000 1. (WSL) có thể tải ứng dụng Terminal trong Windows Store 2. (WSL) mỗi lần mở ubuntu, các bạn cần chạy lệnh sau để mariadb khởi động: `sudo service mysql restart` (tương tự cho một số service khác như memcached, celery) -3. Sau khi cài đặt, các bạn chỉ cần activate virtual env và chạy lệnh runserver là ok. +3. Sau khi cài đặt, các bạn chỉ cần activate virtual env và chạy lệnh runserver là ok +4. Đối với nginx, sau khi config xong theo guide của DMOJ, bạn cần thêm location như sau để sử dụng được tính năng profile image, thay thế `path/to/oj` thành đường dẫn nơi bạn đã clone source code. + +``` + location /profile_images/ { + root /path/to/oj; + } +``` ```jsx . dmojsite/bin/activate From b7c6d45b80ebf7fa0bc29c7a621cd719503ba887 Mon Sep 17 00:00:00 2001 From: Tran Trong Nghia <80335335+emladevops@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:31:59 +0700 Subject: [PATCH 174/478] Update README.md Profile Images configuration --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a30ddcf..e534204 100644 --- a/README.md +++ b/README.md @@ -119,9 +119,9 @@ python3 manage.py runserver 0.0.0.0:8000 4. Đối với nginx, sau khi config xong theo guide của DMOJ, bạn cần thêm location như sau để sử dụng được tính năng profile image, thay thế `path/to/oj` thành đường dẫn nơi bạn đã clone source code. ``` - location /profile_images/ { - root /path/to/oj; - } +location /profile_images/ { + root /path/to/oj; +} ``` ```jsx From fa21cde2c9fd7bb09a47b4ba6b8594a2b700900c Mon Sep 17 00:00:00 2001 From: Tran Trong Nghia <80335335+emladevops@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:32:41 +0700 Subject: [PATCH 175/478] Update README.md Fix typo --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e534204..ea64e92 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,11 @@ python3 manage.py runserver 0.0.0.0:8000 1. (WSL) có thể tải ứng dụng Terminal trong Windows Store 2. (WSL) mỗi lần mở ubuntu, các bạn cần chạy lệnh sau để mariadb khởi động: `sudo service mysql restart` (tương tự cho một số service khác như memcached, celery) 3. Sau khi cài đặt, các bạn chỉ cần activate virtual env và chạy lệnh runserver là ok -4. Đối với nginx, sau khi config xong theo guide của DMOJ, bạn cần thêm location như sau để sử dụng được tính năng profile image, thay thế `path/to/oj` thành đường dẫn nơi bạn đã clone source code. +```jsx +. dmojsite/bin/activate +python3 manage.py runserver +``` +5. Đối với nginx, sau khi config xong theo guide của DMOJ, bạn cần thêm location như sau để sử dụng được tính năng profile image, thay thế `path/to/oj` thành đường dẫn nơi bạn đã clone source code. ``` location /profile_images/ { @@ -124,11 +128,6 @@ location /profile_images/ { } ``` -```jsx -. dmojsite/bin/activate -python3 manage.py runserver -``` - 1. Quy trình dev: 1. Sau khi thay đổi code thì django tự build lại, các bạn chỉ cần F5 2. Một số style nằm trong các file .scss. Các bạn cần recompile css thì mới thấy được thay đổi. From 345e9985e3802a89858315a512e988f90bab0d8c Mon Sep 17 00:00:00 2001 From: Tran Trong Nghia <80335335+emladevops@users.noreply.github.com> Date: Fri, 1 Sep 2023 21:34:11 +0700 Subject: [PATCH 176/478] Beautify README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ea64e92..d4cdc43 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ location /profile_images/ { } ``` -1. Quy trình dev: +6. Quy trình dev: 1. Sau khi thay đổi code thì django tự build lại, các bạn chỉ cần F5 2. Một số style nằm trong các file .scss. Các bạn cần recompile css thì mới thấy được thay đổi. From 9a89c5a15a83bb9fb9309a535fcff0d4d337d40c Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 1 Sep 2023 17:51:40 -0500 Subject: [PATCH 177/478] Make chat ava square --- templates/chat/chat_css.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/chat/chat_css.html b/templates/chat/chat_css.html index 1ae5790..4909345 100644 --- a/templates/chat/chat_css.html +++ b/templates/chat/chat_css.html @@ -162,7 +162,7 @@ stroke-width: 1; } .info-pic { - border-radius: 50%; + border-radius: 5px; margin-left: 1em; } #chat-info { From 41ba0894acb6dd97427589acbd43910d0555adac Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 1 Sep 2023 18:09:30 -0500 Subject: [PATCH 178/478] Add loading bar --- resources/base.scss | 14 ++++++++++++-- resources/common.js | 7 +++++++ resources/vars.scss | 2 ++ templates/base.html | 1 + 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/resources/base.scss b/resources/base.scss index 48ac6af..687249f 100644 --- a/resources/base.scss +++ b/resources/base.scss @@ -233,7 +233,7 @@ header { top: 0; left: 0; right: 0; - height: 48px; + height: $navbar_height; } nav { @@ -377,7 +377,7 @@ hr { } #content { - margin: 48px auto 1em auto; + margin: $navbar_height auto 1em auto; padding-top: 1em; // Header @@ -860,6 +860,16 @@ select { margin-bottom: 1em; } +#loading-bar { + position: fixed; + top: 0; + left: 0; + height: 2px; + background-color: #993932; + width: 0; + z-index: 9999; +} + @media (max-width: 799px) { #user-links, .anon { padding-right: 0.5em; diff --git a/resources/common.js b/resources/common.js index 488fb33..380dc63 100644 --- a/resources/common.js +++ b/resources/common.js @@ -385,6 +385,12 @@ function onWindowReady() { showTooltip(e.trigger, fallbackMessage(e.action)); }); }); + $('a').click(function() { + $("#loading-bar").show(); + $("#loading-bar").animate({ width: "100%" }, 1500, function() { + $(this).hide(); + }); + }); } $(function() { @@ -429,4 +435,5 @@ $(function() { $('html').click(function () { $nav_list.hide(); }); + }); \ No newline at end of file diff --git a/resources/vars.scss b/resources/vars.scss index 4bcda31..7556626 100644 --- a/resources/vars.scss +++ b/resources/vars.scss @@ -10,3 +10,5 @@ $widget_border_radius: 4px; $table_header_rounding: 6px; $monospace-fonts: Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace; + +$navbar_height: 48px; diff --git a/templates/base.html b/templates/base.html index 2e5013e..c5be0ff 100644 --- a/templates/base.html +++ b/templates/base.html @@ -316,6 +316,7 @@ +
{% if request.in_contest %}
From 4401fa7376de499141341a053ed9a802badf1819 Mon Sep 17 00:00:00 2001 From: cuom1999 Date: Fri, 1 Sep 2023 18:20:10 -0500 Subject: [PATCH 179/478] Simplify nav user span --- resources/base.scss | 10 +++++++++- resources/darkmode.css | 18 +++++++++--------- templates/base.html | 4 ++-- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/resources/base.scss b/resources/base.scss index 687249f..13f9d6d 100644 --- a/resources/base.scss +++ b/resources/base.scss @@ -191,11 +191,19 @@ header { display: block; margin: 0; - & > li > a > span { + & > li > span { height: 36px; padding-top: 8px; display: block; white-space: nowrap; + cursor: pointer; + + &:hover { + border-top: 2px solid #9c3706; + color: black; + background: rgba(255, 255, 255, 0.25); + margin: 0; + } & > img { vertical-align: middle; diff --git a/resources/darkmode.css b/resources/darkmode.css index b0562f9..4dba034 100644 --- a/resources/darkmode.css +++ b/resources/darkmode.css @@ -1541,6 +1541,12 @@ header { #user-links { color: rgb(146, 141, 132); } +#user-links > ul > li > span:hover { + border-top-color: rgb(165, 58, 7); + color: rgb(193, 191, 188); + background-image: initial; + background-color: rgba(20, 22, 22, 0.25); +} #nav-shadow { background-image: linear-gradient(rgb(41, 44, 46), rgba(0, 0, 0, 0)); @@ -1793,6 +1799,9 @@ noscript #noscript { .background-footer { color: rgb(126, 119, 107); } +#loading-bar { + background-color: rgb(101, 38, 33); +} @media (min-width: 800px) { #page-container { background-image: initial; @@ -3823,15 +3832,6 @@ mjx-merror { mjx-assistive-mml { border-color: initial !important; } -mjx-stretchy-v > mjx-ext { - border-color: transparent; -} -.recently-attempted ul { - list-style-image: initial; -} -.organization-row:last-child { - border-bottom-color: initial; -} /* Override Style */ .vimvixen-hint { diff --git a/templates/base.html b/templates/base.html index c5be0ff..70ec640 100644 --- a/templates/base.html +++ b/templates/base.html @@ -261,14 +261,14 @@ {% if request.user.is_authenticated %} -