6
502.html
|
@ -49,14 +49,14 @@
|
||||||
<br>
|
<br>
|
||||||
<div class="popup">
|
<div class="popup">
|
||||||
<div>
|
<div>
|
||||||
<img class="logo" src="/logo.png" alt="DMOJ">
|
<img class="logo" src="logo.png" alt="DMOJ">
|
||||||
</div>
|
</div>
|
||||||
<h1 style="width: 100%;">Oops, the DMOJ is down.</h1>
|
<h1 style="width: 100%;">Oops, LQDOJ is down now.</h1>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<hr>
|
<hr>
|
||||||
<br>
|
<br>
|
||||||
<h2 class="msg">But don't worry, we'll be back soon.</h2>
|
<h2 class="msg">But don't worry, we'll be back soon. <br> In the free time, you can read my idol's codes <a href="http://codeforces.com/profile/cuom1999" style="color:red; text-decoration:none">cuom1999</a></h2>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -20,3 +20,6 @@ python dmojauto-conf
|
||||||
- modify .po file
|
- modify .po file
|
||||||
- python manage.py compilemessages
|
- python manage.py compilemessages
|
||||||
- python manage.py compilejsi18n
|
- python manage.py compilejsi18n
|
||||||
|
|
||||||
|
###8. Run chat server:
|
||||||
|
docker run -p 6379:6379 -d redis:2.8
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
|
from .models import Message
|
||||||
|
from .views import format_time
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
|
||||||
|
|
||||||
|
from judge.models.profile import Profile
|
||||||
|
|
||||||
|
|
||||||
class ChatConsumer(AsyncWebsocketConsumer):
|
class ChatConsumer(AsyncWebsocketConsumer):
|
||||||
async def connect(self):
|
async def connect(self):
|
||||||
self.room_name = 'common'
|
self.room_name = 'room'
|
||||||
self.room_group_name = 'chat_%s' % self.room_name
|
self.room_group_name = 'chat_%s' % self.room_name
|
||||||
|
|
||||||
# Join room group
|
# Join room group
|
||||||
|
@ -27,6 +33,8 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
||||||
async def receive(self, text_data):
|
async def receive(self, text_data):
|
||||||
text_data_json = json.loads(text_data)
|
text_data_json = json.loads(text_data)
|
||||||
message = text_data_json['message']
|
message = text_data_json['message']
|
||||||
|
time = save_data_and_get_time(message)
|
||||||
|
message['time'] = format_time(time)
|
||||||
|
|
||||||
# Send message to room group
|
# Send message to room group
|
||||||
await self.channel_layer.group_send(
|
await self.channel_layer.group_send(
|
||||||
|
@ -40,8 +48,17 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
||||||
# Receive message from room group
|
# Receive message from room group
|
||||||
async def chat_message(self, event):
|
async def chat_message(self, event):
|
||||||
message = event['message']
|
message = event['message']
|
||||||
|
|
||||||
# Send message to WebSocket
|
# Send message to WebSocket
|
||||||
await self.send(text_data=json.dumps({
|
await self.send(text_data=json.dumps({
|
||||||
'message': message,
|
'message': message,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
# return time
|
||||||
|
def save_data_and_get_time(message):
|
||||||
|
new_message = Message(body=message['body'],
|
||||||
|
author=Profile.objects
|
||||||
|
.get(pk=message['author_id']),
|
||||||
|
)
|
||||||
|
new_message.save()
|
||||||
|
return new_message.time
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# based on https://github.com/narrowfail/django-channels-chat
|
|
||||||
|
|
||||||
from asgiref.sync import async_to_sync
|
from asgiref.sync import async_to_sync
|
||||||
from channels.layers import get_channel_layer
|
from channels.layers import get_channel_layer
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -18,25 +16,10 @@ class Message(models.Model):
|
||||||
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
|
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True)
|
||||||
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
|
body = models.TextField(verbose_name=_('body of comment'), max_length=8192)
|
||||||
|
|
||||||
def notify_ws_clients(self):
|
|
||||||
# inform client that there is a new message
|
|
||||||
notification = {
|
|
||||||
'type': 'recieve_group_message',
|
|
||||||
'message': '{}'.format(self.id)
|
|
||||||
}
|
|
||||||
channel_layer = get_channel_layer()
|
|
||||||
# print("user.id {}".format(self.user.id))
|
|
||||||
# print("user.id {}".format(self.recipient.id))
|
|
||||||
|
|
||||||
async_to_sync(channel_layer.group_send)("{}".format(self.user.id), notification)
|
|
||||||
async_to_sync(channel_layer.group_send)("{}".format(self.recipient.id), notification)
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
new_message = self.id
|
new_message = self.id
|
||||||
self.body = self.body.strip()
|
self.body = self.body.strip()
|
||||||
super(Message, self).save(*args, **kwargs)
|
super(Message, self).save(*args, **kwargs)
|
||||||
if new_message is None:
|
|
||||||
self.notify_ws_clients()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'chat_box'
|
app_label = 'chat_box'
|
||||||
|
|
|
@ -1,7 +1,50 @@
|
||||||
from django.shortcuts import render
|
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views import View
|
from django.views.generic import ListView
|
||||||
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from judge.jinja2.gravatar import gravatar
|
||||||
|
from .models import Message
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class ChatView(View):
|
def format_time(time):
|
||||||
template_name = 'chat.html'
|
return time.strftime('%H:%M %p %d-%m-%Y')
|
||||||
|
|
||||||
|
|
||||||
|
def format_messages(messages):
|
||||||
|
msg_list = [{
|
||||||
|
'time': format_time(msg.time),
|
||||||
|
'author': str(msg.author),
|
||||||
|
'body': msg.body,
|
||||||
|
'image': gravatar(msg.author, 32),
|
||||||
|
} for msg in messages]
|
||||||
|
return json.dumps(msg_list)
|
||||||
|
|
||||||
|
|
||||||
|
class ChatView(ListView):
|
||||||
|
model = Message
|
||||||
|
context_object_name = 'message'
|
||||||
|
template_name = 'chat/chat.html'
|
||||||
|
title = _('Chat Box')
|
||||||
|
paginate_by = 50
|
||||||
|
paginator = Paginator(Message.objects.all(), paginate_by)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
page = request.GET.get('page')
|
||||||
|
if (page == None):
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
cur_page = self.paginator.get_page(page)
|
||||||
|
return HttpResponse(format_messages(cur_page.object_list))
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context['title'] = self.title
|
||||||
|
|
||||||
|
for msg in context['message']:
|
||||||
|
msg.time = format_time(msg.time)
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
|
@ -1,314 +0,0 @@
|
||||||
#####################################
|
|
||||||
########## Django settings ##########
|
|
||||||
#####################################
|
|
||||||
# See <https://docs.djangoproject.com/en/1.11/ref/settings/>
|
|
||||||
# for more info and help. If you are stuck, you can try Googling about
|
|
||||||
# Django - many of these settings below have external documentation about them.
|
|
||||||
#
|
|
||||||
# The settings listed here are of special interest in configuring the site.
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
# You may use <http://www.miniwebtool.com/django-secret-key-generator/>
|
|
||||||
# to generate this key.
|
|
||||||
SECRET_KEY = 's(8*xdlaiy4r@09cidu#)h%%iey39099g=hp(#+kzz+7vefs4u'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True # Change to False once you are done with runserver testing.
|
|
||||||
|
|
||||||
# Uncomment and set to the domain names this site is intended to serve.
|
|
||||||
# You must do this once you set DEBUG to False.
|
|
||||||
ALLOWED_HOSTS = ['0.0.0.0']
|
|
||||||
|
|
||||||
# Optional apps that DMOJ can make use of.
|
|
||||||
INSTALLED_APPS += (
|
|
||||||
)
|
|
||||||
|
|
||||||
#path to problem folder
|
|
||||||
DMOJ_PROBLEM_DATA_ROOT = '/home/Projects/CP/LQDJudge/problems'
|
|
||||||
|
|
||||||
# Caching. You can use memcached or redis instead.
|
|
||||||
# Documentation: <https://docs.djangoproject.com/en/1.11/topics/cache/>
|
|
||||||
CACHES = {
|
|
||||||
'default': {
|
|
||||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Your database credentials. Only MySQL is supported by DMOJ.
|
|
||||||
# Documentation: <https://docs.djangoproject.com/en/1.11/ref/databases/>
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.mysql',
|
|
||||||
'NAME': 'dmoj',
|
|
||||||
'USER': 'dmoj',
|
|
||||||
'PASSWORD': 'admintl97p1',
|
|
||||||
'HOST': '127.0.0.1',
|
|
||||||
'OPTIONS': {
|
|
||||||
'charset': 'utf8mb4',
|
|
||||||
'sql_mode': 'STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Sessions.
|
|
||||||
# Documentation: <https://docs.djangoproject.com/en/1.11/topics/http/sessions/>
|
|
||||||
#SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
|
|
||||||
|
|
||||||
# Internationalization.
|
|
||||||
# Documentation: <https://docs.djangoproject.com/en/1.11/topics/i18n/>
|
|
||||||
LANGUAGE_CODE = 'vi'
|
|
||||||
DEFAULT_USER_TIME_ZONE = 'Asia/Ho_Chi_Minh'
|
|
||||||
USE_I18N = True
|
|
||||||
USE_L10N = True
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
## django-compressor settings, for speeding up page load times by minifying CSS and JavaScript files.
|
|
||||||
# Documentation: https://django-compressor.readthedocs.io/en/latest/
|
|
||||||
COMPRESS_OUTPUT_DIR = 'cache'
|
|
||||||
COMPRESS_CSS_FILTERS = [
|
|
||||||
'compressor.filters.css_default.CssAbsoluteFilter',
|
|
||||||
'compressor.filters.cssmin.CSSMinFilter',
|
|
||||||
]
|
|
||||||
COMPRESS_JS_FILTERS = ['compressor.filters.jsmin.JSMinFilter']
|
|
||||||
COMPRESS_STORAGE = 'compressor.storage.GzipCompressorFileStorage'
|
|
||||||
STATICFILES_FINDERS += ('compressor.finders.CompressorFinder',)
|
|
||||||
|
|
||||||
|
|
||||||
#########################################
|
|
||||||
########## Email configuration ##########
|
|
||||||
#########################################
|
|
||||||
# See <https://docs.djangoproject.com/en/1.11/topics/email/#email-backends>
|
|
||||||
# for more documentation. You should follow the information there to define
|
|
||||||
# your email settings.
|
|
||||||
|
|
||||||
# Use this if you are just testing.
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
|
||||||
|
|
||||||
# The following block is included for your convenience, if you want
|
|
||||||
# to use Gmail.
|
|
||||||
#EMAIL_USE_TLS = True
|
|
||||||
#EMAIL_HOST = 'smtp.gmail.com'
|
|
||||||
#EMAIL_HOST_USER = '<your account>@gmail.com'
|
|
||||||
#EMAIL_HOST_PASSWORD = '<your password>'
|
|
||||||
#EMAIL_PORT = 587
|
|
||||||
|
|
||||||
# To use Mailgun, uncomment this block.
|
|
||||||
# You will need to run `pip install django-mailgun` for to get `MailgunBackend`.
|
|
||||||
#EMAIL_BACKEND = 'django_mailgun.MailgunBackend'
|
|
||||||
#MAILGUN_ACCESS_KEY = '<your Mailgun access key>'
|
|
||||||
#MAILGUN_SERVER_NAME = '<your Mailgun domain>'
|
|
||||||
|
|
||||||
# You can also use Sendgrid, with `pip install sendgrid-django`.
|
|
||||||
#EMAIL_BACKEND = 'sgbackend.SendGridBackend'
|
|
||||||
#SENDGRID_API_KEY = '<Your SendGrid API Key>'
|
|
||||||
|
|
||||||
# The DMOJ site is able to notify administrators of errors via email,
|
|
||||||
# if configured as shown below.
|
|
||||||
|
|
||||||
# A tuple of (name, email) pairs that specifies those who will be mailed
|
|
||||||
# when the server experiences an error when DEBUG = False.
|
|
||||||
ADMINS = (
|
|
||||||
('luongd', 'doannguyenthanhluong@gmail.com'),
|
|
||||||
)
|
|
||||||
|
|
||||||
# The sender for the aforementioned emails.
|
|
||||||
SERVER_EMAIL = 'LQDOJ: Le Quy Don Online Judge <errors@lqdoj.com>'
|
|
||||||
|
|
||||||
|
|
||||||
##################################################
|
|
||||||
########### Static files configuration. ##########
|
|
||||||
##################################################
|
|
||||||
# See <https://docs.djangoproject.com/en/1.11/howto/static-files/>.
|
|
||||||
|
|
||||||
# Change this to somewhere more permanent., especially if you are using a
|
|
||||||
# webserver to serve the static files. This is the directory where all the
|
|
||||||
# static files DMOJ uses will be collected to.
|
|
||||||
# You must configure your webserver to serve this directory as /static/ in production.
|
|
||||||
STATIC_ROOT = '/home/luongd/Projects/LQDJudge/online-judge/static_root'
|
|
||||||
|
|
||||||
# URL to access static files.
|
|
||||||
#STATIC_URL = '/static/'
|
|
||||||
|
|
||||||
# Uncomment to use hashed filenames with the cache framework.
|
|
||||||
#STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.CachedStaticFilesStorage'
|
|
||||||
|
|
||||||
############################################
|
|
||||||
########## DMOJ-specific settings ##########
|
|
||||||
############################################
|
|
||||||
|
|
||||||
## DMOJ site display settings.
|
|
||||||
SITE_NAME = 'LQDOJ'
|
|
||||||
SITE_LONG_NAME = 'LQDOJ: Le Quy Don Online Judge'
|
|
||||||
SITE_ADMIN_EMAIL = 'admin@example.com'
|
|
||||||
TERMS_OF_SERVICE_URL = '//dmoj.ca/tos' # Use a flatpage.
|
|
||||||
|
|
||||||
## Bridge controls.
|
|
||||||
# The judge connection address and port; where the judges will connect to the site.
|
|
||||||
# You should change this to something your judges can actually connect to
|
|
||||||
# (e.g., a port that is unused and unblocked by a firewall).
|
|
||||||
BRIDGED_JUDGE_ADDRESS = [('0.0.0.0', 9999)]
|
|
||||||
|
|
||||||
# The bridged daemon bind address and port to communicate with the site.
|
|
||||||
BRIDGED_DJANGO_ADDRESS = [('localhost', 9998)]
|
|
||||||
|
|
||||||
## DMOJ features.
|
|
||||||
# Set to True to enable full-text searching for problems.
|
|
||||||
ENABLE_FTS = True
|
|
||||||
|
|
||||||
# Set of email providers to ban when a user registers, e.g., {'throwawaymail.com'}.
|
|
||||||
BAD_MAIL_PROVIDERS = set()
|
|
||||||
|
|
||||||
# The number of submissions that a staff user can rejudge at once without
|
|
||||||
# requiring the permission 'Rejudge a lot of submissions'.
|
|
||||||
# Uncomment to change the submission limit.
|
|
||||||
REJUDGE_SUBMISSION_LIMIT = 10
|
|
||||||
|
|
||||||
## Event server.
|
|
||||||
# Uncomment to enable live updating.
|
|
||||||
# EVENT_DAEMON_USE = True
|
|
||||||
|
|
||||||
# Uncomment this section to use websocket/daemon.js included in the site.
|
|
||||||
#EVENT_DAEMON_POST = '<ws:// URL to post to>'
|
|
||||||
|
|
||||||
# If you are using the defaults from the guide, it is this:
|
|
||||||
EVENT_DAEMON_POST = 'ws://127.0.0.1:15101/'
|
|
||||||
|
|
||||||
# These are the publicly accessed interface configurations.
|
|
||||||
# They should match those used by the script.
|
|
||||||
#EVENT_DAEMON_GET = '<public ws:// URL for clients>'
|
|
||||||
#EVENT_DAEMON_GET_SSL = '<public wss:// URL for clients>'
|
|
||||||
#EVENT_DAEMON_POLL = '<public URL to access the HTTP long polling of event server>'
|
|
||||||
# i.e. the path to /channels/ exposed by the daemon, through whatever proxy setup you have.
|
|
||||||
|
|
||||||
# Using our standard nginx configuration, these should be.
|
|
||||||
#EVENT_DAEMON_GET = 'ws://<your domain>/event/'
|
|
||||||
#EVENT_DAEMON_GET_SSL = 'wss://<your domain>/event/' # Optional
|
|
||||||
#EVENT_DAEMON_POLL = '/channels/'
|
|
||||||
|
|
||||||
# If you would like to use the AMQP-based event server from <https://github.com/DMOJ/event-server>,
|
|
||||||
# uncomment this section instead. This is more involved, and recommended to be done
|
|
||||||
# only after you have a working event server.
|
|
||||||
#EVENT_DAEMON_AMQP = '<amqp:// URL to connect to, including username and password>'
|
|
||||||
#EVENT_DAEMON_AMQP_EXCHANGE = '<AMQP exchange to use>'
|
|
||||||
|
|
||||||
## CDN control.
|
|
||||||
# Base URL for a copy of ace editor.
|
|
||||||
# Should contain ace.js, along with mode-*.js.
|
|
||||||
ACE_URL = '//cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/'
|
|
||||||
JQUERY_JS = '//cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js'
|
|
||||||
SELECT2_JS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
|
|
||||||
SELECT2_CSS_URL = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
|
|
||||||
|
|
||||||
# A map of Earth in Equirectangular projection, for timezone selection.
|
|
||||||
# Please try not to hotlink this poor site.
|
|
||||||
TIMEZONE_MAP = 'http://naturalearth.springercarto.com/ne3_data/8192/textures/3_no_ice_clouds_8k.jpg'
|
|
||||||
|
|
||||||
## Camo (https://github.com/atmos/camo) usage.
|
|
||||||
#CAMO_URL = "<URL to your camo install>"
|
|
||||||
#CAMO_KEY = "<The CAMO_KEY environmental variable you used>"
|
|
||||||
|
|
||||||
# Domains to exclude from being camo'd.
|
|
||||||
#CAMO_EXCLUDE = ("https://dmoj.ml", "https://dmoj.ca")
|
|
||||||
|
|
||||||
# Set to True to use https when dealing with protocol-relative URLs.
|
|
||||||
# See <http://www.paulirish.com/2010/the-protocol-relative-url/> for what they are.
|
|
||||||
#CAMO_HTTPS = False
|
|
||||||
|
|
||||||
# HTTPS level. Affects <link rel='canonical'> elements generated.
|
|
||||||
# Set to 0 to make http URLs canonical.
|
|
||||||
# Set to 1 to make the currently used protocol canonical.
|
|
||||||
# Set to 2 to make https URLs canonical.
|
|
||||||
#DMOJ_HTTPS = 0
|
|
||||||
|
|
||||||
## PDF rendering settings.
|
|
||||||
# Directory to cache the PDF.
|
|
||||||
# PROBLEM_PDF_CACHE = '/home/dmoj-uwsgi/pdfcache'
|
|
||||||
|
|
||||||
# Path to use for nginx's X-Accel-Redirect feature.
|
|
||||||
# Should be an internal location mapped to the above directory.
|
|
||||||
# PROBLEM_PDF_INTERNAL = '/pdfcache'
|
|
||||||
|
|
||||||
# Path to a PhantomJS executable.
|
|
||||||
#PHANTOMJS = '/usr/local/bin/phantomjs'
|
|
||||||
|
|
||||||
# If you can't use PhantomJS or prefer wkhtmltopdf, set the path to wkhtmltopdf executable instead.
|
|
||||||
#WKHTMLTOPDF = '/usr/local/bin/wkhtmltopdf'
|
|
||||||
|
|
||||||
# Note that PhantomJS is preferred over wkhtmltopdf and would be used when both are defined.
|
|
||||||
|
|
||||||
## ======== Logging Settings ========
|
|
||||||
# Documentation: https://docs.djangoproject.com/en/1.9/ref/settings/#logging
|
|
||||||
# https://docs.python.org/2/library/logging.config.html#logging-config-dictschema
|
|
||||||
LOGGING = {
|
|
||||||
'version': 1,
|
|
||||||
'disable_existing_loggers': False,
|
|
||||||
'formatters': {
|
|
||||||
'file': {
|
|
||||||
'format': '%(levelname)s %(asctime)s %(module)s %(message)s',
|
|
||||||
},
|
|
||||||
'simple': {
|
|
||||||
'format': '%(levelname)s %(message)s',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'handlers': {
|
|
||||||
# You may use this handler as example for logging to other files..
|
|
||||||
'bridge': {
|
|
||||||
'level': 'INFO',
|
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
|
||||||
'filename': '<desired bridge log path>',
|
|
||||||
'maxBytes': 10 * 1024 * 1024,
|
|
||||||
'backupCount': 10,
|
|
||||||
'formatter': 'file',
|
|
||||||
},
|
|
||||||
'mail_admins': {
|
|
||||||
'level': 'ERROR',
|
|
||||||
'class': 'dmoj.throttle_mail.ThrottledEmailHandler',
|
|
||||||
},
|
|
||||||
'console': {
|
|
||||||
'level': 'DEBUG',
|
|
||||||
'class': 'logging.StreamHandler',
|
|
||||||
'formatter': 'file',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'loggers': {
|
|
||||||
# Site 500 error mails.
|
|
||||||
'django.request': {
|
|
||||||
'handlers': ['mail_admins'],
|
|
||||||
'level': 'ERROR',
|
|
||||||
'propagate': False,
|
|
||||||
},
|
|
||||||
# Judging logs as received by bridged.
|
|
||||||
'judge.bridge': {
|
|
||||||
'handlers': ['bridge', 'mail_admins'],
|
|
||||||
'level': 'INFO',
|
|
||||||
'propagate': True,
|
|
||||||
},
|
|
||||||
# Catch all log to stderr.
|
|
||||||
'': {
|
|
||||||
'handlers': ['console'],
|
|
||||||
},
|
|
||||||
# Other loggers of interest. Configure at will.
|
|
||||||
# - judge.user: logs naughty user behaviours.
|
|
||||||
# - judge.problem.pdf: PDF generation log.
|
|
||||||
# - judge.html: HTML parsing errors when processing problem statements etc.
|
|
||||||
# - judge.mail.activate: logs for the reply to activate feature.
|
|
||||||
# - event_socket_server
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
## ======== Integration Settings ========
|
|
||||||
## Python Social Auth
|
|
||||||
# Documentation: https://python-social-auth.readthedocs.io/en/latest/
|
|
||||||
# You can define these to enable authentication through the following services.
|
|
||||||
#SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
|
|
||||||
#SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
|
|
||||||
#SOCIAL_AUTH_FACEBOOK_KEY = ''
|
|
||||||
#SOCIAL_AUTH_FACEBOOK_SECRET = ''
|
|
||||||
#SOCIAL_AUTH_GITHUB_SECURE_KEY = ''
|
|
||||||
#SOCIAL_AUTH_GITHUB_SECURE_SECRET = ''
|
|
||||||
#SOCIAL_AUTH_DROPBOX_OAUTH2_KEY = ''
|
|
||||||
#SOCIAL_AUTH_DROPBOX_OAUTH2_SECRET = ''
|
|
||||||
|
|
||||||
## ======== Custom Configuration ========
|
|
||||||
# You may add whatever django configuration you would like here.
|
|
||||||
# Do try to keep it separate so you can quickly patch in new settings.
|
|
|
@ -1,13 +1,12 @@
|
||||||
import chat_box.routing
|
|
||||||
from channels.auth import AuthMiddlewareStack
|
from channels.auth import AuthMiddlewareStack
|
||||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
|
import chat_box.routing
|
||||||
|
|
||||||
application = ProtocolTypeRouter({
|
application = ProtocolTypeRouter({
|
||||||
# (http->django views is added by default)
|
# (http->django views is added by default)
|
||||||
'websocket': AuthMiddlewareStack(
|
'websocket': AuthMiddlewareStack(
|
||||||
URLRouter(
|
URLRouter(
|
||||||
chat_box.routing.websocket_urlpatterns,
|
chat_box.routing.websocket_urlpatterns
|
||||||
),
|
)
|
||||||
),
|
),
|
||||||
})
|
})
|
|
@ -109,7 +109,7 @@ TIMEZONE_MAP = None
|
||||||
TIMEZONE_DETECT_BACKEND = None
|
TIMEZONE_DETECT_BACKEND = None
|
||||||
|
|
||||||
TERMS_OF_SERVICE_URL = None
|
TERMS_OF_SERVICE_URL = None
|
||||||
DEFAULT_USER_LANGUAGE = 'PY3'
|
DEFAULT_USER_LANGUAGE = 'CPP11'
|
||||||
|
|
||||||
PHANTOMJS = ''
|
PHANTOMJS = ''
|
||||||
PHANTOMJS_PDF_ZOOM = 0.75
|
PHANTOMJS_PDF_ZOOM = 0.75
|
||||||
|
@ -431,9 +431,9 @@ EVENT_DAEMON_SUBMISSION_KEY = '6Sdmkx^%pk@GsifDfXcwX*Y7LRF%RGT8vmFpSxFBT$fwS7trc
|
||||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||||
|
|
||||||
# Whatever you do, this better be one of the entries in `LANGUAGES`.
|
# Whatever you do, this better be one of the entries in `LANGUAGES`.
|
||||||
LANGUAGE_CODE = 'en'
|
LANGUAGE_CODE = 'vi'
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'Asia/Ho_Chi_Minh'
|
||||||
DEFAULT_USER_TIME_ZONE = 'America/Toronto'
|
DEFAULT_USER_TIME_ZONE = 'Asia/Ho_Chi_Minh'
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
@ -503,6 +503,8 @@ TESTCASE_VISIBLE_LENGTH = 60
|
||||||
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10240
|
||||||
DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 2621440
|
||||||
|
|
||||||
|
MESSAGES_TO_LOAD = 15
|
||||||
|
|
||||||
ASGI_APPLICATION = 'dmoj.routing.application'
|
ASGI_APPLICATION = 'dmoj.routing.application'
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
'default': {
|
'default': {
|
||||||
|
|
14
dmoj/urls.py
|
@ -4,12 +4,14 @@ from django.conf.urls import include, url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import views as auth_views
|
from django.contrib.auth import views as auth_views
|
||||||
from django.contrib.sitemaps.views import sitemap
|
from django.contrib.sitemaps.views import sitemap
|
||||||
from django.http import Http404, HttpResponsePermanentRedirect
|
from django.http import Http404, HttpResponsePermanentRedirect, HttpResponseRedirect
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.functional import lazystr
|
from django.utils.functional import lazystr
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
|
||||||
from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed
|
from judge.feed import AtomBlogFeed, AtomCommentFeed, AtomProblemFeed, BlogFeed, CommentFeed, ProblemFeed
|
||||||
from judge.forms import CustomAuthenticationForm
|
from judge.forms import CustomAuthenticationForm
|
||||||
|
@ -25,7 +27,6 @@ from judge.views.select2 import AssigneeSelect2View, CommentSelect2View, Contest
|
||||||
ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
|
ContestUserSearchSelect2View, OrganizationSelect2View, ProblemSelect2View, TicketUserSelect2View, \
|
||||||
UserSearchSelect2View, UserSelect2View
|
UserSearchSelect2View, UserSelect2View
|
||||||
|
|
||||||
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
register_patterns = [
|
register_patterns = [
|
||||||
|
@ -367,7 +368,12 @@ urlpatterns = [
|
||||||
|
|
||||||
url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'),
|
url(r'^custom_checker_sample/', about.custom_checker_sample, name='custom_checker_sample'),
|
||||||
|
|
||||||
url(r'chat/', ChatView.as_view(), name='chat'),
|
url(r'^chat/', include([
|
||||||
|
url(r'^$',
|
||||||
|
login_required(ChatView.as_view()),
|
||||||
|
name='chat'),
|
||||||
|
|
||||||
|
])),
|
||||||
]
|
]
|
||||||
|
|
||||||
favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png',
|
favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png', 'android-chrome-72x72.png',
|
||||||
|
@ -382,7 +388,7 @@ favicon_paths = ['apple-touch-icon-180x180.png', 'apple-touch-icon-114x114.png',
|
||||||
|
|
||||||
for favicon in favicon_paths:
|
for favicon in favicon_paths:
|
||||||
urlpatterns.append(url(r'^%s$' % favicon, RedirectView.as_view(
|
urlpatterns.append(url(r'^%s$' % favicon, RedirectView.as_view(
|
||||||
url=lazystr(lambda: static('icons/' + favicon)),
|
url=static('icons/' + favicon)
|
||||||
)))
|
)))
|
||||||
|
|
||||||
handler404 = 'judge.views.error.error404'
|
handler404 = 'judge.views.error.error404'
|
||||||
|
|
36
judge/migrations/0101_custom_validator.py
Normal file
23
judge/migrations/0102_fix_custom_validator.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 2.2.9 on 2020-03-17 05:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('judge', '0101_custom_validator'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='problemdata',
|
||||||
|
name='checker',
|
||||||
|
field=models.CharField(blank=True, choices=[('standard', 'Standard'), ('floats', 'Floats'), ('floatsabs', 'Floats (absolute)'), ('floatsrel', 'Floats (relative)'), ('rstripped', 'Non-trailing spaces'), ('sorted', 'Unordered'), ('identical', 'Byte identical'), ('linecount', 'Line-by-line'), ('custom', 'Custom checker'), ('customval', 'Custom Validator')], max_length=10, verbose_name='checker'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='problemtestcase',
|
||||||
|
name='checker',
|
||||||
|
field=models.CharField(blank=True, choices=[('standard', 'Standard'), ('floats', 'Floats'), ('floatsabs', 'Floats (absolute)'), ('floatsrel', 'Floats (relative)'), ('rstripped', 'Non-trailing spaces'), ('sorted', 'Unordered'), ('identical', 'Byte identical'), ('linecount', 'Line-by-line'), ('custom', 'Custom checker'), ('customval', 'Custom Validator')], max_length=10, verbose_name='checker'),
|
||||||
|
),
|
||||||
|
]
|
18
judge/migrations/0103_fix_custom_validator.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.9 on 2020-03-17 05:17
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('judge', '0102_fix_custom_validator'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name='problemdata',
|
||||||
|
old_name='custom_valid',
|
||||||
|
new_name='custom_validator',
|
||||||
|
),
|
||||||
|
]
|
|
@ -29,7 +29,8 @@ CHECKERS = (
|
||||||
('sorted', _('Unordered')),
|
('sorted', _('Unordered')),
|
||||||
('identical', _('Byte identical')),
|
('identical', _('Byte identical')),
|
||||||
('linecount', _('Line-by-line')),
|
('linecount', _('Line-by-line')),
|
||||||
('custom', _('Custom checker')),
|
('custom', _('Custom checker (PY)')),
|
||||||
|
('customval', _('Custom validator (CPP)')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +53,12 @@ class ProblemData(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
upload_to=problem_directory_file,
|
upload_to=problem_directory_file,
|
||||||
validators=[FileExtensionValidator(allowed_extensions=['py'])])
|
validators=[FileExtensionValidator(allowed_extensions=['py'])])
|
||||||
|
custom_validator = models.FileField(verbose_name=_('custom validator file'),
|
||||||
|
storage=problem_data_storage,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
upload_to=problem_directory_file,
|
||||||
|
validators=[FileExtensionValidator(allowed_extensions=['cpp'])])
|
||||||
__original_zipfile = None
|
__original_zipfile = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -78,6 +85,10 @@ class ProblemData(models.Model):
|
||||||
self.generator.name = _problem_directory_file(new, self.generator.name)
|
self.generator.name = _problem_directory_file(new, self.generator.name)
|
||||||
if self.custom_checker:
|
if self.custom_checker:
|
||||||
self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name)
|
self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name)
|
||||||
|
if self.custom_checker:
|
||||||
|
self.custom_checker.name = _problem_directory_file(new, self.custom_checker.name)
|
||||||
|
if self.custom_validator:
|
||||||
|
self.custom_validator.name = _problem_directory_file(new, self.custom_validator.name)
|
||||||
self.save()
|
self.save()
|
||||||
_update_code.alters_data = True
|
_update_code.alters_data = True
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -9,6 +10,10 @@ from django.core.files.storage import FileSystemStorage
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
|
VALIDATOR_TEMPLATE_PATH = 'validator_template/template.py'
|
||||||
|
|
||||||
|
|
||||||
if os.altsep:
|
if os.altsep:
|
||||||
def split_path_first(path, repath=re.compile('[%s]' % re.escape(os.sep + os.altsep))):
|
def split_path_first(path, repath=re.compile('[%s]' % re.escape(os.sep + os.altsep))):
|
||||||
return repath.split(path, 1)
|
return repath.split(path, 1)
|
||||||
|
@ -63,12 +68,38 @@ class ProblemDataCompiler(object):
|
||||||
raise ProblemDataError(_('Empty batches not allowed.'))
|
raise ProblemDataError(_('Empty batches not allowed.'))
|
||||||
cases.append(batch)
|
cases.append(batch)
|
||||||
|
|
||||||
|
def make_checker_for_validator(case):
|
||||||
|
checker_name = "cppvalidator.py"
|
||||||
|
validator_path = split_path_first(case.custom_validator.name)
|
||||||
|
|
||||||
|
if len(validator_path) != 2:
|
||||||
|
raise ProblemDataError(_('How did you corrupt the custom checker path?'))
|
||||||
|
|
||||||
|
checker = os.path.join(settings.DMOJ_PROBLEM_DATA_ROOT,
|
||||||
|
validator_path[0],
|
||||||
|
checker_name)
|
||||||
|
|
||||||
|
validator_name = validator_path[1]
|
||||||
|
shutil.copy(VALIDATOR_TEMPLATE_PATH, checker)
|
||||||
|
|
||||||
|
# replace {{filecpp}} and {{problemid}} in checker file
|
||||||
|
filedata = open(checker, 'r').read()
|
||||||
|
filedata = filedata.replace('{{filecpp}}', "\'%s\'" % validator_name)
|
||||||
|
filedata = filedata.replace('{{problemid}}', "\'%s\'" % validator_path[0])
|
||||||
|
open(checker, 'w').write(filedata)
|
||||||
|
|
||||||
|
return checker_name
|
||||||
|
|
||||||
def make_checker(case):
|
def make_checker(case):
|
||||||
if (case.checker == 'custom'):
|
if (case.checker == 'custom'):
|
||||||
custom_checker_path = split_path_first(case.custom_checker.name)
|
custom_checker_path = split_path_first(case.custom_checker.name)
|
||||||
if len(custom_checker_path) != 2:
|
if len(custom_checker_path) != 2:
|
||||||
raise ProblemDataError(_('How did you corrupt the custom checker path?'))
|
raise ProblemDataError(_('How did you corrupt the custom checker path?'))
|
||||||
return(custom_checker_path[1])
|
return(custom_checker_path[1])
|
||||||
|
|
||||||
|
if (case.checker == 'customval'):
|
||||||
|
return make_checker_for_validator(case)
|
||||||
|
|
||||||
if case.checker_args:
|
if case.checker_args:
|
||||||
return {
|
return {
|
||||||
'name': case.checker,
|
'name': case.checker,
|
||||||
|
|
|
@ -287,11 +287,11 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
context_object_name = 'problems'
|
context_object_name = 'problems'
|
||||||
template_name = 'problem/list.html'
|
template_name = 'problem/list.html'
|
||||||
paginate_by = 50
|
paginate_by = 50
|
||||||
sql_sort = frozenset(('points', 'ac_rate', 'user_count', 'code'))
|
sql_sort = frozenset(('date', 'points', 'ac_rate', 'user_count', 'code'))
|
||||||
manual_sort = frozenset(('name', 'group', 'solved', 'type'))
|
manual_sort = frozenset(('name', 'group', 'solved', 'type'))
|
||||||
all_sorts = sql_sort | manual_sort
|
all_sorts = sql_sort | manual_sort
|
||||||
default_desc = frozenset(('points', 'ac_rate', 'user_count'))
|
default_desc = frozenset(('date', 'points', 'ac_rate', 'user_count'))
|
||||||
default_sort = 'code'
|
default_sort = '-date'
|
||||||
|
|
||||||
def get_paginator(self, queryset, per_page, orphans=0,
|
def get_paginator(self, queryset, per_page, orphans=0,
|
||||||
allow_empty_first_page=True, **kwargs):
|
allow_empty_first_page=True, **kwargs):
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms import BaseModelFormSet, HiddenInput, ModelForm, NumberInput, Select, formset_factory
|
from django.forms import BaseModelFormSet, HiddenInput, ModelForm, NumberInput, Select, formset_factory, FileInput
|
||||||
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, render
|
from django.shortcuts import get_object_or_404, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -50,9 +50,12 @@ class ProblemDataForm(ModelForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProblemData
|
model = ProblemData
|
||||||
fields = ['zipfile', 'generator', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'custom_checker']
|
fields = ['zipfile', 'checker', 'checker_args', 'custom_checker', 'custom_validator']
|
||||||
widgets = {
|
widgets = {
|
||||||
'checker_args': HiddenInput,
|
'checker_args': HiddenInput,
|
||||||
|
'generator': HiddenInput,
|
||||||
|
'output_limit': HiddenInput,
|
||||||
|
'output_prefix': HiddenInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,14 +65,14 @@ class ProblemCaseForm(ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ProblemTestCase
|
model = ProblemTestCase
|
||||||
fields = ('order', 'type', 'input_file', 'output_file', 'points',
|
fields = ('order', 'type', 'input_file', 'output_file', 'points',
|
||||||
'is_pretest', 'output_limit', 'output_prefix', 'checker', 'checker_args', 'generator_args')
|
'is_pretest', 'checker', 'checker_args') #, 'output_limit', 'output_prefix', 'generator_args')
|
||||||
widgets = {
|
widgets = {
|
||||||
'generator_args': HiddenInput,
|
# 'generator_args': HiddenInput,
|
||||||
'type': Select(attrs={'style': 'width: 100%'}),
|
'type': Select(attrs={'style': 'width: 100%'}),
|
||||||
'points': NumberInput(attrs={'style': 'width: 4em'}),
|
'points': NumberInput(attrs={'style': 'width: 4em'}),
|
||||||
'output_prefix': NumberInput(attrs={'style': 'width: 4.5em'}),
|
# 'output_prefix': NumberInput(attrs={'style': 'width: 4.5em'}),
|
||||||
'output_limit': NumberInput(attrs={'style': 'width: 6em'}),
|
# 'output_limit': NumberInput(attrs={'style': 'width: 6em'}),
|
||||||
'checker_args': HiddenInput,
|
# 'checker_args': HiddenInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ fi
|
||||||
FILES=(sass_processed/style.css sass_processed/content-description.css sass_processed/table.css sass_processed/ranks.css)
|
FILES=(sass_processed/style.css sass_processed/content-description.css sass_processed/table.css sass_processed/ranks.css)
|
||||||
|
|
||||||
cd `dirname $0`
|
cd `dirname $0`
|
||||||
sass --update resources:sass_processed
|
sass resources:sass_processed
|
||||||
|
|
||||||
echo
|
echo
|
||||||
postcss "${FILES[@]}" --verbose --use autoprefixer -d resources
|
postcss "${FILES[@]}" --verbose --use autoprefixer -d resources
|
||||||
|
|
|
@ -115,6 +115,7 @@ body {
|
||||||
font-family: "Segoe UI", "Lucida Grande", Arial, sans-serif;
|
font-family: "Segoe UI", "Lucida Grande", Arial, sans-serif;
|
||||||
color: #000;
|
color: #000;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unselectable {
|
.unselectable {
|
||||||
|
@ -180,7 +181,7 @@ header {
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #FFF;
|
color: #9c3706;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
@ -206,7 +207,7 @@ header {
|
||||||
}
|
}
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
color: #eee;
|
color: darkslateblue;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline;
|
display: inline;
|
||||||
margin-top: 11px;
|
margin-top: 11px;
|
||||||
|
@ -218,20 +219,20 @@ header {
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav-shadow {
|
#nav-shadow {
|
||||||
height: 3px;
|
height: 2px;
|
||||||
background: linear-gradient(rgba(0, 0, 0, 0.5), transparent);
|
background: linear-gradient(#7dc7ff, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav-container {
|
#nav-container {
|
||||||
background: $widget_black;
|
background: #7dc7ff;
|
||||||
|
|
||||||
// opacity: 0.77
|
// opacity: 0.77;
|
||||||
// filter: alpha(opacity=77)
|
// filter: alpha(opacity=77)
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigation {
|
#navigation {
|
||||||
position: fixed;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -276,12 +277,12 @@ nav {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
color: #FFF;
|
color: #9c3706;
|
||||||
padding: 13px 7px;
|
padding: 13px 7px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
|
||||||
&:link {
|
&:link {
|
||||||
color: #FFF;
|
color: #9c3706;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -291,8 +292,9 @@ nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
color: #FFF;
|
// color: #FFF;
|
||||||
background: $highlight_blue;
|
background: $highlight_blue;
|
||||||
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-expand {
|
.nav-expand {
|
||||||
|
@ -370,7 +372,7 @@ hr {
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
margin: 52px auto auto;
|
margin: 1em auto auto;
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
|
|
@ -15,10 +15,10 @@
|
||||||
font-size: 1.7em;
|
font-size: 1.7em;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #5b80b9 !important;
|
color: Maroon !important;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: #0645ad !important;
|
color: #c00000 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
71
resources/chatbox.scss
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#chat-box {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 20em;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
width: 4%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-log {
|
||||||
|
padding: 0;
|
||||||
|
padding-top: 2em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-log li {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.4em;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-submit {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-pic {
|
||||||
|
height: 2.6em;
|
||||||
|
width: 2.6em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
margin-top: 0.1em;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body-message {
|
||||||
|
padding-left: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-time {
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.content-message {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 797 B After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 14 KiB |
BIN
resources/icons/apple-touch-icon-114x114-precomposed.png
Normal file
After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 14 KiB |
BIN
resources/icons/apple-touch-icon-120x120-precomposed.png
Normal file
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 15 KiB |
BIN
resources/icons/apple-touch-icon-144x144-precomposed.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 21 KiB |
BIN
resources/icons/apple-touch-icon-152x152-precomposed.png
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 23 KiB |
BIN
resources/icons/apple-touch-icon-180x180-precomposed.png
Normal file
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 31 KiB |
BIN
resources/icons/apple-touch-icon-57x57-precomposed.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
resources/icons/apple-touch-icon-60x60-precomposed.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
resources/icons/apple-touch-icon-72x72-precomposed.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 6.4 KiB |
BIN
resources/icons/apple-touch-icon-76x76-precomposed.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 7 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 28 KiB |
|
@ -1,12 +1,11 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<browserconfig>
|
<browserconfig>
|
||||||
<msapplication>
|
<msapplication>
|
||||||
<tile>
|
<tile>
|
||||||
<square70x70logo src="/mstile-70x70.png"/>
|
<square70x70logo src="/mstile-70x70.png"/>
|
||||||
<square150x150logo src="/mstile-150x150.png"/>
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
<square310x310logo src="/mstile-310x310.png"/>
|
<square310x310logo src="/mstile-310x310.png"/>
|
||||||
<wide310x150logo src="/mstile-310x150.png"/>
|
<TileColor>#da532c</TileColor>
|
||||||
<TileColor>#00aba9</TileColor>
|
</tile>
|
||||||
</tile>
|
</msapplication>
|
||||||
</msapplication>
|
|
||||||
</browserconfig>
|
</browserconfig>
|
||||||
|
|
Before Width: | Height: | Size: 830 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 24 KiB |
|
@ -1,16 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="44">
|
|
||||||
<g transform="matrix(.08265 0 0 -.0827 -18.736 55.375)">
|
|
||||||
<path fill="#FCDB05" stroke="#FCDB05" stroke-width="10" d="M477.898 612.007c-107.844-.25-215.89-92.284-206.703-227.507 6.614-97.33 93.848-202.26 227.87-192.475 101.47 7.41 194.44 95.456 192.42 213.653-1.96 114.6-97.744 208.65-213.587 206.33m3.533-437.256c-123.62-2.002-228.5 102.77-227.64 227.46.87 125.882 103.18 228.238 229.38 226.488 124.6-1.727 225.85-98.733 224.96-230.748-.82-122.186-101.1-223.51-226.69-223.2"/>
|
|
||||||
<path fill="#fcdb05" d="M515.264 390.9c14.44-20.215 31.333-37.793 47.34-56.057 18.196-20.76 18.438-20.43-3.845-36.05-7.16-5.014-14.16-10.288-20.88-15.868-6.27-5.203-10.51-6.375-15.06 2.458-12.79 24.824-43.17 77.232-46.028 76.716-2.73-.5-32.438-53.09-46.36-77.37-4.567-7.97-8.044-6.92-14.116-1.95-10.807 8.84-21.825 17.57-33.59 25.03-7.504 4.75-7.585 8.08-2.073 14.2 18.23 20.26 57.116 65.45 59.423 68.69-28.8 5.974-56.39 11.873-84.065 17.355-10.37 2.055-13.786 4.4-9.2 16.78 4.32 11.65 10.353 23.38 11.56 36.02 1.067 11.187 6.41 11.39 14.473 7.76 24.793-11.16 77.644-34.635 80.63-35.575 1.073 21.123-7.233 79.865-8.628 88.975-1.027 6.686 1.18 9.43 8.496 9.157 14.638-.545 29.38-1.074 43.94.132 11.26.932 13.05-4.163 11.42-12.94-5.17-27.87-5.52-56.143-7.755-85.808 25.865 11.322 51.147 21.774 75.85 33.45 14 6.613 16.357 6.306 19.824-7.96 2.892-11.905 7.53-23.366 11.466-34.877 4.11-12.01-1.486-13.28-9.816-15.007-27.35-5.66-54.684-11.38-83.012-17.28"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(.53656 0 0 .52273 46.09 -512.12)">
|
|
||||||
<path fill="#FCDB05" d="M1.8 1045v-46.4h16.4q24.7 0 24.7 22.6 0 10.8-6.8 17.3-6.7 6.5-18 6.5H1.7zm10.4-37.9v29.4h5.2q6.8 0 10.6-4.1 3.9-4.1 3.9-11.1 0-6.6-3.8-10.4-3.8-3.8-10.7-3.8h-5.1z"/>
|
|
||||||
<path fill="#ffde05" d="M101.5 1045H91.2v-27.8q0-4.5.4-9.9h-.3q-.8 4.3-1.5 6.1l-10.9 31.5h-8.5l-11.1-31.2q-.5-1.3-1.5-6.5h-.3q.4 6.9.4 12v25.7h-9.4v-46.4h15.3l9.5 27.5q1.1 3.3 1.6 6.6h.2q.9-3.8 1.8-6.7l9.5-27.4h14.9v46.4z"/>
|
|
||||||
<g fill="#ffde05">
|
|
||||||
<path d="M113.6 1018.2q-2.5 0-4.2-1.5-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.8-1.7 1.5-4.4 1.5zm0 23.7q-2.5 0-4.2-1.6-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.9t-4.4 1.5z"/>
|
|
||||||
<path d="M129.6 1018.2q-2.5 0-4.2-1.5-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.8-1.7 1.5-4.4 1.5zm0 23.7q-2.5 0-4.2-1.6-1.7-1.6-1.7-3.8 0-2.3 1.7-3.8t4.3-1.5q2.6 0 4.3 1.5 1.7 1.5 1.7 3.8 0 2.4-1.7 3.9t-4.4 1.5z"/>
|
|
||||||
</g>
|
|
||||||
<path fill="#aaa" d="M161.7 1045.8q-10 0-16.2-6.5-6.3-6.5-6.3-16.9 0-11 6.4-17.8 6.4-6.8 16.9-6.8 9.9 0 16 6.5t6.1 17.1q0 10.9-6.4 17.6-6.3 6.7-16.6 6.7zm.5-39q-5.5 0-8.7 4.1-3.2 4.1-3.2 10.9 0 6.9 3.2 10.9t8.5 4q5.4 0 8.6-3.9 3.2-3.9 3.2-10.8 0-7.2-3.1-11.2-3.1-4-8.4-4z"/>
|
|
||||||
<path fill="#aaa" d="M209.6 1026.6q0 9.3-4.3 14.3-4.3 4.9-12.5 4.9-3.7 0-6.8-1.3v-9.8q2.7 2 6 2 7.1 0 7.1-10.6v-27.6h10.5v28z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.9 KiB |
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "DMOJ",
|
"name": "LQDOJ",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "\/android-chrome-36x36.png",
|
"src": "\/android-chrome-36x36.png",
|
||||||
|
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 14 KiB |
|
@ -2,18 +2,57 @@
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
width="960.000000pt" height="960.000000pt" viewBox="0 0 960.000000 960.000000"
|
width="223.000000pt" height="223.000000pt" viewBox="0 0 223.000000 223.000000"
|
||||||
preserveAspectRatio="xMidYMid meet">
|
preserveAspectRatio="xMidYMid meet">
|
||||||
<metadata>
|
<metadata>
|
||||||
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
Created by potrace 1.11, written by Peter Selinger 2001-2013
|
||||||
</metadata>
|
</metadata>
|
||||||
<g transform="translate(0.000000,960.000000) scale(0.100000,-0.100000)"
|
<g transform="translate(0.000000,223.000000) scale(0.100000,-0.100000)"
|
||||||
fill="#000000" stroke="none">
|
fill="#000000" stroke="none">
|
||||||
<path d="M4430 9579 c-1066 -77 -2049 -478 -2810 -1146 -900 -791 -1457 -1877
|
<path d="M1101 2183 c0 -7 -1 -15 -1 -18 0 -3 -7 -19 -15 -35 -8 -16 -15 -32
|
||||||
-1597 -3115 -25 -218 -25 -828 0 -1048 126 -1122 585 -2089 1362 -2865 773
|
-15 -35 0 -3 0 -6 -1 -7 0 -2 -2 -10 -4 -18 -2 -8 -18 -67 -35 -130 -17 -63
|
||||||
-774 1763 -1251 2865 -1381 183 -21 681 -30 891 -15 951 68 1836 397 2584 961
|
-34 -125 -37 -137 -4 -18 -9 -20 -30 -12 -18 7 -22 6 -17 -4 5 -8 2 -8 -9 2
|
||||||
181 137 295 236 474 413 538 531 923 1151 1161 1867 129 386 200 739 231 1150
|
-13 11 -17 11 -17 1 0 -8 -3 -10 -7 -7 -3 4 -12 2 -20 -4 -7 -6 -13 -8 -13 -4
|
||||||
15 201 6 734 -16 929 -126 1130 -590 2116 -1355 2881 -763 763 -1738 1226
|
0 4 -7 2 -15 -5 -8 -7 -15 -10 -15 -7 0 3 -15 1 -32 -4 -18 -6 -42 -12 -53
|
||||||
-2855 1355 -166 20 -744 28 -935 14z"/>
|
-14 -11 -3 -32 -7 -47 -11 -16 -3 -28 -10 -28 -15 0 -5 -3 -8 -7 -8 -23 4 -43
|
||||||
|
-2 -38 -11 3 -5 1 -10 -5 -10 -6 0 -8 -5 -4 -12 5 -7 2 -9 -7 -6 -11 4 -14 -5
|
||||||
|
-15 -36 -4 -92 -9 -116 -26 -122 -10 -3 -18 -10 -18 -16 0 -6 -3 -8 -6 -5 -4
|
||||||
|
4 -19 -2 -33 -12 -22 -15 -46 -27 -71 -36 -32 -11 -86 -40 -97 -51 -8 -8 -19
|
||||||
|
-12 -24 -9 -5 4 -9 -6 -10 -22 -1 -38 -7 -126 -12 -193 -3 -30 -8 -107 -12
|
||||||
|
-170 -11 -168 -14 -181 -46 -175 -10 2 -19 -1 -19 -7 0 -6 -11 -8 -25 -6 -13
|
||||||
|
3 -24 2 -23 -1 2 -20 -3 -31 -12 -26 -6 4 -7 -1 -3 -11 4 -12 3 -15 -5 -10 -8
|
||||||
|
5 -10 2 -6 -9 4 -9 13 -14 20 -11 7 3 16 0 20 -6 4 -7 3 -8 -4 -4 -8 5 -12 0
|
||||||
|
-12 -13 0 -12 5 -21 12 -21 9 0 9 -3 0 -12 -7 -7 -12 -17 -12 -22 0 -6 5 -4
|
||||||
|
11 4 8 12 10 9 6 -13 -3 -18 0 -30 8 -34 8 -2 16 -12 19 -20 6 -14 4 -14 -10
|
||||||
|
-2 -16 13 -16 12 -4 -11 7 -14 18 -28 24 -32 8 -6 6 -11 -4 -18 -9 -6 -10 -10
|
||||||
|
-3 -10 6 0 13 -10 15 -22 2 -15 13 -24 35 -30 23 -6 37 -19 52 -51 33 -67 199
|
||||||
|
-247 228 -247 7 0 16 -6 20 -13 4 -7 26 -21 48 -32 22 -11 42 -22 45 -25 7 -7
|
||||||
|
53 -27 64 -28 5 0 12 -5 15 -11 4 -5 19 -12 35 -15 15 -3 34 -10 41 -16 10 -8
|
||||||
|
14 -7 18 2 4 10 6 10 6 1 1 -7 6 -13 12 -13 5 0 7 6 3 12 -5 9 -2 9 9 -1 9 -7
|
||||||
|
17 -10 17 -5 0 5 5 2 10 -6 7 -11 10 -11 10 -2 0 7 5 10 10 7 6 -4 8 -11 5
|
||||||
|
-16 -4 -5 -1 -9 4 -9 6 0 11 7 11 16 0 8 4 13 10 9 5 -3 7 -13 4 -23 -4 -10
|
||||||
|
-3 -13 3 -6 8 11 52 12 69 1 5 -3 16 -2 24 3 8 5 22 5 32 -1 12 -6 18 -6 18 1
|
||||||
|
0 5 5 10 11 10 5 0 7 -6 3 -12 -5 -9 -2 -9 9 1 13 10 17 10 17 1 0 -9 4 -9 17
|
||||||
|
1 15 12 16 12 8 -1 -7 -13 -6 -13 7 -2 13 10 17 10 21 0 4 -10 6 -10 6 0 1 6
|
||||||
|
9 12 19 12 10 0 24 6 31 12 11 11 12 10 6 -2 -8 -13 -7 -13 8 -1 9 7 17 11 17
|
||||||
|
7 0 -3 16 0 35 8 19 8 35 12 35 9 0 -3 7 0 15 7 8 7 15 10 15 6 0 -3 32 10 70
|
||||||
|
30 39 21 70 41 70 45 0 4 9 11 21 14 24 8 52 29 82 62 11 12 25 19 31 16 6 -3
|
||||||
|
8 -3 4 2 -4 4 9 22 28 40 18 18 34 37 34 44 0 6 4 11 10 11 5 0 24 25 41 55
|
||||||
|
17 30 35 55 41 55 5 0 7 4 4 9 -3 5 1 14 9 21 8 7 12 16 9 21 -3 5 -2 9 3 8
|
||||||
|
24 -4 43 2 37 13 -4 7 -3 8 5 4 12 -8 44 18 34 28 -4 3 -1 6 5 6 6 0 9 7 6 15
|
||||||
|
-4 8 1 17 10 21 14 5 14 9 4 22 -7 9 -8 13 -2 9 12 -7 38 39 29 53 -3 4 5 10
|
||||||
|
18 14 18 5 19 8 7 15 -12 9 -12 11 3 17 9 3 14 10 11 15 -3 5 0 9 5 9 6 0 11
|
||||||
|
6 11 14 0 8 6 17 13 19 7 4 6 6 -4 6 -9 1 -20 8 -23 17 -4 9 -13 14 -21 10 -8
|
||||||
|
-3 -15 -1 -15 4 0 6 -6 10 -14 10 -17 0 -41 28 -41 49 0 9 -4 22 -8 29 -5 6
|
||||||
|
-4 12 1 12 5 0 6 17 2 40 -5 25 -4 40 3 40 6 0 6 5 0 13 -5 6 -9 56 -9 109 1
|
||||||
|
54 -3 98 -7 98 -5 0 -5 5 -1 12 4 7 6 35 5 62 -2 28 -4 64 -5 80 -1 16 -7 31
|
||||||
|
-14 34 -7 2 -10 8 -7 13 4 5 -1 6 -10 3 -8 -4 -25 0 -38 8 -12 8 -41 20 -64
|
||||||
|
28 -24 7 -43 17 -43 22 0 4 -3 8 -7 7 -16 -2 -53 14 -53 22 0 5 -3 8 -7 7 -10
|
||||||
|
-4 -73 23 -74 31 -2 9 -7 42 -14 86 -3 22 -8 51 -11 65 -4 14 -7 44 -8 67 0
|
||||||
|
22 -4 39 -8 36 -5 -2 -8 0 -8 6 0 10 -93 57 -130 65 -3 1 -15 7 -27 14 -13 7
|
||||||
|
-23 9 -23 5 0 -4 -4 -3 -8 2 -11 16 -102 56 -113 50 -5 -4 -12 1 -15 10 -5 13
|
||||||
|
-9 14 -14 4 -7 -10 -11 -9 -22 2 -8 8 -15 17 -16 22 -1 4 -3 9 -3 12 -1 3 -3
|
||||||
|
10 -4 15 -1 6 -4 12 -5 15 -1 3 -3 8 -4 13 -18 83 -32 132 -37 132 -4 0 -10 8
|
||||||
|
-12 18 -3 9 -5 11 -6 5z"/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 3.8 KiB |
|
@ -12,3 +12,4 @@
|
||||||
@import "submission";
|
@import "submission";
|
||||||
@import "contest";
|
@import "contest";
|
||||||
@import "misc";
|
@import "misc";
|
||||||
|
@import "chatbox";
|
||||||
|
|
|
@ -203,9 +203,7 @@ label[for="language"], label[for="status"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
#test-cases {
|
#test-cases {
|
||||||
.toggle .fa {
|
font-size: 1.1em;
|
||||||
margin-left: -1.28571em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.batch-cases {
|
.batch-cases {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -215,28 +213,13 @@ label[for="language"], label[for="status"] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.batch-cases .case-row td b {
|
// .batch-cases .case-row td b {
|
||||||
font-weight: 500;
|
// font-weight: 500;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.case-row td:nth-child(3) {
|
|
||||||
padding-right: 0;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-row td:nth-child(3) span {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-row td:nth-child(4) {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.case-info {
|
.case-info {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 1em;
|
padding: 1em;
|
||||||
padding-bottom: 3px;
|
|
||||||
padding-top: 3px;
|
|
||||||
border: 1px solid #2980b9;
|
border: 1px solid #2980b9;
|
||||||
border-left-width: .5em;
|
border-left-width: .5em;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -245,18 +228,50 @@ label[for="language"], label[for="status"] {
|
||||||
|
|
||||||
.case-output {
|
.case-output {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-right: 1em;
|
margin-top: 1em;
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(34,36,38,.15);
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 1em 1em;
|
||||||
|
border-radius: .28571429rem;
|
||||||
|
border: 1px solid rgba(34,36,38,.15);
|
||||||
|
font-family: Consolas;
|
||||||
}
|
}
|
||||||
|
|
||||||
table td {
|
#testcases-table {
|
||||||
margin: 0;
|
width: 100%;
|
||||||
padding: 0 5px 0 0;
|
border: 1px solid grey;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
border-spacing: 0;
|
||||||
|
|
||||||
|
thead td:first-child {
|
||||||
|
border-top-left-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead td:last-child {
|
||||||
|
border-top-right-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.6em 0.8em;
|
||||||
|
width: 18.75%;
|
||||||
|
border-bottom: 0.7px grey solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-row td:nth-child(2) {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.case-row:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle {
|
.toggle {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.case-feedback {
|
.case-feedback {
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
@ -266,6 +281,31 @@ label[for="language"], label[for="status"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#overall-row:hover {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overall-result-AC {
|
||||||
|
background: linear-gradient(45deg, #a8ff78, #78ffd6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overall-result-WA {
|
||||||
|
background: linear-gradient(45deg, yellow, red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overall-result-TLE {
|
||||||
|
background: linear-gradient(45deg, #D7DDE8, #757F99);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overall-result-RTE,
|
||||||
|
.overall-result-MLE {
|
||||||
|
background: linear-gradient(45deg, #fceabb, #f8b500)
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-title {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.case-AC {
|
.case-AC {
|
||||||
color: green;
|
color: green;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -42,9 +42,9 @@ $table_header_rounding: 6px;
|
||||||
|
|
||||||
th {
|
th {
|
||||||
height: 2em;
|
height: 2em;
|
||||||
color: #FFF;
|
color: white;
|
||||||
background-color: $widget_black;
|
background-color: $widget_black;
|
||||||
border-color: #555;
|
border-color: #cccccc;
|
||||||
border-width: 1px 1px 0 0;
|
border-width: 1px 1px 0 0;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
$highlight_blue: #2980B9;
|
$highlight_blue: white;
|
||||||
$widget_black: #3b3b3b;
|
$widget_black: #00007d;//68BBE3, add8e6
|
||||||
$border_gray: #ccc;
|
$border_gray: #ccc;
|
||||||
$background_gray: #ededed;
|
$background_gray: #ededed;
|
||||||
$background_light_gray: #fafafa;
|
$background_light_gray: #fafafa;
|
||||||
|
|
|
@ -305,8 +305,8 @@ ul.pagination {
|
||||||
.active-page > {
|
.active-page > {
|
||||||
a {
|
a {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
color: #FFF;
|
color: black;
|
||||||
background-color: $highlight_blue;
|
background-color: #7dc7ff;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block js_media %}
|
{% block js_media %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var chatSocket = new WebSocket(
|
var chatSocket = new WebSocket(
|
||||||
|
@ -8,33 +7,166 @@
|
||||||
</script>
|
</script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(function() {
|
$(function() {
|
||||||
|
let currentPage = 1;
|
||||||
|
|
||||||
|
$('#loader').hide();
|
||||||
|
|
||||||
chatSocket.onmessage = function(e) {
|
chatSocket.onmessage = function(e) {
|
||||||
var data = JSON.parse(e.data);
|
let data = JSON.parse(e.data)
|
||||||
var message = data['message'];
|
data = data['message']
|
||||||
$('#chat-log').append(message + '\n');
|
loadMessage(data['body'],
|
||||||
|
data['author'],
|
||||||
|
data['time'],
|
||||||
|
data['image'],
|
||||||
|
true)
|
||||||
|
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function encodeHTML(content) {
|
||||||
|
return content.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
|
||||||
|
return '&#'+i.charCodeAt(0)+';';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMessage(content, user, time, image, isNew) {
|
||||||
|
if (isNew) content = encodeHTML(content)
|
||||||
|
li = `<li class="message">
|
||||||
|
<img src="${image}" class="profile-pic">
|
||||||
|
<div class="body-message">
|
||||||
|
<div class="user-time">
|
||||||
|
<a href="{{ url('user_page') }}/${user}" class="user">
|
||||||
|
${user}
|
||||||
|
</a>
|
||||||
|
<span class="time">${time}</span>
|
||||||
|
</div>
|
||||||
|
<span class="content-message">${content} </span>
|
||||||
|
</div>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</li>`
|
||||||
|
ul = $('#chat-log')
|
||||||
|
if (isNew) {
|
||||||
|
ul.append(li)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ul.prepend(li)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(function init_chatlog() {
|
||||||
|
ul = $('#chat-log')
|
||||||
|
{% for msg in message %}
|
||||||
|
loadMessage(`{{msg.body}}`, `{{msg.author}}`, `{{msg.time}}`, `{{gravatar(msg.author, 32)}}`)
|
||||||
|
{% endfor %}
|
||||||
|
$('#chat-box').scrollTop($('#chat-box')[0].scrollHeight);
|
||||||
|
})()
|
||||||
|
|
||||||
|
function scrollTopOfBottom(container) {
|
||||||
|
return container[0].scrollHeight - container.innerHeight()
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollContainer(container, loader) {
|
||||||
|
container.scroll(function() {
|
||||||
|
if (container.scrollTop() == 0) {
|
||||||
|
if (currentPage < {{paginator.num_pages}}) {
|
||||||
|
currentPage++;
|
||||||
|
loader.show();
|
||||||
|
$.ajax({
|
||||||
|
url: `{{request.path}}?page=${currentPage}`,
|
||||||
|
success: function(data) {
|
||||||
|
let lastMsg = $('.message:first')
|
||||||
|
let lastMsgPos = scrollTopOfBottom(container)
|
||||||
|
|
||||||
|
data = JSON.parse(data)
|
||||||
|
setTimeout( () => {
|
||||||
|
for (msg of data) {
|
||||||
|
loadMessage(msg.body, msg.author, msg.time, msg.image)
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.hide()
|
||||||
|
|
||||||
|
// scroll to last msg
|
||||||
|
container.scrollTop(
|
||||||
|
scrollTopOfBottom(container) - lastMsgPos
|
||||||
|
)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
|
||||||
|
scrollContainer($('#chat-box'), $('#loader'))
|
||||||
|
|
||||||
|
|
||||||
|
$("#chat-submit").click(function() {
|
||||||
|
if ($("#chat-input").val().trim()) {
|
||||||
|
let body = $('#chat-input').val().trim();
|
||||||
|
let img = '{{ gravatar(request.user, 32) }}'
|
||||||
|
|
||||||
|
message = {
|
||||||
|
'body': body,
|
||||||
|
'image': img,
|
||||||
|
'author': '{{request.profile}}',
|
||||||
|
'author_id': {{request.profile.id}},
|
||||||
|
}
|
||||||
|
|
||||||
|
chatSocket.send(JSON.stringify({
|
||||||
|
'message': message
|
||||||
|
}));
|
||||||
|
|
||||||
|
$('#chat-input').val('').focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
chatSocket.onclose = function(e) {
|
chatSocket.onclose = function(e) {
|
||||||
console.error('Chat socket closed unexpectedly');
|
console.error('Chat socket closed unexpectedly');
|
||||||
};
|
};
|
||||||
$('#chat-message-input').focus();
|
|
||||||
$('#chat-message-input').keyup(function(e) {
|
$("#chat-log").change(function() {
|
||||||
if (e.keyCode === 13) { // enter, return
|
$('#chat-log').scrollTop($('#chat-log')[0].scrollHeight);
|
||||||
$('#chat-message-submit').click();
|
});
|
||||||
|
|
||||||
|
$('#chat-input').focus();
|
||||||
|
|
||||||
|
$('#chat-input').keydown(function(e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
if (e.ctrlKey || e.shiftKey) {
|
||||||
|
var val = this.value;
|
||||||
|
if (typeof this.selectionStart == "number" && typeof this.selectionEnd == "number") {
|
||||||
|
var start = this.selectionStart;
|
||||||
|
this.value = val.slice(0, start) + "\n" + val.slice(this.selectionEnd);
|
||||||
|
this.selectionStart = this.selectionEnd = start + 1;
|
||||||
|
} else if (document.selection && document.selection.createRange) {
|
||||||
|
this.focus();
|
||||||
|
var range = document.selection.createRange();
|
||||||
|
range.text = "\r\n";
|
||||||
|
range.collapse(false);
|
||||||
|
range.select();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#chat-submit').click();
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
});
|
});
|
||||||
$("#chat-message-submit").click(function() {
|
|
||||||
var message = "{{ request.user }}: " + $('input#chat-message-input').val();
|
|
||||||
chatSocket.send(JSON.stringify({
|
|
||||||
'message': message,
|
|
||||||
}));
|
|
||||||
$('input#chat-message-input').val('');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock js_media %}
|
{% endblock js_media %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<textarea id="chat-log" cols="100" rows="20"></textarea><br/>
|
<div id="chat-area">
|
||||||
<input id="chat-message-input" type="text" size="100"/><br/>
|
<div id="chat-box">
|
||||||
<button id="chat-message-submit"> Send </button>
|
<img src="http://opengraphicdesign.com/wp-content/uploads/2009/01/loader64.gif" id="loader">
|
||||||
|
<ul id="chat-log">
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{_('Your message')}}
|
||||||
|
|
||||||
|
<textarea rows="6" id="chat-input"></textarea>
|
||||||
|
</div>
|
||||||
|
<button id="chat-submit"> Send </button>
|
||||||
{% endblock body %}
|
{% endblock body %}
|
||||||
|
|
|
@ -95,21 +95,29 @@
|
||||||
}).change();
|
}).change();
|
||||||
}
|
}
|
||||||
|
|
||||||
function checker_custom($checker, $custom_checker) {
|
(function toggle_custom() {
|
||||||
$tr = $custom_checker.parent().parent();
|
let $custom_checker = $('#id_problem-data-custom_checker')
|
||||||
|
let $checker = $('#id_problem-data-checker')
|
||||||
|
let $validator = $('#id_problem-data-custom_validator')
|
||||||
|
|
||||||
|
$tr_checker = $custom_checker.parent().parent();
|
||||||
|
$tr_validator = $validator.parent().parent()
|
||||||
|
|
||||||
$td = $checker.parent();
|
$td = $checker.parent();
|
||||||
var $sample = $("<a/>",{
|
var $sample = $("<a/>",{
|
||||||
text: "Sample Checker",
|
text: "{{_('Instruction')}}",
|
||||||
style: "margin-left:3em;",
|
style: "margin-left:3em;",
|
||||||
target: "_blank",
|
target: "_blank",
|
||||||
href: "{{url('custom_checker_sample')}}"
|
href: "{{url('custom_checker_sample')}}"
|
||||||
}).appendTo($td);
|
}).appendTo($td);
|
||||||
|
|
||||||
$checker.change(function () {
|
$checker.change(function () {
|
||||||
$tr.toggle($checker.val().startsWith('custom')).change();
|
$tr_checker.toggle($checker.val() == 'custom').change();
|
||||||
|
$tr_validator.toggle($checker.val() == 'customval').change();
|
||||||
$sample.toggle($checker.val().startsWith('custom')).change();
|
$sample.toggle($checker.val().startsWith('custom')).change();
|
||||||
}).change();
|
}).change();
|
||||||
}
|
})();
|
||||||
|
|
||||||
|
|
||||||
function swap_row($a, $b) {
|
function swap_row($a, $b) {
|
||||||
var $a_order = $a.find('input[id$=order]'), $b_order = $b.find('input[id$=order]');
|
var $a_order = $a.find('input[id$=order]'), $b_order = $b.find('input[id$=order]');
|
||||||
|
@ -123,7 +131,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
checker_precision($('#id_problem-data-checker'));
|
checker_precision($('#id_problem-data-checker'));
|
||||||
checker_custom($('#id_problem-data-checker'), $('#id_problem-data-custom_checker'));
|
|
||||||
|
|
||||||
$table.on('add-row', function (e, $tr) {
|
$table.on('add-row', function (e, $tr) {
|
||||||
var $order = $tr.find('input').filter('[id$=order]').attr('type', 'hidden').val(++order);
|
var $order = $tr.find('input').filter('[id$=order]').attr('type', 'hidden').val(++order);
|
||||||
|
@ -225,7 +232,7 @@
|
||||||
$('a#fill-testcases').click(function () {
|
$('a#fill-testcases').click(function () {
|
||||||
var inFiles = [], outFiles = [];
|
var inFiles = [], outFiles = [];
|
||||||
for (var i = 0; i < window.valid_files.length; i++) {
|
for (var i = 0; i < window.valid_files.length; i++) {
|
||||||
if (window.valid_files[i].endsWith(".in")) {
|
if (window.valid_files[i].endsWith(".in") || window.valid_files[i].endsWith(".inp")) {
|
||||||
inFiles.push(window.valid_files[i]);
|
inFiles.push(window.valid_files[i]);
|
||||||
}
|
}
|
||||||
if (window.valid_files[i].endsWith(".out")) {
|
if (window.valid_files[i].endsWith(".out")) {
|
||||||
|
@ -233,7 +240,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (inFiles.length == 0) {
|
if (inFiles.length == 0) {
|
||||||
alert("No input/output files. Make sure your files' suffices are .in/.out");
|
alert("No input/output files. Make sure your files' suffices are .in(p)/.out");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (inFiles.length != outFiles.length) {
|
if (inFiles.length != outFiles.length) {
|
||||||
|
@ -248,6 +255,9 @@
|
||||||
for (var i = 0; i < inFiles.length; i++) {
|
for (var i = 0; i < inFiles.length; i++) {
|
||||||
$("#id_cases-" + i + "-input_file").val(inFiles[i]).change();
|
$("#id_cases-" + i + "-input_file").val(inFiles[i]).change();
|
||||||
}
|
}
|
||||||
|
for (var i = 0; i < outFiles.length; i++) {
|
||||||
|
$("#id_cases-" + i + "-output_file").val(outFiles[i]).change();
|
||||||
|
}
|
||||||
// add points
|
// add points
|
||||||
if ($('#problem-type').val() == "ICPC") {
|
if ($('#problem-type').val() == "ICPC") {
|
||||||
for (i = 0; i + 1 < inFiles.length; i++) {
|
for (i = 0; i + 1 < inFiles.length; i++) {
|
||||||
|
@ -303,26 +313,6 @@
|
||||||
$("input[name$='DELETE']").attr('checked', false);
|
$("input[name$='DELETE']").attr('checked', false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var $controls = $('#column-visible');
|
|
||||||
var problem = $controls.attr('data-problem');
|
|
||||||
$controls.find('input').change(function () {
|
|
||||||
var $this = $(this), suffix = $this.attr('data-suffix'), checked = $this.is(':checked');
|
|
||||||
$table.find('.' + suffix.replace(/_/g, '-')).toggle(checked);
|
|
||||||
localStorage.setItem('data-visible:' + problem + ':' + suffix, checked ? '1' : '0')
|
|
||||||
}).each(function () {
|
|
||||||
var $this = $(this), suffix = $this.attr('data-suffix'), filled = false;
|
|
||||||
filled = localStorage.getItem('data-visible:' + problem + ':' + suffix);
|
|
||||||
if (filled !== null)
|
|
||||||
filled = filled == '1';
|
|
||||||
else {
|
|
||||||
filled = false;
|
|
||||||
$table.find('[id$=' + suffix + ']').each(function () {
|
|
||||||
filled |= !!$(this).val();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$this.prop('checked', filled).trigger('change');
|
|
||||||
});
|
|
||||||
}).change();
|
}).change();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -448,29 +438,7 @@
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</table>
|
</table>
|
||||||
<div id="column-visible" data-problem="{{ problem.code }}">
|
<input type="submit" value="{{ _('Apply!') }}" class="button" id="submit-button">
|
||||||
<strong>{{ _('Show columns:') }}</strong>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" data-suffix="output_prefix">
|
|
||||||
{{ _('Output prefix') }}
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" data-suffix="output_limit">
|
|
||||||
{{ _('Output limit') }}
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" data-suffix="checker">
|
|
||||||
{{ _('Checker') }}
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" data-suffix="generator_args">
|
|
||||||
{{ _('Generator args') }}
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="delete-all">
|
|
||||||
{{ _('Delete all') }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<table id="case-table" class="table">
|
<table id="case-table" class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -480,12 +448,10 @@
|
||||||
<th>{{ _('Output file') }}</th>
|
<th>{{ _('Output file') }}</th>
|
||||||
<th>{{ _('Points') }}</th>
|
<th>{{ _('Points') }}</th>
|
||||||
<th>{{ _('Pretest?') }}</th>
|
<th>{{ _('Pretest?') }}</th>
|
||||||
<th class="output-prefix">{{ _('Output prefix') }}</th>
|
|
||||||
<th class="output-limit">{{ _('Output limit') }}</th>
|
|
||||||
<th class="checker">{{ _('Checker') }}</th>
|
|
||||||
<th class="generator-args">{{ _('Generator args') }}</th>
|
|
||||||
{% if cases_formset.can_delete %}
|
{% if cases_formset.can_delete %}
|
||||||
<th>{{ _('Delete?') }}
|
<th>{{ _('Delete?') }}
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" name="delete-all" id="delete-all">
|
||||||
</th>
|
</th>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -514,25 +480,15 @@
|
||||||
</td>
|
</td>
|
||||||
<td>{{ form.points.errors }}{{ form.points }}</td>
|
<td>{{ form.points.errors }}{{ form.points }}</td>
|
||||||
<td>{{ form.is_pretest.errors }}{{ form.is_pretest }}</td>
|
<td>{{ form.is_pretest.errors }}{{ form.is_pretest }}</td>
|
||||||
<td class="output-prefix">{{ form.output_prefix.errors }}{{ form.output_prefix }}</td>
|
|
||||||
<td class="output-limit">{{ form.output_limit.errors }}{{ form.output_limit }}</td>
|
|
||||||
<td class="checker">
|
|
||||||
{{ form.checker.errors }}{{ form.checker }}{{ form.checker_args.errors }}{{ form.checker_args }}
|
|
||||||
</td>
|
|
||||||
<td class="generator-args">{{ form.generator_args.errors }}{{ form.generator_args }}
|
|
||||||
<a href="javascript:void(0)" class="edit-generator-args">
|
|
||||||
<i class="fa fa-pencil"></i>
|
|
||||||
{{ _('Edit') }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
{% if cases_formset.can_delete %}
|
{% if cases_formset.can_delete %}
|
||||||
<td>{{ form.DELETE }}</td>
|
<td>{{ form.DELETE }}
|
||||||
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<input type="submit" value="{{ _('Submit!') }}" class="button" id="submit-button">
|
<input type="submit" value="{{ _('Apply!') }}" class="button" id="submit-button">
|
||||||
<a id="add-case-row" href="#"><i class="fa fa-plus"></i> {{ _('Add new case') }}</a>
|
<a id="add-case-row" href="#"><i class="fa fa-plus"></i> {{ _('Add new case') }}</a>
|
||||||
</form>
|
</form>
|
||||||
<div style="display: none" class="generator-args-editor"><textarea></textarea><a class="button">Save</a></div>
|
<div style="display: none" class="generator-args-editor"><textarea></textarea><a class="button">Save</a></div>
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<br>
|
<br>
|
||||||
<hr class="half-hr">
|
<hr>
|
||||||
<br>
|
<br>
|
||||||
<div class="source-wrap">
|
<div class="source-wrap">
|
||||||
<table>
|
<table>
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
{% if submission.error %}
|
{% if submission.error %}
|
||||||
<h3>{{ _('Compilation Warnings') }}</h3>
|
<h3>{{ _('Compilation Warnings') }}</h3>
|
||||||
<pre>{{ submission.error|ansi2html }}</pre>
|
<pre>{{ submission.error|ansi2html }}</pre>
|
||||||
<hr class="half-hr"><br>
|
<hr><br>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if is_pretest %}
|
{% if is_pretest %}
|
||||||
<h3>{{ _('Pretest Execution Results') }}</h3>
|
<h3>{{ _('Pretest Execution Results') }}</h3>
|
||||||
|
@ -33,7 +33,48 @@
|
||||||
<br>
|
<br>
|
||||||
<div class="batch-cases">
|
<div class="batch-cases">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<table>{% for case in batch.cases %}
|
<table id="testcases-table">
|
||||||
|
{% if submission.is_graded %}
|
||||||
|
{% if submission.result != 'AB' %}
|
||||||
|
<thead>
|
||||||
|
|
||||||
|
<tr id="overall-row" class="case-row overall-result-{{submission.result}}">
|
||||||
|
|
||||||
|
<td><span class="col-title">{{_('Overall: ')}}</span>
|
||||||
|
{% if request.in_contest and submission.contest_or_none %}
|
||||||
|
{% with contest=submission.contest_or_none %}
|
||||||
|
({{ _('%(points)s/%(total)s points', points=contest.points|roundfloat(3),
|
||||||
|
total=contest.problem.points|floatformat(-1)) }})
|
||||||
|
{% endwith %}
|
||||||
|
{% else %}
|
||||||
|
{{ _('%(points)s/%(total)s', points=submission.points|roundfloat(3),
|
||||||
|
total=submission.problem.points|floatformat(-1)) }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><span class="case-{{submission.result}}">{{submission.long_status}}</span></td>
|
||||||
|
|
||||||
|
<td><span class="col-title">{{_('Point: ')}}</span>
|
||||||
|
{{ submission.case_points|floatformat(0) }}/{{ submission.case_total|floatformat(0) }}
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><span class="col-title">{{_('Time: ')}}</span>
|
||||||
|
{% if submission.result == "TLE" %}
|
||||||
|
<span>---</span>
|
||||||
|
{% else %}
|
||||||
|
<span title="{{ submission.time }}s">{{ (submission.time * 1000)|floatformat(0) }} ms</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td><span class="col-title">{{_('Memory: ')}}</span>{{ submission.memory|kbdetailformat }}</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for case in batch.cases %}
|
||||||
<tr id="{{ case.id }}" class="case-row toggle closed">
|
<tr id="{{ case.id }}" class="case-row toggle closed">
|
||||||
<td>
|
<td>
|
||||||
{%- if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) -%}
|
{%- if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) -%}
|
||||||
|
@ -50,26 +91,25 @@
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span title="{{ case.long_status }}" class="case-{{ case.status }}">
|
<span title="{{ case.long_status }}" class="case-{{ case.status }}">
|
||||||
{%- if case.status == 'SC' %}—{% else %}{{ case.status }}{% endif -%}
|
{%- if case.status == 'SC' %}—{% else %}{{ case.long_status }}{% endif -%}
|
||||||
</span>
|
</span>
|
||||||
{%- if case.feedback %} ({{ case.feedback }}){% endif -%}
|
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{% if not batch.id %}
|
||||||
|
<td><span class="col-title">{{_('Point')}}: </span> {{ case.points|floatformat(0) }}/{{ case.total|floatformat(0) }}</td>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
{%- if case.status != 'SC' -%}
|
{%- if case.status != 'SC' -%}
|
||||||
{%- if case.status == 'TLE' -%}
|
{%- if case.status == 'TLE' -%}
|
||||||
[><span>{{ time_limit|floatformat(3) }}s,</span>
|
<span><span class="col-title">{{_('Time')}}: ></span>{{ (time_limit * 1000)|floatformat(0) }} ms</span>
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
[<span title="{{ case.time }}s">{{ case.time|floatformat(3) }}s,</span>
|
<span title="{{ case.time }}s"><span class="col-title">{{_('Time')}}: </span>{{ (case.time * 1000)|floatformat(0) }} ms</span>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>{% if case.status != 'SC' %}{{ case.memory|kbdetailformat }}]{% endif %}</td>
|
<td>{% if case.status != 'SC' %}<span class="col-title">{{_('Memory')}}: </span> {{ case.memory|kbdetailformat }}{% endif %}</td>
|
||||||
|
|
||||||
{% if not batch.id %}
|
|
||||||
<td>({{ case.points|floatformat(0) }}/{{ case.total|floatformat(0) }})</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) %}
|
{% if ((prefix_length is none or prefix_length > 0) or (request.user.is_superuser)) %}
|
||||||
|
@ -77,22 +117,30 @@
|
||||||
<td colspan="5">
|
<td colspan="5">
|
||||||
<div class="case-info">
|
<div class="case-info">
|
||||||
{% set curr_data = cases_data[case.case] %}
|
{% set curr_data = cases_data[case.case] %}
|
||||||
|
{% if curr_data != null %}
|
||||||
<strong>{{ _('Input:') }}</strong>
|
<strong>{{ _('Input:') }}</strong>
|
||||||
<pre class="case-output">{{ curr_data['input']|linebreaksbr }}</pre>
|
<pre class="case-output">{{ curr_data['input']|linebreaksbr }}</pre>
|
||||||
<strong>{{ _('Your output:') }}</strong>
|
{% endif %}
|
||||||
|
|
||||||
|
<strong>{{ _('Output:') }}</strong>
|
||||||
<pre class="case-output">{{ case.output|linebreaksbr }}</pre>
|
<pre class="case-output">{{ case.output|linebreaksbr }}</pre>
|
||||||
|
|
||||||
|
{% if curr_data != null %}
|
||||||
<strong>{{ _('Answer:') }}</strong>
|
<strong>{{ _('Answer:') }}</strong>
|
||||||
<pre class="case-output">{{ curr_data['answer']|linebreaksbr }}</pre>
|
<pre class="case-output">{{ curr_data['answer']|linebreaksbr }}</pre>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if case.extended_feedback or case.feedback %}
|
||||||
|
<strong>{{ _('Judge feedback:') }}</strong>
|
||||||
|
{% if case.feedback %}
|
||||||
|
<pre class="case-output">{{ case.feedback|linebreaksbr }}</pre>
|
||||||
|
{% endif %}
|
||||||
|
{% if case.extended_feedback %}
|
||||||
|
<pre class="case-output">{{ case.extended_feedback|linebreaksbr }}</pre>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{% if case.extended_feedback %}
|
|
||||||
<td colspan="5" class="case-ext-feedback">
|
|
||||||
<div class="case-info">
|
|
||||||
<strong>{{ _('Judge feedback') }}</strong>
|
|
||||||
<pre class="case-output">{{ case.extended_feedback|linebreaksbr }}</pre>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
{% endif %}
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -104,29 +152,6 @@
|
||||||
{% if submission.is_graded %}
|
{% if submission.is_graded %}
|
||||||
<br>
|
<br>
|
||||||
{% if submission.result != "AB" %}
|
{% if submission.result != "AB" %}
|
||||||
<b>{{ _('Resources:') }}</b>
|
|
||||||
{% if submission.result == "TLE" %}
|
|
||||||
<span>---,</span>
|
|
||||||
{% else %}
|
|
||||||
<span title="{{ submission.time }}s">{{ submission.time|floatformat(3) }}s,</span>
|
|
||||||
{% endif %}
|
|
||||||
{{ submission.memory|kbdetailformat }}
|
|
||||||
<br>
|
|
||||||
{% if is_pretest %}
|
|
||||||
<b>{{ _('Final pretest score:') }}</b>
|
|
||||||
{% else %}
|
|
||||||
<b>{{ _('Final score:') }}</b>
|
|
||||||
{% endif %}
|
|
||||||
{{ submission.case_points|floatformat(0) }}/{{ submission.case_total|floatformat(0) }}
|
|
||||||
{% if request.in_contest and submission.contest_or_none %}
|
|
||||||
{% with contest=submission.contest_or_none %}
|
|
||||||
({{ _('%(points)s/%(total)s points', points=contest.points|roundfloat(3),
|
|
||||||
total=contest.problem.points|floatformat(-1)) }})
|
|
||||||
{% endwith %}
|
|
||||||
{% else %}
|
|
||||||
({{ _('%(points)s/%(total)s points', points=submission.points|roundfloat(3),
|
|
||||||
total=submission.problem.points|floatformat(-1)) }})
|
|
||||||
{% endif %}
|
|
||||||
{% if is_pretest and submission.result == "AC" %}
|
{% if is_pretest and submission.result == "AC" %}
|
||||||
<br>
|
<br>
|
||||||
<i>{{ _('Passing pretests does not guarantee a full score on system tests.') }}</i>
|
<i>{{ _('Passing pretests does not guarantee a full score on system tests.') }}</i>
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
<hr class="half-hr">
|
<hr>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div id="test-cases">{% include "submission/status-testcases.html" %}</div>
|
<div id="test-cases">{% include "submission/status-testcases.html" %}</div>
|
||||||
|
@ -81,7 +81,7 @@
|
||||||
{% if request.user == submission.user.user or perms.judge.abort_any_submission %}
|
{% if request.user == submission.user.user or perms.judge.abort_any_submission %}
|
||||||
<div id="abort-button">
|
<div id="abort-button">
|
||||||
<br>
|
<br>
|
||||||
<hr class="half-hr">
|
<hr>
|
||||||
<br>
|
<br>
|
||||||
<form action="{{ url('submission_abort', submission.id) }}" method="post">
|
<form action="{{ url('submission_abort', submission.id) }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
50
validator_template/template.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from dmoj.contrib import contrib_modules
|
||||||
|
from dmoj.error import InternalError
|
||||||
|
from dmoj.judgeenv import env, get_problem_root
|
||||||
|
from dmoj.result import CheckerResult
|
||||||
|
from dmoj.utils.helper_files import compile_with_auxiliary_files, mktemp
|
||||||
|
from dmoj.utils.unicode import utf8text
|
||||||
|
|
||||||
|
executor = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_executor(files, lang, compiler_time_limit, problem_id):
|
||||||
|
global executor
|
||||||
|
|
||||||
|
if executor is None:
|
||||||
|
if not isinstance(files, list):
|
||||||
|
files = [files]
|
||||||
|
filenames = [os.path.join(get_problem_root(problem_id), f) for f in files]
|
||||||
|
executor = compile_with_auxiliary_files(filenames, lang, compiler_time_limit)
|
||||||
|
|
||||||
|
return executor
|
||||||
|
|
||||||
|
|
||||||
|
def check(process_output, judge_output, judge_input,
|
||||||
|
problem_id={{problemid}},
|
||||||
|
files={{filecpp}},
|
||||||
|
lang='CPP14',
|
||||||
|
time_limit=10,
|
||||||
|
memory_limit=1024**2,
|
||||||
|
compiler_time_limit=10,
|
||||||
|
feedback=True, type='default',
|
||||||
|
point_value=None, **kwargs) -> CheckerResult:
|
||||||
|
executor = get_executor(files, lang, compiler_time_limit, problem_id)
|
||||||
|
|
||||||
|
if type not in contrib_modules:
|
||||||
|
raise InternalError('%s is not a valid return code parser' % type)
|
||||||
|
|
||||||
|
with mktemp(judge_input) as input_file, mktemp(process_output) as output_file, mktemp(judge_output) as judge_file:
|
||||||
|
process = executor.launch(input_file.name, output_file.name, judge_file.name, stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE, memory=memory_limit, time=time_limit)
|
||||||
|
|
||||||
|
proc_output, error = map(utf8text, process.communicate())
|
||||||
|
|
||||||
|
return contrib_modules[type].ContribModule.parse_return_code(process, executor, point_value, time_limit,
|
||||||
|
memory_limit,
|
||||||
|
feedback=utf8text(proc_output)
|
||||||
|
if feedback else None, name='checker',
|
||||||
|
stderr=error)
|