Merge branch 'LQDJudge:master' into master
This commit is contained in:
commit
0403c4be31
63 changed files with 2242 additions and 1361 deletions
|
@ -11,3 +11,7 @@ repos:
|
|||
rev: 22.12.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/hadialqattan/pycln
|
||||
rev: 'v2.3.0'
|
||||
hooks:
|
||||
- id: pycln
|
||||
|
|
33
chat_box/migrations/0015_room_last_msg_time.py
Normal file
33
chat_box/migrations/0015_room_last_msg_time.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 3.2.18 on 2023-11-02 01:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate(apps, schema_editor):
|
||||
Room = apps.get_model("chat_box", "Room")
|
||||
Message = apps.get_model("chat_box", "Message")
|
||||
|
||||
for room in Room.objects.all():
|
||||
messages = room.message_set
|
||||
last_msg = messages.first()
|
||||
if last_msg:
|
||||
room.last_msg_time = last_msg.time
|
||||
room.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("chat_box", "0014_userroom_unread_count"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="room",
|
||||
name="last_msg_time",
|
||||
field=models.DateTimeField(
|
||||
db_index=True, null=True, verbose_name="last seen"
|
||||
),
|
||||
),
|
||||
migrations.RunPython(migrate, migrations.RunPython.noop, atomic=True),
|
||||
]
|
|
@ -1,6 +1,7 @@
|
|||
from django.db import models
|
||||
from django.db.models import CASCADE, Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
from judge.models.profile import Profile
|
||||
|
@ -17,25 +18,40 @@ class Room(models.Model):
|
|||
user_two = models.ForeignKey(
|
||||
Profile, related_name="user_two", verbose_name="user 2", on_delete=CASCADE
|
||||
)
|
||||
last_msg_time = models.DateTimeField(
|
||||
verbose_name=_("last seen"), null=True, db_index=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = "chat_box"
|
||||
|
||||
@cache_wrapper(prefix="Rc")
|
||||
def contain(self, profile):
|
||||
return self.user_one == profile or self.user_two == profile
|
||||
@cache_wrapper(prefix="Rinfo")
|
||||
def _info(self):
|
||||
last_msg = self.message_set.first()
|
||||
return {
|
||||
"user_ids": [self.user_one.id, self.user_two.id],
|
||||
"last_message": last_msg.body if last_msg else None,
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def _cached_info(self):
|
||||
return self._info()
|
||||
|
||||
def contain(self, profile):
|
||||
return profile.id in self._cached_info["user_ids"]
|
||||
|
||||
@cache_wrapper(prefix="Rou")
|
||||
def other_user(self, profile):
|
||||
return self.user_one if profile == self.user_two else self.user_two
|
||||
|
||||
@cache_wrapper(prefix="Rus")
|
||||
def other_user_id(self, profile):
|
||||
user_ids = self._cached_info["user_ids"]
|
||||
return sum(user_ids) - profile.id
|
||||
|
||||
def users(self):
|
||||
return [self.user_one, self.user_two]
|
||||
|
||||
@cache_wrapper(prefix="Rlmb")
|
||||
def last_message_body(self):
|
||||
return self.message_set.first().body
|
||||
return self._cached_info["last_message"]
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
|
|
|
@ -29,7 +29,6 @@ from django.utils import timezone
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import reverse
|
||||
|
||||
import datetime
|
||||
|
||||
from judge import event_poster as event
|
||||
from judge.jinja2.gravatar import gravatar
|
||||
|
@ -38,8 +37,6 @@ from judge.models import Friend
|
|||
from chat_box.models import Message, Profile, Room, UserRoom, Ignore
|
||||
from chat_box.utils import encrypt_url, decrypt_url, encrypt_channel, get_unread_boxes
|
||||
|
||||
import json
|
||||
|
||||
|
||||
class ChatView(ListView):
|
||||
context_object_name = "message"
|
||||
|
@ -87,8 +84,8 @@ class ChatView(ListView):
|
|||
self.room_id = request_room
|
||||
self.messages = (
|
||||
Message.objects.filter(hidden=False, room=self.room_id, id__lt=last_id)
|
||||
.select_related("author", "author__user")
|
||||
.defer("author__about", "author__user_script")[:page_size]
|
||||
.select_related("author")
|
||||
.only("body", "time", "author__rating", "author__display_rank")[:page_size]
|
||||
)
|
||||
if not only_messages:
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
@ -207,7 +204,9 @@ def post_message(request):
|
|||
},
|
||||
)
|
||||
else:
|
||||
Room.last_message_body.dirty(room)
|
||||
Room._info.dirty(room)
|
||||
room.last_msg_time = new_message.time
|
||||
room.save()
|
||||
|
||||
for user in room.users():
|
||||
event.post(
|
||||
|
@ -354,11 +353,11 @@ def get_online_status(profile, other_profile_ids, rooms=None):
|
|||
room = Room.objects.get(id=i["room"])
|
||||
other_profile = room.other_user(profile)
|
||||
count[other_profile.id] = i["unread_count"]
|
||||
rooms = Room.objects.filter(id__in=rooms)
|
||||
for room in rooms:
|
||||
room = Room.objects.get(id=room)
|
||||
other_profile = room.other_user(profile)
|
||||
last_msg[other_profile.id] = room.last_message_body()
|
||||
room_of_user[other_profile.id] = room.id
|
||||
other_profile_id = room.other_user_id(profile)
|
||||
last_msg[other_profile_id] = room.last_message_body()
|
||||
room_of_user[other_profile_id] = room.id
|
||||
|
||||
for other_profile in other_profiles:
|
||||
is_online = False
|
||||
|
@ -392,9 +391,6 @@ def get_status_context(profile, include_ignored=False):
|
|||
recent_profile = (
|
||||
Room.objects.filter(Q(user_one=profile) | Q(user_two=profile))
|
||||
.annotate(
|
||||
last_msg_time=Subquery(
|
||||
Message.objects.filter(room=OuterRef("pk")).values("time")[:1]
|
||||
),
|
||||
other_user=Case(
|
||||
When(user_one=profile, then="user_two"),
|
||||
default="user_one",
|
||||
|
@ -415,28 +411,15 @@ def get_status_context(profile, include_ignored=False):
|
|||
.values_list("id", flat=True)
|
||||
)
|
||||
|
||||
all_user_status = (
|
||||
queryset.filter(last_access__gte=last_5_minutes)
|
||||
.annotate(is_online=Case(default=True, output_field=BooleanField()))
|
||||
.order_by("-rating")
|
||||
.exclude(id__in=admin_list)
|
||||
.exclude(id__in=recent_profile_ids)
|
||||
.values_list("id", flat=True)[:30]
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
"title": "Recent",
|
||||
"title": _("Recent"),
|
||||
"user_list": get_online_status(profile, recent_profile_ids, recent_rooms),
|
||||
},
|
||||
{
|
||||
"title": "Admin",
|
||||
"title": _("Admin"),
|
||||
"user_list": get_online_status(profile, admin_list),
|
||||
},
|
||||
{
|
||||
"title": "Other",
|
||||
"user_list": get_online_status(profile, all_user_status),
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ https://docs.djangoproject.com/en/1.11/ref/settings/
|
|||
"""
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import datetime
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
|
|
|
@ -1100,6 +1100,11 @@ urlpatterns = [
|
|||
internal.InternalProblem.as_view(),
|
||||
name="internal_problem",
|
||||
),
|
||||
url(
|
||||
r"^problem_votes$",
|
||||
internal.get_problem_votes,
|
||||
name="internal_problem_votes",
|
||||
),
|
||||
url(
|
||||
r"^request_time$",
|
||||
internal.InternalRequestTime.as_view(),
|
||||
|
|
|
@ -3,7 +3,6 @@ import json
|
|||
import logging
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
from collections import deque, namedtuple
|
||||
from operator import itemgetter
|
||||
|
||||
|
@ -25,6 +24,7 @@ from judge.models import (
|
|||
Submission,
|
||||
SubmissionTestCase,
|
||||
)
|
||||
from judge.bridge.utils import VanishedSubmission
|
||||
|
||||
logger = logging.getLogger("judge.bridge")
|
||||
json_log = logging.getLogger("judge.json.bridge")
|
||||
|
@ -94,12 +94,6 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
|
||||
def on_disconnect(self):
|
||||
self._stop_ping.set()
|
||||
if self._working:
|
||||
logger.error(
|
||||
"Judge %s disconnected while handling submission %s",
|
||||
self.name,
|
||||
self._working,
|
||||
)
|
||||
self.judges.remove(self)
|
||||
if self.name is not None:
|
||||
self._disconnected()
|
||||
|
@ -119,16 +113,6 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
None,
|
||||
0,
|
||||
)
|
||||
# Submission.objects.filter(id=self._working).update(
|
||||
# status="IE", result="IE", error=""
|
||||
# )
|
||||
# json_log.error(
|
||||
# self._make_json_log(
|
||||
# sub=self._working,
|
||||
# action="close",
|
||||
# info="IE due to shutdown on grading",
|
||||
# )
|
||||
# )
|
||||
|
||||
def _authenticate(self, id, key):
|
||||
try:
|
||||
|
@ -327,6 +311,9 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
|
||||
def submit(self, id, problem, language, source):
|
||||
data = self.get_related_submission_data(id)
|
||||
if not data:
|
||||
self._update_internal_error_submission(id, "Submission vanished")
|
||||
raise VanishedSubmission()
|
||||
self._working = id
|
||||
self._working_data = {
|
||||
"problem": problem,
|
||||
|
@ -675,8 +662,11 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
self._free_self(packet)
|
||||
|
||||
id = packet["submission-id"]
|
||||
self._update_internal_error_submission(id, packet["message"])
|
||||
|
||||
def _update_internal_error_submission(self, id, message):
|
||||
if Submission.objects.filter(id=id).update(
|
||||
status="IE", result="IE", error=packet["message"]
|
||||
status="IE", result="IE", error=message
|
||||
):
|
||||
event.post(
|
||||
"sub_%s" % Submission.get_id_secret(id), {"type": "internal-error"}
|
||||
|
@ -684,9 +674,9 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
self._post_update_submission(id, "internal-error", done=True)
|
||||
json_log.info(
|
||||
self._make_json_log(
|
||||
packet,
|
||||
sub=id,
|
||||
action="internal-error",
|
||||
message=packet["message"],
|
||||
message=message,
|
||||
finish=True,
|
||||
result="IE",
|
||||
)
|
||||
|
@ -695,10 +685,10 @@ class JudgeHandler(ZlibPacketHandler):
|
|||
logger.warning("Unknown submission: %s", id)
|
||||
json_log.error(
|
||||
self._make_json_log(
|
||||
packet,
|
||||
sub=id,
|
||||
action="internal-error",
|
||||
info="unknown submission",
|
||||
message=packet["message"],
|
||||
message=message,
|
||||
finish=True,
|
||||
result="IE",
|
||||
)
|
||||
|
|
|
@ -3,6 +3,8 @@ from collections import namedtuple
|
|||
from operator import attrgetter
|
||||
from threading import RLock
|
||||
|
||||
from judge.bridge.utils import VanishedSubmission
|
||||
|
||||
try:
|
||||
from llist import dllist
|
||||
except ImportError:
|
||||
|
@ -39,6 +41,8 @@ class JudgeList(object):
|
|||
)
|
||||
try:
|
||||
judge.submit(id, problem, language, source)
|
||||
except VanishedSubmission:
|
||||
pass
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Failed to dispatch %d (%s, %s) to %s",
|
||||
|
|
2
judge/bridge/utils.py
Normal file
2
judge/bridge/utils.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
class VanishedSubmission(Exception):
|
||||
pass
|
|
@ -40,7 +40,10 @@ def cache_wrapper(prefix, timeout=None):
|
|||
def _get(key):
|
||||
if not l0_cache:
|
||||
return cache.get(key)
|
||||
return l0_cache.get(key) or cache.get(key)
|
||||
result = l0_cache.get(key)
|
||||
if result is None:
|
||||
result = cache.get(key)
|
||||
return result
|
||||
|
||||
def _set_l0(key, value):
|
||||
if l0_cache:
|
||||
|
@ -56,7 +59,7 @@ def cache_wrapper(prefix, timeout=None):
|
|||
result = _get(cache_key)
|
||||
if result is not None:
|
||||
_set_l0(cache_key, result)
|
||||
if result == NONE_RESULT:
|
||||
if type(result) == str and result == NONE_RESULT:
|
||||
result = None
|
||||
return result
|
||||
result = func(*args, **kwargs)
|
||||
|
|
|
@ -144,14 +144,16 @@ class CommentedDetailView(TemplateResponseMixin, SingleObjectMixin, View):
|
|||
|
||||
def get(self, request, *args, **kwargs):
|
||||
target_comment = None
|
||||
self.object = self.get_object()
|
||||
if "comment-id" in request.GET:
|
||||
comment_id = int(request.GET["comment-id"])
|
||||
try:
|
||||
comment_id = int(request.GET["comment-id"])
|
||||
comment_obj = Comment.objects.get(id=comment_id)
|
||||
except Comment.DoesNotExist:
|
||||
except (Comment.DoesNotExist, ValueError):
|
||||
raise Http404
|
||||
if comment_obj.linked_object != self.object:
|
||||
raise Http404
|
||||
target_comment = comment_obj.get_root()
|
||||
self.object = self.get_object()
|
||||
return self.render_to_response(
|
||||
self.get_context_data(
|
||||
object=self.object,
|
||||
|
|
|
@ -11,7 +11,6 @@ from django.contrib.auth.models import User
|
|||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.forms import (
|
||||
CharField,
|
||||
|
@ -52,7 +51,6 @@ from judge.widgets import (
|
|||
DateTimePickerWidget,
|
||||
ImageWidget,
|
||||
)
|
||||
from judge.tasks import rescore_contest
|
||||
|
||||
|
||||
def fix_unicode(string, unsafe=tuple("\u202a\u202b\u202d\u202e")):
|
||||
|
@ -282,16 +280,9 @@ class EditOrganizationContestForm(ModelForm):
|
|||
"view_contest_scoreboard",
|
||||
]:
|
||||
self.fields[field].widget.data_url = (
|
||||
self.fields[field].widget.get_url() + "?org_id=1"
|
||||
self.fields[field].widget.get_url() + f"?org_id={self.org_id}"
|
||||
)
|
||||
|
||||
def save(self, commit=True):
|
||||
res = super(EditOrganizationContestForm, self).save(commit=False)
|
||||
if commit:
|
||||
res.save()
|
||||
transaction.on_commit(rescore_contest.s(res.key).delay)
|
||||
return res
|
||||
|
||||
class Meta:
|
||||
model = Contest
|
||||
fields = (
|
||||
|
|
7
judge/logging.py
Normal file
7
judge/logging.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import logging
|
||||
|
||||
error_log = logging.getLogger("judge.errors")
|
||||
|
||||
|
||||
def log_exception(msg):
|
||||
error_log.exception(msg)
|
|
@ -1,6 +1,5 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from judge.models import *
|
||||
from collections import defaultdict
|
||||
import csv
|
||||
import os
|
||||
from django.conf import settings
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,9 +1,10 @@
|
|||
import numpy as np
|
||||
from django.conf import settings
|
||||
import os
|
||||
from django.core.cache import cache
|
||||
import hashlib
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.conf import settings
|
||||
|
||||
from judge.caching import cache_wrapper
|
||||
|
||||
|
||||
|
@ -13,14 +14,13 @@ class CollabFilter:
|
|||
|
||||
# name = 'collab_filter' or 'collab_filter_time'
|
||||
def __init__(self, name):
|
||||
embeddings = np.load(
|
||||
self.embeddings = np.load(
|
||||
os.path.join(settings.ML_OUTPUT_PATH, name + "/embeddings.npz"),
|
||||
allow_pickle=True,
|
||||
)
|
||||
arr0, arr1 = embeddings.files
|
||||
_, problem_arr = self.embeddings.files
|
||||
self.name = name
|
||||
self.user_embeddings = embeddings[arr0]
|
||||
self.problem_embeddings = embeddings[arr1]
|
||||
self.problem_embeddings = self.embeddings[problem_arr]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -44,18 +44,32 @@ class CollabFilter:
|
|||
scores = u.dot(V.T)
|
||||
return scores
|
||||
|
||||
def _get_embedding_version(self):
|
||||
first_problem = self.problem_embeddings[0]
|
||||
array_bytes = first_problem.tobytes()
|
||||
hash_object = hashlib.sha256(array_bytes)
|
||||
hash_bytes = hash_object.digest()
|
||||
return hash_bytes.hex()[:5]
|
||||
|
||||
@cache_wrapper(prefix="CFgue", timeout=86400)
|
||||
def _get_user_embedding(self, user_id, embedding_version):
|
||||
user_arr, _ = self.embeddings.files
|
||||
user_embeddings = self.embeddings[user_arr]
|
||||
if user_id >= len(user_embeddings):
|
||||
return user_embeddings[0]
|
||||
return user_embeddings[user_id]
|
||||
|
||||
def get_user_embedding(self, user_id):
|
||||
version = self._get_embedding_version()
|
||||
return self._get_user_embedding(user_id, version)
|
||||
|
||||
@cache_wrapper(prefix="user_recommendations", timeout=3600)
|
||||
def user_recommendations(self, user, problems, measure=DOT, limit=None):
|
||||
uid = user.id
|
||||
if uid >= len(self.user_embeddings):
|
||||
uid = 0
|
||||
scores = self.compute_scores(
|
||||
self.user_embeddings[uid], self.problem_embeddings, measure
|
||||
)
|
||||
def user_recommendations(self, user_id, problems, measure=DOT, limit=None):
|
||||
user_embedding = self.get_user_embedding(user_id)
|
||||
scores = self.compute_scores(user_embedding, self.problem_embeddings, measure)
|
||||
|
||||
res = [] # [(score, problem)]
|
||||
for pid in problems:
|
||||
# pid = problem.id
|
||||
if pid < len(scores):
|
||||
res.append((scores[pid], pid))
|
||||
|
||||
|
|
|
@ -99,11 +99,13 @@ class Contest(models.Model, PageVotable, Bookmarkable):
|
|||
)
|
||||
authors = models.ManyToManyField(
|
||||
Profile,
|
||||
verbose_name=_("authors"),
|
||||
help_text=_("These users will be able to edit the contest."),
|
||||
related_name="authors+",
|
||||
)
|
||||
curators = models.ManyToManyField(
|
||||
Profile,
|
||||
verbose_name=_("curators"),
|
||||
help_text=_(
|
||||
"These users will be able to edit the contest, "
|
||||
"but will not be listed as authors."
|
||||
|
@ -113,6 +115,7 @@ class Contest(models.Model, PageVotable, Bookmarkable):
|
|||
)
|
||||
testers = models.ManyToManyField(
|
||||
Profile,
|
||||
verbose_name=_("testers"),
|
||||
help_text=_(
|
||||
"These users will be able to view the contest, " "but not edit it."
|
||||
),
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import errno
|
||||
from operator import attrgetter
|
||||
from math import sqrt
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
|
@ -557,7 +556,7 @@ class Problem(models.Model, PageVotable, Bookmarkable):
|
|||
|
||||
def save(self, *args, **kwargs):
|
||||
super(Problem, self).save(*args, **kwargs)
|
||||
if self.code != self.__original_code:
|
||||
if self.__original_code and self.code != self.__original_code:
|
||||
if hasattr(self, "data_files") or self.pdf_description:
|
||||
try:
|
||||
problem_data_storage.rename(self.__original_code, self.code)
|
||||
|
|
|
@ -162,10 +162,10 @@ class ProblemData(models.Model):
|
|||
get_file_cachekey(file),
|
||||
)
|
||||
cache.delete(cache_key)
|
||||
except BadZipFile:
|
||||
except (BadZipFile, FileNotFoundError):
|
||||
pass
|
||||
if self.zipfile != self.__original_zipfile and self.__original_zipfile:
|
||||
self.__original_zipfile.delete(save=False)
|
||||
if self.zipfile != self.__original_zipfile:
|
||||
self.__original_zipfile.delete(save=False)
|
||||
return super(ProblemData, self).save(*args, **kwargs)
|
||||
|
||||
def has_yml(self):
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import csv
|
||||
from tempfile import mktemp
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
|
|
|
@ -5,7 +5,6 @@ from django import forms
|
|||
from django.forms import ClearableFileInput
|
||||
|
||||
import os, os.path
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
__all__ = ("handle_upload", "save_upload", "FineUploadForm", "FineUploadFileInput")
|
||||
|
|
|
@ -2,7 +2,6 @@ import hashlib
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import yaml
|
||||
import zipfile
|
||||
|
||||
|
@ -13,6 +12,8 @@ from django.urls import reverse
|
|||
from django.utils.translation import gettext as _
|
||||
from django.core.cache import cache
|
||||
|
||||
from judge.logging import log_exception
|
||||
|
||||
if os.altsep:
|
||||
|
||||
def split_path_first(
|
||||
|
@ -324,11 +325,13 @@ def get_problem_case(problem, files):
|
|||
settings.DMOJ_PROBLEM_DATA_ROOT, str(problem.data_files.zipfile)
|
||||
)
|
||||
if not os.path.exists(archive_path):
|
||||
raise Exception('archive file "%s" does not exist' % archive_path)
|
||||
log_exception('archive file "%s" does not exist' % archive_path)
|
||||
return {}
|
||||
try:
|
||||
archive = zipfile.ZipFile(archive_path, "r")
|
||||
except zipfile.BadZipfile:
|
||||
raise Exception('bad archive: "%s"' % archive_path)
|
||||
log_exception('bad archive: "%s"' % archive_path)
|
||||
return {}
|
||||
|
||||
for file in uncached_files:
|
||||
cache_key = "problem_archive:%s:%s" % (problem.code, get_file_cachekey(file))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from collections import defaultdict
|
||||
from math import e
|
||||
import os, zipfile
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
from enum import Enum
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
|
@ -10,6 +10,7 @@ from django.db.models import Case, Count, ExpressionWrapper, F, Max, Q, When
|
|||
from django.db.models.fields import FloatField
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _, gettext_noop
|
||||
from django.http import Http404
|
||||
|
||||
from judge.models import Problem, Submission
|
||||
from judge.ml.collab_filter import CollabFilter
|
||||
|
@ -249,3 +250,72 @@ def finished_submission(sub):
|
|||
keys += ["contest_complete:%d" % participation.id]
|
||||
keys += ["contest_attempted:%d" % participation.id]
|
||||
cache.delete_many(keys)
|
||||
|
||||
|
||||
class RecommendationType(Enum):
|
||||
HOT_PROBLEM = 1
|
||||
CF_DOT = 2
|
||||
CF_COSINE = 3
|
||||
CF_TIME_DOT = 4
|
||||
CF_TIME_COSINE = 5
|
||||
|
||||
|
||||
# Return a list of list. Each inner list correspond to each type in types
|
||||
def get_user_recommended_problems(
|
||||
user_id,
|
||||
problem_ids,
|
||||
recommendation_types,
|
||||
limits,
|
||||
shuffle=False,
|
||||
):
|
||||
cf_model = CollabFilter("collab_filter")
|
||||
cf_time_model = CollabFilter("collab_filter_time")
|
||||
|
||||
def get_problem_ids_from_type(rec_type, limit):
|
||||
if type(rec_type) == int:
|
||||
try:
|
||||
rec_type = RecommendationType(rec_type)
|
||||
except ValueError:
|
||||
raise Http404()
|
||||
if rec_type == RecommendationType.HOT_PROBLEM:
|
||||
return [
|
||||
problem.id
|
||||
for problem in hot_problems(timedelta(days=7), limit)
|
||||
if problem.id in set(problem_ids)
|
||||
]
|
||||
if rec_type == RecommendationType.CF_DOT:
|
||||
return cf_model.user_recommendations(
|
||||
user_id, problem_ids, cf_model.DOT, limit
|
||||
)
|
||||
if rec_type == RecommendationType.CF_COSINE:
|
||||
return cf_model.user_recommendations(
|
||||
user_id, problem_ids, cf_model.COSINE, limit
|
||||
)
|
||||
if rec_type == RecommendationType.CF_TIME_DOT:
|
||||
return cf_time_model.user_recommendations(
|
||||
user_id, problem_ids, cf_model.DOT, limit
|
||||
)
|
||||
if rec_type == RecommendationType.CF_TIME_COSINE:
|
||||
return cf_time_model.user_recommendations(
|
||||
user_id, problem_ids, cf_model.COSINE, limit
|
||||
)
|
||||
return []
|
||||
|
||||
all_problems = []
|
||||
for rec_type, limit in zip(recommendation_types, limits):
|
||||
all_problems += get_problem_ids_from_type(rec_type, limit)
|
||||
if shuffle:
|
||||
seed = datetime.now().strftime("%d%m%Y")
|
||||
random.Random(seed).shuffle(all_problems)
|
||||
|
||||
# deduplicate problems
|
||||
res = []
|
||||
used_pid = set()
|
||||
|
||||
for obj in all_problems:
|
||||
if type(obj) == tuple:
|
||||
obj = obj[1]
|
||||
if obj not in used_pid:
|
||||
res.append(obj)
|
||||
used_pid.add(obj)
|
||||
return res
|
||||
|
|
|
@ -144,7 +144,11 @@ class ContestList(
|
|||
context_object_name = "past_contests"
|
||||
all_sorts = frozenset(("name", "user_count", "start_time"))
|
||||
default_desc = frozenset(("name", "user_count"))
|
||||
default_sort = "-start_time"
|
||||
|
||||
def get_default_sort_order(self, request):
|
||||
if request.GET.get("contest") and settings.ENABLE_FTS:
|
||||
return "-relevance"
|
||||
return "-start_time"
|
||||
|
||||
@cached_property
|
||||
def _now(self):
|
||||
|
@ -157,7 +161,7 @@ class ContestList(
|
|||
if request.GET.get("show_orgs"):
|
||||
self.show_orgs = 1
|
||||
|
||||
if "orgs" in self.request.GET and self.request.profile:
|
||||
if self.request.GET.get("orgs") and self.request.profile:
|
||||
try:
|
||||
self.org_query = list(map(int, request.GET.getlist("orgs")))
|
||||
if not self.request.user.is_superuser:
|
||||
|
@ -165,8 +169,10 @@ class ContestList(
|
|||
i
|
||||
for i in self.org_query
|
||||
if i
|
||||
in self.request.profile.organizations.values_list(
|
||||
"id", flat=True
|
||||
in set(
|
||||
self.request.profile.organizations.values_list(
|
||||
"id", flat=True
|
||||
)
|
||||
)
|
||||
]
|
||||
except ValueError:
|
||||
|
@ -181,7 +187,7 @@ class ContestList(
|
|||
.prefetch_related("tags", "organizations", "authors", "curators", "testers")
|
||||
)
|
||||
|
||||
if "contest" in self.request.GET:
|
||||
if self.request.GET.get("contest"):
|
||||
self.contest_query = query = " ".join(
|
||||
self.request.GET.getlist("contest")
|
||||
).strip()
|
||||
|
@ -249,10 +255,7 @@ class ContestList(
|
|||
context["org_query"] = self.org_query
|
||||
context["show_orgs"] = int(self.show_orgs)
|
||||
if self.request.profile:
|
||||
if self.request.user.is_superuser:
|
||||
context["organizations"] = Organization.objects.all()
|
||||
else:
|
||||
context["organizations"] = self.request.profile.organizations.all()
|
||||
context["organizations"] = self.request.profile.organizations.all()
|
||||
context["page_type"] = "list"
|
||||
context.update(self.get_sort_context())
|
||||
context.update(self.get_sort_paginate_context())
|
||||
|
@ -419,7 +422,14 @@ class ContestDetail(
|
|||
return []
|
||||
res = []
|
||||
for organization in self.object.organizations.all():
|
||||
can_edit = False
|
||||
if self.request.profile.can_edit_organization(organization):
|
||||
can_edit = True
|
||||
if self.request.profile in organization and self.object.is_editable_by(
|
||||
self.request.user
|
||||
):
|
||||
can_edit = True
|
||||
if can_edit:
|
||||
res.append(organization)
|
||||
return res
|
||||
|
||||
|
@ -441,16 +451,32 @@ class ContestDetail(
|
|||
.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||
)
|
||||
context["editable_organizations"] = self.get_editable_organizations()
|
||||
context["is_clonable"] = is_contest_clonable(self.request, self.object)
|
||||
return context
|
||||
|
||||
|
||||
class ContestClone(
|
||||
ContestMixin, PermissionRequiredMixin, TitleMixin, SingleObjectFormView
|
||||
):
|
||||
def is_contest_clonable(request, contest):
|
||||
if not request.profile:
|
||||
return False
|
||||
if not Organization.objects.filter(admins=request.profile).exists():
|
||||
return False
|
||||
if request.user.has_perm("judge.clone_contest"):
|
||||
return True
|
||||
if contest.ended:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ContestClone(ContestMixin, TitleMixin, SingleObjectFormView):
|
||||
title = _("Clone Contest")
|
||||
template_name = "contest/clone.html"
|
||||
form_class = ContestCloneForm
|
||||
permission_required = "judge.clone_contest"
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
contest = super().get_object(queryset)
|
||||
if not is_contest_clonable(self.request, contest):
|
||||
raise Http404()
|
||||
return contest
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
|
@ -475,6 +501,7 @@ class ContestClone(
|
|||
contest.is_visible = False
|
||||
contest.user_count = 0
|
||||
contest.key = form.cleaned_data["key"]
|
||||
contest.is_rated = False
|
||||
contest.save()
|
||||
|
||||
contest.tags.set(tags)
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.utils.translation import gettext as _, gettext_lazy
|
|||
from django.db.models import Count, Q
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import render
|
||||
|
||||
from judge.utils.diggpaginator import DiggPaginator
|
||||
from judge.models import VolunteerProblemVote, Problem
|
||||
|
@ -21,7 +22,7 @@ class InternalView(object):
|
|||
class InternalProblem(InternalView, ListView):
|
||||
model = Problem
|
||||
title = _("Internal problems")
|
||||
template_name = "internal/problem.html"
|
||||
template_name = "internal/problem/problem.html"
|
||||
paginate_by = 100
|
||||
context_object_name = "problems"
|
||||
|
||||
|
@ -63,6 +64,28 @@ class InternalProblem(InternalView, ListView):
|
|||
return context
|
||||
|
||||
|
||||
def get_problem_votes(request):
|
||||
if not request.user.is_superuser:
|
||||
return HttpResponseForbidden()
|
||||
try:
|
||||
problem = Problem.objects.get(id=request.GET.get("id"))
|
||||
except:
|
||||
return HttpResponseForbidden()
|
||||
votes = (
|
||||
problem.volunteer_user_votes.select_related("voter")
|
||||
.prefetch_related("types")
|
||||
.order_by("id")
|
||||
)
|
||||
return render(
|
||||
request,
|
||||
"internal/problem/votes.html",
|
||||
{
|
||||
"problem": problem,
|
||||
"votes": votes,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class RequestTimeMixin(object):
|
||||
def get_requests_data(self):
|
||||
logger = logging.getLogger(self.log_name)
|
||||
|
|
|
@ -73,6 +73,7 @@ from judge.views.problem import ProblemList
|
|||
from judge.views.contests import ContestList
|
||||
from judge.views.submission import AllSubmissions, SubmissionsListBase
|
||||
from judge.views.feed import FeedView
|
||||
from judge.tasks import rescore_contest
|
||||
|
||||
__all__ = [
|
||||
"OrganizationList",
|
||||
|
@ -394,7 +395,7 @@ class OrganizationContestMixin(
|
|||
model = Contest
|
||||
|
||||
def is_contest_editable(self, request, contest):
|
||||
return request.profile in contest.authors.all() or self.can_edit_organization(
|
||||
return contest.is_editable_by(request.user) or self.can_edit_organization(
|
||||
self.organization
|
||||
)
|
||||
|
||||
|
@ -947,7 +948,7 @@ class EditOrganizationContest(
|
|||
|
||||
def get_content_title(self):
|
||||
href = reverse("contest_view", args=[self.contest.key])
|
||||
return mark_safe(f'Edit <a href="{href}">{self.contest.key}</a>')
|
||||
return mark_safe(_("Edit") + f' <a href="{href}">{self.contest.key}</a>')
|
||||
|
||||
def get_object(self):
|
||||
return self.contest
|
||||
|
@ -960,6 +961,19 @@ class EditOrganizationContest(
|
|||
self.object.organizations.add(self.organization)
|
||||
self.object.is_organization_private = True
|
||||
self.object.save()
|
||||
|
||||
if any(
|
||||
f in form.changed_data
|
||||
for f in (
|
||||
"start_time",
|
||||
"end_time",
|
||||
"time_limit",
|
||||
"format_config",
|
||||
"format_name",
|
||||
"freeze_after",
|
||||
)
|
||||
):
|
||||
transaction.on_commit(rescore_contest.s(self.object.key).delay)
|
||||
return res
|
||||
|
||||
def get_problem_formset(self, post=False):
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import logging
|
||||
import os
|
||||
import shutil
|
||||
from datetime import timedelta, datetime
|
||||
from operator import itemgetter
|
||||
from random import randrange
|
||||
import random
|
||||
from copy import deepcopy
|
||||
|
||||
from django.core.cache import cache
|
||||
|
@ -77,6 +75,8 @@ from judge.utils.problems import (
|
|||
user_attempted_ids,
|
||||
user_completed_ids,
|
||||
get_related_problems,
|
||||
get_user_recommended_problems,
|
||||
RecommendationType,
|
||||
)
|
||||
from judge.utils.strings import safe_float_or_none, safe_int_or_none
|
||||
from judge.utils.tickets import own_ticket_filter
|
||||
|
@ -834,24 +834,34 @@ class ProblemFeed(ProblemList, FeedView):
|
|||
title = _("Problem feed")
|
||||
feed_type = None
|
||||
|
||||
# arr = [[], [], ..]
|
||||
def merge_recommendation(self, arr):
|
||||
seed = datetime.now().strftime("%d%m%Y")
|
||||
merged_array = []
|
||||
for a in arr:
|
||||
merged_array += a
|
||||
random.Random(seed).shuffle(merged_array)
|
||||
def get_recommended_problem_ids(self, queryset):
|
||||
user_id = self.request.profile.id
|
||||
problem_ids = queryset.values_list("id", flat=True)
|
||||
rec_types = [
|
||||
RecommendationType.CF_DOT,
|
||||
RecommendationType.CF_COSINE,
|
||||
RecommendationType.CF_TIME_DOT,
|
||||
RecommendationType.CF_TIME_COSINE,
|
||||
RecommendationType.HOT_PROBLEM,
|
||||
]
|
||||
limits = [100, 100, 100, 100, 20]
|
||||
shuffle = True
|
||||
|
||||
res = []
|
||||
used_pid = set()
|
||||
allow_debug_type = (
|
||||
self.request.user.is_impersonate or self.request.user.is_superuser
|
||||
)
|
||||
if allow_debug_type and "debug_type" in self.request.GET:
|
||||
try:
|
||||
debug_type = int(self.request.GET.get("debug_type"))
|
||||
except ValueError:
|
||||
raise Http404()
|
||||
rec_types = [debug_type]
|
||||
limits = [100]
|
||||
shuffle = False
|
||||
|
||||
for obj in merged_array:
|
||||
if type(obj) == tuple:
|
||||
obj = obj[1]
|
||||
if obj not in used_pid:
|
||||
res.append(obj)
|
||||
used_pid.add(obj)
|
||||
return res
|
||||
return get_user_recommended_problems(
|
||||
user_id, problem_ids, rec_types, limits, shuffle
|
||||
)
|
||||
|
||||
def get_queryset(self):
|
||||
if self.feed_type == "volunteer":
|
||||
|
@ -885,40 +895,8 @@ class ProblemFeed(ProblemList, FeedView):
|
|||
if not settings.ML_OUTPUT_PATH or not user:
|
||||
return queryset.order_by("?").add_i18n_name(self.request.LANGUAGE_CODE)
|
||||
|
||||
cf_model = CollabFilter("collab_filter")
|
||||
cf_time_model = CollabFilter("collab_filter_time")
|
||||
q = self.get_recommended_problem_ids(queryset)
|
||||
|
||||
queryset = queryset.values_list("id", flat=True)
|
||||
hot_problems_recommendations = [
|
||||
problem.id
|
||||
for problem in hot_problems(timedelta(days=7), 20)
|
||||
if problem.id in set(queryset)
|
||||
]
|
||||
|
||||
q = self.merge_recommendation(
|
||||
[
|
||||
cf_model.user_recommendations(user, queryset, cf_model.DOT, 100),
|
||||
cf_model.user_recommendations(
|
||||
user,
|
||||
queryset,
|
||||
cf_model.COSINE,
|
||||
100,
|
||||
),
|
||||
cf_time_model.user_recommendations(
|
||||
user,
|
||||
queryset,
|
||||
cf_time_model.COSINE,
|
||||
100,
|
||||
),
|
||||
cf_time_model.user_recommendations(
|
||||
user,
|
||||
queryset,
|
||||
cf_time_model.DOT,
|
||||
100,
|
||||
),
|
||||
hot_problems_recommendations,
|
||||
]
|
||||
)
|
||||
queryset = Problem.objects.filter(id__in=q)
|
||||
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ from judge.utils.fine_uploader import (
|
|||
FineUploadForm,
|
||||
)
|
||||
from judge.views.problem import ProblemMixin
|
||||
from judge.logging import log_exception
|
||||
|
||||
mimetypes.init()
|
||||
mimetypes.add_type("application/x-yaml", ".yml")
|
||||
|
@ -249,6 +250,9 @@ class ProblemDataView(TitleMixin, ProblemManagerMixin):
|
|||
return ZipFile(data.zipfile.path).namelist()
|
||||
except BadZipfile:
|
||||
return []
|
||||
except FileNotFoundError as e:
|
||||
log_exception(e)
|
||||
return []
|
||||
return []
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.views.generic import TemplateView
|
||||
from django.utils.translation import gettext as _
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.http import HttpResponseForbidden, JsonResponse
|
||||
from judge.models import Contest
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
|
@ -21,7 +21,7 @@ class Resolver(TemplateView):
|
|||
hidden_subtasks = self.contest.format.get_hidden_subtasks()
|
||||
num_problems = len(problems)
|
||||
problem_sub = [0] * num_problems
|
||||
sub_frozen = [0] * num_problems
|
||||
sub_frozen = [[] for _ in range(num_problems)]
|
||||
problems_json = {str(i): {} for i in range(1, num_problems + 1)}
|
||||
|
||||
users = {}
|
||||
|
@ -126,10 +126,8 @@ class Resolver(TemplateView):
|
|||
|
||||
for i in hidden_subtasks:
|
||||
order = id_to_order[i]
|
||||
if hidden_subtasks[i]:
|
||||
sub_frozen[order - 1] = min(hidden_subtasks[i])
|
||||
else:
|
||||
sub_frozen[order - 1] = problem_sub[order - 1] + 1
|
||||
sub_frozen[order - 1] = list(hidden_subtasks[i])
|
||||
|
||||
return {
|
||||
"problem_sub": problem_sub,
|
||||
"sub_frozen": sub_frozen,
|
||||
|
@ -143,8 +141,15 @@ class Resolver(TemplateView):
|
|||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if request.user.is_superuser:
|
||||
self.contest = Contest.objects.get(key=kwargs.get("contest"))
|
||||
if self.contest.format.has_hidden_subtasks:
|
||||
return super(Resolver, self).get(request, *args, **kwargs)
|
||||
return HttpResponseForbidden()
|
||||
if not request.user.is_superuser:
|
||||
return HttpResponseForbidden()
|
||||
self.contest = Contest.objects.get(key=kwargs.get("contest"))
|
||||
if not self.contest.format.has_hidden_subtasks:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if self.request.GET.get("json"):
|
||||
json_dumps_params = {"ensure_ascii": False}
|
||||
return JsonResponse(
|
||||
self.get_contest_json(), json_dumps_params=json_dumps_params
|
||||
)
|
||||
return super(Resolver, self).get(request, *args, **kwargs)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import json
|
||||
import os.path
|
||||
import zipfile
|
||||
from operator import attrgetter
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -196,8 +195,8 @@ def get_cases_data(submission):
|
|||
continue
|
||||
count += 1
|
||||
problem_data[count] = {
|
||||
"input": case_data[case.input_file] if case.input_file else "",
|
||||
"answer": case_data[case.output_file] if case.output_file else "",
|
||||
"input": case_data.get(case.input_file, "") if case.input_file else "",
|
||||
"answer": case_data.get(case.output_file, "") if case.output_file else "",
|
||||
}
|
||||
|
||||
return problem_data
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -39,12 +39,6 @@ msgstr "Đăng ký tên"
|
|||
msgid "Report"
|
||||
msgstr "Báo cáo"
|
||||
|
||||
msgid "Insert Image"
|
||||
msgstr "Chèn hình ảnh"
|
||||
|
||||
msgid "Save"
|
||||
msgstr "Lưu"
|
||||
|
||||
msgid "2sat"
|
||||
msgstr ""
|
||||
|
||||
|
@ -600,3 +594,8 @@ msgstr ""
|
|||
msgid "z-function"
|
||||
msgstr ""
|
||||
|
||||
#~ msgid "Insert Image"
|
||||
#~ msgstr "Chèn hình ảnh"
|
||||
|
||||
#~ msgid "Save"
|
||||
#~ msgstr "Lưu"
|
||||
|
|
|
@ -147,8 +147,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.blog-box:hover {
|
||||
.blog-box:hover, .blog-box:not(.pre-expand-blog) {
|
||||
border-color: #8a8a8a;
|
||||
box-shadow: 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.blog-description {
|
||||
|
|
|
@ -269,10 +269,10 @@ a {
|
|||
.actionbar-button {
|
||||
cursor: pointer;
|
||||
padding: 0.8em;
|
||||
border: 0.2px solid lightgray;
|
||||
border-radius: 5em;
|
||||
font-weight: bold;
|
||||
display: inherit;
|
||||
background: lightgray;
|
||||
}
|
||||
.actionbar-block {
|
||||
display: flex;
|
||||
|
@ -287,7 +287,7 @@ a {
|
|||
border-radius: 5em 0 0 5em;
|
||||
}
|
||||
.actionbar-button:hover {
|
||||
background: lightgray;
|
||||
background: darkgray;
|
||||
}
|
||||
.dislike-button {
|
||||
padding-left: 0.5em;
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
margin: 1.5em 0 1.5em 0;
|
||||
padding: 1em;
|
||||
border: 1px solid $border_gray;
|
||||
background-color: #f8f8f8;
|
||||
|
|
|
@ -1956,8 +1956,10 @@ input::placeholder {
|
|||
background-color: rgb(24, 26, 27);
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 5px;
|
||||
}
|
||||
.blog-box:hover {
|
||||
.blog-box:hover,
|
||||
.blog-box:not(.pre-expand-blog) {
|
||||
border-color: rgb(81, 88, 91);
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 2px;
|
||||
}
|
||||
.problem-feed-name a {
|
||||
color: rgb(102, 177, 250);
|
||||
|
@ -2091,6 +2093,9 @@ ul.problem-list {
|
|||
#comment-announcement:hover {
|
||||
background-color: rgb(96, 104, 108);
|
||||
}
|
||||
.new-problem-info {
|
||||
background-color: rgb(54, 39, 0);
|
||||
}
|
||||
.admin a,
|
||||
.admin {
|
||||
color: rgb(232, 230, 227) !important;
|
||||
|
@ -2780,11 +2785,12 @@ a.voted {
|
|||
border-left-color: rgb(48, 52, 54);
|
||||
}
|
||||
.actionbar .actionbar-button {
|
||||
border-color: rgb(60, 65, 68);
|
||||
background-image: initial;
|
||||
background-color: rgb(49, 53, 55);
|
||||
}
|
||||
.actionbar .actionbar-button:hover {
|
||||
background-image: initial;
|
||||
background-color: rgb(49, 53, 55);
|
||||
background-color: rgb(73, 79, 82);
|
||||
}
|
||||
.actionbar .dislike-button {
|
||||
border-left-color: initial;
|
||||
|
|
|
@ -25,17 +25,17 @@ div.dmmd-preview-has-content div.dmmd-preview-content {
|
|||
}
|
||||
|
||||
div.dmmd-no-button div.dmmd-preview-update {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.dmmd-no-button div.dmmd-preview-content {
|
||||
padding-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
div.dmmd-no-button:not(.dmmd-preview-has-content) {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.dmmd-preview-stale {
|
||||
background: repeating-linear-gradient(-45deg, #fff, #fff 10px, #f8f8f8 10px, #f8f8f8 20px);
|
||||
background: repeating-linear-gradient(-45deg, #fff, #fff 10px, #f8f8f8 10px, #f8f8f8 20px);
|
||||
}
|
||||
|
|
|
@ -4,59 +4,59 @@
|
|||
if you are not yet familiar with Fine Uploader UI.
|
||||
-->
|
||||
<script type="text/template" id="qq-template">
|
||||
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
|
||||
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
|
||||
</div>
|
||||
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
<div class="qq-upload-button-selector qq-upload-button">
|
||||
<div>Upload a file</div>
|
||||
</div>
|
||||
<span class="qq-drop-processing-selector qq-drop-processing">
|
||||
<span>Processing dropped files...</span>
|
||||
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
|
||||
</span>
|
||||
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
|
||||
<li>
|
||||
<div class="qq-progress-bar-container-selector">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
|
||||
</div>
|
||||
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
|
||||
<span class="qq-upload-file-selector qq-upload-file"></span>
|
||||
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
|
||||
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
|
||||
<span class="qq-upload-size-selector qq-upload-size"></span>
|
||||
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
|
||||
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
|
||||
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
|
||||
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">No</button>
|
||||
<button type="button" class="qq-ok-button-selector">Yes</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Cancel</button>
|
||||
<button type="button" class="qq-ok-button-selector">Ok</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
|
||||
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
|
||||
</div>
|
||||
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
<div class="qq-upload-button-selector qq-upload-button">
|
||||
<div>Upload a file</div>
|
||||
</div>
|
||||
<span class="qq-drop-processing-selector qq-drop-processing">
|
||||
<span>Processing dropped files...</span>
|
||||
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
|
||||
</span>
|
||||
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
|
||||
<li>
|
||||
<div class="qq-progress-bar-container-selector">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
|
||||
</div>
|
||||
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
|
||||
<span class="qq-upload-file-selector qq-upload-file"></span>
|
||||
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
|
||||
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
|
||||
<span class="qq-upload-size-selector qq-upload-size"></span>
|
||||
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
|
||||
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
|
||||
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
|
||||
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">No</button>
|
||||
<button type="button" class="qq-ok-button-selector">Yes</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Cancel</button>
|
||||
<button type="button" class="qq-ok-button-selector">Ok</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</script>
|
||||
|
|
|
@ -5,78 +5,78 @@
|
|||
on how to customize this template.
|
||||
-->
|
||||
<script type="text/template" id="qq-template">
|
||||
<div class="qq-uploader-selector qq-uploader qq-gallery" qq-drop-area-text="Drop files here">
|
||||
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
|
||||
</div>
|
||||
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
<div class="qq-upload-button-selector qq-upload-button">
|
||||
<div>Upload a file</div>
|
||||
</div>
|
||||
<span class="qq-drop-processing-selector qq-drop-processing">
|
||||
<span>Processing dropped files...</span>
|
||||
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
|
||||
</span>
|
||||
<ul class="qq-upload-list-selector qq-upload-list" role="region" aria-live="polite" aria-relevant="additions removals">
|
||||
<li>
|
||||
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
|
||||
<div class="qq-progress-bar-container-selector qq-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
|
||||
</div>
|
||||
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
|
||||
<div class="qq-thumbnail-wrapper">
|
||||
<img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale>
|
||||
</div>
|
||||
<button type="button" class="qq-upload-cancel-selector qq-upload-cancel">X</button>
|
||||
<button type="button" class="qq-upload-retry-selector qq-upload-retry">
|
||||
<span class="qq-btn qq-retry-icon" aria-label="Retry"></span>
|
||||
Retry
|
||||
</button>
|
||||
|
||||
<div class="qq-file-info">
|
||||
<div class="qq-file-name">
|
||||
<span class="qq-upload-file-selector qq-upload-file"></span>
|
||||
<span class="qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon" aria-label="Edit filename"></span>
|
||||
</div>
|
||||
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
|
||||
<span class="qq-upload-size-selector qq-upload-size"></span>
|
||||
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">
|
||||
<span class="qq-btn qq-delete-icon" aria-label="Delete"></span>
|
||||
</button>
|
||||
<button type="button" class="qq-btn qq-upload-pause-selector qq-upload-pause">
|
||||
<span class="qq-btn qq-pause-icon" aria-label="Pause"></span>
|
||||
</button>
|
||||
<button type="button" class="qq-btn qq-upload-continue-selector qq-upload-continue">
|
||||
<span class="qq-btn qq-continue-icon" aria-label="Continue"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">No</button>
|
||||
<button type="button" class="qq-ok-button-selector">Yes</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Cancel</button>
|
||||
<button type="button" class="qq-ok-button-selector">Ok</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<div class="qq-uploader-selector qq-uploader qq-gallery" qq-drop-area-text="Drop files here">
|
||||
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
|
||||
</div>
|
||||
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
<div class="qq-upload-button-selector qq-upload-button">
|
||||
<div>Upload a file</div>
|
||||
</div>
|
||||
<span class="qq-drop-processing-selector qq-drop-processing">
|
||||
<span>Processing dropped files...</span>
|
||||
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
|
||||
</span>
|
||||
<ul class="qq-upload-list-selector qq-upload-list" role="region" aria-live="polite" aria-relevant="additions removals">
|
||||
<li>
|
||||
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
|
||||
<div class="qq-progress-bar-container-selector qq-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
|
||||
</div>
|
||||
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
|
||||
<div class="qq-thumbnail-wrapper">
|
||||
<img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale>
|
||||
</div>
|
||||
<button type="button" class="qq-upload-cancel-selector qq-upload-cancel">X</button>
|
||||
<button type="button" class="qq-upload-retry-selector qq-upload-retry">
|
||||
<span class="qq-btn qq-retry-icon" aria-label="Retry"></span>
|
||||
Retry
|
||||
</button>
|
||||
|
||||
<div class="qq-file-info">
|
||||
<div class="qq-file-name">
|
||||
<span class="qq-upload-file-selector qq-upload-file"></span>
|
||||
<span class="qq-edit-filename-icon-selector qq-btn qq-edit-filename-icon" aria-label="Edit filename"></span>
|
||||
</div>
|
||||
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
|
||||
<span class="qq-upload-size-selector qq-upload-size"></span>
|
||||
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">
|
||||
<span class="qq-btn qq-delete-icon" aria-label="Delete"></span>
|
||||
</button>
|
||||
<button type="button" class="qq-btn qq-upload-pause-selector qq-upload-pause">
|
||||
<span class="qq-btn qq-pause-icon" aria-label="Pause"></span>
|
||||
</button>
|
||||
<button type="button" class="qq-btn qq-upload-continue-selector qq-upload-continue">
|
||||
<span class="qq-btn qq-continue-icon" aria-label="Continue"></span>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">No</button>
|
||||
<button type="button" class="qq-ok-button-selector">Yes</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Cancel</button>
|
||||
<button type="button" class="qq-ok-button-selector">Ok</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</script>
|
||||
|
|
|
@ -5,60 +5,60 @@
|
|||
on how to customize this template.
|
||||
-->
|
||||
<script type="text/template" id="qq-simple-thumbnails-template">
|
||||
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
|
||||
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
|
||||
</div>
|
||||
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
<div class="qq-upload-button-selector qq-upload-button">
|
||||
<div>Upload a file</div>
|
||||
</div>
|
||||
<span class="qq-drop-processing-selector qq-drop-processing">
|
||||
<span>Processing dropped files...</span>
|
||||
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
|
||||
</span>
|
||||
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
|
||||
<li>
|
||||
<div class="qq-progress-bar-container-selector">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
|
||||
</div>
|
||||
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
|
||||
<img class="qq-thumbnail-selector" qq-max-size="100" qq-server-scale>
|
||||
<span class="qq-upload-file-selector qq-upload-file"></span>
|
||||
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
|
||||
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
|
||||
<span class="qq-upload-size-selector qq-upload-size"></span>
|
||||
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
|
||||
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
|
||||
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
|
||||
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">No</button>
|
||||
<button type="button" class="qq-ok-button-selector">Yes</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Cancel</button>
|
||||
<button type="button" class="qq-ok-button-selector">Ok</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<div class="qq-uploader-selector qq-uploader" qq-drop-area-text="Drop files here">
|
||||
<div class="qq-total-progress-bar-container-selector qq-total-progress-bar-container">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-total-progress-bar-selector qq-progress-bar qq-total-progress-bar"></div>
|
||||
</div>
|
||||
<div class="qq-upload-drop-area-selector qq-upload-drop-area" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
<div class="qq-upload-button-selector qq-upload-button">
|
||||
<div>Upload a file</div>
|
||||
</div>
|
||||
<span class="qq-drop-processing-selector qq-drop-processing">
|
||||
<span>Processing dropped files...</span>
|
||||
<span class="qq-drop-processing-spinner-selector qq-drop-processing-spinner"></span>
|
||||
</span>
|
||||
<ul class="qq-upload-list-selector qq-upload-list" aria-live="polite" aria-relevant="additions removals">
|
||||
<li>
|
||||
<div class="qq-progress-bar-container-selector">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="qq-progress-bar-selector qq-progress-bar"></div>
|
||||
</div>
|
||||
<span class="qq-upload-spinner-selector qq-upload-spinner"></span>
|
||||
<img class="qq-thumbnail-selector" qq-max-size="100" qq-server-scale>
|
||||
<span class="qq-upload-file-selector qq-upload-file"></span>
|
||||
<span class="qq-edit-filename-icon-selector qq-edit-filename-icon" aria-label="Edit filename"></span>
|
||||
<input class="qq-edit-filename-selector qq-edit-filename" tabindex="0" type="text">
|
||||
<span class="qq-upload-size-selector qq-upload-size"></span>
|
||||
<button type="button" class="qq-btn qq-upload-cancel-selector qq-upload-cancel">Cancel</button>
|
||||
<button type="button" class="qq-btn qq-upload-retry-selector qq-upload-retry">Retry</button>
|
||||
<button type="button" class="qq-btn qq-upload-delete-selector qq-upload-delete">Delete</button>
|
||||
<span role="status" class="qq-upload-status-text-selector qq-upload-status-text"></span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Close</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">No</button>
|
||||
<button type="button" class="qq-ok-button-selector">Yes</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">Cancel</button>
|
||||
<button type="button" class="qq-ok-button-selector">Ok</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</script>
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
.mwe-math-mathml-inline {
|
||||
display: inline !important;
|
||||
display: inline !important;
|
||||
}
|
||||
|
||||
.mwe-math-mathml-display {
|
||||
display: block !important;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block !important;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.mwe-math-mathml-a11y {
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mwe-math-fallback-image-inline {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mwe-math-fallback-image-display {
|
||||
display: block;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
display: block;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Latin Modern Math';
|
||||
src: url('libs/latinmodernmath/latinmodern-math.eot'); /* IE9 Compat Modes */
|
||||
src: local('Latin Modern Math'), local('LatinModernMath-Regular'),
|
||||
font-family: 'Latin Modern Math';
|
||||
src: url('libs/latinmodernmath/latinmodern-math.eot'); /* IE9 Compat Modes */
|
||||
src: local('Latin Modern Math'), local('LatinModernMath-Regular'),
|
||||
url('libs/latinmodernmath/latinmodern-math.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('libs/latinmodernmath/latinmodern-math.woff2') format('woff2'), /* Modern Browsers */
|
||||
url('libs/latinmodernmath/latinmodern-math.woff') format('woff'), /* Modern Browsers */
|
||||
url('libs/latinmodernmath/latinmodern-math.ttf') format('truetype'); /* Safari, Android, iOS */
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
math {
|
||||
font-family: "Latin Modern Math";
|
||||
font-family: "Latin Modern Math";
|
||||
}
|
||||
|
||||
img.inline-math {
|
||||
display: inline;
|
||||
display: inline;
|
||||
}
|
|
@ -416,3 +416,19 @@ ul.problem-list {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-problem-info {
|
||||
background-color: #fff6dd;
|
||||
border-radius: 25px;
|
||||
font-size: 14px;
|
||||
height: 25px;
|
||||
width: 100%;
|
||||
display: table;
|
||||
padding: 5px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.info-block {
|
||||
display:table-cell;
|
||||
vertical-align:middle;
|
||||
}
|
|
@ -113,7 +113,7 @@
|
|||
navigator.clipboard
|
||||
.writeText(link)
|
||||
.then(() => {
|
||||
showTooltip(element, "Copied link", 'n');
|
||||
showTooltip(element, "{{_('Copied link')}}", 'n');
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -75,112 +75,6 @@
|
|||
<link rel="stylesheet" href="{{ static('darkmode-svg.css') }}">
|
||||
{% endcompress %}
|
||||
{% endif %}
|
||||
{% if not INLINE_JQUERY %}
|
||||
<script src="{{ JQUERY_JS }}"></script>
|
||||
{% endif %}
|
||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||
{% compress js %}
|
||||
<script>{{ inlinei18n(LANGUAGE_CODE)|safe }}</script>
|
||||
{% if INLINE_JQUERY %}
|
||||
<script src="{{ static('libs/jquery-3.4.1.min.js') }}"></script>
|
||||
{% endif %}
|
||||
<script src="{{ static('libs/jquery-cookie.js') }}"></script>
|
||||
<script src="{{ static('libs/jquery-taphold.js') }}"></script>
|
||||
<script src="{{ static('libs/jquery.unveil.js') }}"></script>
|
||||
<script src="{{ static('libs/moment.js') }}"></script>
|
||||
<script src="{{ static('libs/select2/select2.js') }}"></script>
|
||||
<script src="{{ static('libs/clipboard/clipboard.js') }}"></script>
|
||||
{% include "extra_js.html" %}
|
||||
<script src="{{ static('common.js') }}"></script>
|
||||
<script src="{{ static('libs/clipboard/tooltip.js') }}"></script>
|
||||
<script>
|
||||
moment.locale('{{ LANGUAGE_CODE }}');
|
||||
$(function () {
|
||||
$('img.unveil').unveil(200);
|
||||
});
|
||||
const loading_page = `{% include "loading-page.html" %}`;
|
||||
</script>
|
||||
{% endcompress %}
|
||||
|
||||
{% block js_media %}{% endblock %}
|
||||
{% if request.in_contest %}
|
||||
<script>$(function () {
|
||||
if ($("#contest-time-remaining").length) {
|
||||
count_down($("#contest-time-remaining"));
|
||||
}
|
||||
|
||||
var selected = null,
|
||||
x_pos = 0, y_pos = 0,
|
||||
x_elem = 0, y_elem = 0;
|
||||
|
||||
$('#contest-info').mousedown(function () {
|
||||
selected = $(this);
|
||||
x_elem = x_pos - selected.offset().left;
|
||||
y_elem = y_pos - (selected.offset().top - $(window).scrollTop());
|
||||
return false;
|
||||
});
|
||||
|
||||
if (localStorage.getItem("contest_timer_position")) {
|
||||
data = localStorage.getItem("contest_timer_position").split(":");
|
||||
$("#contest-info").css({
|
||||
left: data[0],
|
||||
top: data[1]
|
||||
});
|
||||
}
|
||||
|
||||
$("#contest-info").show();
|
||||
|
||||
$("#contest-info-toggle").on('click', function() {
|
||||
$.post("{{url('contest_mode_ajax')}}", function() {
|
||||
window.location.reload();
|
||||
})
|
||||
});
|
||||
|
||||
$(document).mousemove(function (e) {
|
||||
x_pos = e.screenX;
|
||||
y_pos = e.screenY;
|
||||
|
||||
if (selected !== null) {
|
||||
left_px = (x_pos - x_elem);
|
||||
top_px = (y_pos - y_elem);
|
||||
left_px = Math.max(Math.min(left_px, window.innerWidth), 0) / window.innerWidth * 100 + '%';
|
||||
top_px = Math.max(Math.min(top_px, window.innerHeight), 0) / window.innerHeight * 100 + '%';
|
||||
localStorage.setItem("contest_timer_position", left_px + ":" + top_px);
|
||||
|
||||
selected.css({
|
||||
left: left_px,
|
||||
top: top_px
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).mouseup(function () {
|
||||
selected = null;
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<script>
|
||||
window.user = {
|
||||
email: '{{ request.user.email|escapejs }}',
|
||||
id: '{{ request.user.id|escapejs }}',
|
||||
name: '{{ request.user.username|escapejs }}'
|
||||
};
|
||||
</script>
|
||||
{% else %}
|
||||
<script>window.user = {};</script>
|
||||
{% endif %}
|
||||
|
||||
{% if misc_config.analytics %}
|
||||
{{ misc_config.analytics|safe }}
|
||||
{% endif %}
|
||||
|
||||
{# Don't run userscript since it may be malicious #}
|
||||
{% if request.user.is_authenticated and request.profile.user_script and not request.user.is_impersonate %}
|
||||
<script type="text/javascript">{{ request.profile.user_script|safe }}</script>
|
||||
{% endif %}
|
||||
|
||||
<noscript>
|
||||
<style>
|
||||
|
@ -378,12 +272,121 @@
|
|||
<div id="announcement">{{ i18n_config.announcement|safe }}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not INLINE_JQUERY %}
|
||||
<script src="{{ JQUERY_JS }}"></script>
|
||||
{% endif %}
|
||||
<script src="https://unpkg.com/@popperjs/core@2"></script>
|
||||
{% compress js %}
|
||||
<script>{{ inlinei18n(LANGUAGE_CODE)|safe }}</script>
|
||||
{% if INLINE_JQUERY %}
|
||||
<script src="{{ static('libs/jquery-3.4.1.min.js') }}"></script>
|
||||
{% endif %}
|
||||
<script src="{{ static('libs/jquery-cookie.js') }}"></script>
|
||||
<script src="{{ static('libs/jquery-taphold.js') }}"></script>
|
||||
<script src="{{ static('libs/jquery.unveil.js') }}"></script>
|
||||
<script src="{{ static('libs/moment.js') }}"></script>
|
||||
<script src="{{ static('libs/select2/select2.js') }}"></script>
|
||||
<script src="{{ static('libs/clipboard/clipboard.js') }}"></script>
|
||||
{% include "extra_js.html" %}
|
||||
<script src="{{ static('common.js') }}"></script>
|
||||
<script src="{{ static('libs/clipboard/tooltip.js') }}"></script>
|
||||
<script>
|
||||
moment.locale('{{ LANGUAGE_CODE }}');
|
||||
$(function () {
|
||||
$('img.unveil').unveil(200);
|
||||
});
|
||||
</script>
|
||||
{% endcompress %}
|
||||
|
||||
{% block js_media %}{% endblock %}
|
||||
{% if request.in_contest %}
|
||||
<script>$(function () {
|
||||
if ($("#contest-time-remaining").length) {
|
||||
count_down($("#contest-time-remaining"));
|
||||
}
|
||||
|
||||
var selected = null,
|
||||
x_pos = 0, y_pos = 0,
|
||||
x_elem = 0, y_elem = 0;
|
||||
|
||||
$('#contest-info').mousedown(function () {
|
||||
selected = $(this);
|
||||
x_elem = x_pos - selected.offset().left;
|
||||
y_elem = y_pos - (selected.offset().top - $(window).scrollTop());
|
||||
return false;
|
||||
});
|
||||
|
||||
if (localStorage.getItem("contest_timer_position")) {
|
||||
data = localStorage.getItem("contest_timer_position").split(":");
|
||||
$("#contest-info").css({
|
||||
left: data[0],
|
||||
top: data[1]
|
||||
});
|
||||
}
|
||||
|
||||
$("#contest-info").show();
|
||||
|
||||
$("#contest-info-toggle").on('click', function() {
|
||||
$.post("{{url('contest_mode_ajax')}}", function() {
|
||||
window.location.reload();
|
||||
})
|
||||
});
|
||||
|
||||
$(document).mousemove(function (e) {
|
||||
x_pos = e.screenX;
|
||||
y_pos = e.screenY;
|
||||
|
||||
if (selected !== null) {
|
||||
left_px = (x_pos - x_elem);
|
||||
top_px = (y_pos - y_elem);
|
||||
left_px = Math.max(Math.min(left_px, window.innerWidth), 0) / window.innerWidth * 100 + '%';
|
||||
top_px = Math.max(Math.min(top_px, window.innerHeight), 0) / window.innerHeight * 100 + '%';
|
||||
localStorage.setItem("contest_timer_position", left_px + ":" + top_px);
|
||||
|
||||
selected.css({
|
||||
left: left_px,
|
||||
top: top_px
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).mouseup(function () {
|
||||
selected = null;
|
||||
})
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<script>
|
||||
window.user = {
|
||||
email: '{{ request.user.email|escapejs }}',
|
||||
id: '{{ request.user.id|escapejs }}',
|
||||
name: '{{ request.user.username|escapejs }}'
|
||||
};
|
||||
</script>
|
||||
{% else %}
|
||||
<script>window.user = {};</script>
|
||||
{% endif %}
|
||||
|
||||
{% if misc_config.analytics %}
|
||||
{{ misc_config.analytics|safe }}
|
||||
{% endif %}
|
||||
|
||||
{# Don't run userscript since it may be malicious #}
|
||||
{% if request.user.is_authenticated and request.profile.user_script and not request.user.is_impersonate %}
|
||||
<script type="text/javascript">{{ request.profile.user_script|safe }}</script>
|
||||
{% endif %}
|
||||
|
||||
<div id="extra_js">
|
||||
{% block extra_js %}{% endblock %}
|
||||
</div>
|
||||
{% block bodyend %}{% endblock %}
|
||||
{% block footer %}
|
||||
<footer>
|
||||
<span id="footer-content">
|
||||
<br>
|
||||
<a class="background-footer" target="_blank" href="https://dmoj.ca">proudly powered by <b>DMOJ</b></a><a target="_blank" href="https://github.com/LQDJudge/online-judge"> | developed by LQDJudge team</a> |
|
||||
<a class="background-footer" target="_blank" href="https://dmoj.ca">proudly powered by <b>DMOJ</b></a>|<a target="_blank" href="https://github.com/LQDJudge/online-judge"> developed by LQDJudge team</a>
|
||||
{% if i18n_config.footer %}
|
||||
{{ i18n_config.footer|safe }} |
|
||||
{% endif %}
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
{% block title %} {{_('Chat Box')}} {% endblock %}
|
||||
{% block js_media %}
|
||||
|
||||
<script type="text/javascript" src="{{ static('mathjax3_config.js') }}"></script>
|
||||
{% if REQUIRE_JAX %}
|
||||
{% include "mathjax-load.html" %}
|
||||
{% endif %}
|
||||
{% include "comments/math.html" %}
|
||||
<script type="text/javascript" src="{{ static('event.js') }}"></script>
|
||||
<script type="module" src="https://unpkg.com/emoji-picker-element@1"></script>
|
||||
{% compress js %}
|
||||
|
@ -79,7 +82,7 @@
|
|||
{% include 'chat/user_online_status.html' %}
|
||||
</div>
|
||||
<div id="chat-box">
|
||||
<img src="{{static('loading.gif')}}" id="loader">
|
||||
<img src="{{static('loading.gif')}}" id="loader" style="display: none;">
|
||||
<ul id="chat-log">
|
||||
{% include 'chat/message_list.html' %}
|
||||
</ul>
|
||||
|
|
|
@ -10,7 +10,15 @@
|
|||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 20px;
|
||||
width: 16px;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
#chat-input::-webkit-scrollbar {
|
||||
width: 22px;
|
||||
}
|
||||
#chat-input::-webkit-scrollbar-thumb {
|
||||
border: 10px solid transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
|
@ -144,9 +152,11 @@
|
|||
transition: box-shadow 0.3s ease-in-out;
|
||||
width: 80%;
|
||||
resize: none;
|
||||
height: 80%;
|
||||
height: 70%;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
margin-top: auto;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
#chat-input:focus {
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
<script type="text/javascript">
|
||||
let META_HEADER = [
|
||||
"{{_('Recent')}}",
|
||||
"{{_('Following')}}",
|
||||
"{{_('Admin')}}",
|
||||
"{{_('Other')}}",
|
||||
];
|
||||
let isMobile = window.matchMedia("only screen and (max-width: 799px)").matches;
|
||||
|
||||
function load_next_page(last_id, refresh_html=false) {
|
||||
|
@ -561,7 +555,7 @@
|
|||
this.style.height = (this.scrollHeight) + 'px';
|
||||
$(this).css('border-radius', '30px');
|
||||
} else {
|
||||
$(this).css('height', '80%');
|
||||
$(this).css('height', '70%');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<li class="message" id="message-{{ message.id }}" message-id="{{ message.id }}">
|
||||
<a href="{{ url('user_page', message.author.user.username) }}">
|
||||
<a href="{{ url('user_page', message.author.username) }}">
|
||||
<img src="{{ gravatar(message.author, 135) }}" class="profile-pic">
|
||||
</a>
|
||||
<div class="body-message">
|
||||
<div class="user-time">
|
||||
<span class="username {{ message.author.css_class }}">
|
||||
<a href="{{ url('user_page', message.author.user.username) }}">
|
||||
{{ message.author }}
|
||||
<a href="{{ url('user_page', message.author.username) }}">
|
||||
{{ message.author.username }}
|
||||
</a>
|
||||
</span>
|
||||
<span class="time">
|
||||
|
|
|
@ -7,7 +7,3 @@
|
|||
{% else %}
|
||||
<center id="empty_msg">{{_('You are connect now. Say something to start the conversation.')}}</center>
|
||||
{% endif %}
|
||||
{% if REQUIRE_JAX %}
|
||||
{% include "mathjax-load.html" %}
|
||||
{% endif %}
|
||||
{% include "comments/math.html" %}
|
|
@ -145,6 +145,8 @@
|
|||
$comment_loading.hide();
|
||||
var $comment = $("#comment-" + id + "-children");
|
||||
$comment.append(data);
|
||||
MathJax.typeset($('#comments')[0]);
|
||||
register_time($('.time-with-rel'));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -187,6 +189,7 @@
|
|||
$comment.append(data);
|
||||
}
|
||||
MathJax.typeset($('#comments')[0]);
|
||||
register_time($('.time-with-rel'));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
<div><label class="inline-header grayed">{{ _('Enter a new key for the cloned contest:') }}</label></div>
|
||||
<div id="contest-key-container"><span class="fullwidth">{{ form.key }}</span></div>
|
||||
<div><label class="inline-header grayed">{{ _('Group:') }}</label></div>
|
||||
<div><label class="inline-header grayed">{{ _('Group') }}:</label></div>
|
||||
{{form.organization}}
|
||||
{% if form.organization.errors %}
|
||||
<div id="form-errors">
|
||||
|
|
|
@ -36,7 +36,4 @@
|
|||
{% endif %}
|
||||
{{ make_tab_item('edit', 'fa fa-edit', url('admin:judge_contest_change', contest.id), _('Edit')) }}
|
||||
{% endif %}
|
||||
{% if perms.judge.clone_contest %}
|
||||
{{ make_tab_item('clone', 'fa fa-copy', url('contest_clone', contest.key), _('Clone')) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -82,11 +82,16 @@
|
|||
{{ contest.description|markdown|reference|str|safe }}
|
||||
{% endcache %}
|
||||
</div>
|
||||
{% if editable_organizations %}
|
||||
<div>
|
||||
{% if editable_organizations or is_clonable %}
|
||||
<div style="display: flex; gap: 0.5em;">
|
||||
{% for org in editable_organizations %}
|
||||
<span> [<a href="{{ url('organization_contest_edit', org.id , org.slug , contest.key) }}">{{ _('Edit in') }} {{org.slug}}</a>]</span>
|
||||
{% endfor %}
|
||||
{% if is_clonable %}
|
||||
<span>
|
||||
[<a href="{{url('contest_clone', contest.key)}}"}}>{{_('Clone')}}</a>]
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block user_footer %}
|
||||
{% if user.first_name %}
|
||||
{% if user.user.first_name %}
|
||||
<div style="font-weight: 600; display: none" class="fullname gray">
|
||||
{{ user.first_name if user.first_name else ''}}
|
||||
{{ user.user.first_name }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if user.last_name %}
|
||||
<div class="school gray" style="display: none"><a style="font-weight: 600">
|
||||
{{- user.last_name -}}
|
||||
</a></div>
|
||||
{% if user.user.last_name %}
|
||||
<div class="school gray" style="display: none"><div style="font-weight: 600">
|
||||
{{- user.user.last_name -}}
|
||||
</div></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Courses</title>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
|
@ -1,19 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Enrolling</h1>
|
||||
{% for course in enrolling %}
|
||||
<h2> {{ course }} </h2>
|
||||
<h2> {{ course }} </h2>
|
||||
{% endfor %}
|
||||
<h1> Available </h1>
|
||||
{% for course in available %}
|
||||
<h2> {{ course }} </h2>
|
||||
<h2> {{ course }} </h2>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
|
@ -16,8 +16,9 @@
|
|||
$('.vote-detail').each(function() {
|
||||
$(this).on('click', function() {
|
||||
var pid = $(this).attr('pid');
|
||||
$('.detail').hide();
|
||||
$('#detail-'+pid).show();
|
||||
$.get("{{url('internal_problem_votes')}}?id="+pid, function(data) {
|
||||
$('#detail').html(data);
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
||||
|
@ -59,37 +60,8 @@
|
|||
|
||||
{% block right_sidebar %}
|
||||
<div style="display: block; width: 100%">
|
||||
<div><a href="{{url('admin:judge_volunteerproblemvote_changelist')}}">{{_('Admin')}}</a></div>
|
||||
{% for problem in problems %}
|
||||
<div class="detail" id="detail-{{problem.id}}" style="display: none;">
|
||||
<h3>{{_('Votes for problem') }} {{problem.name}}</h3>
|
||||
<ol>
|
||||
{% for vote in problem.volunteer_user_votes.order_by('id') %}
|
||||
<li>
|
||||
<h4> {{link_user(vote.voter)}} </h4>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:10%">{{_('Knowledge')}}</td>
|
||||
<td>{{vote.knowledge_points}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{_('Thinking')}}</td>
|
||||
<td>{{vote.thinking_points}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{_('Types')}}</td>
|
||||
<td>{{vote.types.all() | join(', ')}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{_('Feedback')}}</td>
|
||||
<td>{{vote.feedback}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<a href="{{url('admin:judge_volunteerproblemvote_changelist')}}">{{_('Admin')}}</a>
|
||||
<div class="detail" id="detail">
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
29
templates/internal/problem/votes.html
Normal file
29
templates/internal/problem/votes.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
<h3>{{_('Votes for problem') }} {{problem.name}}</h3>
|
||||
<ol>
|
||||
{% for vote in votes %}
|
||||
<li>
|
||||
<h4> {{link_user(vote.voter)}} </h4>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width:10%">{{_('Knowledge')}}</td>
|
||||
<td>{{vote.knowledge_points}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{_('Thinking')}}</td>
|
||||
<td>{{vote.thinking_points}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{_('Types')}}</td>
|
||||
<td>{{vote.types.all() | join(', ')}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{_('Feedback')}}</td>
|
||||
<td>{{vote.feedback}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</div>
|
|
@ -29,12 +29,6 @@
|
|||
height: 2em;
|
||||
padding-top: 4px;
|
||||
}
|
||||
@media(min-width: 800px) {
|
||||
#content {
|
||||
width: 99%;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@media(max-width: 799px) {
|
||||
#content {
|
||||
width: 100%;
|
||||
|
|
|
@ -117,7 +117,6 @@
|
|||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="spacer"></span>
|
||||
{% if has_render %}
|
||||
<a href="{{ url('problem_pdf', problem.code) }}" class="view-pdf" target="_blank">
|
||||
|
@ -135,6 +134,7 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block info_float %}
|
||||
{% if request.user.is_authenticated and request.in_contest_mode and submission_limit %}
|
||||
{% if submissions_left > 0 %}
|
||||
|
@ -204,53 +204,6 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
|
||||
<hr style="padding-top: 0.3em">
|
||||
|
||||
<div class="problem-info-entry">
|
||||
<i class="fa fa-check fa-fw"></i><span class="pi-name">{{ _('Points:') }}</span>
|
||||
<span class="pi-value">
|
||||
{% if contest_problem %}
|
||||
{{ contest_problem.points }}{% if contest_problem.partial %} {{ _('(partial)') }}{% endif %}
|
||||
{% else %}
|
||||
{{ problem.points|floatformat }}{% if problem.partial %} {{ _('(partial)') }}{% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="problem-info-entry">
|
||||
<i class="fa fa-clock-o fa-fw"></i><span class="pi-name">{{ _('Time limit:') }}</span>
|
||||
<span class="pi-value">{{ problem.time_limit }}s</span>
|
||||
</div>
|
||||
<div class="problem-lang-limits">
|
||||
{% for name, limit in problem.language_time_limit %}
|
||||
<div class="lang-limit">
|
||||
<span class="lang-name">{{ name }}</span>
|
||||
<span class="lang-tl">{{ limit }}s</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="problem-info-entry">
|
||||
<i class="fa fa-server fa-fw"></i><span class="pi-name">{{ _('Memory limit:') }}</span>
|
||||
<span class="pi-value">{{ problem.memory_limit|kbsimpleformat }}</span>
|
||||
</div>
|
||||
<div class="problem-lang-limits">
|
||||
{% for name, limit in problem.language_memory_limit %}
|
||||
<div class="lang-limit">
|
||||
<span class="lang-name">{{ name }}</span>
|
||||
<span class="lang-ml">{{ limit|kbsimpleformat }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="problem-info-entry">
|
||||
<i class="fa fa-file-o fa-fw"></i><span class="pi-name">{{ _('Input:') }}</span>
|
||||
<span class="pi-value">
|
||||
{{ fileio_input or _('stdin') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="problem-info-entry">
|
||||
<i class="fa fa-file fa-fw"></i><span class="pi-name">{{ _('Output:') }}</span>
|
||||
<span class="pi-value">{{ fileio_output or _('stdout') }}</span>
|
||||
</div>
|
||||
|
||||
<hr style="padding-top: 0.7em">
|
||||
|
||||
{% cache 86400 'problem_authors' problem.id LANGUAGE_CODE %}
|
||||
|
@ -342,6 +295,40 @@
|
|||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class = "new-problem-info center">
|
||||
<span class="info-block">
|
||||
<i class="fa fa-check fa-fw"></i><span class="pi-name">{{ _('Points:') }}</span>
|
||||
<span class="new-pi-value">
|
||||
{% if contest_problem %}
|
||||
{{ contest_problem.points }} {% if contest_problem.partial %}(p){% endif %}
|
||||
{% else %}
|
||||
{{ problem.points|floatformat }} {% if problem.partial %}(p){% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="info-block">
|
||||
<i class="fa fa-clock-o fa-fw"></i><span class="pi-name">{{ _('Time limit:') }}</span>
|
||||
<span class="new-pi-value">{{ problem.time_limit }}s</span>
|
||||
</span>
|
||||
|
||||
<span class="info-block">
|
||||
<i class="fa fa-server fa-fw"></i><span class="pi-name">{{ _('Memory limit:') }}</span>
|
||||
<span class="new-pi-value">{{ problem.memory_limit|kbsimpleformat }}</span>
|
||||
</span>
|
||||
|
||||
<span class="info-block">
|
||||
<i class="fa fa-file-o fa-fw"></i><span class="pi-name">{{ _('Input:') }}</span>
|
||||
<span class="new-pi-value">
|
||||
{{ fileio_input or _('stdin') }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="info-block">
|
||||
<i class="fa fa-file fa-fw"></i><span class="pi-name">{{ _('Output:') }}</span>
|
||||
<span class="new-pi-value">{{ fileio_output or _('stdout') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{% cache 86400 'problem_html' problem.id MATH_ENGINE LANGUAGE_CODE %}
|
||||
{{ description|markdown(lazy_load=True)|reference|str|safe }}
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
|
||||
{% block js_media %}
|
||||
<script type="text/javascript">
|
||||
let loadingPage;
|
||||
|
||||
function activateBlogBoxOnClick() {
|
||||
$('.blog-box').on('click', function () {
|
||||
var $description = $(this).children('.blog-description');
|
||||
|
@ -64,13 +66,16 @@
|
|||
$elem.addClass('active');
|
||||
}
|
||||
$(window).off("scroll");
|
||||
$('.middle-right-content').html(loading_page);
|
||||
$('.middle-right-content').html(loadingPage);
|
||||
$.get(url, function (data) {
|
||||
var reload_content = $(data).find('.middle-right-content');
|
||||
var bodyend_script = $(data).find('#extra_js');
|
||||
if (reload_content.length) {
|
||||
window.history.pushState("", "", url);
|
||||
$('html, body').animate({scrollTop: 0}, 'fast');
|
||||
$('.middle-right-content').html(reload_content.first().html());
|
||||
$('#extra_js').html(bodyend_script.first().html());
|
||||
$("#loading-bar").hide().css({ width: 0});
|
||||
if (reload_content.hasClass("wrapper")) {
|
||||
$('.middle-right-content').addClass("wrapper");
|
||||
}
|
||||
|
@ -83,7 +88,6 @@
|
|||
activateBlogBoxOnClick();
|
||||
$('.xdsoft_datetimepicker').hide();
|
||||
registerNavigation();
|
||||
$("#loading-bar").hide().css({ width: 0});
|
||||
}
|
||||
else {
|
||||
window.location.href = url;
|
||||
|
@ -112,6 +116,9 @@
|
|||
navigateTo($(this), true);
|
||||
});
|
||||
registerNavigation();
|
||||
$.get("{{static('html/loading-page.html')}}", function(data) {
|
||||
loadingPage = data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -129,7 +136,6 @@
|
|||
{% block left_sidebar %}{% endblock %}
|
||||
<div class="middle-right-content {{'wrapper' if is_two_column}}">
|
||||
{% block three_col_media %}{% endblock %}
|
||||
{% block three_col_js %}{% endblock %}
|
||||
<div class="middle-content">
|
||||
{% block middle_title %}{% endblock %}
|
||||
{% block middle_content %}{% endblock %}
|
||||
|
@ -140,6 +146,10 @@
|
|||
{% block after_posts %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% block three_col_js %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyend %}
|
||||
{{ super() }}
|
||||
{% if REQUIRE_JAX %}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue