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:
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):
return _("Editorial for %s") % self.problem.name

View file

@ -24,6 +24,7 @@ from .models import (
Profile,
Submission,
NavigationBar,
Solution,
)
@ -120,14 +121,7 @@ def comment_update(sender, instance, **kwargs):
@receiver(post_save, sender=BlogPost)
def post_update(sender, instance, **kwargs):
cache.delete_many(
[
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,))]
)
cache.delete(make_template_fragment_key("post_content", (instance.id,)))
BlogPost.get_authors.dirty(instance)
@ -175,3 +169,8 @@ def contest_submission_update(sender, instance, **kwargs):
@receiver(post_save, sender=NavigationBar)
def navbar_update(sender, instance, **kwargs):
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,
When,
IntegerField,
Sum,
)
from django.db.models.functions import Coalesce
from django.db.utils import ProgrammingError
@ -644,6 +645,16 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
queryset = queryset.filter(points__gte=self.point_start)
if self.point_end is not None:
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()
def get_queryset(self):

View file

@ -36,7 +36,17 @@ from django.template.loader import render_to_string
from reversion import revisions
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.ratings import rating_class, rating_progress
from judge.tasks import import_users
@ -54,10 +64,13 @@ from judge.utils.views import (
TitleMixin,
generic_message,
SingleObjectFormView,
DiggPaginatorMixin,
)
from judge.utils.infinite_paginator import InfinitePaginationMixin
from judge.views.problem import ProblemList
from .contests import ContestRanking
__all__ = [
"UserPage",
"UserAboutPage",
@ -305,17 +318,49 @@ class UserProblemsPage(UserPage):
return context
class UserBookMarkPage(UserPage):
class UserBookMarkPage(DiggPaginatorMixin, ListView, UserPage):
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):
context = super(UserBookMarkPage, self).get_context_data(**kwargs)
bookmark_list = MakeBookMark.objects.filter(user=self.object)
context["blogs"] = bookmark_list.filter(bookmark__page__startswith="b")
context["problems"] = bookmark_list.filter(bookmark__page__startswith="p")
context["contests"] = bookmark_list.filter(bookmark__page__startswith="c")
context["solutions"] = bookmark_list.filter(bookmark__page__startswith="s")
context["current_tab"] = self.current_tab
context["user"] = self.user
context["page_prefix"] = (
self.request.path + "?tab=" + self.current_tab + "&page="
)
context["first_page_href"] = self.request.path
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() {
// http://stackoverflow.com/a/1060034/1090657
var hidden = 'hidden';
@ -464,6 +491,7 @@ function onWindowReady() {
errorList.nextAll('input, select, textarea').first().after(errorList);
});
register_all_toggles();
activateBlogBoxOnClick();
}
$(function() {

View file

@ -36,8 +36,8 @@
</h2>
<div class="blog-description">
<div class="summary content-description">
{% cache 86400 'post_summary' post.id %}
{{ post.summary|default(post.content, true)|markdown(lazy_load=True)|reference|str|safe }}
{% cache 86400 'post_content' post.id %}
{{ post.content|markdown(lazy_load=True)|reference|str|safe }}
{% endcache %}
</div>
<div class="show-more"> {{_("...More")}} </div>

View file

@ -134,112 +134,7 @@
</div>
{% endblock %}
{% 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, 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 %}
{% from "contest/macros.html" import contest_head, time_left, user_count, contest_join, contest_format_user %}
{% block middle_content %}
<div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;">
@ -298,7 +193,7 @@
</div>
</div>
<div class="info-contest" style="flex: 0.5;">
{{ contest_format_user(contest) }}
{{ contest_format_user(contest, request) }}
</div>
<div class="participate-button">
{{ contest_join(contest, request) }}
@ -336,7 +231,7 @@
</div>
</div>
<div class="info-contest" style="flex: 0.5;">
{{ contest_format_user(contest) }}
{{ contest_format_user(contest, request) }}
</div>
<div class="participate-button">
{{ contest_join(contest, request) }}
@ -373,7 +268,7 @@
</div>
</div>
<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>
{% endfor %}
@ -402,7 +297,7 @@
</div>
</div>
<div class="info-contest" style="flex: 0.5;">
{{ contest_format_user(contest) }}
{{ contest_format_user(contest, request) }}
</div>
<div class="participate-button">
{% 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++;
renderKatex($('.middle-content')[0]);
onWindowReady();
activateBlogBoxOnClick();
})
}

