mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
ui: 允许自定义首页布局
This commit is contained in:
1 parent
dbf51849d5
commit
cc08538bdb
12 files changed
+248
-188
No files matched your search
@@ -156,7 +156,6 @@ contest_edit: 编辑比赛
|
|||||||
contest_main: 比赛
|
contest_main: 比赛
|
||||||
contest_scoreboard: 比赛成绩表
|
contest_scoreboard: 比赛成绩表
|
||||||
Contest: 比赛
|
Contest: 比赛
|
||||||
Contests on main: 主页展示的比赛数量
|
|
||||||
Contests per page: 每页展示的比赛数量
|
Contests per page: 每页展示的比赛数量
|
||||||
Continue: 继续
|
Continue: 继续
|
||||||
Contributed Problems: 贡献的题目
|
Contributed Problems: 贡献的题目
|
||||||
@@ -243,7 +242,6 @@ discussion_edit: 编辑讨论
|
|||||||
discussion_main: 讨论
|
discussion_main: 讨论
|
||||||
discussion_node: 讨论
|
discussion_node: 讨论
|
||||||
Discussion: 讨论
|
Discussion: 讨论
|
||||||
Discussions on main: 主页展示的讨论数量
|
|
||||||
Discussions per page: 每页展示的讨论数量
|
Discussions per page: 每页展示的讨论数量
|
||||||
Discussions: 讨论
|
Discussions: 讨论
|
||||||
Display name {1} you want to set is used by others.: 您想要设置的显示名 {1} 已经被其他人使用了。
|
Display name {1} you want to set is used by others.: 您想要设置的显示名 {1} 已经被其他人使用了。
|
||||||
@@ -367,7 +365,6 @@ homework_main: 作业
|
|||||||
homework_scoreboard: 作业成绩表
|
homework_scoreboard: 作业成绩表
|
||||||
homework_status: 作业成绩表
|
homework_status: 作业成绩表
|
||||||
Homework: 作业
|
Homework: 作业
|
||||||
Homeworks on main: 主页展示的作业数量
|
|
||||||
Homeworks per page: 每页展示的作业数量
|
Homeworks per page: 每页展示的作业数量
|
||||||
Host: 主办人
|
Host: 主办人
|
||||||
hour(s): 小时
|
hour(s): 小时
|
||||||
@@ -779,7 +776,6 @@ training_detail: 训练详情
|
|||||||
training_edit: 编辑训练
|
training_edit: 编辑训练
|
||||||
training_main: 训练
|
training_main: 训练
|
||||||
Training: 训练
|
Training: 训练
|
||||||
Trainings on main: 主页展示的训练数量
|
|
||||||
Trainings per page: 每页展示的训练数量
|
Trainings per page: 每页展示的训练数量
|
||||||
Type: 类型
|
Type: 类型
|
||||||
UI Language: 用户界面语言
|
UI Language: 用户界面语言
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "2.27.0",
|
"version": "2.27.1",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "dist/loader.js",
|
"main": "dist/loader.js",
|
||||||
"typings": "dist/loader.d.ts",
|
"typings": "dist/loader.d.ts",
|
||||||
|
|||||||
@@ -1,3 +1,11 @@
|
|||||||
|
homepage:
|
||||||
|
type: yaml
|
||||||
|
desc: Homepage Config
|
||||||
|
default:
|
||||||
|
contest: 5
|
||||||
|
homework: 10
|
||||||
|
training: 10
|
||||||
|
discussion: 20
|
||||||
langs:
|
langs:
|
||||||
type: yaml
|
type: yaml
|
||||||
desc: Code language settings
|
desc: Code language settings
|
||||||
|
|||||||
@@ -30,85 +30,90 @@ import BlackListModel from '../model/blacklist';
|
|||||||
const { geoip, useragent } = global.Hydro.lib;
|
const { geoip, useragent } = global.Hydro.lib;
|
||||||
|
|
||||||
class HomeHandler extends Handler {
|
class HomeHandler extends Handler {
|
||||||
async homework(domainId: string) {
|
uids = new Set<number>();
|
||||||
|
|
||||||
|
collectUser(uids: number[]) {
|
||||||
|
uids.forEach((uid) => this.uids.add(uid));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getHomework(domainId: string, limit: number = 5) {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_HOMEWORK)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_HOMEWORK)) {
|
||||||
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK)
|
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK)
|
||||||
.sort('beginAt', -1)
|
.sort('beginAt', -1).limit(limit).toArray();
|
||||||
.limit(system.get('pagination.homework_main'))
|
|
||||||
.toArray();
|
|
||||||
const tsdict = await contest.getListStatus(
|
const tsdict = await contest.getListStatus(
|
||||||
domainId, this.user._id,
|
domainId, this.user._id,
|
||||||
tdocs.map((tdoc) => tdoc.docId), document.TYPE_HOMEWORK,
|
tdocs.map((tdoc) => tdoc.docId), document.TYPE_HOMEWORK,
|
||||||
);
|
);
|
||||||
return [tdocs, tsdict];
|
return ['homework', tdocs, tsdict];
|
||||||
}
|
}
|
||||||
return [[], {}];
|
return ['homework', [], {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async contest(domainId: string) {
|
async getContest(domainId: string, limit: number = 10) {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_CONTEST)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_CONTEST)) {
|
||||||
const tdocs = await contest.getMulti(domainId)
|
const tdocs = await contest.getMulti(domainId)
|
||||||
.sort('beginAt', -1)
|
.sort('beginAt', -1).limit(limit).toArray();
|
||||||
.limit(system.get('pagination.contest_main'))
|
|
||||||
.toArray();
|
|
||||||
const tsdict = await contest.getListStatus(
|
const tsdict = await contest.getListStatus(
|
||||||
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
||||||
);
|
);
|
||||||
return [tdocs, tsdict];
|
return ['contest', tdocs, tsdict];
|
||||||
}
|
}
|
||||||
return [[], {}];
|
return ['contest', [], {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async training(domainId: string) {
|
async getTraining(domainId: string, limit: number = 10) {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_TRAINING)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_TRAINING)) {
|
||||||
const tdocs = await training.getMulti(domainId)
|
const tdocs = await training.getMulti(domainId)
|
||||||
.sort('_id', 1)
|
.sort('_id', 1).limit(limit).toArray();
|
||||||
.limit(system.get('pagination.training_main'))
|
|
||||||
.toArray();
|
|
||||||
const tsdict = await training.getListStatus(
|
const tsdict = await training.getListStatus(
|
||||||
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
domainId, this.user._id, tdocs.map((tdoc) => tdoc.docId),
|
||||||
);
|
);
|
||||||
return [tdocs, tsdict];
|
return ['training', tdocs, tsdict];
|
||||||
}
|
}
|
||||||
return [[], {}];
|
return ['training', [], {}];
|
||||||
}
|
}
|
||||||
|
|
||||||
async discussion(domainId: string): Promise<[any[], any]> {
|
async getDiscussion(domainId: string, limit: number = 20) {
|
||||||
if (this.user.hasPerm(PERM.PERM_VIEW_DISCUSSION)) {
|
if (this.user.hasPerm(PERM.PERM_VIEW_DISCUSSION)) {
|
||||||
const ddocs = await discussion.getMulti(domainId)
|
const ddocs = await discussion.getMulti(domainId).limit(limit).toArray();
|
||||||
.limit(system.get('pagination.discussion_main'))
|
|
||||||
.toArray();
|
|
||||||
const vndict = await discussion.getListVnodes(
|
const vndict = await discussion.getListVnodes(
|
||||||
domainId, ddocs, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN),
|
domainId, ddocs, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN),
|
||||||
);
|
);
|
||||||
return [ddocs, vndict];
|
this.collectUser(ddocs.map((ddoc) => ddoc.owner));
|
||||||
|
return ['discussion', ddocs, vndict];
|
||||||
}
|
}
|
||||||
return [[], {}];
|
return ['discussion', [], {}];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRanking(domainId: string, limit: number = 50) {
|
||||||
|
if (this.user.hasPerm(PERM.PERM_VIEW_RANKING)) {
|
||||||
|
const dudocs = await domain.getMultiUserInDomain(domainId)
|
||||||
|
.sort({ rp: -1 }).project({ uid: 1 }).limit(limit).toArray();
|
||||||
|
const uids = dudocs.map((dudoc) => dudoc.uid);
|
||||||
|
this.collectUser(uids);
|
||||||
|
return ['ranking', uids];
|
||||||
|
}
|
||||||
|
return ['ranking', []];
|
||||||
}
|
}
|
||||||
|
|
||||||
async get({ domainId }) {
|
async get({ domainId }) {
|
||||||
const [
|
const homepageConfig = system.get('hydrooj.homepage');
|
||||||
[htdocs, htsdict], [tdocs, tsdict],
|
const tasks = [];
|
||||||
[trdocs, trsdict], [ddocs, vndict],
|
const info = yaml.load(homepageConfig) as any;
|
||||||
] = await Promise.all([
|
for (const key in info) {
|
||||||
this.homework(domainId), this.contest(domainId),
|
const func = `get${key.replace(/^[a-z]/, (i) => i.toUpperCase())}`;
|
||||||
this.training(domainId), this.discussion(domainId),
|
if (!this[func]) continue;
|
||||||
]);
|
tasks.push(this[func](domainId, +info[key]));
|
||||||
|
}
|
||||||
|
const contents = await Promise.all(tasks);
|
||||||
const [udict, dodoc, vnodes] = await Promise.all([
|
const [udict, dodoc, vnodes] = await Promise.all([
|
||||||
user.getList(domainId, ddocs.map((ddoc) => ddoc.owner)),
|
user.getList(domainId, Array.from(this.uids)),
|
||||||
domain.get(domainId),
|
domain.get(domainId),
|
||||||
discussion.getNodes(domainId),
|
discussion.getNodes(domainId),
|
||||||
]);
|
]);
|
||||||
this.response.template = 'main.html';
|
this.response.template = 'main.html';
|
||||||
this.response.body = {
|
this.response.body = {
|
||||||
htdocs,
|
contents,
|
||||||
htsdict,
|
|
||||||
tdocs,
|
|
||||||
tsdict,
|
|
||||||
trdocs,
|
|
||||||
trsdict,
|
|
||||||
ddocs,
|
|
||||||
vndict,
|
|
||||||
udict,
|
udict,
|
||||||
domain: dodoc,
|
domain: dodoc,
|
||||||
vnodes,
|
vnodes,
|
||||||
|
|||||||
@@ -191,10 +191,6 @@ SystemSetting(
|
|||||||
Setting('setting_basic', 'pagination.solution', 20, 'number', 'pagination.solution', 'Solutions per page'),
|
Setting('setting_basic', 'pagination.solution', 20, 'number', 'pagination.solution', 'Solutions per page'),
|
||||||
Setting('setting_basic', 'pagination.training', 10, 'number', 'pagination.training', 'Trainings per page'),
|
Setting('setting_basic', 'pagination.training', 10, 'number', 'pagination.training', 'Trainings per page'),
|
||||||
Setting('setting_basic', 'pagination.reply', 50, 'number', 'pagination.reply', 'Replies per page'),
|
Setting('setting_basic', 'pagination.reply', 50, 'number', 'pagination.reply', 'Replies per page'),
|
||||||
Setting('setting_basic', 'pagination.homework_main', 5, 'number', 'pagination.homework_main', 'Homeworks on main'),
|
|
||||||
Setting('setting_basic', 'pagination.contest_main', 10, 'number', 'pagination.contest_main', 'Contests on main'),
|
|
||||||
Setting('setting_basic', 'pagination.training_main', 10, 'number', 'pagination.training_main', 'Trainings on main'),
|
|
||||||
Setting('setting_basic', 'pagination.discussion_main', 20, 'number', 'pagination.discussion_main', 'Discussions on main'),
|
|
||||||
Setting('setting_session', 'session.keys', [String.random(32)], 'text', 'session.keys', 'session.keys', FLAG_HIDDEN),
|
Setting('setting_session', 'session.keys', [String.random(32)], 'text', 'session.keys', 'session.keys', FLAG_HIDDEN),
|
||||||
Setting('setting_session', 'session.secure', false, 'boolean', 'session.secure', 'session.secure'),
|
Setting('setting_session', 'session.secure', false, 'boolean', 'session.secure', 'session.secure'),
|
||||||
Setting('setting_session', 'session.saved_expire_seconds', 3600 * 24 * 30,
|
Setting('setting_session', 'session.saved_expire_seconds', 3600 * 24 * 30,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hydrooj/ui-default",
|
"name": "@hydrooj/ui-default",
|
||||||
"version": "4.9.0",
|
"version": "4.9.1",
|
||||||
"author": "undefined <i@undefined.moe>",
|
"author": "undefined <i@undefined.moe>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "hydro.js",
|
"main": "hydro.js",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
.page--ranking
|
.page--ranking, .page--homepage
|
||||||
.col--rank
|
.col--rank
|
||||||
width: rem(70px)
|
width: rem(70px)
|
||||||
position: relative
|
position: relative
|
||||||
|
|||||||
@@ -16,145 +16,35 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="large-9 columns">
|
<div class="large-9 columns">
|
||||||
{% if tdocs.length %}
|
{% for node in contents %}
|
||||||
<div class="section page--contest_main">
|
{% if node[0] == 'contest' %}
|
||||||
<div class="section__header">
|
{% set tdocs = node[1] %}
|
||||||
<h1 class="section__title">{{ _('Contest') }}</h1>
|
{% set tsdict = node[2] %}
|
||||||
</div>
|
{% include "partials/homepage/contest.html" %}
|
||||||
<ol class="section__list contest__list">
|
{% elif node[0] == 'homework' %}
|
||||||
{%- for tdoc in tdocs -%}
|
{% set htdocs = node[1] %}
|
||||||
<li class="section__list__item contest__item contest-type--{{ tdoc.rule }}">
|
{% set htsdict = node[2] %}
|
||||||
<div class="media">
|
{% include "partials/homepage/homework.html" %}
|
||||||
<div class="media__left medium">
|
{% elif node[0] == 'training' %}
|
||||||
<div class="contest__date numbox">
|
{% set tdocs = node[1] %}
|
||||||
<div class="numbox__num large">{{ datetimeSpan(tdoc['beginAt'], false, '%d')|safe }}</div>
|
{% set tsdict = node[2] %}
|
||||||
<div class="numbox__text">{{ datetimeSpan(tdoc['beginAt'], false, '%Y-%m')|safe }}</div>
|
{% include "partials/homepage/training.html" %}
|
||||||
</div>
|
{% elif node[0] == 'ranking' %}
|
||||||
</div>
|
{% set uids = node[1] %}
|
||||||
<div class="media__body medium">
|
{% include "partials/homepage/ranking.html" %}
|
||||||
<h1 class="contest__title"><a href="{{ url('contest_detail', tid=tdoc.docId) }}" data-emoji-enabled>{{ tdoc.title }}</a></h1>
|
{% elif node[0] == 'discussion' %}
|
||||||
<ul class="supplementary list">
|
{% set ddocs = node[1] %}
|
||||||
<li>
|
{% set vndict = node[2] %}
|
||||||
<a href="{{ url('contest_main', query={rule:tdoc.rule}) }}" class="contest-type-tag"><span class="icon icon-award"></span>{{ model.contest.RULES[tdoc.rule].TEXT }}</a>
|
{% if ddocs.length %}
|
||||||
</li>
|
<div class="section page--discussion_main">
|
||||||
<li>
|
<div class="section__header">
|
||||||
<span class="icon icon-schedule--fill text-blue"></span> {{ contest.render_duration(tdoc) }} {{ _('hour(s)') }}
|
<h1 class="section__title">{{ _('Discussion') }}</h1>
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span class="icon icon-user--multiple"></span> {{ tdoc['attend']|default(0) }}
|
|
||||||
</li>
|
|
||||||
{% if tsdict[tdoc.docId]['attend'] == 1 %}
|
|
||||||
<li class="contest__info-attended">
|
|
||||||
<span class="icon icon-check"></span> {{ _('Attended') }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% include "partials/discussion_list.html" %}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
{% endif %}
|
||||||
{%- endfor -%}
|
{% endif %}
|
||||||
</ol>
|
{% endfor %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if htdocs.length %}
|
|
||||||
<div class="section page--homework_main">
|
|
||||||
<div class="section__header">
|
|
||||||
<h1 class="section__title">{{ _('Homework') }}</h1>
|
|
||||||
</div>
|
|
||||||
<ol class="section__list homework__list">
|
|
||||||
{%- for tdoc in htdocs -%}
|
|
||||||
<li class="section__list__item homework__item">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media__left medium">
|
|
||||||
<div class="homework__date numbox">
|
|
||||||
<div class="numbox__num large">{{ datetimeSpan(tdoc['penaltySince'], false, '%d')|safe }}</div>
|
|
||||||
<div class="numbox__text">{{ datetimeSpan(tdoc['penaltySince'], false, '%Y-%m')|safe }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media__body medium">
|
|
||||||
<h1 class="homework__title"><a href="{{ url('homework_detail', tid=tdoc.docId) }}" data-emoji-enabled>{{ tdoc['title'] }}</a></h1>
|
|
||||||
<ul class="supplementary list">
|
|
||||||
<li>
|
|
||||||
{{ _('Status') }}: <span class="homework-status--text {{ model.contest.getStatus(tdoc) }}">{{ _(model.contest.statusText(tdoc)) }}</span>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{{ _('Open Since') }}: {{ datetimeSpan(tdoc['beginAt'])|safe }}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{% if model.contest.isExtended(tdoc) or model.contest.isDone(tdoc) %}
|
|
||||||
{{ _('Hard Deadline') }}: {{ datetimeSpan(tdoc['endAt'])|safe }}
|
|
||||||
{% else %}
|
|
||||||
{{ _('Deadline') }}: {{ datetimeSpan(tdoc['penaltySince'])|safe }}
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
{% if htsdict[tdoc['docId']]['attend'] == 1 %}
|
|
||||||
<li class="homework__info-attended">
|
|
||||||
<span class="icon icon-check"></span> {{ _('Claimed') }}
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{%- endfor -%}
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if trdocs.length %}
|
|
||||||
<div class="section page--training_main">
|
|
||||||
<div class="section__header">
|
|
||||||
<h1 class="section__title">{{ _('Training') }}</h1>
|
|
||||||
</div>
|
|
||||||
{% set tdocs = trdocs %}
|
|
||||||
{% set tsdict = trsdict %}
|
|
||||||
<ol class="section__list all primary training__list">
|
|
||||||
{%- for tdoc in tdocs -%}
|
|
||||||
<li class="section__list__item training__item">
|
|
||||||
<div class="media">
|
|
||||||
<div class="media__left middle">
|
|
||||||
<div class="training__participants numbox">
|
|
||||||
<div class="numbox__num large">{{ tdoc['enroll']|default(0) }}</div>
|
|
||||||
<div class="numbox__text">{{ _('Enrolled') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="media__body middle">
|
|
||||||
<h1 class="training__title"><a href="{{ url('training_detail', tid=tdoc.docId) }}" data-emoji-enabled>{{ tdoc.title }}</a></h1>
|
|
||||||
<div class="training__intro typo">
|
|
||||||
<p>{{ tdoc['content'] }}</p>
|
|
||||||
</div>
|
|
||||||
<ul class="supplementary list training__progress">
|
|
||||||
<li>
|
|
||||||
<span class="icon icon-flag text-blue"></span> {{ _('{0} sections').format(tdoc['dag']|length) }}, {{ _('{0} problems').format(model.training.getPids(tdoc.dag)|length) }}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{% if tsdict[tdoc.docId]['enroll'] %}
|
|
||||||
{% if not tsdict[tdoc.docId]['done'] %}
|
|
||||||
<span class="icon training-status--icon progress"></span>
|
|
||||||
<span class="training-status--text progress">{{ _('Completed') }} {{ (100 * tsdict[tdoc.docId]['donePids']|length / model.training.getPids(tdoc.dag)|length)|round|int }}%</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="icon training-status--icon done"></span>
|
|
||||||
<span class="training-status--text done">{{ _('Completed') }} 100%</span>
|
|
||||||
{% endif %}
|
|
||||||
{% elif handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
|
||||||
<span class="icon training-status--icon outside"></span> <span class="training-status--text outside">{{ _('Not Enrolled') }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{%- endfor -%}
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if ddocs.length %}
|
|
||||||
<div class="section page--discussion_main">
|
|
||||||
<div class="section__header">
|
|
||||||
<h1 class="section__title">{{ _('Discussion') }}</h1>
|
|
||||||
</div>
|
|
||||||
{% include "partials/discussion_list.html" %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="large-3 columns">
|
<div class="large-3 columns">
|
||||||
<div class="section side nojs--hide">
|
<div class="section side nojs--hide">
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{% if tdocs.length %}
|
||||||
|
<div class="section page--contest_main">
|
||||||
|
<div class="section__header">
|
||||||
|
<h1 class="section__title">{{ _('Contest') }}</h1>
|
||||||
|
</div>
|
||||||
|
<ol class="section__list contest__list">
|
||||||
|
{%- for tdoc in tdocs -%}
|
||||||
|
<li class="section__list__item contest__item contest-type--{{ tdoc.rule }}">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media__left medium">
|
||||||
|
<div class="contest__date numbox">
|
||||||
|
<div class="numbox__num large">{{ datetimeSpan(tdoc['beginAt'], false, '%d')|safe }}</div>
|
||||||
|
<div class="numbox__text">{{ datetimeSpan(tdoc['beginAt'], false, '%Y-%m')|safe }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="media__body medium">
|
||||||
|
<h1 class="contest__title"><a href="{{ url('contest_detail', tid=tdoc.docId) }}" data-emoji-enabled>{{ tdoc.title }}</a></h1>
|
||||||
|
<ul class="supplementary list">
|
||||||
|
<li>
|
||||||
|
<a href="{{ url('contest_main', query={rule:tdoc.rule}) }}" class="contest-type-tag"><span class="icon icon-award"></span>{{ model.contest.RULES[tdoc.rule].TEXT }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="icon icon-schedule--fill text-blue"></span> {{ contest.render_duration(tdoc) }} {{ _('hour(s)') }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="icon icon-user--multiple"></span> {{ tdoc['attend']|default(0) }}
|
||||||
|
</li>
|
||||||
|
{% if tsdict[tdoc.docId]['attend'] == 1 %}
|
||||||
|
<li class="contest__info-attended">
|
||||||
|
<span class="icon icon-check"></span> {{ _('Attended') }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{%- endfor -%}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
{% if htdocs.length %}
|
||||||
|
<div class="section page--homework_main">
|
||||||
|
<div class="section__header">
|
||||||
|
<h1 class="section__title">{{ _('Homework') }}</h1>
|
||||||
|
</div>
|
||||||
|
<ol class="section__list homework__list">
|
||||||
|
{%- for tdoc in htdocs -%}
|
||||||
|
<li class="section__list__item homework__item">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media__left medium">
|
||||||
|
<div class="homework__date numbox">
|
||||||
|
<div class="numbox__num large">{{ datetimeSpan(tdoc['penaltySince'], false, '%d')|safe }}</div>
|
||||||
|
<div class="numbox__text">{{ datetimeSpan(tdoc['penaltySince'], false, '%Y-%m')|safe }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="media__body medium">
|
||||||
|
<h1 class="homework__title"><a href="{{ url('homework_detail', tid=tdoc.docId) }}" data-emoji-enabled>{{ tdoc['title'] }}</a></h1>
|
||||||
|
<ul class="supplementary list">
|
||||||
|
<li>
|
||||||
|
{{ _('Status') }}: <span class="homework-status--text {{ model.contest.getStatus(tdoc) }}">{{ _(model.contest.statusText(tdoc)) }}</span>
|
||||||
|
</li>
|
||||||
|
<li>{{ _('Open Since') }}: {{ datetimeSpan(tdoc['beginAt'])|safe }}</li>
|
||||||
|
<li>
|
||||||
|
{% if model.contest.isExtended(tdoc) or model.contest.isDone(tdoc) %}
|
||||||
|
{{ _('Hard Deadline') }}: {{ datetimeSpan(tdoc['endAt'])|safe }}
|
||||||
|
{% else %}
|
||||||
|
{{ _('Deadline') }}: {{ datetimeSpan(tdoc['penaltySince'])|safe }}
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% if htsdict[tdoc['docId']]['attend'] == 1 %}
|
||||||
|
<li class="homework__info-attended">
|
||||||
|
<span class="icon icon-check"></span> {{ _('Claimed') }}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{%- endfor -%}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
{% if uids.length %}
|
||||||
|
<div class="section">
|
||||||
|
<div class="section__header">
|
||||||
|
<h1 class="section__title">{{ _('Ranking') }}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="section__body no-padding">
|
||||||
|
<div>
|
||||||
|
<table class="data-table">
|
||||||
|
<colgroup>
|
||||||
|
<col class="col--rank">
|
||||||
|
<col class="col--user">
|
||||||
|
<col class="col--rp">
|
||||||
|
<col class="col--bio">
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="col--rank">{{ _('Rank') }}</th>
|
||||||
|
<th class="col--user">{{ _('Username') }}</th>
|
||||||
|
<th class="col--rp">{{ _('RP') }}</th>
|
||||||
|
<th class="col--bio">{{ _('Bio') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{%- for uid in uids -%}
|
||||||
|
{% set udoc = udict[uid] %}
|
||||||
|
<tr>
|
||||||
|
<td class="col--rank">{{ loop.index }}</td>
|
||||||
|
<td class="col--user">{{ user.render_inline(udoc) }}</td>
|
||||||
|
<td class="col--rp">{{ udoc.rp|default(1500)|round(0) }}</td>
|
||||||
|
<td class="col--bio">{{ udoc.bio|default('')|truncate(64, true)|markdownInline|safe }}</td>
|
||||||
|
</tr>
|
||||||
|
{%- endfor -%}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
{% if trdocs.length %}
|
||||||
|
<div class="section page--training_main">
|
||||||
|
<div class="section__header">
|
||||||
|
<h1 class="section__title">{{ _('Training') }}</h1>
|
||||||
|
</div>
|
||||||
|
<ol class="section__list all primary training__list">
|
||||||
|
{%- for tdoc in tdocs -%}
|
||||||
|
<li class="section__list__item training__item">
|
||||||
|
<div class="media">
|
||||||
|
<div class="media__left middle">
|
||||||
|
<div class="training__participants numbox">
|
||||||
|
<div class="numbox__num large">{{ tdoc['enroll']|default(0) }}</div>
|
||||||
|
<div class="numbox__text">{{ _('Enrolled') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="media__body middle">
|
||||||
|
<h1 class="training__title"><a href="{{ url('training_detail', tid=tdoc.docId) }}" data-emoji-enabled>{{ tdoc.title }}</a></h1>
|
||||||
|
<div class="training__intro typo">
|
||||||
|
<p>{{ tdoc['content'] }}</p>
|
||||||
|
</div>
|
||||||
|
<ul class="supplementary list training__progress">
|
||||||
|
<li>
|
||||||
|
<span class="icon icon-flag text-blue"></span> {{ _('{0} sections').format(tdoc['dag']|length) }}, {{ _('{0} problems').format(model.training.getPids(tdoc.dag)|length) }}
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
{% if tsdict[tdoc.docId]['enroll'] %}
|
||||||
|
{% if not tsdict[tdoc.docId]['done'] %}
|
||||||
|
<span class="icon training-status--icon progress"></span>
|
||||||
|
<span class="training-status--text progress">{{ _('Completed') }} {{ (100 * tsdict[tdoc.docId]['donePids']|length / model.training.getPids(tdoc.dag)|length)|round|int }}%</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="icon training-status--icon done"></span>
|
||||||
|
<span class="training-status--text done">{{ _('Completed') }} 100%</span>
|
||||||
|
{% endif %}
|
||||||
|
{% elif handler.user.hasPriv(PRIV.PRIV_USER_PROFILE) %}
|
||||||
|
<span class="icon training-status--icon outside"></span> <span class="training-status--text outside">{{ _('Not Enrolled') }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{%- endfor -%}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
Reference in new issue
Block a user