New home UI
This commit is contained in:
parent
e8ee2ac4aa
commit
d10173df5d
14 changed files with 478 additions and 222 deletions
|
@ -110,13 +110,17 @@ urlpatterns = [
|
|||
url(r'^accounts/', include(register_patterns)),
|
||||
url(r'^', include('social_django.urls')),
|
||||
|
||||
url(r'^feed/', include([
|
||||
url(r'^problems/$', problem.ProblemFeed.as_view(), name='problem_feed'),
|
||||
url(r'^tickets/$', blog.TicketFeed.as_view(), name='ticket_feed'),
|
||||
url(r'^comments/$', blog.CommentFeed.as_view(), name='comment_feed'),
|
||||
])),
|
||||
url(r'^problems/$', problem.ProblemList.as_view(), name='problem_list'),
|
||||
url(r'^problems/random/$', problem.RandomProblem.as_view(), name='problem_random'),
|
||||
|
||||
url(r'^problem/(?P<problem>[^/]+)', include([
|
||||
url(r'^$', problem.ProblemDetail.as_view(), name='problem_detail'),
|
||||
url(r'^/editorial$', problem.ProblemSolution.as_view(), name='problem_editorial'),
|
||||
url(r'^/comments$', problem.ProblemComments.as_view(), name='problem_comments'),
|
||||
url(r'^/raw$', problem.ProblemRaw.as_view(), name='problem_raw'),
|
||||
url(r'^/pdf$', problem.ProblemPdfView.as_view(), name='problem_pdf'),
|
||||
url(r'^/pdf/(?P<language>[a-z-]+)$', problem.ProblemPdfView.as_view(), name='problem_pdf'),
|
||||
|
|
|
@ -65,7 +65,9 @@ class Comment(MPTTModel):
|
|||
problem_access = CacheDict(lambda code: Problem.objects.get(code=code).is_accessible_by(user))
|
||||
contest_access = CacheDict(lambda key: Contest.objects.get(key=key).is_accessible_by(user))
|
||||
blog_access = CacheDict(lambda id: BlogPost.objects.get(id=id).can_see(user))
|
||||
|
||||
|
||||
if n == -1:
|
||||
n = len(queryset)
|
||||
if user.is_superuser:
|
||||
return queryset[:n]
|
||||
if batch is None:
|
||||
|
@ -105,7 +107,7 @@ class Comment(MPTTModel):
|
|||
try:
|
||||
link = None
|
||||
if self.page.startswith('p:'):
|
||||
link = reverse('problem_comments', args=(self.page[2:],))
|
||||
link = reverse('problem_detail', args=(self.page[2:],))
|
||||
elif self.page.startswith('c:'):
|
||||
link = reverse('contest_view', args=(self.page[2:],))
|
||||
elif self.page.startswith('b:'):
|
||||
|
|
|
@ -17,10 +17,8 @@ from judge.utils.tickets import filter_visible_tickets
|
|||
from judge.utils.views import TitleMixin
|
||||
|
||||
|
||||
class PostList(ListView):
|
||||
model = BlogPost
|
||||
paginate_by = 10
|
||||
context_object_name = 'posts'
|
||||
# General view for all content list on home feed
|
||||
class FeedView(ListView):
|
||||
template_name = 'blog/list.html'
|
||||
title = None
|
||||
|
||||
|
@ -29,6 +27,56 @@ class PostList(ListView):
|
|||
return DiggPaginator(queryset, per_page, body=6, padding=2,
|
||||
orphans=orphans, allow_empty_first_page=allow_empty_first_page, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(FeedView, self).get_context_data(**kwargs)
|
||||
context['has_clarifications'] = False
|
||||
if self.request.user.is_authenticated:
|
||||
participation = self.request.profile.current_contest
|
||||
if participation:
|
||||
clarifications = ProblemClarification.objects.filter(problem__in=participation.contest.problems.all())
|
||||
context['has_clarifications'] = clarifications.count() > 0
|
||||
context['clarifications'] = clarifications.order_by('-date')
|
||||
if participation.contest.is_editable_by(self.request.user):
|
||||
context['can_edit_contest'] = True
|
||||
|
||||
context['page_titles'] = CacheDict(lambda page: Comment.get_page_title(page))
|
||||
|
||||
context['user_count'] = lazy(Profile.objects.count, int, int)
|
||||
context['problem_count'] = lazy(Problem.objects.filter(is_public=True).count, int, int)
|
||||
context['submission_count'] = lazy(Submission.objects.count, int, int)
|
||||
context['language_count'] = lazy(Language.objects.count, int, int)
|
||||
|
||||
now = timezone.now()
|
||||
|
||||
# Dashboard stuff
|
||||
# if self.request.user.is_authenticated:
|
||||
# user = self.request.profile
|
||||
# context['recently_attempted_problems'] = (Submission.objects.filter(user=user)
|
||||
# .exclude(problem__in=user_completed_ids(user))
|
||||
# .values_list('problem__code', 'problem__name', 'problem__points')
|
||||
# .annotate(points=Max('points'), latest=Max('date'))
|
||||
# .order_by('-latest')
|
||||
# [:settings.DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT])
|
||||
|
||||
visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True) \
|
||||
.order_by('start_time')
|
||||
|
||||
context['current_contests'] = visible_contests.filter(start_time__lte=now, end_time__gt=now)
|
||||
context['future_contests'] = visible_contests.filter(start_time__gt=now)
|
||||
|
||||
visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True)
|
||||
|
||||
context['top_rated'] = Profile.objects.order_by('-rating')[:10]
|
||||
context['top_scorer'] = Profile.objects.order_by('-performance_points')[:10]
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class PostList(FeedView):
|
||||
model = BlogPost
|
||||
paginate_by = 10
|
||||
context_object_name = 'posts'
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = BlogPost.objects.filter(visible=True, publish_on__lte=timezone.now()) \
|
||||
.order_by('-sticky', '-publish_on') \
|
||||
|
@ -45,25 +93,7 @@ class PostList(ListView):
|
|||
context['title'] = self.title or _('Page %d of Posts') % context['page_obj'].number
|
||||
context['first_page_href'] = reverse('home')
|
||||
context['page_prefix'] = reverse('blog_post_list')
|
||||
context['comments'] = Comment.most_recent(self.request.user, 25)
|
||||
context['new_problems'] = Problem.objects.filter(is_public=True, is_organization_private=False) \
|
||||
.order_by('-date', '-id')[:settings.DMOJ_BLOG_NEW_PROBLEM_COUNT]
|
||||
context['page_titles'] = CacheDict(lambda page: Comment.get_page_title(page))
|
||||
|
||||
context['has_clarifications'] = False
|
||||
if self.request.user.is_authenticated:
|
||||
participation = self.request.profile.current_contest
|
||||
if participation:
|
||||
clarifications = ProblemClarification.objects.filter(problem__in=participation.contest.problems.all())
|
||||
context['has_clarifications'] = clarifications.count() > 0
|
||||
context['clarifications'] = clarifications.order_by('-date')
|
||||
if participation.contest.is_editable_by(self.request.user):
|
||||
context['can_edit_contest'] = True
|
||||
context['user_count'] = lazy(Profile.objects.count, int, int)
|
||||
context['problem_count'] = lazy(Problem.objects.filter(is_public=True).count, int, int)
|
||||
context['submission_count'] = lazy(Submission.objects.count, int, int)
|
||||
context['language_count'] = lazy(Language.objects.count, int, int)
|
||||
|
||||
context['feed_type'] = 'blog'
|
||||
context['post_comment_counts'] = {
|
||||
int(page[2:]): count for page, count in
|
||||
Comment.objects
|
||||
|
@ -71,40 +101,55 @@ class PostList(ListView):
|
|||
.values_list('page').annotate(count=Count('page')).order_by()
|
||||
}
|
||||
|
||||
now = timezone.now()
|
||||
return context
|
||||
|
||||
# Dashboard stuff
|
||||
if self.request.user.is_authenticated:
|
||||
user = self.request.profile
|
||||
context['recently_attempted_problems'] = (Submission.objects.filter(user=user)
|
||||
.exclude(problem__in=user_completed_ids(user))
|
||||
.values_list('problem__code', 'problem__name', 'problem__points')
|
||||
.annotate(points=Max('points'), latest=Max('date'))
|
||||
.order_by('-latest')
|
||||
[:settings.DMOJ_BLOG_RECENTLY_ATTEMPTED_PROBLEMS_COUNT])
|
||||
|
||||
visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True) \
|
||||
.order_by('start_time')
|
||||
|
||||
context['current_contests'] = visible_contests.filter(start_time__lte=now, end_time__gt=now)
|
||||
context['future_contests'] = visible_contests.filter(start_time__gt=now)
|
||||
class TicketFeed(FeedView):
|
||||
model = Ticket
|
||||
context_object_name = 'tickets'
|
||||
paginate_by = 30
|
||||
|
||||
visible_contests = Contest.get_visible_contests(self.request.user).filter(is_visible=True)
|
||||
if self.request.user.is_authenticated:
|
||||
profile = self.request.profile
|
||||
context['own_open_tickets'] = (Ticket.objects.filter(Q(user=profile) | Q(assignees__in=[profile]), is_open=True).order_by('-id')
|
||||
.prefetch_related('linked_item').select_related('user__user'))
|
||||
def get_queryset(self, is_own=False):
|
||||
profile = self.request.profile
|
||||
if is_own:
|
||||
if self.request.user.is_authenticated:
|
||||
return (Ticket.objects.filter(Q(user=profile) | Q(assignees__in=[profile]), is_open=True).order_by('-id')
|
||||
.prefetch_related('linked_item').select_related('user__user'))
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
profile = None
|
||||
context['own_open_tickets'] = []
|
||||
# Superusers better be staffs, not the spell-casting kind either.
|
||||
if self.request.user.is_staff:
|
||||
tickets = (Ticket.objects.order_by('-id').filter(is_open=True).prefetch_related('linked_item')
|
||||
.select_related('user__user'))
|
||||
return filter_visible_tickets(tickets, self.request.user, profile)
|
||||
else:
|
||||
return []
|
||||
|
||||
# Superusers better be staffs, not the spell-casting kind either.
|
||||
if self.request.user.is_staff:
|
||||
tickets = (Ticket.objects.order_by('-id').filter(is_open=True).prefetch_related('linked_item')
|
||||
.select_related('user__user'))
|
||||
context['open_tickets'] = filter_visible_tickets(tickets, self.request.user, profile)[:10]
|
||||
else:
|
||||
context['open_tickets'] = []
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(TicketFeed, self).get_context_data(**kwargs)
|
||||
context['feed_type'] = 'ticket'
|
||||
context['first_page_href'] = self.request.path
|
||||
context['page_prefix'] = '?page='
|
||||
context['title'] = _('Ticket feed')
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class CommentFeed(FeedView):
|
||||
model = Comment
|
||||
context_object_name = 'comments'
|
||||
paginate_by = 50
|
||||
|
||||
def get_queryset(self):
|
||||
return Comment.most_recent(self.request.user, 1000)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CommentFeed, self).get_context_data(**kwargs)
|
||||
context['feed_type'] = 'comment'
|
||||
context['first_page_href'] = self.request.path
|
||||
context['page_prefix'] = '?page='
|
||||
context['title'] = _('Comment feed')
|
||||
|
||||
return context
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ from django.utils.functional import cached_property
|
|||
from django.utils.html import escape, format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext as _, gettext_lazy
|
||||
from django.views.generic import DetailView, ListView, View
|
||||
from django.views.generic import ListView, View
|
||||
from django.views.generic.base import TemplateResponseMixin
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
||||
|
@ -38,6 +38,7 @@ from judge.utils.problems import contest_attempted_ids, contest_completed_ids, h
|
|||
from judge.utils.strings import safe_float_or_none, safe_int_or_none
|
||||
from judge.utils.tickets import own_ticket_filter
|
||||
from judge.utils.views import QueryStringSortMixin, SingleObjectFormView, TitleMixin, generic_message
|
||||
from judge.views.blog import FeedView
|
||||
|
||||
|
||||
def get_contest_problem(problem, profile):
|
||||
|
@ -155,10 +156,13 @@ class ProblemRaw(ProblemMixin, TitleMixin, TemplateResponseMixin, SingleObjectMi
|
|||
))
|
||||
|
||||
|
||||
class ProblemDetail(ProblemMixin, SolvedProblemMixin, DetailView):
|
||||
class ProblemDetail(ProblemMixin, SolvedProblemMixin, CommentedDetailView):
|
||||
context_object_name = 'problem'
|
||||
template_name = 'problem/problem.html'
|
||||
|
||||
def get_comment_page(self):
|
||||
return 'p:%s' % self.object.code
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ProblemDetail, self).get_context_data(**kwargs)
|
||||
user = self.request.user
|
||||
|
@ -235,6 +239,7 @@ class ProblemDetail(ProblemMixin, SolvedProblemMixin, DetailView):
|
|||
context['min_possible_vote'] = 100
|
||||
return context
|
||||
|
||||
|
||||
class DeleteVote(ProblemMixin, SingleObjectMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
return HttpResponseForbidden(status=405, content_type='text/plain')
|
||||
|
@ -273,21 +278,6 @@ class Vote(ProblemMixin, SingleObjectMixin, View):
|
|||
return JsonResponse(form.errors, status=400)
|
||||
|
||||
|
||||
class ProblemComments(ProblemMixin, TitleMixin, CommentedDetailView):
|
||||
context_object_name = 'problem'
|
||||
template_name = 'problem/comments.html'
|
||||
|
||||
def get_title(self):
|
||||
return _('Disscuss {0}').format(self.object.name)
|
||||
|
||||
def get_content_title(self):
|
||||
return format_html(_(u'Discuss <a href="{1}">{0}</a>'), self.object.name,
|
||||
reverse('problem_detail', args=[self.object.code]))
|
||||
|
||||
def get_comment_page(self):
|
||||
return 'p:%s' % self.object.code
|
||||
|
||||
|
||||
class LatexError(Exception):
|
||||
pass
|
||||
|
||||
|
@ -592,6 +582,49 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
|||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
|
||||
class ProblemFeed(FeedView):
|
||||
model = Problem
|
||||
context_object_name = 'problems'
|
||||
paginate_by = 50
|
||||
title = _('Problem feed')
|
||||
|
||||
@cached_property
|
||||
def profile(self):
|
||||
if not self.request.user.is_authenticated:
|
||||
return None
|
||||
return self.request.profile
|
||||
|
||||
def get_unsolved_queryset(self):
|
||||
filter = Q(is_public=True)
|
||||
if self.profile is not None:
|
||||
filter |= Q(authors=self.profile)
|
||||
filter |= Q(curators=self.profile)
|
||||
filter |= Q(testers=self.profile)
|
||||
queryset = Problem.objects.filter(filter).select_related('group').defer('description')
|
||||
if not self.request.user.has_perm('see_organization_problem'):
|
||||
filter = Q(is_organization_private=False)
|
||||
if self.profile is not None:
|
||||
filter |= Q(organizations__in=self.profile.organizations.all())
|
||||
queryset = queryset.filter(filter)
|
||||
if self.profile is not None:
|
||||
queryset = queryset.exclude(id__in=Submission.objects.filter(user=self.profile, points=F('problem__points'))
|
||||
.values_list('problem__id', flat=True))
|
||||
return queryset.distinct()
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = self.get_unsolved_queryset()
|
||||
return queryset.order_by('?')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ProblemFeed, self).get_context_data(**kwargs)
|
||||
context['first_page_href'] = self.request.path
|
||||
context['page_prefix'] = '?page='
|
||||
context['feed_type'] = 'problem'
|
||||
context['title'] = self.title
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class LanguageTemplateAjax(View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
try:
|
||||
|
|
|
@ -232,7 +232,7 @@ header {
|
|||
}
|
||||
|
||||
#navigation {
|
||||
position: relative;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
@ -382,7 +382,7 @@ hr {
|
|||
}
|
||||
|
||||
#content {
|
||||
margin: 1em auto auto;
|
||||
margin: 4.5em auto 1em auto;
|
||||
|
||||
// Header
|
||||
width: 90%;
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
.blog-content {
|
||||
padding-right: 0em;
|
||||
flex: 73.5%;
|
||||
vertical-align: top;
|
||||
margin-right: 0;
|
||||
width: 100%;
|
||||
|
||||
.post {
|
||||
border: 1px dotted grey;
|
||||
|
@ -33,7 +33,8 @@
|
|||
}
|
||||
|
||||
.blog-sidebar {
|
||||
flex: 26.5%;
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.blog-sidebox {
|
||||
|
@ -88,6 +89,19 @@
|
|||
color: #555;
|
||||
}
|
||||
|
||||
@media (max-width: 799px) {
|
||||
.left-sidebar-header {
|
||||
display: none;
|
||||
}
|
||||
.left-sidebar-item {
|
||||
display: inline-block;
|
||||
}
|
||||
.blog-left-sidebar {
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
@media (min-width: 800px) {
|
||||
.blog-content, .blog-sidebar {
|
||||
display: block !important;
|
||||
|
@ -104,6 +118,28 @@
|
|||
#blog-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.blog-content {
|
||||
max-width: 71.5%;
|
||||
margin-left: 10%;
|
||||
}
|
||||
|
||||
.blog-sidebar {
|
||||
width: 18%;
|
||||
}
|
||||
|
||||
.blog-left-sidebar {
|
||||
width: 8%;
|
||||
margin-right: 1em;
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
margin-top: -4em;
|
||||
padding-top: 4em;
|
||||
}
|
||||
|
||||
.feed-table {
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
|
||||
#mobile.tabs {
|
||||
|
@ -135,3 +171,63 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blog-box {
|
||||
border-bottom: 1px solid black;
|
||||
width: 90%;
|
||||
margin-bottom: 2.5em;
|
||||
padding: 0.5em 1.25em;
|
||||
background-color: white;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.blog-description {
|
||||
max-height: 20em;
|
||||
overflow: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
.problem-feed-name {
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
.problem-feed-name a {
|
||||
color: #0645ad;
|
||||
}
|
||||
.problem-feed-info-entry {
|
||||
display: inline;
|
||||
float: right;
|
||||
}
|
||||
.problem-feed-types {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.blog-left-sidebar {
|
||||
background-color: #f0f1f3;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.left-sidebar-item {
|
||||
padding: 1em 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
.left-sidebar-item:hover {
|
||||
background-color: lightgray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sidebar-icon {
|
||||
font-size: x-large;
|
||||
margin-bottom: 0.1em;
|
||||
color: black;
|
||||
}
|
||||
.left-sidebar-header {
|
||||
text-align: center;
|
||||
padding-bottom: 1em;
|
||||
border-bottom: 1px solid black;
|
||||
color: black;
|
||||
border-radius: 0;
|
||||
}
|
||||
.feed-table {
|
||||
margin: 0;
|
||||
}
|
|
@ -1,18 +1,16 @@
|
|||
<section class="{% if post.sticky %}sticky {% endif %}post">
|
||||
<h2 class="title">
|
||||
<a href="{{ url('blog_post', post.id, post.slug) }}">{{ post.title }}</a>
|
||||
</h2>
|
||||
<span style="float: right">
|
||||
<section class="{% if post.sticky %}sticky {% endif %}blog-box">
|
||||
<div style="margin-bottom: 0.5em">
|
||||
<span class="time">
|
||||
{%- if post.sticky %}<i title="Sticky" class="fa fa-star fa-fw"></i>{% endif -%}
|
||||
{% with authors=post.authors.all() %}
|
||||
{%- if authors -%}
|
||||
<img src="{{gravatar(authors[0])}}" style="width: 1.5em; border-radius: 50%; margin-bottom: -0.3em">
|
||||
<span class="post-authors">{{ link_users(authors) }}</span>
|
||||
{%- endif -%}
|
||||
{% endwith %}
|
||||
{{_('posted')}} {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}}
|
||||
• {{ relative_time(post.publish_on, abs=_('on {time}'), rel=_('{time}')) -}}
|
||||
{%- if post.sticky %} • <i title="Sticky" class="fa fa-star fa-fw"></i>{% endif -%}
|
||||
</span>
|
||||
<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">
|
||||
|
@ -20,8 +18,10 @@
|
|||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
<div style="clear:both"></div>
|
||||
</div>
|
||||
<h2 class="title">
|
||||
<a href="{{ url('blog_post', post.id, post.slug) }}">{{ post.title }}</a>
|
||||
</h2>
|
||||
{% if post.is_organization_private and show_organization_private_icon %}
|
||||
<div class="organization-tags">
|
||||
{% for org in post.organizations.all() %}
|
||||
|
@ -33,7 +33,7 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="summary content-description">
|
||||
<div class="summary content-description blog-description">
|
||||
{% cache 86400 'post_summary' post.id %}
|
||||
{{ post.summary|default(post.content, true)|markdown('blog', 'svg', lazy_load=True)|reference|str|safe }}
|
||||
{% endcache %}
|
||||
|
|
|
@ -16,30 +16,10 @@
|
|||
clear: both;
|
||||
}
|
||||
}
|
||||
.post {
|
||||
margin: 0 2%;
|
||||
}
|
||||
.time {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.post:first-child {
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
|
||||
.own-open-tickets .title a, .open-tickets .title a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.own-open-tickets .object, .open-tickets .object {
|
||||
margin-left: 1em;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.open-tickets .user {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.no-clarifications-message {
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
|
@ -56,6 +36,11 @@
|
|||
#add-clarification:hover {
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
#content {
|
||||
width: 99%;
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -81,6 +66,35 @@
|
|||
$('.blog-content').hide();
|
||||
$('.blog-sidebar').show();
|
||||
});
|
||||
$('.blog-description').on('click', function() {
|
||||
var max_height = $(this).css('max-height');
|
||||
if (max_height !== 'fit-content') {
|
||||
$(this).css('max-height', 'fit-content');
|
||||
$(this).parent().css('background-image', 'inherit')
|
||||
.css('padding-bottom', '0.5em');
|
||||
$(this).css('cursor', 'auto');
|
||||
}
|
||||
})
|
||||
$('.blog-description').each(function() {
|
||||
if ($(this).prop('scrollHeight') > $(this).height() ) {
|
||||
$(this).parent().css('background-image', '-webkit-linear-gradient(bottom, lightgray, lightgray 3%, transparent 8%, transparent 100%)');
|
||||
$(this).parent().css('padding-bottom', '0');
|
||||
$(this).css('cursor', 'pointer');
|
||||
}
|
||||
});
|
||||
$('.left-sidebar-item').on('click', function() {
|
||||
var url = $(this).attr('data-href');
|
||||
window.location.replace(url);
|
||||
});
|
||||
{% if feed_type == 'blog' %}
|
||||
$('#news-icon').css('color', 'green');
|
||||
{% elif feed_type == 'problem' %}
|
||||
$('#problems-icon').css('color', 'green');
|
||||
{% elif feed_type == 'ticket' %}
|
||||
$('#tickets-icon').css('color', 'green');
|
||||
{% elif feed_type == 'comment' %}
|
||||
$('#comments-icon').css('color', 'green');
|
||||
{% endif %}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -90,20 +104,50 @@
|
|||
<div id="mobile" class="tabs">
|
||||
<ul>
|
||||
<li id="blog-tab" class="tab active"><a href="#">
|
||||
<i class="tab-icon fa fa-info-circle"></i> {{ _('Blog') }}
|
||||
<i class="tab-icon fa fa-info-circle"></i> {{ _('Feed') }}
|
||||
</a></li>
|
||||
<li id="event-tab" class="tab"><a href="#"><i class="tab-icon fa fa-rss"></i> {{ _('Events') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="blog-container">
|
||||
<div class="blog-content sidebox">
|
||||
<h3>{{ _('News') }} <i class="fa fa-terminal"></i></h3>
|
||||
<div class="sidebox-content">
|
||||
{% set show_organization_private_icon=True %}
|
||||
<div class="blog-left-sidebar">
|
||||
<h3 class="left-sidebar-header">{{_('Content')}}</h3>
|
||||
<div class="left-sidebar-item" data-href="{{url('home')}}">
|
||||
<div class="sidebar-icon" id="news-icon"><i class="fa fa-rss"></i></div>
|
||||
{{_('News')}}
|
||||
</div>
|
||||
<div class="left-sidebar-item" data-href="{{url('problem_feed')}}">
|
||||
<div class="sidebar-icon" id="problems-icon"><i class="fa fa-tasks"></i></div>
|
||||
{{_('Problems')}}
|
||||
</div>
|
||||
<div class="left-sidebar-item" data-href="{{url('comment_feed')}}">
|
||||
<div class="sidebar-icon" id="comments-icon"><i class="fa fa-comments"></i></div>
|
||||
{{_('Comments')}}
|
||||
</div>
|
||||
<div class="left-sidebar-item" data-href="{{url('ticket_feed')}}">
|
||||
<div class="sidebar-icon" id="tickets-icon"><i class="fa fa-question-circle"></i></div>
|
||||
{{_('Tickets')}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="blog-content">
|
||||
{% set show_organization_private_icon=True %}
|
||||
{% if feed_type == 'blog' %}
|
||||
{% for post in posts %}
|
||||
{% include "blog/content.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif feed_type == 'problem' %}
|
||||
{% for problem in problems %}
|
||||
{% include "problem/feed.html" %}
|
||||
{% endfor %}
|
||||
{% elif feed_type == 'ticket' %}
|
||||
{% for ticket in tickets %}
|
||||
{% include "ticket/feed.html" %}
|
||||
{% endfor %}
|
||||
{% elif feed_type == 'comment' %}
|
||||
{% for comment in comments %}
|
||||
{% include "comments/feed.html" %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if page_obj.num_pages > 1 %}
|
||||
<div style="margin-bottom:10px;margin-top:10px">{% include "list-pages.html" %}</div>
|
||||
{% endif %}
|
||||
|
@ -178,85 +222,36 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if own_open_tickets %}
|
||||
<div class="blog-sidebox sidebox">
|
||||
<h3>{{ _('My open tickets') }} <i class="fa fa-question-circle"></i></h3>
|
||||
<div class="sidebox-content">
|
||||
<ul class="own-open-tickets">
|
||||
{% for ticket in own_open_tickets %}
|
||||
<li>
|
||||
<div class="title">
|
||||
<a href="{{ url('ticket', ticket.id) }}">{{ ticket.title }}</a>
|
||||
</div>
|
||||
<div class="object">
|
||||
<a href="{{ ticket.linked_item.get_absolute_url() }}">
|
||||
{{ ticket.linked_item|item_title }}</a>
|
||||
</div>
|
||||
<div>{{ link_user(ticket.user) }}</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if open_tickets %}
|
||||
<div class="blog-sidebox sidebox">
|
||||
<h3>{{ _('New tickets') }} <i class="fa fa-exclamation-circle"></i></h3>
|
||||
<div class="sidebox-content">
|
||||
<ul class="open-tickets">
|
||||
{% for ticket in open_tickets %}
|
||||
<li>
|
||||
<div class="title">
|
||||
<a href="{{ url('ticket', ticket.id) }}">{{ ticket.title }}</a>
|
||||
</div>
|
||||
<div class="object">
|
||||
<a href="{{ ticket.linked_item.get_absolute_url() }}">
|
||||
{{ ticket.linked_item|item_title }}</a>
|
||||
</div>
|
||||
<div>{{ link_user(ticket.user) }}</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="blog-sidebox sidebox">
|
||||
<h3>{{ _('New problems') }} <i class="fa fa-puzzle-piece"></i>
|
||||
</h3>
|
||||
<div class="sidebox-content">
|
||||
<ul class="problem-list">
|
||||
{% for problem in new_problems %}
|
||||
<li><a href="{{ url('problem_detail', problem.code) }}">{{ problem.name }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<span class="rssatom">
|
||||
<a href="{{ url('problem_rss') }}"><span><i class="fa fa-rss"></i></span> RSS</a>
|
||||
/
|
||||
<a href="{{ url('problem_atom') }}">Atom</a>
|
||||
</span>
|
||||
<h3>{{ _('Top Rating') }} <i class="fa fa-trophy"></i></h3>
|
||||
<div class="sidebox-content" style="padding: 0; border: 0">
|
||||
<table class="table feed-table">
|
||||
<tbody>
|
||||
{% for user in top_rated %}
|
||||
<tr>
|
||||
<td style="padding: 7px 2px"><b>{{loop.index}}</b></td>
|
||||
<td>{{link_user(user)}}</td>
|
||||
<td>{{user.rating}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="blog-sidebox sidebox">
|
||||
<h3>{{ _('Comment stream') }} <i class="fa fa-comments"></i></h3>
|
||||
<div class="sidebox-content">
|
||||
<ul>
|
||||
{% for comment in comments %}
|
||||
<li>
|
||||
<span style="padding-left:0.25em" class="poster">
|
||||
{{ link_user(comment.author) }}
|
||||
</span> →
|
||||
<a href="{{ comment.link }}#comment-{{ comment.id }}">{{ page_titles[comment.page] }}</a>
|
||||
</li>{% endfor %}
|
||||
</ul>
|
||||
<span class="rssatom">
|
||||
<a href="{{ url('comment_rss') }}"><span><i class="fa fa-rss"></i></span> RSS</a>
|
||||
/
|
||||
<a href="{{ url('comment_atom') }}">Atom</a>
|
||||
</span>
|
||||
<h3>{{ _('Top Score') }} <i class="fa fa-trophy"></i></h3>
|
||||
<div class="sidebox-content" style="padding: 0; border: 0">
|
||||
<table class="table feed-table">
|
||||
<tbody>
|
||||
{% for user in top_scorer %}
|
||||
<tr>
|
||||
<td style="padding: 7px 2px"><b>{{loop.index}}</b></td>
|
||||
<td>{{link_user(user)}}</td>
|
||||
<td>{{ user.performance_points|floatformat(0) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<style>
|
||||
#content {
|
||||
margin: -1em 1em 0 0;
|
||||
margin: 2.5em 1em 0 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
|
|
18
templates/comments/feed.html
Normal file
18
templates/comments/feed.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<div class="blog-box">
|
||||
<h3 class="problem-feed-name">
|
||||
<a href="{{ comment.link }}#comment-{{ comment.id }}">
|
||||
{{ page_titles[comment.page] }}
|
||||
</a>
|
||||
</h3>
|
||||
{% with author=comment.author %}
|
||||
{% if author %}
|
||||
<div class="problem-feed-info-entry">
|
||||
<i class="fa fa-pencil-square-o fa-fw"></i>
|
||||
<span class="pi-value">{{ link_user(author) }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class='blog-description content-description'>
|
||||
{{ comment.body |markdown("comment", MATH_ENGINE)|reference|str|safe }}
|
||||
</div>
|
||||
</div>
|
|
@ -1,28 +0,0 @@
|
|||
{% extends "common-content.html" %}
|
||||
|
||||
{% block content_js_media %}
|
||||
{% include "comments/media-js.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content_media %}
|
||||
{% include "comments/media-css.html" %}
|
||||
<style>
|
||||
#comment-header {
|
||||
display: none;
|
||||
}
|
||||
.no-comments-message {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
{% include "comments/list.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyend %}
|
||||
{% if REQUIRE_JAX %}
|
||||
{% include "mathjax-load.html" %}
|
||||
{% endif %}
|
||||
{% include "comments/math.html" %}
|
||||
{% endblock %}
|
28
templates/problem/feed.html
Normal file
28
templates/problem/feed.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<div class="blog-box">
|
||||
<h3 class="problem-feed-name">
|
||||
<a href="{{ url('problem_detail', problem.code) }}">
|
||||
{{ problem.name }}
|
||||
</a>
|
||||
</h3>
|
||||
{% with authors=problem.authors.all() %}
|
||||
{% 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 %}
|
||||
{% endwith %}
|
||||
{% if true %}
|
||||
<div class="problem-feed-types">
|
||||
<i class="fa fa-tag"></i>
|
||||
{% for type in problem.types_list %}
|
||||
<span class="type-tag">{{ type }}</span>{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class='blog-description content-description'>
|
||||
{% cache 86400 'problem_html' problem.id MATH_ENGINE LANGUAGE_CODE %}
|
||||
{{ problem.description|markdown("problem", MATH_ENGINE)|reference|str|safe }}
|
||||
{% endcache %}
|
||||
</div>
|
||||
</div>
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "common-content.html" %}
|
||||
{% block content_media %}
|
||||
{% include "comments/media-css.html" %}
|
||||
<style>
|
||||
.title-state {
|
||||
font-size: 2em;
|
||||
|
@ -54,10 +55,30 @@
|
|||
#clarification_header:hover {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
#comment-announcement {
|
||||
margin-top: 1em;
|
||||
background-color: lightgray;
|
||||
border-radius: 30px;
|
||||
padding: 0.5em;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
color: dimgrey;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#comment-announcement:hover {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
#comment-section {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content_js_media %}
|
||||
{% include "comments/media-js.html" %}
|
||||
{% if request.in_contest_mode %}
|
||||
<script type="text/javascript">
|
||||
window.register_contest_notification("{{url('contest_clarification_ajax', request.participation.contest.key)}}");
|
||||
|
@ -79,6 +100,14 @@
|
|||
$('#clarification_header_container').hide();
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
})
|
||||
$('#comment-announcement').on('click', function() {
|
||||
$('#comment-section').show();
|
||||
$('#comment-announcement').hide();
|
||||
})
|
||||
|
||||
if (window.location.href.includes('#comment')) {
|
||||
$('#comment-announcement').click();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -164,13 +193,9 @@
|
|||
{% endif %}
|
||||
<div><a href="{{ url('chronological_submissions', problem.code) }}">{{ _('All submissions') }}</a></div>
|
||||
<div><a href="{{ url('ranked_submissions', problem.code) }}">{{ _('Best submissions') }}</a></div>
|
||||
|
||||
{% if not contest_problem or not contest_problem.contest.use_clarifications %}
|
||||
<hr>
|
||||
<div><a href="{{ url('problem_comments', problem.code) }}">{{ _('Discuss') }}</a></div>
|
||||
{% endif %}
|
||||
{% if editorial and editorial.is_public and
|
||||
not (request.user.is_authenticated and request.profile.current_contest) %}
|
||||
<hr>
|
||||
<div><a href="{{ url('problem_editorial', problem.code) }}">{{ _('Read editorial') }}</a></div>
|
||||
{% endif %}
|
||||
{% if can_edit_problem %}
|
||||
|
@ -362,6 +387,17 @@
|
|||
{%- endif -%}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if not (contest_problem and contest_problem.contest.use_clarifications) %}
|
||||
<div id="comment-announcement-container">
|
||||
<div id="comment-announcement">
|
||||
{% if has_comments %}
|
||||
{{_('View comments')}} ({{comment_list.count()}})
|
||||
{% else %}
|
||||
{{_('Be the first to comment')}}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block comments %}
|
||||
|
@ -381,6 +417,10 @@
|
|||
<p class="no-comments-message">{{ _('No clarifications have been made at this time.') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div id="comment-section">
|
||||
{% include "comments/list.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<iframe name="raw_problem" id="raw_problem"></iframe>
|
||||
{% endblock %}
|
||||
|
|
23
templates/ticket/feed.html
Normal file
23
templates/ticket/feed.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
<div class="blog-box">
|
||||
<h3 class="problem-feed-name">
|
||||
<a href="{{ url('ticket', ticket.id) }}">
|
||||
{{ ticket.title }}
|
||||
</a>
|
||||
</h3>
|
||||
{% with author=ticket.user %}
|
||||
{% if author %}
|
||||
<div class="problem-feed-info-entry">
|
||||
<i class="fa fa-pencil-square-o fa-fw"></i>
|
||||
<span class="pi-value">{{ link_user(author) }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="problem-feed-types">
|
||||
<i class="fa fa-tag"></i>
|
||||
<a href="{{ ticket.linked_item.get_absolute_url() }}">
|
||||
{{ ticket.linked_item|item_title }}</a>
|
||||
</div>
|
||||
<div class='blog-description content-description'>
|
||||
{{ ticket.messages.last().body |markdown("ticket", MATH_ENGINE)|reference|str|safe }}
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in a new issue