Redesign Bookmark (#112)

This commit is contained in:
Phuoc Anh Kha Le 2024-05-29 00:14:42 -05:00 committed by GitHub
parent 829e6a802d
commit c6acfa5e05
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 398 additions and 243 deletions

View file

@ -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

View file

@ -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,)))

View file

@ -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):

View file

@ -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

View file

@ -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() {

View file

@ -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>

View file

@ -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 %}

View 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 %}

View file

@ -32,7 +32,6 @@
window.page++; window.page++;
renderKatex($('.middle-content')[0]); renderKatex($('.middle-content')[0]);
onWindowReady(); onWindowReady();
activateBlogBoxOnClick();
}) })
} }

View file

@ -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" %}

View file

@ -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')

View file

@ -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();

View file

@ -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 %}
&#8226;
{{ 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 %}
&#8226;
{{ 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 %}