Redesign Bookmark (#112)
This commit is contained in:
parent
829e6a802d
commit
c6acfa5e05
13 changed files with 398 additions and 243 deletions
|
@ -704,6 +704,10 @@ class Solution(models.Model, PageVotable, Bookmarkable):
|
||||||
else:
|
else:
|
||||||
return reverse("problem_editorial", args=[problem.code])
|
return reverse("problem_editorial", args=[problem.code])
|
||||||
|
|
||||||
|
@cache_wrapper(prefix="Sga", expected_type=models.query.QuerySet)
|
||||||
|
def get_authors(self):
|
||||||
|
return self.authors.only("id")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return _("Editorial for %s") % self.problem.name
|
return _("Editorial for %s") % self.problem.name
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ from .models import (
|
||||||
Profile,
|
Profile,
|
||||||
Submission,
|
Submission,
|
||||||
NavigationBar,
|
NavigationBar,
|
||||||
|
Solution,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,14 +121,7 @@ def comment_update(sender, instance, **kwargs):
|
||||||
|
|
||||||
@receiver(post_save, sender=BlogPost)
|
@receiver(post_save, sender=BlogPost)
|
||||||
def post_update(sender, instance, **kwargs):
|
def post_update(sender, instance, **kwargs):
|
||||||
cache.delete_many(
|
cache.delete(make_template_fragment_key("post_content", (instance.id,)))
|
||||||
[
|
|
||||||
make_template_fragment_key("post_summary", (instance.id,)),
|
|
||||||
"blog_slug:%d" % instance.id,
|
|
||||||
"blog_feed:%d" % instance.id,
|
|
||||||
]
|
|
||||||
+ [make_template_fragment_key("post_content", (instance.id,))]
|
|
||||||
)
|
|
||||||
BlogPost.get_authors.dirty(instance)
|
BlogPost.get_authors.dirty(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@ -175,3 +169,8 @@ def contest_submission_update(sender, instance, **kwargs):
|
||||||
@receiver(post_save, sender=NavigationBar)
|
@receiver(post_save, sender=NavigationBar)
|
||||||
def navbar_update(sender, instance, **kwargs):
|
def navbar_update(sender, instance, **kwargs):
|
||||||
judge.template_context._nav_bar.dirty()
|
judge.template_context._nav_bar.dirty()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Solution)
|
||||||
|
def solution_update(sender, instance, **kwargs):
|
||||||
|
cache.delete(make_template_fragment_key("solution_content", (instance.id,)))
|
||||||
|
|
|
@ -22,6 +22,7 @@ from django.db.models import (
|
||||||
Q,
|
Q,
|
||||||
When,
|
When,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
|
Sum,
|
||||||
)
|
)
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.db.utils import ProgrammingError
|
from django.db.utils import ProgrammingError
|
||||||
|
@ -644,6 +645,16 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
queryset = queryset.filter(points__gte=self.point_start)
|
queryset = queryset.filter(points__gte=self.point_start)
|
||||||
if self.point_end is not None:
|
if self.point_end is not None:
|
||||||
queryset = queryset.filter(points__lte=self.point_end)
|
queryset = queryset.filter(points__lte=self.point_end)
|
||||||
|
|
||||||
|
queryset = queryset.annotate(
|
||||||
|
has_public_editorial=Sum(
|
||||||
|
Case(
|
||||||
|
When(solution__is_public=True, then=1),
|
||||||
|
default=0,
|
||||||
|
output_field=IntegerField(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
return queryset.distinct()
|
return queryset.distinct()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
|
@ -36,7 +36,17 @@ from django.template.loader import render_to_string
|
||||||
from reversion import revisions
|
from reversion import revisions
|
||||||
|
|
||||||
from judge.forms import UserForm, ProfileForm, ProfileInfoForm
|
from judge.forms import UserForm, ProfileForm, ProfileInfoForm
|
||||||
from judge.models import Profile, Rating, Submission, Friend, ProfileInfo
|
from judge.models import (
|
||||||
|
Profile,
|
||||||
|
Rating,
|
||||||
|
Submission,
|
||||||
|
Friend,
|
||||||
|
ProfileInfo,
|
||||||
|
BlogPost,
|
||||||
|
Problem,
|
||||||
|
Contest,
|
||||||
|
Solution,
|
||||||
|
)
|
||||||
from judge.performance_points import get_pp_breakdown
|
from judge.performance_points import get_pp_breakdown
|
||||||
from judge.ratings import rating_class, rating_progress
|
from judge.ratings import rating_class, rating_progress
|
||||||
from judge.tasks import import_users
|
from judge.tasks import import_users
|
||||||
|
@ -54,10 +64,13 @@ from judge.utils.views import (
|
||||||
TitleMixin,
|
TitleMixin,
|
||||||
generic_message,
|
generic_message,
|
||||||
SingleObjectFormView,
|
SingleObjectFormView,
|
||||||
|
DiggPaginatorMixin,
|
||||||
)
|
)
|
||||||
from judge.utils.infinite_paginator import InfinitePaginationMixin
|
from judge.utils.infinite_paginator import InfinitePaginationMixin
|
||||||
|
from judge.views.problem import ProblemList
|
||||||
from .contests import ContestRanking
|
from .contests import ContestRanking
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"UserPage",
|
"UserPage",
|
||||||
"UserAboutPage",
|
"UserAboutPage",
|
||||||
|
@ -305,17 +318,49 @@ class UserProblemsPage(UserPage):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
class UserBookMarkPage(UserPage):
|
class UserBookMarkPage(DiggPaginatorMixin, ListView, UserPage):
|
||||||
template_name = "user/user-bookmarks.html"
|
template_name = "user/user-bookmarks.html"
|
||||||
|
context_object_name = "bookmarks"
|
||||||
|
paginate_by = 10
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.current_tab = self.request.GET.get("tab", "problems")
|
||||||
|
self.user = self.object = self.get_object()
|
||||||
|
return super(UserBookMarkPage, self).get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
model = None
|
||||||
|
if self.current_tab == "posts":
|
||||||
|
model = BlogPost
|
||||||
|
elif self.current_tab == "contests":
|
||||||
|
model = Contest
|
||||||
|
elif self.current_tab == "editorials":
|
||||||
|
model = Solution
|
||||||
|
else:
|
||||||
|
model = Problem
|
||||||
|
|
||||||
|
q = MakeBookMark.objects.filter(user=self.user).select_related("bookmark")
|
||||||
|
q = q.filter(bookmark__content_type=ContentType.objects.get_for_model(model))
|
||||||
|
object_ids = q.values_list("bookmark__object_id", flat=True)
|
||||||
|
|
||||||
|
res = model.objects.filter(id__in=object_ids)
|
||||||
|
if self.current_tab == "contests":
|
||||||
|
res = res.prefetch_related("organizations", "tags")
|
||||||
|
elif self.current_tab == "editorials":
|
||||||
|
res = res.select_related("problem")
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super(UserBookMarkPage, self).get_context_data(**kwargs)
|
context = super(UserBookMarkPage, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
bookmark_list = MakeBookMark.objects.filter(user=self.object)
|
context["current_tab"] = self.current_tab
|
||||||
context["blogs"] = bookmark_list.filter(bookmark__page__startswith="b")
|
context["user"] = self.user
|
||||||
context["problems"] = bookmark_list.filter(bookmark__page__startswith="p")
|
|
||||||
context["contests"] = bookmark_list.filter(bookmark__page__startswith="c")
|
context["page_prefix"] = (
|
||||||
context["solutions"] = bookmark_list.filter(bookmark__page__startswith="s")
|
self.request.path + "?tab=" + self.current_tab + "&page="
|
||||||
|
)
|
||||||
|
context["first_page_href"] = self.request.path
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
|
@ -358,6 +358,33 @@ function register_copy_clipboard($elements, callback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activateBlogBoxOnClick() {
|
||||||
|
$('.blog-box').on('click', function () {
|
||||||
|
var $description = $(this).children('.blog-description');
|
||||||
|
var max_height = $description.css('max-height');
|
||||||
|
if (max_height !== 'fit-content') {
|
||||||
|
$description.css('max-height', 'fit-content');
|
||||||
|
$(this).css('cursor', 'auto');
|
||||||
|
$(this).removeClass('pre-expand-blog');
|
||||||
|
$(this).children().children('.show-more').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.blog-box').each(function () {
|
||||||
|
var $precontent = $(this).children('.blog-description').height();
|
||||||
|
var $content = $(this).children().children('.content-description').height();
|
||||||
|
if ($content == undefined) {
|
||||||
|
$content = $(this).children().children('.md-typeset').height()
|
||||||
|
}
|
||||||
|
if ($content > $precontent - 30) {
|
||||||
|
$(this).addClass('pre-expand-blog');
|
||||||
|
$(this).css('cursor', 'pointer');
|
||||||
|
} else {
|
||||||
|
$(this).children().children('.show-more').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function onWindowReady() {
|
function onWindowReady() {
|
||||||
// http://stackoverflow.com/a/1060034/1090657
|
// http://stackoverflow.com/a/1060034/1090657
|
||||||
var hidden = 'hidden';
|
var hidden = 'hidden';
|
||||||
|
@ -464,6 +491,7 @@ function onWindowReady() {
|
||||||
errorList.nextAll('input, select, textarea').first().after(errorList);
|
errorList.nextAll('input, select, textarea').first().after(errorList);
|
||||||
});
|
});
|
||||||
register_all_toggles();
|
register_all_toggles();
|
||||||
|
activateBlogBoxOnClick();
|
||||||
}
|
}
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
|
@ -36,8 +36,8 @@
|
||||||
</h2>
|
</h2>
|
||||||
<div class="blog-description">
|
<div class="blog-description">
|
||||||
<div class="summary content-description">
|
<div class="summary content-description">
|
||||||
{% cache 86400 'post_summary' post.id %}
|
{% cache 86400 'post_content' post.id %}
|
||||||
{{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }}
|
{{ post.content|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
{% endcache %}
|
{% endcache %}
|
||||||
</div>
|
</div>
|
||||||
<div class="show-more"> {{_("...More")}} </div>
|
<div class="show-more"> {{_("...More")}} </div>
|
||||||
|
|
|
@ -134,112 +134,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% macro contest_head(contest) %}
|
{% from "contest/macros.html" import contest_head, time_left, user_count, contest_join, contest_format_user %}
|
||||||
<a href="{{ url('contest_view', contest.key) }}" class="contest-list-title" style="margin-right: 5px;">
|
|
||||||
{{contest.name}}
|
|
||||||
</a>
|
|
||||||
<div class="contest-tags">
|
|
||||||
{% if not contest.is_visible %}
|
|
||||||
<span class="contest-tag contest-tag-hidden">
|
|
||||||
<i class="fa fa-eye-slash"></i> {{ _('hidden') }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if contest.is_editable %}
|
|
||||||
<span class="contest-tag contest-tag-edit">
|
|
||||||
<a href="{{ url('organization_contest_edit', organization.id, organization.slug, contest.key) }}" class="white">
|
|
||||||
<i class="fa fa-edit"></i> {{ _('Edit') }}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if contest.is_private %}
|
|
||||||
<span class="contest-tag contest-tag-private">
|
|
||||||
<i class="fa fa-lock"></i> {{ _('private') }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% if not hide_contest_orgs %}
|
|
||||||
{% if contest.is_organization_private %}
|
|
||||||
{% for org in contest.organizations.all() %}
|
|
||||||
{% include "organization/tag.html" %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% if contest.is_rated %}
|
|
||||||
<span class="contest-tag contest-tag-rated">
|
|
||||||
<i class="fa fa-bar-chart"></i> {{ _('rated') }}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
{% for tag in contest.tags.all() %}
|
|
||||||
<span style="background-color: {{ tag.color }}" class="contest-tag">
|
|
||||||
<a href="{{ url('contest_tag', tag.name) }}"
|
|
||||||
style="color: {{ tag.text_color }}"
|
|
||||||
data-featherlight="{{ url('contest_tag_ajax', tag.name) }}">
|
|
||||||
{{- tag.name -}}
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro time_left(contest) %}
|
|
||||||
<div class="time-left">
|
|
||||||
{% if contest.time_limit %}
|
|
||||||
<div>
|
|
||||||
<b>{{_("Start")}}</b>: {{ contest.start_time|date(_("H:i d/m/Y")) }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<b>{{_("End")}}</b>: {{ contest.end_time|date(_("H:i d/m/Y")) }}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div>
|
|
||||||
<b>{{_("Start")}}</b>: {{ contest.start_time|date(_("H:i d/m/Y")) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div>
|
|
||||||
<b>{{_("Length")}}</b>:
|
|
||||||
{% if contest.time_limit %}
|
|
||||||
{% trans time_limit=contest.time_limit|timedelta('localized-no-seconds') %}{{ time_limit }}{% endtrans %}
|
|
||||||
{% else %}
|
|
||||||
{% trans duration=contest.contest_window_length|timedelta('localized-no-seconds') %}{{ duration }}{% endtrans %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro user_count(contest, user) %}
|
|
||||||
{% if contest.can_see_own_scoreboard(user) %}
|
|
||||||
<a href="{{ url('contest_ranking', contest.key) }}"><i class="fa fa-users"></i> {{ contest.user_count }}</a>
|
|
||||||
{% else %}
|
|
||||||
<i class="fa fa-users"></i>{{ contest.user_count }}
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro contest_join(contest, request) %}
|
|
||||||
{% if request.in_contest and request.participation.contest == contest %}
|
|
||||||
<button class="small" disabled>{{ _('In contest') }}</button>
|
|
||||||
{% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %}
|
|
||||||
<form action="{{ url('contest_join', contest.key) }}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="submit" class="unselectable button full small"
|
|
||||||
value="{{ _('Spectate') }}">
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
<form action="{{ url('contest_join', contest.key) }}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="submit" class="unselectable button full small join-warning"
|
|
||||||
value="{{ _('Join') }}">
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% macro contest_format_user(contest, show_user=True) %}
|
|
||||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
|
||||||
<div class="contest-title"> {{ _('Format') }} </div>
|
|
||||||
<div style="flex-grow: 1">{{ contest.format.name }}</div>
|
|
||||||
{% if show_user %}
|
|
||||||
<div class="contest-title">{{ user_count(contest, request.user) }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
{% block middle_content %}
|
{% block middle_content %}
|
||||||
<div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;">
|
<div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;">
|
||||||
|
@ -298,7 +193,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-contest" style="flex: 0.5;">
|
<div class="info-contest" style="flex: 0.5;">
|
||||||
{{ contest_format_user(contest) }}
|
{{ contest_format_user(contest, request) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="participate-button">
|
<div class="participate-button">
|
||||||
{{ contest_join(contest, request) }}
|
{{ contest_join(contest, request) }}
|
||||||
|
@ -336,7 +231,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-contest" style="flex: 0.5;">
|
<div class="info-contest" style="flex: 0.5;">
|
||||||
{{ contest_format_user(contest) }}
|
{{ contest_format_user(contest, request) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="participate-button">
|
<div class="participate-button">
|
||||||
{{ contest_join(contest, request) }}
|
{{ contest_join(contest, request) }}
|
||||||
|
@ -373,7 +268,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-contest" style="flex: 0.5;">
|
<div class="info-contest" style="flex: 0.5;">
|
||||||
{{ contest_format_user(contest, show_user=False) }}
|
{{ contest_format_user(contest, request, show_user=False) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -402,7 +297,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-contest" style="flex: 0.5;">
|
<div class="info-contest" style="flex: 0.5;">
|
||||||
{{ contest_format_user(contest) }}
|
{{ contest_format_user(contest, request) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="participate-button">
|
<div class="participate-button">
|
||||||
{% if request.in_contest and request.participation.contest == contest %}
|
{% if request.in_contest and request.participation.contest == contest %}
|
||||||
|
|
106
templates/contest/macros.html
Normal file
106
templates/contest/macros.html
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
{% macro contest_head(contest) %}
|
||||||
|
<a href="{{ url('contest_view', contest.key) }}" class="contest-list-title" style="margin-right: 5px;">
|
||||||
|
{{contest.name}}
|
||||||
|
</a>
|
||||||
|
<div class="contest-tags">
|
||||||
|
{% if not contest.is_visible %}
|
||||||
|
<span class="contest-tag contest-tag-hidden">
|
||||||
|
<i class="fa fa-eye-slash"></i> {{ _('hidden') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if contest.is_editable %}
|
||||||
|
<span class="contest-tag contest-tag-edit">
|
||||||
|
<a href="{{ url('organization_contest_edit', organization.id, organization.slug, contest.key) }}" class="white">
|
||||||
|
<i class="fa fa-edit"></i> {{ _('Edit') }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if contest.is_private %}
|
||||||
|
<span class="contest-tag contest-tag-private">
|
||||||
|
<i class="fa fa-lock"></i> {{ _('private') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if not hide_contest_orgs %}
|
||||||
|
{% if contest.is_organization_private %}
|
||||||
|
{% for org in contest.organizations.all() %}
|
||||||
|
{% include "organization/tag.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if contest.is_rated %}
|
||||||
|
<span class="contest-tag contest-tag-rated">
|
||||||
|
<i class="fa fa-bar-chart"></i> {{ _('rated') }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% for tag in contest.tags.all() %}
|
||||||
|
<span style="background-color: {{ tag.color }}" class="contest-tag">
|
||||||
|
<a href="{{ url('contest_tag', tag.name) }}"
|
||||||
|
style="color: {{ tag.text_color }}"
|
||||||
|
data-featherlight="{{ url('contest_tag_ajax', tag.name) }}">
|
||||||
|
{{- tag.name -}}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro time_left(contest) %}
|
||||||
|
<div class="time-left">
|
||||||
|
{% if contest.time_limit %}
|
||||||
|
<div>
|
||||||
|
<b>{{_("Start")}}</b>: {{ contest.start_time|date(_("H:i d/m/Y")) }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>{{_("End")}}</b>: {{ contest.end_time|date(_("H:i d/m/Y")) }}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div>
|
||||||
|
<b>{{_("Start")}}</b>: {{ contest.start_time|date(_("H:i d/m/Y")) }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<b>{{_("Length")}}</b>:
|
||||||
|
{% if contest.time_limit %}
|
||||||
|
{% trans time_limit=contest.time_limit|timedelta('localized-no-seconds') %}{{ time_limit }}{% endtrans %}
|
||||||
|
{% else %}
|
||||||
|
{% trans duration=contest.contest_window_length|timedelta('localized-no-seconds') %}{{ duration }}{% endtrans %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro user_count(contest, user) %}
|
||||||
|
{% if contest.can_see_own_scoreboard(user) %}
|
||||||
|
<a href="{{ url('contest_ranking', contest.key) }}"><i class="fa fa-users"></i> {{ contest.user_count }}</a>
|
||||||
|
{% else %}
|
||||||
|
<i class="fa fa-users"></i>{{ contest.user_count }}
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro contest_join(contest, request) %}
|
||||||
|
{% if request.in_contest and request.participation.contest == contest %}
|
||||||
|
<button class="small" disabled>{{ _('In contest') }}</button>
|
||||||
|
{% elif request.profile.id in contest.editor_ids or request.profile.id in contest.tester_ids %}
|
||||||
|
<form action="{{ url('contest_join', contest.key) }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" class="unselectable button full small"
|
||||||
|
value="{{ _('Spectate') }}">
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<form action="{{ url('contest_join', contest.key) }}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit" class="unselectable button full small join-warning"
|
||||||
|
value="{{ _('Join') }}">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro contest_format_user(contest, request, show_user=True) %}
|
||||||
|
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||||
|
<div class="contest-title"> {{ _('Format') }} </div>
|
||||||
|
<div style="flex-grow: 1">{{ contest.format.name }}</div>
|
||||||
|
{% if show_user %}
|
||||||
|
<div class="contest-title">{{ user_count(contest, request.user) }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
|
@ -32,7 +32,6 @@
|
||||||
window.page++;
|
window.page++;
|
||||||
renderKatex($('.middle-content')[0]);
|
renderKatex($('.middle-content')[0]);
|
||||||
onWindowReady();
|
onWindowReady();
|
||||||
activateBlogBoxOnClick();
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,9 @@
|
||||||
<p>Authors: {{ link_users(authors) }}</p>
|
<p>Authors: {{ link_users(authors) }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{{ solution.content|markdown|reference|str|safe }}
|
{% cache 86400 'solution_content' solution.id %}
|
||||||
|
{{ solution.content|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
|
{% endcache %}
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
{% include "actionbar/list.html" %}
|
{% include "actionbar/list.html" %}
|
||||||
|
|
|
@ -113,7 +113,7 @@
|
||||||
|
|
||||||
$('#go').click(clean_submit);
|
$('#go').click(clean_submit);
|
||||||
|
|
||||||
$('input#full_text, input#hide_solved, input#show_types, input#have_editorial, input#show_solved_only').click(function () {
|
$('input#full_text, input#hide_solved, input#show_types, input#have_editorial, input#show_solved_only, input#show_editorial').click(function () {
|
||||||
prep_form();
|
prep_form();
|
||||||
($('<form>').attr('action', window.location.pathname + '?' + form_serialize())
|
($('<form>').attr('action', window.location.pathname + '?' + form_serialize())
|
||||||
.append($('<input>').attr('type', 'hidden').attr('name', 'csrfmiddlewaretoken')
|
.append($('<input>').attr('type', 'hidden').attr('name', 'csrfmiddlewaretoken')
|
||||||
|
|
|
@ -30,33 +30,6 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let loadingPage;
|
let loadingPage;
|
||||||
|
|
||||||
function activateBlogBoxOnClick() {
|
|
||||||
$('.blog-box').on('click', function () {
|
|
||||||
var $description = $(this).children('.blog-description');
|
|
||||||
var max_height = $description.css('max-height');
|
|
||||||
if (max_height !== 'fit-content') {
|
|
||||||
$description.css('max-height', 'fit-content');
|
|
||||||
$(this).css('cursor', 'auto');
|
|
||||||
$(this).removeClass('pre-expand-blog');
|
|
||||||
$(this).children().children('.show-more').hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.blog-box').each(function () {
|
|
||||||
var $precontent = $(this).children('.blog-description').height();
|
|
||||||
var $content = $(this).children().children('.content-description').height();
|
|
||||||
if ($content == undefined) {
|
|
||||||
$content = $(this).children().children('.md-typeset').height()
|
|
||||||
}
|
|
||||||
if ($content > $precontent - 30) {
|
|
||||||
$(this).addClass('pre-expand-blog');
|
|
||||||
$(this).css('cursor', 'pointer');
|
|
||||||
} else {
|
|
||||||
$(this).children().children('.show-more').hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateTo($elem, update_sidebar = false) {
|
function navigateTo($elem, update_sidebar = false) {
|
||||||
var url = $elem.attr('href');
|
var url = $elem.attr('href');
|
||||||
|
|
||||||
|
@ -86,7 +59,6 @@
|
||||||
$(document).prop('title', $(data).filter('title').text());
|
$(document).prop('title', $(data).filter('title').text());
|
||||||
renderKatex($('.middle-right-content')[0]);
|
renderKatex($('.middle-right-content')[0]);
|
||||||
onWindowReady();
|
onWindowReady();
|
||||||
activateBlogBoxOnClick();
|
|
||||||
$('.xdsoft_datetimepicker').hide();
|
$('.xdsoft_datetimepicker').hide();
|
||||||
registerNavigation();
|
registerNavigation();
|
||||||
}
|
}
|
||||||
|
@ -114,7 +86,6 @@
|
||||||
window.addEventListener('popstate', (e) => {
|
window.addEventListener('popstate', (e) => {
|
||||||
window.location.href = e.currentTarget.location.href;
|
window.location.href = e.currentTarget.location.href;
|
||||||
});
|
});
|
||||||
activateBlogBoxOnClick();
|
|
||||||
|
|
||||||
$('.left-sidebar-item').on('click', function (e) {
|
$('.left-sidebar-item').on('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -7,92 +7,187 @@
|
||||||
{% include "user/user-tabs.html" %}
|
{% include "user/user-tabs.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% from "contest/macros.html" import contest_head, time_left, user_count, contest_format_user %}
|
||||||
|
|
||||||
{% block user_content %}
|
{% block user_content %}
|
||||||
{% if blogs %}
|
<div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;">
|
||||||
<div class="bookmark-group">
|
<ul>
|
||||||
<h3 class="unselectable toggle closed">
|
<li class="{{'active' if current_tab=='problems'}}">
|
||||||
<span class="fa fa-chevron-right fa-fw"></span>{{ _('Posts') }} ({{ blogs|length }})
|
<a href="?tab=problems">{{ _('Problems') }}</a>
|
||||||
</h3>
|
</li>
|
||||||
<table style="display: none" class="table toggled">
|
<li class="{{'active' if current_tab=='contests'}}">
|
||||||
<tbody>
|
<a href="?tab=contests">{{ _('Contests') }}</a>
|
||||||
{% for post in blogs %}
|
</li>
|
||||||
{% set object = post.bookmark.linked_object %}
|
<li class="{{'active' if current_tab=='editorials'}}">
|
||||||
{% if object %}
|
<a href="?tab=editorials">{{ _('Editorials') }}</a>
|
||||||
<tr>
|
</li>
|
||||||
<td class="bookmark-name">
|
<li class="{{'active' if current_tab=='posts'}}">
|
||||||
<a href="{{ url('blog_post', object.id, object.slug) }}">{{ object.title}} </a>
|
<a href="?tab=posts">{{ _('Posts') }}</a>
|
||||||
</td>
|
</li>
|
||||||
</tr>
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if current_tab == 'problems' %}
|
||||||
|
{% if bookmarks %}
|
||||||
|
{% for problem in bookmarks %}
|
||||||
|
<div class="blog-box">
|
||||||
|
<h3 class="problem-feed-name">
|
||||||
|
<a href="{{ url('problem_detail', problem.code) }}">
|
||||||
|
{{ problem.name }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
{% with authors=problem.get_authors() %}
|
||||||
|
{% if authors %}
|
||||||
|
<div class="problem-feed-info-entry">
|
||||||
|
<i class="fa fa-pencil-square-o fa-fw"></i>
|
||||||
|
<span class="pi-value">{{ link_users(authors) }}</span>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endwith %}
|
||||||
</tbody>
|
<div class="problem-feed-types">
|
||||||
</table>
|
<i class="fa fa-tag"></i>
|
||||||
</div>
|
*{{problem.points | int}}
|
||||||
{% endif %}
|
</div>
|
||||||
{% if problems %}
|
<div class="blog-description">
|
||||||
<div class="bookmark-group">
|
<div class='content-description'>
|
||||||
<h3 class="unselectable toggle closed">
|
{% cache 86400 'problem_html' problem.id LANGUAGE_CODE %}
|
||||||
<span class="fa fa-chevron-right fa-fw"></span>{{ _('Problems') }} ({{ problems|length }})
|
{{ problem.description|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
</h3>
|
{% endcache %}
|
||||||
<table style="display: none" class="table toggled">
|
{% if problem.pdf_description %}
|
||||||
<tbody>
|
<embed src="{{url('problem_pdf_description', problem.code)}}" width="100%" height="500" type="application/pdf"
|
||||||
{% for problem in problems %}
|
style="margin-top: 0.5em">
|
||||||
{% set object = problem.bookmark.linked_object %}
|
{% endif %}
|
||||||
{% if object %}
|
</div>
|
||||||
<tr>
|
<div class="show-more"> {{_("...More")}} </div>
|
||||||
<td class="bookmark-name">
|
</div>
|
||||||
<a href="{{ url('problem_detail', object.code) }}">{{ object.name}} </a>
|
</div>
|
||||||
</td>
|
{% endfor %}
|
||||||
</tr>
|
{% if page_obj and page_obj.num_pages > 1 %}
|
||||||
{% endif %}
|
<div style="margin-top: 10px;">
|
||||||
{% endfor %}
|
{% include "list-pages.html" %}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
{% endif %}
|
||||||
</div>
|
{% else %}
|
||||||
{% endif %}
|
<i> {{ _('There is no saved problem.') }} </i>
|
||||||
{% if contests %}
|
{% endif %}
|
||||||
<div class="bookmark-group">
|
|
||||||
<h3 class="unselectable toggle closed">
|
|
||||||
<span class="fa fa-chevron-right fa-fw"></span>{{ _('Contests') }} ({{ contests|length }})
|
|
||||||
</h3>
|
|
||||||
<table style="display: none" class="table toggled">
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for contest in contests %}
|
|
||||||
{% set object = contest.bookmark.linked_object %}
|
|
||||||
{% if object %}
|
|
||||||
<tr>
|
|
||||||
<td class="bookmark-name">
|
|
||||||
<a href="{{ url('contest_view', object.key) }}">{{ object.name}} </a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if solutions %}
|
|
||||||
<div class="bookmark-group">
|
|
||||||
<h3 class="unselectable toggle closed">
|
|
||||||
<span class="fa fa-chevron-right fa-fw"></span>{{ _('Editorials') }} ({{ solutions|length }})
|
|
||||||
</h3>
|
|
||||||
<table style="display: none" class="table toggled">
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for solution in solutions %}
|
|
||||||
{% set object = solution.bookmark.linked_object %}
|
|
||||||
{% if object %}
|
|
||||||
<tr>
|
|
||||||
<td class="bookmark-name">
|
|
||||||
<a href="{{ url('problem_editorial', object.problem.code) }}">{{ object.problem.name}} </a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_tab == 'contests' %}
|
||||||
|
{% if bookmarks %}
|
||||||
|
{% for contest in bookmarks %}
|
||||||
|
<div class="list-contest">
|
||||||
|
<div class="info-contest">
|
||||||
|
<div class="contest-title"> {{ _('Contests') }} </div>
|
||||||
|
{{ contest_head(contest) }}
|
||||||
|
</div>
|
||||||
|
<div class="info-contest">
|
||||||
|
<div class="contest-title"> {{ _('Time') }} </div>
|
||||||
|
<div class="contest-block">
|
||||||
|
{{ time_left(contest) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info-contest" style="flex: 0.5;">
|
||||||
|
{{ contest_format_user(contest, request) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if page_obj and page_obj.num_pages > 1 %}
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
{% include "list-pages.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<i> {{ _('There is no saved contest.') }} </i>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_tab == 'editorials' %}
|
||||||
|
{% if bookmarks %}
|
||||||
|
{% for solution in bookmarks %}
|
||||||
|
<section class="blog-box">
|
||||||
|
<div style="margin-bottom: 0.5em">
|
||||||
|
<span class="post-content-header time">
|
||||||
|
{% with authors=solution.get_authors() %}
|
||||||
|
{%- if authors -%}
|
||||||
|
<span class="user-img" style="width: 1.5em; height: 1.5em">
|
||||||
|
<img src="{{gravatar(authors[0])}}" loading="lazy">
|
||||||
|
</span>
|
||||||
|
<span class="post-authors">{{ link_users(authors) }}</span>
|
||||||
|
{%- endif -%}
|
||||||
|
{% endwith %}
|
||||||
|
•
|
||||||
|
{{ relative_time(solution.publish_on) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">
|
||||||
|
<a href="{{ url('problem_editorial', solution.problem.code) }}">{{ solution.problem.name }}</a>
|
||||||
|
</h2>
|
||||||
|
<div class="blog-description">
|
||||||
|
<div class="summary content-description">
|
||||||
|
{% cache 86400 'solution_content' solution.id %}
|
||||||
|
{{ solution.content|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
|
{% endcache %}
|
||||||
|
</div>
|
||||||
|
<div class="show-more"> {{_("...More")}} </div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
{% if page_obj and page_obj.num_pages > 1 %}
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
{% include "list-pages.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<i> {{ _('There is no saved editorial.') }} </i>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_tab == 'posts' %}
|
||||||
|
{% if bookmarks %}
|
||||||
|
{% for post in bookmarks %}
|
||||||
|
<section class="blog-box">
|
||||||
|
<div style="margin-bottom: 0.5em">
|
||||||
|
<span class="post-content-header time">
|
||||||
|
{% with authors=post.get_authors() %}
|
||||||
|
{%- if authors -%}
|
||||||
|
<span class="user-img" style="width: 1.5em; height: 1.5em">
|
||||||
|
<img src="{{gravatar(authors[0])}}" loading="lazy">
|
||||||
|
</span>
|
||||||
|
<span class="post-authors">{{ link_users(authors) }}</span>
|
||||||
|
{%- endif -%}
|
||||||
|
{% endwith %}
|
||||||
|
•
|
||||||
|
{{ relative_time(post.publish_on) }}
|
||||||
|
</span>
|
||||||
|
<span style="float: right">
|
||||||
|
<a href="{{ url('blog_post', post.id, post.slug) }}#comments" class="blog-comment-count-link">
|
||||||
|
<i class="fa fa-comments blog-comment-icon"></i>
|
||||||
|
<span class="blog-comment-count">
|
||||||
|
{{ comment_count(post) }}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 class="title">
|
||||||
|
<a href="{{ url('blog_post', post.id, post.slug) }}">{{ post.title }}</a>
|
||||||
|
</h2>
|
||||||
|
<div class="blog-description">
|
||||||
|
<div class="summary content-description">
|
||||||
|
{% cache 86400 'post_content' post.id %}
|
||||||
|
{{ post.content|markdown(lazy_load=True)|reference|str|safe }}
|
||||||
|
{% endcache %}
|
||||||
|
</div>
|
||||||
|
<div class="show-more"> {{_("...More")}} </div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endfor %}
|
||||||
|
{% if page_obj and page_obj.num_pages > 1 %}
|
||||||
|
<div style="margin-top: 10px;">
|
||||||
|
{% include "list-pages.html" %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<i> {{ _('There is no saved post.') }} </i>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue