mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-22 08:18:41 +00:00
refactor(web/user): user_info v2 (#6)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
a9583e605e
@ -816,6 +816,10 @@ CREATE TABLE `user_info` (
|
||||
`last_login` timestamp NOT NULL DEFAULT 0,
|
||||
`last_visited` timestamp NOT NULL DEFAULT 0,
|
||||
`images_size_limit` int(11) UNSIGNED NOT NULL DEFAULT 104857600, /* 100 MiB */
|
||||
`codeforces_handle` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`github` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`website` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
|
||||
`avatar_source` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'gravatar',
|
||||
PRIMARY KEY (`username`),
|
||||
KEY `ac_num` (`ac_num`,`username`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
|
||||
|
@ -78,6 +78,9 @@
|
||||
<p class="card-text">
|
||||
快使用 Gravatar 吧!Gravatar 地址:<a href="https://cn.gravatar.com" target="_blank">https://cn.gravatar.com</a>。进去后注册个帐号然后与邮箱绑定并上传头像,就 OK 啦!
|
||||
</p>
|
||||
<p class="card-text">
|
||||
上不去 Gravatar?没关系,我们现在也支持 QQ 头像了!你只需要前往 “更改个人信息” 页面填写自己的 QQ 号,并将 “头像来源” 选为 “QQ” 就可以让你的 QQ 头像显示在 S2OJ 上啦!
|
||||
</p>
|
||||
|
||||
<h5 class="mt-4">递归 10<sup>7</sup> 层怎么没爆栈啊</h5>
|
||||
<p class="card-text">
|
||||
|
@ -1,4 +1,7 @@
|
||||
<?php
|
||||
requireLib('bootstrap5');
|
||||
requireLib('calendar_heatmap');
|
||||
|
||||
if (!Auth::check() && UOJConfig::$data['switch']['force-login']) {
|
||||
redirectToLogin();
|
||||
}
|
||||
@ -13,138 +16,9 @@
|
||||
become404Page();
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
if (!isset($_COOKIE['bootstrap4'])) {
|
||||
$REQUIRE_LIB['bootstrap5'] = '';
|
||||
$REQUIRE_LIB['calendar_heatmap'] = '';
|
||||
} else {
|
||||
$REQUIRE_LIB['github_contribution_graph'] = '';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php echoUOJPageHeader($user['username'] . ' - ' . UOJLocale::get('user profile')) ?>
|
||||
<?php if (isset($REQUIRE_LIB['bootstrap5'])): ?>
|
||||
<?php uojIncludeView('user-info', array('user' => $user, 'myUser' => $myUser)) ?>
|
||||
<?php else: ?>
|
||||
<?php
|
||||
$esc_sex = HTML::escape($user['sex']);
|
||||
$col_sex="color:blue";
|
||||
if ($esc_sex == "M") {
|
||||
$esc_sex="♂";
|
||||
$col_sex="color:blue";
|
||||
} elseif ($esc_sex == "F") {
|
||||
$esc_sex="♀";
|
||||
$col_sex="color:red";
|
||||
} else {
|
||||
$esc_sex="";
|
||||
$col_sex="color:black";
|
||||
}
|
||||
?>
|
||||
<div class="card border-info">
|
||||
<h5 class="card-header bg-info"><?= UOJLocale::get('user profile') ?></h5>
|
||||
<div class="card-body">
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4 order-md-9">
|
||||
<img class="media-object img-thumbnail d-block mx-auto" alt="<?= $user['username'] ?> Avatar" src="<?= HTML::avatar_addr($user, 256) ?>" />
|
||||
</div>
|
||||
<div class="col-md-8 order-md-1">
|
||||
<h2><span class="uoj-honor" data-realname="<?= $user['realname'] ?>"><?= $user['username'] ?></span> <span><strong style="<?= $col_sex ?>"><?= $esc_sex ?></strong></span></h2>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<h4 class="list-group-item-heading"><?= UOJLocale::get('email') ?></h4>
|
||||
<p class="list-group-item-text"><?= HTML::escape($user['email']) ?></p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<h4 class="list-group-item-heading"><?= UOJLocale::get('QQ') ?></h4>
|
||||
<p class="list-group-item-text"><?= HTML::escape($user['qq'] != 0 ? $user['qq'] : 'Unfilled') ?></p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<h4 class="list-group-item-heading"><?= UOJLocale::get('motto') ?></h4>
|
||||
<div class="list-group-item-text"><?= HTML::purifier_inline()->purify(HTML::parsedown()->line($user['motto'])) ?></div>
|
||||
</div>
|
||||
|
||||
<?php if (isSuperUser($myUser)): ?>
|
||||
<div class="list-group-item">
|
||||
<h4 class="list-group-item-heading">register time</h4>
|
||||
<p class="list-group-item-text"><?= $user['register_time'] ?></p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<h4 class="list-group-item-heading">remote_addr</h4>
|
||||
<p class="list-group-item-text"><?= $user['remote_addr'] ?></p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<h4 class="list-group-item-heading">http_x_forwarded_for</h4>
|
||||
<p class="list-group-item-text"><?= $user['http_x_forwarded_for'] ?></p>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (Auth::check()): ?>
|
||||
<?php if (Auth::id() != $user['username']): ?>
|
||||
<a type="button" class="btn btn-info btn-sm" href="/user_msg?enter=<?= $user['username'] ?>"><span class="glyphicon glyphicon-envelope"></span> <?= UOJLocale::get('send private message') ?></a>
|
||||
<?php else: ?>
|
||||
<a type="button" class="btn btn-info btn-sm" href="/user/<?= $user['username'] ?>/edit"><span class="glyphicon glyphicon-pencil"></span> <?= UOJLocale::get('modify my profile') ?></a>
|
||||
<?php endif ?>
|
||||
<?php endif ?>
|
||||
|
||||
<a type="button" class="btn btn-success btn-sm" href="<?= HTML::blog_url($user['username'], '/') ?>"><span class="glyphicon glyphicon-arrow-right"></span> <?= UOJLocale::get('visit his blog', $username) ?></a>
|
||||
<a type="button" class="btn btn-success btn-sm" href="<?= HTML::blog_url($user['username'], '/self_reviews') ?>"><span class="glyphicon glyphicon-arrow-right"></span> 查看 <?= $username ?> 的所有赛后总结</a>
|
||||
|
||||
<div class="top-buffer-lg"></div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<?php
|
||||
$_result = DB::query("select date(submit_time), problem_id from submissions where submitter = '{$username}' and score = 100 and date(submit_time) between date_sub(curdate(), interval 1 year) and curdate()");
|
||||
$result = [];
|
||||
$vis = [];
|
||||
while ($row = DB::fetch($_result)) {
|
||||
$id = $row['date(submit_time)'] . ':' . $row['problem_id'];
|
||||
if (!$vis[$id]) {
|
||||
$vis[$id] = 1;
|
||||
$result[strtotime($row['date(submit_time)']) * 1000]++;
|
||||
}
|
||||
}
|
||||
?>
|
||||
<h4 class="list-group-item-heading"><?= UOJLocale::get('n accepted in last year', count($result)) ?></h4>
|
||||
<div id="accepted-graph"></div>
|
||||
<script>
|
||||
var accepted_graph_data = [
|
||||
<?php
|
||||
foreach ($result as $key => $val) {
|
||||
echo "{ timestamp: {$key}, count: {$val} }, ";
|
||||
}
|
||||
?>
|
||||
];
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#accepted-graph').github_graph({
|
||||
data: accepted_graph_data,
|
||||
texts: ['AC', 'AC'],
|
||||
h_days: ['Tue', 'Thu', 'Sat'],
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<?php
|
||||
$ac_problems = DB::selectAll("select a.problem_id as problem_id, b.title as title from best_ac_submissions a inner join problems b on a.problem_id = b.id where submitter = '{$user['username']}';");
|
||||
?>
|
||||
<h4 class="list-group-item-heading"><?= UOJLocale::get('accepted problems').':'.UOJLocale::get('n problems in total', count($ac_problems))?> </h4>
|
||||
<ul class="list-group-item-text nav">
|
||||
<?php
|
||||
foreach ($ac_problems as $problem) {
|
||||
echo '<li class="mr-1 mb-1"><a href="/problem/', $problem['problem_id'], '" role="button" class="btn btn-light h-100" style="width: 12rem;">#', $problem['problem_id'], '. ', $problem['title'], '</a></li>';
|
||||
}
|
||||
|
||||
if (empty($ac_problems)) {
|
||||
echo UOJLocale::get('none');
|
||||
}
|
||||
?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?php uojIncludeView('user-info', array('user' => $user, 'myUser' => $myUser)) ?>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
@ -1,4 +1,8 @@
|
||||
<?php
|
||||
requireLib('bootstrap5');
|
||||
requireLib('md5');
|
||||
requirePHPLib('form');
|
||||
|
||||
if (!Auth::check()) {
|
||||
redirectToLogin();
|
||||
}
|
||||
@ -11,202 +15,475 @@
|
||||
become403Page();
|
||||
}
|
||||
|
||||
function handlePost() {
|
||||
global $myUser, $user;
|
||||
if ($user['username'] == Auth::id()) {
|
||||
if (!isset($_POST['old_password'])) {
|
||||
return '无效表单';
|
||||
}
|
||||
$old_password = $_POST['old_password'];
|
||||
if (!validatePassword($old_password) || !checkPassword($user, $old_password)) {
|
||||
return "失败:密码错误。";
|
||||
}
|
||||
}
|
||||
if ($_POST['ptag']) {
|
||||
$password = $_POST['password'];
|
||||
if (!validatePassword($password)) {
|
||||
return "失败:无效密码。";
|
||||
}
|
||||
$password = getPasswordToStore($password, $user['username']);
|
||||
DB::update("update user_info set password = '$password' where username = '{$user['username']}'");
|
||||
}
|
||||
if (isset($_GET['tab'])) {
|
||||
$cur_tab = $_GET['tab'];
|
||||
} else {
|
||||
$cur_tab = 'profile';
|
||||
}
|
||||
|
||||
$tabs_info = [
|
||||
'profile' => [
|
||||
'name' => '<i class="bi bi-person-fill"></i> 个人资料',
|
||||
'url' => "/user/{$user['username']}/edit/profile",
|
||||
],
|
||||
'password' => [
|
||||
'name' => '<i class="bi bi-lock-fill"></i> 修改密码',
|
||||
'url' => "/user/{$user['username']}/edit/password",
|
||||
],
|
||||
'privilege' => [
|
||||
'name' => '<i class="bi bi-key-fill"></i> 特权',
|
||||
'url' => "/user/{$user['username']}/edit/privilege",
|
||||
]
|
||||
];
|
||||
|
||||
if (!isset($tabs_info[$cur_tab])) {
|
||||
become404Page();
|
||||
}
|
||||
|
||||
$email = $_POST['email'];
|
||||
if (!validateEmail($email)) {
|
||||
return "失败:无效电子邮箱。";
|
||||
}
|
||||
$esc_email = DB::escape($email);
|
||||
DB::update("update user_info set email = '$esc_email' where username = '{$user['username']}'");
|
||||
if ($cur_tab == 'profile') {
|
||||
$update_profile_form = new UOJForm('update_profile');
|
||||
$username = UOJLocale::get('username');
|
||||
$avatar = UOJLocale::get('avatar');
|
||||
$update_profile_form->appendHTML(<<<EOD
|
||||
<div class="mb-3">
|
||||
<label for="input-username" class="form-label">$username</label>
|
||||
<input type="text" class="form-control" id="input-username" aria-describedby="help-username" value="{$user['username']}" disabled>
|
||||
<div id="help-username" class="form-text">用户名不能被修改。</div>
|
||||
</div>
|
||||
EOD);
|
||||
$update_profile_form->addVCheckboxes('avatar_source', [
|
||||
'gravatar' => 'Gravatar',
|
||||
'qq' => 'QQ',
|
||||
], UOJLocale::get('user::avatar source'), $user['avatar_source']);
|
||||
$change_avatar_help = UOJLocale::get('change avatar help');
|
||||
$update_profile_form->appendHTML(<<<EOD
|
||||
<div style="margin-top: -1.25rem;" class="mb-3 small text-muted">
|
||||
$change_avatar_help
|
||||
</div>
|
||||
EOD);
|
||||
$update_profile_form->addVInput('email', 'email', UOJLocale::get('email'), $user['email'],
|
||||
function($email, &$vdata) {
|
||||
if (!validateEmail($email)) {
|
||||
return 'Email 格式不合法。';
|
||||
}
|
||||
|
||||
if ($_POST['Qtag']) {
|
||||
$qq = $_POST['qq'];
|
||||
if (!validateQQ($qq)) {
|
||||
return "失败:无效QQ。";
|
||||
}
|
||||
$esc_qq = DB::escape($qq);
|
||||
DB::update("update user_info set qq = '$esc_qq' where username = '{$user['username']}'");
|
||||
$vdata['email'] = $email;
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
$update_profile_form->addVInput('qq', 'text', UOJLocale::get('QQ'), $user['qq'] == 0 ? '' : $user['qq'],
|
||||
function($qq, &$vdata) {
|
||||
if ($qq && !validateQQ($qq)) {
|
||||
return 'QQ 格式不合法。';
|
||||
}
|
||||
|
||||
$vdata['qq'] = $qq;
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
$update_profile_form->addVInput('github', 'text', 'GitHub', $user['github'],
|
||||
function($github, &$vdata) {
|
||||
if ($github && !validateGitHubUsername($github)) {
|
||||
return 'GitHub 用户名不合法。';
|
||||
}
|
||||
|
||||
$vdata['github'] = $github;
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
if (isSuperUser($myUser)) {
|
||||
$update_profile_form->addVInput('school', 'text', UOJLocale::get('school'), $user['school'],
|
||||
function($school, &$vdata) {
|
||||
$vdata['school'] = $school;
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
} else {
|
||||
DB::update("update user_info set QQ = NULL where username = '{$user['username']}'");
|
||||
$school = UOJLocale::get('school');
|
||||
$update_profile_form->appendHTML(<<<EOD
|
||||
<div class="mb-3">
|
||||
<label for="input-school" class="form-label">$school</label>
|
||||
<input type="text" class="form-control" id="input-school" aria-describedby="help-school" value="{$user['school']}" disabled>
|
||||
<div id="help-school" class="form-text">只有管理员才能修改用户所属学校。</div>
|
||||
</div>
|
||||
EOD);
|
||||
}
|
||||
if ($_POST['sex'] == "U" || $_POST['sex'] == 'M' || $_POST['sex'] == 'F') {
|
||||
$sex = $_POST['sex'];
|
||||
$esc_sex = DB::escape($sex);
|
||||
DB::update("update user_info set sex = '$esc_sex' where username = '{$user['username']}'");
|
||||
}
|
||||
|
||||
if (validateMotto($_POST['motto'])) {
|
||||
$esc_motto = DB::escape($_POST['motto']);
|
||||
DB::update("update user_info set motto = '$esc_motto' where username = '{$user['username']}'");
|
||||
}
|
||||
|
||||
return "ok";
|
||||
$update_profile_form->addVCheckboxes('sex', [
|
||||
'U' => UOJLocale::get('refuse to answer'),
|
||||
'M' => UOJLocale::get('male'),
|
||||
'F' => UOJLocale::get('female'),
|
||||
], UOJLocale::get('sex'), $user['sex']);
|
||||
$update_profile_form->addVInput('motto', 'text', UOJLocale::get('motto'), $user['motto'],
|
||||
function($motto, &$vdata) {
|
||||
if (!validateMotto($motto)) {
|
||||
return '格言格式不合法';
|
||||
}
|
||||
|
||||
$vdata['motto'] = $motto;
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
$update_profile_form->addVInput('codeforces_handle', 'text', UOJLocale::get('codeforces handle'), $user['codeforces_handle'],
|
||||
function($codeforces_handle, &$vdata) {
|
||||
if ($codeforces_handle && !validateUsername($codeforces_handle)) {
|
||||
return 'Codeforces 用户名格式不合法。';
|
||||
}
|
||||
|
||||
$vdata['codeforces_handle'] = $codeforces_handle;
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
$update_profile_form->addVInput('website', 'text', UOJLocale::get('user::website'), $user['website'],
|
||||
function($url, &$vdata) {
|
||||
if ($url && !validateURL($url)) {
|
||||
return '链接格式不合法。';
|
||||
}
|
||||
|
||||
$vdata['website'] = $url;
|
||||
|
||||
return '';
|
||||
}, null);
|
||||
$update_profile_form->handle = function(&$vdata) use ($user, $myUser) {
|
||||
$esc_email = DB::escape($vdata['email']);
|
||||
$esc_qq = DB::escape($vdata['qq']);
|
||||
$esc_github = DB::escape($vdata['github']);
|
||||
$esc_sex = DB::escape($_POST['sex']);
|
||||
$esc_motto = DB::escape($vdata['motto']);
|
||||
$esc_codeforces_handle = DB::escape($vdata['codeforces_handle']);
|
||||
$esc_website = DB::escape($vdata['website']);
|
||||
$esc_avatar_source = DB::escape($_POST['avatar_source']);
|
||||
|
||||
if (isSuperUser($myUser)) {
|
||||
$esc_school = DB::escape($vdata['school']);
|
||||
|
||||
DB::update("UPDATE user_info SET school = '$esc_school' WHERE username = '{$user['username']}'");
|
||||
}
|
||||
|
||||
DB::update("UPDATE user_info SET email = '$esc_email', qq = '$esc_qq', sex = '$esc_sex', motto = '$esc_motto', codeforces_handle = '$esc_codeforces_handle', github = '$esc_github', website = '$esc_website', avatar_source = '$esc_avatar_source' WHERE username = '{$user['username']}'");
|
||||
|
||||
header('Content-Type: application/json');
|
||||
die(json_encode(['status' => 'success']));
|
||||
};
|
||||
$update_profile_form->submit_button_config['margin_class'] = 'mt-3';
|
||||
$update_profile_form->submit_button_config['text'] = '更新';
|
||||
$update_profile_form->setAjaxSubmit(<<<EOD
|
||||
function(res) {
|
||||
if (res.status === 'success') {
|
||||
$('#result-alert')
|
||||
.html('个人信息修改成功!')
|
||||
.addClass('alert-success')
|
||||
.removeClass('alert-danger')
|
||||
.show();
|
||||
} else {
|
||||
$('#result-alert')
|
||||
.html('个人信息修改失败。' + (res.message || ''))
|
||||
.removeClass('alert-success')
|
||||
.addClass('alert-danger')
|
||||
.show();
|
||||
}
|
||||
if (isset($_POST['change'])) {
|
||||
die(handlePost());
|
||||
|
||||
$(window).scrollTop(0);
|
||||
}
|
||||
EOD);
|
||||
$update_profile_form->runAtServer();
|
||||
} elseif ($cur_tab == 'password') {
|
||||
if (isset($_POST['submit-change_password']) && $_POST['submit-change_password'] == 'change_password') {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$old_password = $_POST['current_password'];
|
||||
$new_password = $_POST['new_password'];
|
||||
|
||||
if (!validatePassword($old_password) || !checkPassword($user, $old_password)) {
|
||||
die(json_encode(['status' => 'error', 'message' => '旧密码错误']));
|
||||
}
|
||||
|
||||
if (!validatePassword($new_password)) {
|
||||
die(json_encode(['status' => 'error', 'message' => '新密码不合法']));
|
||||
}
|
||||
|
||||
if ($old_password == $new_password) {
|
||||
die(json_encode(['status' => 'error', 'message' => '新密码不能与旧密码相同']));
|
||||
}
|
||||
|
||||
$password = getPasswordToStore($new_password, $user['username']);
|
||||
DB::update("UPDATE `user_info` SET `password` = '$password' where `username` = '{$user['username']}'");
|
||||
die(json_encode(['status' => 'success', 'message' => '密码修改成功']));
|
||||
}
|
||||
} elseif ($cur_tab == 'privilege') {
|
||||
if (isset($_POST['submit-privilege']) && $_POST['submit-privilege'] == 'privilege' && isSuperUser($myUser)) {
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$user['usertype'] = 'student';
|
||||
|
||||
if ($_POST['user_type'] == 'teacher') {
|
||||
removeUserType($user, 'student');
|
||||
addUserType($user, 'teacher');
|
||||
} else {
|
||||
addUserType($user, 'student');
|
||||
}
|
||||
|
||||
if ($_POST['problem_uploader'] == 'yes') {
|
||||
addUserType($user, 'problem_uploader');
|
||||
}
|
||||
|
||||
if ($_POST['problem_manager'] == 'yes') {
|
||||
addUserType($user, 'problem_manager');
|
||||
}
|
||||
|
||||
if ($_POST['contest_judger'] == 'yes') {
|
||||
addUserType($user, 'contest_judger');
|
||||
}
|
||||
|
||||
if ($_POST['contest_only'] == 'yes') {
|
||||
addUserType($user, 'contest_only');
|
||||
}
|
||||
|
||||
DB::update("UPDATE `user_info` SET `usertype` = '{$user['usertype']}' where `username` = '{$user['username']}'");
|
||||
|
||||
die(json_encode(['status' => 'success', 'message' => '权限修改成功']));
|
||||
}
|
||||
}
|
||||
|
||||
$pageTitle = $user['username'] == $myUser['username']
|
||||
? UOJLocale::get('modify my profile')
|
||||
: UOJLocale::get('modify his profile', $user['username'])
|
||||
?>
|
||||
<?php
|
||||
$REQUIRE_LIB['dialog'] = '';
|
||||
$REQUIRE_LIB['md5'] = '';
|
||||
?>
|
||||
<?php echoUOJPageHeader(UOJLocale::get('modify my profile')) ?>
|
||||
<h2 class="page-header">
|
||||
<?php if ($user['username'] == Auth::id()): ?>
|
||||
<?= UOJLocale::get('modify my profile') ?>
|
||||
<?php else: ?>
|
||||
修改 <?= $user['username'] ?> 的个人信息
|
||||
<?php endif ?>
|
||||
</h2>
|
||||
<?php if (isSuperUser($myUser)): ?>
|
||||
<p>您正在使用管理特权修改 <?= $user['username'] ?> 的个人信息。</p>
|
||||
|
||||
<?php echoUOJPageHeader($pageTitle) ?>
|
||||
|
||||
<h1 class="h2">
|
||||
<?= $pageTitle ?>
|
||||
</h1>
|
||||
|
||||
<div class="row mt-4">
|
||||
<!-- left col -->
|
||||
<div class="col-md-3">
|
||||
|
||||
<div class="list-group">
|
||||
<?php foreach ($tabs_info as $id => $tab): ?>
|
||||
<a
|
||||
role="button"
|
||||
class="list-group-item list-group-item-action <?= $cur_tab == $id ? 'active' : '' ?>"
|
||||
href="<?= $tab['url'] ?>">
|
||||
<?= $tab['name'] ?>
|
||||
</a>
|
||||
<?php endforeach ?>
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="btn btn-light d-block mt-2 w-100 text-start text-primary"
|
||||
style="--bs-btn-hover-bg: #d3d4d570; --bs-btn-hover-border-color: transparent;"
|
||||
href="<?= HTML::url("/user/{$user['username']}") ?>">
|
||||
<i class="bi bi-arrow-left"></i> 返回
|
||||
</a>
|
||||
|
||||
<?php if (isSuperUser($myUser) && $user['username'] != $myUser['username']): ?>
|
||||
<div class="alert alert-warning mt-3 small" role="alert">
|
||||
您正在使用管理特权查看并编辑其它用户的资料。
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<form id="form-update" class="form-horizontal">
|
||||
<?php if ($user['username'] == Auth::id()): ?>
|
||||
<h4><?= UOJLocale::get('please enter your password for authorization') ?></h4>
|
||||
<div id="div-old_password" class="form-group">
|
||||
<label for="input-old_password" class="col-sm-2 control-label"><?= UOJLocale::get('password') ?></label>
|
||||
<div class="col-sm-3">
|
||||
<input type="password" class="form-control" name="old_password" id="input-old_password" placeholder="<?= UOJLocale::get('enter your password') ?>" maxlength="20" />
|
||||
<span class="help-block" id="help-old_password"></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<h4><?= UOJLocale::get('please enter your new profile') ?></h4>
|
||||
<div id="div-password" class="form-group">
|
||||
<label for="input-password" class="col-sm-2 control-label"><?= UOJLocale::get('new password') ?></label>
|
||||
<div class="col-sm-3">
|
||||
<input type="password" class="form-control" id="input-password" name="password" placeholder="<?= UOJLocale::get('enter your new password') ?>" maxlength="20" />
|
||||
<input type="password" class="form-control top-buffer-sm" id="input-confirm_password" placeholder="<?= UOJLocale::get('re-enter your new password') ?>" maxlength="20" />
|
||||
<span class="help-block" id="help-password"><?= UOJLocale::get('leave it blank if you do not want to change the password') ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div-email" class="form-group">
|
||||
<label for="input-email" class="col-sm-2 control-label"><?= UOJLocale::get('email') ?></label>
|
||||
<div class="col-sm-3">
|
||||
<input type="email" class="form-control" name="email" id="input-email" value="<?=$user['email']?>" placeholder="<?= UOJLocale::get('enter your email') ?>" maxlength="50" />
|
||||
<span class="help-block" id="help-email"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div-qq" class="form-group">
|
||||
<label for="input-qq" class="col-sm-2 control-label"><?= UOJLocale::get('QQ') ?></label>
|
||||
<div class="col-sm-3">
|
||||
<input type="text" class="form-control" name="qq" id="input-qq" value="<?= $user['qq'] != 0 ? $user['qq'] : '' ?>" placeholder="<?= UOJLocale::get('enter your QQ') ?>" maxlength="50" />
|
||||
<span class="help-block" id="help-qq"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div-sex" class="form-group">
|
||||
<label for="input-sex" class="col-sm-2 control-label"><?= UOJLocale::get('sex') ?></label>
|
||||
<div class="col-sm-3">
|
||||
<select class="form-control" id="input-sex" name="sex">
|
||||
<option value="U"<?= $user['sex'] == 'U' ? ' selected="selected"' : ''?>><?= UOJLocale::get('refuse to answer') ?></option>
|
||||
<option value="M"<?= $user['sex'] == 'M' ? ' selected="selected"' : ''?>><?= UOJLocale::get('male') ?></option>
|
||||
<option value="F"<?= $user['sex'] == 'F' ? ' selected="selected"' : ''?>><?= UOJLocale::get('female') ?></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div-motto" class="form-group">
|
||||
<label for="input-motto" class="col-sm-2 control-label"><?= UOJLocale::get('motto') ?></label>
|
||||
<div class="col-sm-3">
|
||||
<textarea class="form-control" id="input-motto" name="motto"><?=HTML::escape($user['motto'])?></textarea>
|
||||
<span class="help-block" id="help-motto">格言支持 Markdown 语法。</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-3">
|
||||
<p class="form-control-static"><strong><?= UOJLocale::get('change avatar help') ?></strong></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-3">
|
||||
<button type="submit" id="button-submit" class="btn btn-secondary"><?= UOJLocale::get('submit') ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">
|
||||
function validateUpdatePost() {
|
||||
var ok = true;
|
||||
ok &= getFormErrorAndShowHelp('email', validateEmail);
|
||||
</div>
|
||||
<!-- end left col -->
|
||||
|
||||
<?php if ($user['username'] == Auth::id()): ?>
|
||||
ok &= getFormErrorAndShowHelp('old_password', validatePassword);
|
||||
<?php endif ?>
|
||||
<!-- right col -->
|
||||
<div class="col-md-9">
|
||||
<?php if ($cur_tab == 'profile'): ?>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
|
||||
<?php $update_profile_form->printHTML() ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php elseif ($cur_tab == 'password'): ?>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
|
||||
<form method="post" id="form-change_password">
|
||||
<div class="mb-3">
|
||||
<label for="input-current_password" class="form-label">
|
||||
<?= UOJLocale::get('current password') ?>
|
||||
</label>
|
||||
<input type="password" class="form-control" id="input-current_password" placeholder="<?= UOJLocale::get('enter your password') ?>" maxlength="20">
|
||||
<div id="help-current_password" class="invalid-feedback"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="input-new_password" class="form-label">
|
||||
<?= UOJLocale::get('new password') ?>
|
||||
</label>
|
||||
<input type="password" class="form-control" id="input-new_password" placeholder="<?= UOJLocale::get('enter your new password') ?>" maxlength="20">
|
||||
<div id="help-new_password" class="invalid-feedback"></div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="input-confirm_password" class="form-label">
|
||||
<?= UOJLocale::get('confirm new password') ?>
|
||||
</label>
|
||||
<input type="password" class="form-control" id="input-confirm_password" placeholder="<?= UOJLocale::get('re-enter your new password') ?>" maxlength="20">
|
||||
<div id="help-confirm_password" class="invalid-feedback"></div>
|
||||
</div>
|
||||
<?php if (isSuperUser($myUser) && $user['username'] != $myUser['username']): ?>
|
||||
<div class="alert alert-warning mb-0" role="alert">
|
||||
如需修改其他用户的密码,请前往 <a href="/super-manage/users" class="alert-link">系统管理</a> 页面操作。
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
if ($('#input-password').val().length > 0)
|
||||
ok &= getFormErrorAndShowHelp('password', validateSettingPassword);
|
||||
if ($('#input-qq').val().length > 0)
|
||||
ok &= getFormErrorAndShowHelp('qq', validateQQ);
|
||||
ok &= getFormErrorAndShowHelp('motto', validateMotto);
|
||||
return ok;
|
||||
}
|
||||
function submitUpdatePost() {
|
||||
if (!validateUpdatePost())
|
||||
return;
|
||||
$.post('', {
|
||||
change : '',
|
||||
etag : $('#input-email').val().length,
|
||||
ptag : $('#input-password').val().length,
|
||||
Qtag : $('#input-qq').val().length,
|
||||
email : $('#input-email').val(),
|
||||
password : md5($('#input-password').val(), "<?= getPasswordClientSalt() ?>"),
|
||||
<?php if ($user['username'] == Auth::id()): ?>
|
||||
old_password : md5($('#input-old_password').val(), "<?= getPasswordClientSalt() ?>"),
|
||||
<?php endif ?>
|
||||
qq : $('#input-qq').val(),
|
||||
sex : $('#input-sex').val(),
|
||||
motto : $('#input-motto').val()
|
||||
}, function(msg) {
|
||||
if (msg == 'ok') {
|
||||
BootstrapDialog.show({
|
||||
title : '修改成功',
|
||||
message : '用户信息修改成功',
|
||||
type : BootstrapDialog.TYPE_SUCCESS,
|
||||
buttons : [{
|
||||
label: '好的',
|
||||
action: function(dialog) {
|
||||
dialog.close();
|
||||
<div class="text-center">
|
||||
<button type="submit" id="button-submit-change_password" name="submit-change_password" value="change_password" class="mt-3 btn btn-secondary">更新</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
$('#form-change_password').submit(function() {
|
||||
var ok = true;
|
||||
|
||||
$('#result-alert').hide();
|
||||
|
||||
ok &= getFormErrorAndShowHelp('current_password', validatePassword);
|
||||
ok &= getFormErrorAndShowHelp('new_password', validateSettingPassword);
|
||||
|
||||
if (ok) {
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
data: {
|
||||
'submit-change_password': 'change_password',
|
||||
'current_password': md5($('#input-current_password').val(), "<?= getPasswordClientSalt() ?>"),
|
||||
'new_password': md5($('#input-new_password').val(), "<?= getPasswordClientSalt() ?>"),
|
||||
},
|
||||
success: function(res) {
|
||||
if (res.status === 'success') {
|
||||
$('#result-alert')
|
||||
.html('密码修改成功!')
|
||||
.addClass('alert-success')
|
||||
.removeClass('alert-danger')
|
||||
.show();
|
||||
} else {
|
||||
$('#result-alert')
|
||||
.html('密码修改失败。' + (res.message || ''))
|
||||
.removeClass('alert-success')
|
||||
.addClass('alert-danger')
|
||||
.show();
|
||||
}
|
||||
}],
|
||||
onhidden : function(dialog) {
|
||||
window.location.href = '/user/<?=$user['username']?>';
|
||||
|
||||
$(window).scrollTop(0);
|
||||
},
|
||||
error: function() {
|
||||
$('#result-alert')
|
||||
.html('密码修改失败:请求失败。')
|
||||
.removeClass('alert-success')
|
||||
.addClass('alert-danger')
|
||||
.show();
|
||||
|
||||
$(window).scrollTop(0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
BootstrapDialog.show({
|
||||
title : '修改失败',
|
||||
message : msg,
|
||||
type : BootstrapDialog.TYPE_DANGER,
|
||||
buttons: [{
|
||||
label: '好的',
|
||||
action: function(dialog) {
|
||||
dialog.close();
|
||||
}
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
$(document).ready(function(){$('#form-update').submit(function(e) {submitUpdatePost();e.preventDefault();});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
<?php elseif ($cur_tab == 'privilege'): ?>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div id="result-alert" class="alert" role="alert" style="display: none"></div>
|
||||
<form id="form-privilege" method="post">
|
||||
<?php if (isSuperUser($myUser)): ?>
|
||||
<fieldset>
|
||||
<?php else: ?>
|
||||
<fieldset disabled>
|
||||
<?php endif ?>
|
||||
<div class="input-group mb-3">
|
||||
<label for="input-user_type" class="form-label">
|
||||
<?= UOJLocale::get('user::user type') ?>
|
||||
</label>
|
||||
<div class="form-check ms-3">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="student" id="input-user_type" <?= hasUserType($user, 'student') && !hasUserType($user, 'teacher') ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="input-user_type">
|
||||
<?= UOJLocale::get('user::student') ?>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check ms-2">
|
||||
<input class="form-check-input" type="radio" name="user_type" value="teacher" id="input-user_type_2" <?= hasUserType($user, 'teacher') ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="input-user_type_2">
|
||||
<?= UOJLocale::get('user::teacher') ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" name="problem_uploader" id="input-problem_uploader" <?= hasUserType($user, 'problem_uploader') ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="input-problem_uploader">
|
||||
<?= UOJLocale::get('user::problem uploader') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" name="problem_manager" id="input-problem_manager" <?= hasUserType($user, 'problem_manager') ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="input-problem_manager">
|
||||
<?= UOJLocale::get('user::problem manager') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" name="contest_judger" id="input-contest_judger" <?= hasUserType($user, 'contest_judger') ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="input-contest_judger">
|
||||
<?= UOJLocale::get('user::contest judger') ?>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" name="contest_only" id="input-contest_only" <?= hasUserType($user, 'contest_only') ? 'checked' : '' ?>>
|
||||
<label class="form-check-label" for="input-contest_only">
|
||||
<?= UOJLocale::get('user::contest only') ?>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<?php if (isSuperUser($myUser)): ?>
|
||||
<div class="text-center">
|
||||
<button type="submit" id="button-submit-privilege" name="submit-privilege" value="privilege" class="mt-3 btn btn-secondary">更新</button>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</form>
|
||||
<script>
|
||||
$('#form-privilege').submit(function(e) {
|
||||
$('#result-alert').hide();
|
||||
|
||||
$.post('', {
|
||||
user_type: $('input[name=user_type]:checked').val(),
|
||||
problem_uploader: $('input[name=problem_uploader]').prop('checked') ? 'yes' : 'no',
|
||||
problem_manager: $('input[name=problem_manager]').prop('checked') ? 'yes' : 'no',
|
||||
contest_judger: $('input[name=contest_judger]').prop('checked') ? 'yes' : 'no',
|
||||
contest_only: $('input[name=contest_only]').prop('checked') ? 'yes' : 'no',
|
||||
'submit-privilege': 'privilege',
|
||||
}, function(res) {
|
||||
if (res && res.status === 'success') {
|
||||
$('#result-alert')
|
||||
.html('权限修改成功!')
|
||||
.addClass('alert-success')
|
||||
.removeClass('alert-danger')
|
||||
.show();
|
||||
|
||||
$(window).scrollTop(0);
|
||||
} else {
|
||||
$('#result-alert')
|
||||
.html('权限修改失败。' + (res.message || ''))
|
||||
.removeClass('alert-success')
|
||||
.addClass('alert-danger')
|
||||
.show();
|
||||
|
||||
$(window).scrollTop(0);
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<!-- end right col -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php echoUOJPageFooter() ?>
|
||||
|
@ -159,7 +159,7 @@ EOD;
|
||||
public function addVSelect($name, $options, $label_text, $default_value) {
|
||||
$default_value = htmlspecialchars($default_value);
|
||||
$html = <<<EOD
|
||||
<div id="div-$name">
|
||||
<div id="div-$name" class="mb-3">
|
||||
<label for="input-$name" class="control-label">$label_text</label>
|
||||
<select class="form-control form-select" id="input-{$name}" name="$name">
|
||||
|
||||
@ -225,6 +225,40 @@ EOD;
|
||||
EOD;
|
||||
$this->addNoVal($name, $html);
|
||||
}
|
||||
public function addVCheckboxes($name, $options, $label_text, $default_value) {
|
||||
$default_value = htmlspecialchars($default_value);
|
||||
$html = <<<EOD
|
||||
<div id="div-$name" class="input-group mb-3">
|
||||
<label class="form-label me-3">$label_text</label>
|
||||
EOD;
|
||||
foreach ($options as $opt_name => $opt_label) {
|
||||
$html .= <<<EOD
|
||||
<div class="form-check ms-2">
|
||||
EOD;
|
||||
if ($opt_name != $default_value) {
|
||||
$html .= <<<EOD
|
||||
<input class="form-check-input" type="radio" id="input-$name-$opt_name" name="$name" value="$opt_name">
|
||||
EOD;
|
||||
} else {
|
||||
$html .= <<<EOD
|
||||
<input class="form-check-input" type="radio" id="input-$name-$opt_name" name="$name" value="$opt_name" checked="checked">
|
||||
EOD;
|
||||
}
|
||||
$html .= <<<EOD
|
||||
<label class="form-check-label" for="input-$name-$opt_name">$opt_label</label>
|
||||
</div>
|
||||
EOD;
|
||||
}
|
||||
$html .= <<<EOD
|
||||
</div>
|
||||
EOD;
|
||||
$this->add($name, $html,
|
||||
function($opt) use ($options) {
|
||||
return isset($options[$opt]) ? '' : "无效选项";
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
public function addCKEditor($name, $label_text, $default_value, $validator_php, $validator_js) {
|
||||
$default_value = htmlspecialchars($default_value);
|
||||
global $REQUIRE_LIB;
|
||||
@ -534,14 +568,15 @@ EOD;
|
||||
if ($field['validator_js'] != 'always_ok') {
|
||||
echo <<<EOD
|
||||
if (${field['name']}_err) {
|
||||
$('#div-${field['name']}').addClass('has-error');
|
||||
$('#div-${field['name']}').addClass('has-error'); // for bootstrap4
|
||||
$('#input-${field['name']}').addClass('is-invalid');
|
||||
$('#help-${field['name']}').text(${field['name']}_err);
|
||||
ok = false;
|
||||
} else {
|
||||
$('#div-${field['name']}').removeClass('has-error');
|
||||
$('#div-${field['name']}').removeClass('has-error'); // for bootstrap4
|
||||
$('#input-${field['name']}').removeClass('is-invalid');
|
||||
$('#help-${field['name']}').text('');
|
||||
}
|
||||
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
@ -562,11 +597,13 @@ EOD;
|
||||
$(this).find("input[type='file']").each(function() {
|
||||
for (var i = 0; i < this.files.length; i++) {
|
||||
if (this.files[i].size > 10 * 1024 * 1024) {
|
||||
$('#div-' + $(this).attr('name')).addClass('has-error');
|
||||
$('#div-' + $(this).attr('name')).addClass('has-error'); // for bootstrap4
|
||||
$('#input-' + $(this).attr('name')).addClass('is-invalid');
|
||||
$('#help-' + $(this).attr('name')).text('文件大小不能超过10M');
|
||||
ok = false;
|
||||
} else {
|
||||
$('#div-' + $(this).attr('name')).removeClass('has-error');
|
||||
$('#div-' + $(this).attr('name')).removeClass('has-error'); // for bootstrap4
|
||||
$('#input-' + $(this).attr('name')).removeClass('is-invalid');
|
||||
$('#help-' + $(this).attr('name')).text('');
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ function blog_name_decode($name) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
function addUserType($user, $type) {
|
||||
function addUserType(&$user, $type) {
|
||||
$usertype = explode(',', $user['usertype']);
|
||||
if (!in_array($type, $usertype)) {
|
||||
$usertype[] = $type;
|
||||
@ -137,7 +137,7 @@ function addUserType($user, $type) {
|
||||
$user['usertype'] = implode(',', $usertype);
|
||||
return $user;
|
||||
}
|
||||
function removeUserType($user, $type) {
|
||||
function removeUserType(&$user, $type) {
|
||||
$usertype = explode(',', $user['usertype']);
|
||||
if (in_array($type, $usertype)) {
|
||||
$usertype = array_diff($usertype, array($type));
|
||||
|
@ -55,3 +55,7 @@ function validateURL($url) {
|
||||
function validateString($str) {
|
||||
return preg_match('/[^0-9a-zA-Z]/', $str) !== true;
|
||||
}
|
||||
|
||||
function validateGitHubUsername($username) {
|
||||
return is_string($username) && preg_match('/^[a-zA-Z0-9_-]{1,20}$/', $username);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ return [
|
||||
'private message' => 'Private Message',
|
||||
'system message' => 'System Message',
|
||||
'system manage' => 'System Manage',
|
||||
'avatar' => 'Avatar',
|
||||
'contests' => 'Contests',
|
||||
'problems' => 'Problems',
|
||||
'problems lists' => 'Problems Lists',
|
||||
@ -35,11 +36,15 @@ return [
|
||||
'username' => 'Username',
|
||||
'password' => 'Password',
|
||||
'new password' => 'New password',
|
||||
'current password' => 'Current password',
|
||||
'confirm new password' => 'Confirm new password',
|
||||
'verification code' => 'Verification code',
|
||||
'email' => 'Email',
|
||||
'QQ' => 'QQ',
|
||||
'school' => 'School',
|
||||
'sex' => 'Sex',
|
||||
'motto' => 'Motto',
|
||||
'codeforces handle' => 'Codeforces handle',
|
||||
'view all' => 'View all',
|
||||
'hidden' => 'Hidden',
|
||||
'appraisal' => 'Appraisal',
|
||||
@ -58,6 +63,9 @@ return [
|
||||
'user profile' => 'User profile',
|
||||
'send private message' => 'Send private message',
|
||||
'modify my profile' => 'Modify my profile',
|
||||
'modify his profile' => function($name) {
|
||||
return "Modify $name's profile";
|
||||
},
|
||||
'visit his blog' => function($name) {
|
||||
return "Visit $name's blog";
|
||||
},
|
||||
@ -68,7 +76,7 @@ return [
|
||||
'please enter your password for authorization' => 'Please enter your password for authorization',
|
||||
'please enter your new profile' => 'Please enter your new profile',
|
||||
'leave it blank if you do not want to change the password' => 'Leave it blank if you do not want to change the password',
|
||||
'change avatar help' => 'Do you want to change your avatar? Please see <a href="/faq">Help</a>',
|
||||
'change avatar help' => 'Do you want to change your avatar? Please see <a class="text-decoration-none" href="/faq">Help</a>',
|
||||
'enter your username' => 'Enter your username',
|
||||
'enter your email' => 'Enter your email',
|
||||
'enter your password' => 'Enter your password',
|
||||
@ -90,16 +98,6 @@ return [
|
||||
'friend links' => 'Frequently Used Links',
|
||||
'server time' => 'Server Time',
|
||||
'opensource project' => 'OpenSource Project, modified by S2OJ',
|
||||
'admin' => 'Admin',
|
||||
'student' => 'Student',
|
||||
'teacher' => 'Teacher',
|
||||
'problem uploader' => 'Problem Uploader',
|
||||
'problem manager' => 'Problem Manager',
|
||||
'contest judger' => 'Contest Judger',
|
||||
'contest only' => 'Contest Only',
|
||||
'last active at' => 'Last active at',
|
||||
'online' => 'Online',
|
||||
'offline' => 'Offline',
|
||||
'apps' => 'Apps',
|
||||
'image hosting' => 'Image Hosting',
|
||||
'html to markdown' => 'HTML to Markdown',
|
||||
|
@ -9,6 +9,7 @@ return [
|
||||
'private message' => '私信',
|
||||
'system message' => '系统消息',
|
||||
'system manage' => '系统管理',
|
||||
'avatar' => '头像',
|
||||
'contests' => '比赛',
|
||||
'problems' => '题库',
|
||||
'problems lists' => '题单',
|
||||
@ -35,11 +36,15 @@ return [
|
||||
'username' => '用户名',
|
||||
'password' => '密码',
|
||||
'new password' => '新密码',
|
||||
'current password' => '当前密码',
|
||||
'confirm new password' => '确认新密码',
|
||||
'verification code' => '验证码',
|
||||
'email' => 'Email',
|
||||
'QQ' => 'QQ',
|
||||
'school' => '学校',
|
||||
'sex' => '性别',
|
||||
'motto' => '格言',
|
||||
'codeforces handle' => 'Codeforces 用户名',
|
||||
'view all' => '查看全部',
|
||||
'hidden' => '隐藏',
|
||||
'appraisal' => '评价',
|
||||
@ -58,6 +63,9 @@ return [
|
||||
'user profile' => '用户信息',
|
||||
'send private message' => '发送私信',
|
||||
'modify my profile' => '更改个人信息',
|
||||
'modify his profile' => function($name) {
|
||||
return "更改 $name 的个人信息";
|
||||
},
|
||||
'visit his blog' => function($name) {
|
||||
return "访问 $name 的博客";
|
||||
},
|
||||
@ -68,7 +76,7 @@ return [
|
||||
'please enter your password for authorization' => '请输入您的密码进行身份验证',
|
||||
'please enter your new profile' => '请输入新的个人信息',
|
||||
'leave it blank if you do not want to change the password' => '如果不想修改密码请留空',
|
||||
'change avatar help' => '想改头像?见<a href="/faq">帮助</a>',
|
||||
'change avatar help' => '想改头像?见 <a class="text-decoration-none" href="/faq">帮助</a>',
|
||||
'enter your username' => '输入用户名',
|
||||
'enter your email' => '输入 Email',
|
||||
'enter your password' => '输入密码',
|
||||
@ -90,16 +98,6 @@ return [
|
||||
'friend links' => '常用链接',
|
||||
'server time' => '服务器时间',
|
||||
'opensource project' => '开源项目,S2OJ 魔改版',
|
||||
'admin' => '管理员',
|
||||
'student' => '学生',
|
||||
'teacher' => '老师',
|
||||
'problem uploader' => '题目上传者',
|
||||
'problem manager' => '题目管理员',
|
||||
'contest judger' => '比赛评测员',
|
||||
'contest only' => '仅比赛用户',
|
||||
'online' => '在线',
|
||||
'offline' => '离线',
|
||||
'last active at' => '最后活动于',
|
||||
'apps' => '应用',
|
||||
'image hosting' => '图床',
|
||||
'html to markdown' => 'HTML 转 Markdown',
|
||||
|
17
web/app/locale/user/en.php
Normal file
17
web/app/locale/user/en.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
return [
|
||||
'belongs to these groups' => 'Belongs to these groups:',
|
||||
'avatar source' => 'Avatar source',
|
||||
'website' => 'Website',
|
||||
'user type' => 'User type',
|
||||
'admin' => 'Admin',
|
||||
'student' => 'Student',
|
||||
'teacher' => 'Teacher',
|
||||
'problem uploader' => 'Problem Uploader',
|
||||
'problem manager' => 'Problem Manager',
|
||||
'contest judger' => 'Contest Judger',
|
||||
'contest only' => 'Contest Only',
|
||||
'last active at' => 'Last active at',
|
||||
'online' => 'Online',
|
||||
'offline' => 'Offline',
|
||||
];
|
17
web/app/locale/user/zh-cn.php
Normal file
17
web/app/locale/user/zh-cn.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
return [
|
||||
'belongs to these groups' => '属于这些小组:',
|
||||
'avatar source' => '头像来源',
|
||||
'website' => '网址',
|
||||
'user type' => '用户类型',
|
||||
'admin' => '管理员',
|
||||
'student' => '学生',
|
||||
'teacher' => '老师',
|
||||
'problem uploader' => '题目上传者',
|
||||
'problem manager' => '题目管理员',
|
||||
'contest judger' => '比赛评测员',
|
||||
'contest only' => '仅比赛用户',
|
||||
'online' => '在线',
|
||||
'offline' => '离线',
|
||||
'last active at' => '最后活动于',
|
||||
];
|
@ -8,6 +8,20 @@ class HTML {
|
||||
return strip_tags($str);
|
||||
}
|
||||
public static function avatar_addr($user, $size) {
|
||||
if ($user['avatar_source'] == 'qq' && $user['qq']) {
|
||||
$s = '5';
|
||||
|
||||
if ($size <= 40) {
|
||||
$s = '2';
|
||||
} elseif ($size <= 100) {
|
||||
$s = '3';
|
||||
} elseif ($size <= 140) {
|
||||
$s = '4';
|
||||
}
|
||||
|
||||
return "https://q1.qlogo.cn/g?b=qq&nk={$user['qq']}&s=$s";
|
||||
}
|
||||
|
||||
return '//gravatar.loli.net/avatar/' . md5(strtolower(trim($user['email']))) . "?d=mm&s=$size";
|
||||
}
|
||||
|
||||
@ -24,14 +38,14 @@ class HTML {
|
||||
return '<input type="hidden" name="_token" value="'.crsf_token().'" />';
|
||||
}
|
||||
public static function div_vinput($name, $type, $label_text, $default_value) {
|
||||
return '<div id="'."div-$name".'">'
|
||||
. '<label for="'."input-$name".'" class="control-label">'.$label_text.'</label>'
|
||||
return '<div id="'."div-$name".'" class="mb-3">'
|
||||
. '<label for="'."input-$name".'" class="control-label form-label">'.$label_text.'</label>'
|
||||
. '<input type="'.$type.'" class="form-control" name="'.$name.'" id="'."input-$name".'" value="'.HTML::escape($default_value).'" />'
|
||||
. '<span class="help-block" id="'."help-$name".'"></span>'
|
||||
. '<span class="help-block invalid-feedback" id="'."help-$name".'"></span>'
|
||||
. '</div>';
|
||||
}
|
||||
public static function div_vtextarea($name, $label_text, $default_value) {
|
||||
return '<div id="'."div-$name".'">'
|
||||
return '<div id="'."div-$name".'" class="mb-3">'
|
||||
. '<label for="'."input-$name".'" class="control-label">'.$label_text.'</label>'
|
||||
. '<textarea class="form-control" name="'.$name.'" id="'."input-$name".'">'.HTML::escape($default_value).'</textarea>'
|
||||
. '<span class="help-block" id="'."help-$name".'"></span>'
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
class UOJLocale {
|
||||
public static $supported_locales = array('zh-cn', 'en');
|
||||
public static $supported_modules = array('basic', 'contests', 'problems', 'time');
|
||||
public static $supported_modules = array('basic', 'contests', 'problems', 'time', 'user');
|
||||
public static $data = array();
|
||||
public static $required = array();
|
||||
|
||||
|
@ -105,7 +105,7 @@ class Upgrader {
|
||||
|
||||
public static function upgradeToLatest() {
|
||||
$names = array_filter(scandir(self::upgraderRoot()), function ($name) {
|
||||
return is_dir(self::upgraderRoot().'/'.$name) && preg_match('/^\d+_[a-zA-Z_]+$/', $name);
|
||||
return is_dir(self::upgraderRoot().'/'.$name) && preg_match('/^\d+_[0-9a-zA-Z_]+$/', $name);
|
||||
});
|
||||
|
||||
natsort($names);
|
||||
|
@ -72,7 +72,7 @@ Route::group([
|
||||
Route::any('/reset-password', '/reset_pw.php');
|
||||
|
||||
Route::any('/user/{username}', '/user_info.php');
|
||||
Route::any('/user/{username}/edit', '/user_info_edit.php');
|
||||
Route::any('/user/{username}/edit(?:/{tab})?', '/user_info_edit.php');
|
||||
Route::any('/user_msg', '/user_msg.php');
|
||||
Route::any('/user/{username}/system_msg', '/user_system_msg.php');
|
||||
|
||||
|
1
web/app/upgrade/6_user_info_v2/README.md
Normal file
1
web/app/upgrade/6_user_info_v2/README.md
Normal file
@ -0,0 +1 @@
|
||||
ref: https://github.com/renbaoshuo/S2OJ/pull/6
|
4
web/app/upgrade/6_user_info_v2/down.sql
Normal file
4
web/app/upgrade/6_user_info_v2/down.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `codeforces_handle`;
|
||||
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `github`;
|
||||
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `website`;
|
||||
ALTER TABLE `user_info` DROP COLUMN IF EXISTS `avatar_source`;
|
4
web/app/upgrade/6_user_info_v2/up.sql
Normal file
4
web/app/upgrade/6_user_info_v2/up.sql
Normal file
@ -0,0 +1,4 @@
|
||||
ALTER TABLE `user_info` ADD COLUMN `codeforces_handle` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
|
||||
ALTER TABLE `user_info` ADD COLUMN `github` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
|
||||
ALTER TABLE `user_info` ADD COLUMN `website` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '';
|
||||
ALTER TABLE `user_info` ADD COLUMN `avatar_source` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'gravatar';
|
@ -219,12 +219,6 @@
|
||||
<?= HTML::js_src('/js/ckeditor/ckeditor.js') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($REQUIRE_LIB['github_contribution_graph'])): ?>
|
||||
<!-- github contribution graph -->
|
||||
<?= HTML::css_link('/css/github_contribution_graph.css') ?>
|
||||
<?= HTML::js_src('/js/jquery.github_contribution_graph.js') ?>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (isset($REQUIRE_LIB['calendar_heatmap'])): ?>
|
||||
<!-- jquery-calendar-heatmap -->
|
||||
<?= HTML::css_link('/css/jquery.calendar_heatmap.min.css') ?>
|
||||
|
@ -68,7 +68,7 @@ function fTime($time, $gran = -1) {
|
||||
<div class="card-body">
|
||||
<?php if ($user['usergroup'] == 'S'): ?>
|
||||
<span class="badge bg-secondary">
|
||||
<?= UOJLocale::get('admin') ?>
|
||||
<?= UOJLocale::get('user::admin') ?>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
<h3>
|
||||
@ -102,35 +102,107 @@ function fTime($time, $gran = -1) {
|
||||
<li class="list-group-item">
|
||||
<i class="bi bi-key-fill me-1"></i>
|
||||
<?php foreach (explode(',', $user['usertype']) as $idx => $type): ?>
|
||||
<?php if ($idx): ?>, <?php endif ?>
|
||||
<?php if ($type == 'teacher'): ?>
|
||||
<?= UOJLocale::get('teacher') ?>
|
||||
<?php elseif ($type == 'student'): ?>
|
||||
<?= UOJLocale::get('student') ?>
|
||||
<?php elseif ($type == 'problem_uploader'): ?>
|
||||
<?= UOJLocale::get('problem uploader') ?>
|
||||
<?php elseif ($type == 'problem_manager'): ?>
|
||||
<?= UOJLocale::get('problem manager') ?>
|
||||
<?php elseif ($type == 'contest_judger'): ?>
|
||||
<?= UOJLocale::get('contest judger') ?>
|
||||
<?php elseif ($type == 'contest_only'): ?>
|
||||
<?= UOJLocale::get('contest only') ?>
|
||||
<?php else: ?>
|
||||
<?= HTML::escape($type) ?>
|
||||
<?php endif ?>
|
||||
<?php if ($idx): ?>,<?php endif ?>
|
||||
<span><?= UOJLocale::get('user::' . str_replace('_', ' ', $type)) ?: HTML::escape($type) ?></span>
|
||||
<?php endforeach ?>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if ($user['email']): ?>
|
||||
<li class="list-group-item">
|
||||
<i class="bi bi-envelope-fill me-1"></i>
|
||||
<a class="text-decoration-none text-body" href="mailto:<?= HTML::escape($user['email']) ?>">
|
||||
<?= HTML::escape($user['email']) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if ($user['qq']): ?>
|
||||
<li class="list-group-item">
|
||||
<i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16"><path d="M433.754 420.445c-11.526 1.393-44.86-52.741-44.86-52.741 0 31.345-16.136 72.247-51.051 101.786 16.842 5.192 54.843 19.167 45.803 34.421-7.316 12.343-125.51 7.881-159.632 4.037-34.122 3.844-152.316 8.306-159.632-4.037-9.045-15.25 28.918-29.214 45.783-34.415-34.92-29.539-51.059-70.445-51.059-101.792 0 0-33.334 54.134-44.859 52.741-5.37-.65-12.424-29.644 9.347-99.704 10.261-33.024 21.995-60.478 40.144-105.779C60.683 98.063 108.982.006 224 0c113.737.006 163.156 96.133 160.264 214.963 18.118 45.223 29.912 72.85 40.144 105.778 21.768 70.06 14.716 99.053 9.346 99.704z" fill="currentColor"/></svg></i>
|
||||
<?= HTML::escape($user['qq']) ?>
|
||||
<a class="text-decoration-none text-body" href="http://wpa.qq.com/msgrd?v=3&uin=<?= HTML::escape($user['qq']) ?>&site=qq&menu=yes" target="_blank">
|
||||
<?= HTML::escape($user['qq']) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if ($user['github']): ?>
|
||||
<li class="list-group-item">
|
||||
<i class="bi bi-github me-1"></i>
|
||||
<a class="text-decoration-none text-body" href="https://github.com/<?= HTML::escape($user['github']) ?>" target="_blank">
|
||||
<?= HTML::escape($user['github']) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if ($user['codeforces_handle']): ?>
|
||||
<li class="list-group-item d-flex align-items-center">
|
||||
<div class="flex-shrink-0"><i class="align-text-bottom me-1"><svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="0 0 24 24" width="16" height="16"><title>Codeforces</title><path d="M4.5 7.5C5.328 7.5 6 8.172 6 9v10.5c0 .828-.672 1.5-1.5 1.5h-3C.673 21 0 20.328 0 19.5V9c0-.828.673-1.5 1.5-1.5h3zm9-4.5c.828 0 1.5.672 1.5 1.5v15c0 .828-.672 1.5-1.5 1.5h-3c-.827 0-1.5-.672-1.5-1.5v-15c0-.828.673-1.5 1.5-1.5h3zm9 7.5c.828 0 1.5.672 1.5 1.5v7.5c0 .828-.672 1.5-1.5 1.5h-3c-.828 0-1.5-.672-1.5-1.5V12c0-.828.672-1.5 1.5-1.5h3z"/></svg></i> </div>
|
||||
<div>
|
||||
<a id="codeforces-profile-link" class="text-decoration-none" href="https://codeforces.com/profile/<?= $user['codeforces_handle'] ?>" target="_blank" style="color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;">
|
||||
<?= $user['codeforces_handle'] ?>
|
||||
</a>
|
||||
<div id="codeforces-rating" style="font-family: verdana, arial, sans-serif; line-height: 1.2em; text-transform: capitalize;"></div>
|
||||
</div>
|
||||
<script>
|
||||
function getRatingColor(rating) {
|
||||
if (rating >= 2400) return 'ff0000';
|
||||
if (rating >= 2100) return 'ff8c00';
|
||||
if (rating >= 1900) return 'aa00aa';
|
||||
if (rating >= 1600) return '0000ff';
|
||||
if (rating >= 1400) return '03a89e';
|
||||
if (rating >= 1200) return '008000';
|
||||
return '808080';
|
||||
}
|
||||
|
||||
function showCodeforcesRating(handle, rating, text) {
|
||||
var color = '#' + getRatingColor(rating);
|
||||
|
||||
$('#codeforces-profile-link')
|
||||
.html(rating >= 3000 ? ('<span style="color:#000!important">' + handle[0] + '</span>' + handle.substring(1)) : handle)
|
||||
.css('color', color)
|
||||
.css('font-family', 'Helvetica Neue, Helvetica, Arial, sans-serif')
|
||||
.css('font-size', '1.1em')
|
||||
.css('font-weight', 'bold');
|
||||
$('#codeforces-rating')
|
||||
.html(text + ', ' + rating)
|
||||
.css('color', color);
|
||||
}
|
||||
|
||||
function processCodeforcesInfoData(data) {
|
||||
if (!data || data.status !== 'OK' || !data.result || !data.result.length) return;
|
||||
|
||||
var result = data.result[0];
|
||||
|
||||
if (result.rating) {
|
||||
showCodeforcesRating(result.handle, result.rating, result.rank);
|
||||
} else {
|
||||
showCodeforcesRating(result.handle, 0, 'Unrated');
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
if (('Promise' in window) && ('any' in Promise)) {
|
||||
// race
|
||||
Promise.any([
|
||||
fetch('https://codeforces.com/api/user.info?handles=<?= $user['codeforces_handle'] ?>'),
|
||||
fetch('https://codeforc.es/api/user.info?handles=<?= $user['codeforces_handle'] ?>')
|
||||
]).then(function(res) {
|
||||
return res.json();
|
||||
}).then(function(data) {
|
||||
processCodeforcesInfoData(data);
|
||||
});
|
||||
} else {
|
||||
$.get('https://codeforces.com/api/user.info?handles=<?= $user['codeforces_handle'] ?>', function(data) {
|
||||
processCodeforcesInfoData(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
<?php if ($user['website']): ?>
|
||||
<li class="list-group-item">
|
||||
<i class="bi bi-link-45deg me-1"></i>
|
||||
<a class="text-decoration-none text-body text-break" href="<?= HTML::escape($user['website']) ?>" target="_blank">
|
||||
<?= HTML::escape($user['website']) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
@ -139,16 +211,16 @@ function fTime($time, $gran = -1) {
|
||||
<?php if (time() - $last_visited < 60 * 15): // 15 mins ?>
|
||||
<span class="text-success">
|
||||
<i class="bi bi-circle-fill me-1"></i>
|
||||
<?= UOJLocale::get('online') ?>
|
||||
<?= UOJLocale::get('user::online') ?>
|
||||
</span>
|
||||
<?php else: ?>
|
||||
<span class="text-danger">
|
||||
<i class="bi bi-circle-fill me-1"></i>
|
||||
<?= UOJLocale::get('offline') ?>
|
||||
<?= UOJLocale::get('user::offline') ?>
|
||||
</span>
|
||||
<?php if ($last_visited > 0): ?>
|
||||
<span class="text-muted small">
|
||||
, <?= UOJLocale::get('last active at') ?>
|
||||
, <?= UOJLocale::get('user::last active at') ?>
|
||||
<?= fTime($last_visited, 0) ?>
|
||||
</span>
|
||||
<?php endif ?>
|
||||
@ -182,10 +254,34 @@ function fTime($time, $gran = -1) {
|
||||
|
||||
<a class="nav-link" href="<?= HTML::blog_url($user['username'], '/self_reviews') ?>">
|
||||
<i class="bi bi-arrow-right-square"></i>
|
||||
赛后总结
|
||||
<?= UOJLocale::get('contests::contest self reviews') ?>
|
||||
</a>
|
||||
</nav>
|
||||
<div class="card card-default mb-2">
|
||||
|
||||
<?php if (!isset($is_blog_aboutme)): ?>
|
||||
<?php $groups = queryGroupsOfUser($user['username']) ?>
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title h5">
|
||||
<?= UOJLocale::get('user::belongs to these groups') ?>
|
||||
</h4>
|
||||
<ul class="mb-0">
|
||||
<?php foreach ($groups as $group): ?>
|
||||
<li>
|
||||
<a class="text-decoration-none" href="<?= HTML::url('/group/'.$group['id']) ?>">
|
||||
<?= $group['title'] ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach ?>
|
||||
<?php if (!count($groups)): ?>
|
||||
<?= UOJLocale::get('none') ?>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<?php
|
||||
$_result = DB::query("select date_format(submit_time, '%Y-%m-%d'), problem_id from submissions where submitter = '{$user['username']}' and score = 100 and date(submit_time) between date_sub(curdate(), interval 1 year) and curdate()");
|
||||
@ -214,8 +310,8 @@ while ($row = DB::fetch($_result)) {
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-body">
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<?php $ac_problems = DB::selectAll("select a.problem_id as problem_id, b.title as title from best_ac_submissions a inner join problems b on a.problem_id = b.id where submitter = '{$user['username']}' order by id") ?>
|
||||
<h4 class="card-title h5">
|
||||
<?= UOJLocale::get('accepted problems').': '.UOJLocale::get('n problems in total', count($ac_problems))?>
|
||||
@ -233,7 +329,7 @@ while ($row = DB::fetch($_result)) {
|
||||
<?= UOJLocale::get('none'); ?>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (isSuperUser($myUser)): ?>
|
||||
|
@ -1,24 +0,0 @@
|
||||
.svg-tip {
|
||||
padding: 10px;
|
||||
background: rgba(0,0,0,0.8);
|
||||
color: #bbb;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.svg-tip:after {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
height: 5px;
|
||||
width: 5px;
|
||||
bottom: -10px;
|
||||
margin: 0 0 0 -5px;
|
||||
content: " ";
|
||||
border: 5px solid transparent;
|
||||
border-top-color: rgba(0,0,0,0.8);
|
||||
}
|
@ -1,308 +0,0 @@
|
||||
/**
|
||||
* Jquery plugin to render like contribution graph on Github.
|
||||
*
|
||||
* @see {@link https://github.com/bachvtuan/Github-Contribution-Graph}
|
||||
* @author bachvtuan@gmail.com
|
||||
* @license MIT License
|
||||
* @since 0.1.0
|
||||
*/
|
||||
|
||||
//Format string
|
||||
if (!String.prototype.formatString) {
|
||||
String.prototype.formatString = function () {
|
||||
var args = arguments;
|
||||
return this.replace(/{(\d+)}/g, function (match, number) {
|
||||
return typeof args[number] != "undefined" ? args[number] : match;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
(function ($) {
|
||||
$.fn.github_graph = function (options) {
|
||||
//If the number less than 10, add Zero before it
|
||||
var prettyNumber = function (number) {
|
||||
return number < 10
|
||||
? "0" + number.toString()
|
||||
: (number = number.toString());
|
||||
};
|
||||
|
||||
/*
|
||||
Count the number on each day and store the object
|
||||
*/
|
||||
var processListTimeStamp = function (list_timestamp) {
|
||||
//The result will store into this varriable
|
||||
obj_timestamp = {};
|
||||
for (var i = 0; i < list_timestamp.length; i++) {
|
||||
var _type = typeof list_timestamp[i];
|
||||
var _d =
|
||||
_type == "number"
|
||||
? new Date(list_timestamp[i])
|
||||
: new Date(list_timestamp[i].timestamp);
|
||||
|
||||
var display_date = getDisplayDate(_d);
|
||||
var increase = _type == "number" ? 1 : list_timestamp[i].count;
|
||||
if (!obj_timestamp[display_date]) {
|
||||
obj_timestamp[display_date] = increase;
|
||||
} else {
|
||||
obj_timestamp[display_date] += increase;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var getDisplayDate = function (date_obj) {
|
||||
var pretty_month = prettyNumber(date_obj.getMonth() + 1);
|
||||
var pretty_date = prettyNumber(date_obj.getDate());
|
||||
return "{0}-{1}-{2}".formatString(
|
||||
date_obj.getFullYear(),
|
||||
pretty_month,
|
||||
pretty_date
|
||||
);
|
||||
};
|
||||
|
||||
var getCount = function (display_date) {
|
||||
if (obj_timestamp[display_date]) {
|
||||
return obj_timestamp[display_date];
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
var getColor = function (count) {
|
||||
if (typeof settings.colors[0] == "string") {
|
||||
return count > settings.colors.length - 1
|
||||
? settings.colors[settings.colors.length - 1]
|
||||
: settings.colors[count];
|
||||
}
|
||||
|
||||
const isLargeNumber = (element) => element.count > count;
|
||||
i = settings.colors.findIndex(isLargeNumber);
|
||||
return i == -1
|
||||
? settings.colors[settings.colors.length - 1].color
|
||||
: settings.colors[i - 1].color;
|
||||
};
|
||||
|
||||
var start = function () {
|
||||
processListTimeStamp(settings.data);
|
||||
var wrap_chart = _this;
|
||||
|
||||
settings.colors_length = settings.colors.length;
|
||||
var radius = settings.border.radius;
|
||||
var hoverColor = settings.border.hover_color;
|
||||
var clickCallback = settings.click;
|
||||
|
||||
var start_date;
|
||||
if (settings.start_date == null) {
|
||||
// if set null, will get from 365 days from now
|
||||
start_date = new Date();
|
||||
start_date.setMonth(start_date.getMonth() - 12);
|
||||
start_date.setDate(start_date.getDate() + 1);
|
||||
} else {
|
||||
// formats:
|
||||
// - YYYY-MM-DD
|
||||
// - YYYY/MM/DD
|
||||
start_date = new Date(settings.start_date);
|
||||
}
|
||||
|
||||
end_date = new Date(start_date);
|
||||
end_date.setMonth(end_date.getMonth() + 12);
|
||||
end_date.setDate(end_date.getDate() - 1);
|
||||
|
||||
var loop_html = "";
|
||||
var step = 13;
|
||||
|
||||
var month_position = [];
|
||||
month_position.push({ month_index: start_date.getMonth(), x: 0 });
|
||||
var using_month = start_date.getMonth();
|
||||
|
||||
var week = 0;
|
||||
var g_x = week * step;
|
||||
var item_html =
|
||||
'<g transform="translate(' + g_x.toString() + ', 0)">';
|
||||
|
||||
for (
|
||||
;
|
||||
start_date.getTime() <= end_date.getTime();
|
||||
start_date.setDate(start_date.getDate() + 1)
|
||||
) {
|
||||
if (start_date.getDay() == 0) {
|
||||
var item_html =
|
||||
'<g transform="translate(' + g_x.toString() + ', 0)">';
|
||||
}
|
||||
|
||||
var month_in_day = start_date.getMonth();
|
||||
var data_date = getDisplayDate(start_date);
|
||||
|
||||
if (start_date.getDay() == 0 && month_in_day != using_month) {
|
||||
using_month = month_in_day;
|
||||
month_position.push({ month_index: using_month, x: g_x });
|
||||
}
|
||||
var count = getCount(data_date);
|
||||
var color = getColor(count);
|
||||
|
||||
var y = start_date.getDay() * step;
|
||||
item_html +=
|
||||
'<rect class="day" width="11" height="11" y="' +
|
||||
y +
|
||||
'" fill="' +
|
||||
color +
|
||||
'" data-count="' +
|
||||
count +
|
||||
'" data-date="' +
|
||||
data_date +
|
||||
'" rx="' +
|
||||
radius +
|
||||
'" ry="' +
|
||||
radius +
|
||||
'"/>';
|
||||
|
||||
if (start_date.getDay() == 6) {
|
||||
item_html += "</g>";
|
||||
loop_html += item_html;
|
||||
|
||||
item_html = null;
|
||||
|
||||
week++;
|
||||
g_x = week * step;
|
||||
}
|
||||
}
|
||||
|
||||
if (item_html != null) {
|
||||
item_html += "</g>";
|
||||
loop_html += item_html;
|
||||
}
|
||||
|
||||
//trick
|
||||
if (month_position[1].x - month_position[0].x < 40) {
|
||||
//Fix ugly graph by remove first item
|
||||
month_position.shift(0);
|
||||
}
|
||||
|
||||
for (var i = 0; i < month_position.length; i++) {
|
||||
var item = month_position[i];
|
||||
var month_name = settings.month_names[item.month_index];
|
||||
loop_html +=
|
||||
'<text x="' +
|
||||
item.x +
|
||||
'" y="-5" class="month">' +
|
||||
month_name +
|
||||
"</text>";
|
||||
}
|
||||
|
||||
//Add Monday, Wenesday, Friday label
|
||||
loop_html +=
|
||||
'<text text-anchor="middle" class="wday" dx="-10" dy="22">{0}</text>'.formatString(
|
||||
settings.h_days[0]
|
||||
) +
|
||||
'<text text-anchor="middle" class="wday" dx="-10" dy="48">{0}</text>'.formatString(
|
||||
settings.h_days[1]
|
||||
) +
|
||||
'<text text-anchor="middle" class="wday" dx="-10" dy="74">{0}</text>'.formatString(
|
||||
settings.h_days[2]
|
||||
);
|
||||
|
||||
//Fixed size for now with width= 721 and height = 110
|
||||
var wire_html =
|
||||
'<svg width="721" height="110" viewBox="0 0 721 110" class="js-calendar-graph-svg">' +
|
||||
'<g transform="translate(20, 20)">' +
|
||||
loop_html +
|
||||
"</g>" +
|
||||
"</svg>";
|
||||
|
||||
wrap_chart.html(wire_html);
|
||||
|
||||
$(_this)
|
||||
.find(".day")
|
||||
.on("click", function () {
|
||||
if (clickCallback) {
|
||||
clickCallback(
|
||||
$(this).attr("data-date"),
|
||||
parseInt($(this).attr("data-count"))
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$(_this)
|
||||
.find(".day")
|
||||
.hover(
|
||||
function () {
|
||||
$(this).attr(
|
||||
"style",
|
||||
"stroke-width: 1; stroke: " + hoverColor
|
||||
);
|
||||
},
|
||||
function () {
|
||||
$(this).attr("style", "stroke-width:0");
|
||||
}
|
||||
);
|
||||
|
||||
_this.find("rect").on("mouseenter", mouseEnter);
|
||||
_this.find("rect").on("mouseleave", mouseLeave);
|
||||
appendTooltip();
|
||||
};
|
||||
|
||||
var mouseLeave = function (evt) {
|
||||
$(".svg-tip").hide();
|
||||
};
|
||||
|
||||
//handle event mouseenter when enter into rect element
|
||||
var mouseEnter = function (evt) {
|
||||
var target_offset = $(evt.target).offset();
|
||||
var count = $(evt.target).attr("data-count");
|
||||
var date = $(evt.target).attr("data-date");
|
||||
|
||||
var count_text = count > 1 ? settings.texts[1] : settings.texts[0];
|
||||
var text = "{0} {1} on {2}".formatString(count, count_text, date);
|
||||
|
||||
var svg_tip = $(".svg-tip").show();
|
||||
svg_tip.html(text);
|
||||
var svg_width = Math.round(svg_tip.width() / 2 + 5);
|
||||
var svg_height = svg_tip.height() * 2 + 10;
|
||||
|
||||
svg_tip.css({ top: target_offset.top - svg_height - 5 });
|
||||
svg_tip.css({ left: target_offset.left - svg_width });
|
||||
};
|
||||
//Append tooltip to display when mouse enter the rect element
|
||||
//Default is display:none
|
||||
var appendTooltip = function () {
|
||||
if ($(".svg-tip").length == 0) {
|
||||
$("body").append(
|
||||
'<div class="svg-tip svg-tip-one-line" style="display:none" ></div>'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
var settings = $.extend(
|
||||
{
|
||||
colors: ["#eeeeee", "#d6e685", "#8cc665", "#44a340", "#44a340"],
|
||||
border: {
|
||||
radius: 2,
|
||||
hover_color: "#999",
|
||||
},
|
||||
click: null,
|
||||
start_date: null,
|
||||
//List of name months
|
||||
month_names: [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
],
|
||||
h_days: ["M", "W", "F"],
|
||||
//Default is empty, it can be overrided
|
||||
data: [],
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
var _this = $(this);
|
||||
|
||||
start();
|
||||
};
|
||||
})(jQuery);
|
Loading…
Reference in New Issue
Block a user