View file

@ -27,7 +27,9 @@
<p>Authors: {{ link_users(authors) }}</p>
{% endif %}
{% endwith %}
{{ solution.content|markdown|reference|str|safe }}
{% cache 86400 'solution_content' solution.id %}
{{ solution.content|markdown(lazy_load=True)|reference|str|safe }}
{% endcache %}
</div>
<hr>
{% include "actionbar/list.html" %}

View file

@ -113,7 +113,7 @@
$('#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();
($('<form>').attr('action', window.location.pathname + '?' + form_serialize())
.append($('<input>').attr('type', 'hidden').attr('name', 'csrfmiddlewaretoken')

View file

@ -30,33 +30,6 @@
<script type="text/javascript">
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) {
var url = $elem.attr('href');
@ -86,7 +59,6 @@
$(document).prop('title', $(data).filter('title').text());
renderKatex($('.middle-right-content')[0]);
onWindowReady();
activateBlogBoxOnClick();
$('.xdsoft_datetimepicker').hide();
registerNavigation();
}
@ -114,7 +86,6 @@
window.addEventListener('popstate', (e) => {
window.location.href = e.currentTarget.location.href;
});
activateBlogBoxOnClick();
$('.left-sidebar-item').on('click', function (e) {
e.preventDefault();

View file

@ -7,92 +7,187 @@
{% include "user/user-tabs.html" %}
{% endblock %}
{% from "contest/macros.html" import contest_head, time_left, user_count, contest_format_user %}
{% block user_content %}
{% if blogs %}
<div class="bookmark-group">
<h3 class="unselectable toggle closed">
<span class="fa fa-chevron-right fa-fw"></span>{{ _('Posts') }} ({{ blogs|length }})
<div class="tabs tabs-no-flex" style="width: 100%;margin-left: auto;margin-right: auto;">
<ul>
<li class="{{'active' if current_tab=='problems'}}">
<a href="?tab=problems">{{ _('Problems') }}</a>
</li>
<li class="{{'active' if current_tab=='contests'}}">
<a href="?tab=contests">{{ _('Contests') }}</a>
</li>
<li class="{{'active' if current_tab=='editorials'}}">
<a href="?tab=editorials">{{ _('Editorials') }}</a>
</li>
<li class="{{'active' if current_tab=='posts'}}">
<a href="?tab=posts">{{ _('Posts') }}</a>
</li>
</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>
<table style="display: none" class="table toggled">
<tbody>
{% for post in blogs %}
{% set object = post.bookmark.linked_object %}
{% if object %}
<tr>
<td class="bookmark-name">
<a href="{{ url('blog_post', object.id, object.slug) }}">{{ object.title}} </a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% 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 %}
{% if problems %}
<div class="bookmark-group">
<h3 class="unselectable toggle closed">
<span class="fa fa-chevron-right fa-fw"></span>{{ _('Problems') }} ({{ problems|length }})
</h3>
<table style="display: none" class="table toggled">
<tbody>
{% for problem in problems %}
{% set object = problem.bookmark.linked_object %}
{% if object %}
<tr>
<td class="bookmark-name">
<a href="{{ url('problem_detail', object.code) }}">{{ object.name}} </a>
</td>
</tr>
{% endwith %}
<div class="problem-feed-types">
<i class="fa fa-tag"></i>
*{{problem.points | int}}
</div>
<div class="blog-description">
<div class='content-description'>
{% cache 86400 'problem_html' problem.id LANGUAGE_CODE %}
{{ problem.description|markdown(lazy_load=True)|reference|str|safe }}
{% endcache %}
{% if problem.pdf_description %}
<embed src="{{url('problem_pdf_description', problem.code)}}" width="100%" height="500" type="application/pdf"
style="margin-top: 0.5em">
{% endif %}
</div>
<div class="show-more"> {{_("...More")}} </div>
</div>
</div>
{% endfor %}
</tbody>
</table>
{% if page_obj and page_obj.num_pages > 1 %}
<div style="margin-top: 10px;">
{% include "list-pages.html" %}
</div>
{% endif %}
{% if contests %}
<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>
{% else %}
<i> {{ _('There is no saved problem.') }} </i>
{% 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 %}
{% 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 %}