diff --git a/app/.default-userinfo.php b/app/.default-userinfo.php index 0b2b413..fadd576 100644 --- a/app/.default-userinfo.php +++ b/app/.default-userinfo.php @@ -132,4 +132,4 @@ function (str) { return $res; } ) -); \ No newline at end of file +); diff --git a/app/controllers/click_zan.php b/app/controllers/click_zan.php index 9f7282b..ac1363c 100644 --- a/app/controllers/click_zan.php +++ b/app/controllers/click_zan.php @@ -16,6 +16,9 @@ function validateZan() { if (!Auth::check()) { die('
please login
'); } + if (!isBlogAllowedUser(Auth::user())) { + die('
permission denied
'); + } $id = $_POST['id']; $delta = $_POST['delta']; diff --git a/app/controllers/collection.php b/app/controllers/collection.php new file mode 100644 index 0000000..774f327 --- /dev/null +++ b/app/controllers/collection.php @@ -0,0 +1,152 @@ += 0 && $state <= 2)) die('invalid state'); + $username = Auth::id(); + DB::insert("insert into collection_state (id, username, state) values ('$id', '$username', $state) on duplicate key update state = $state"); + die('ok'); + } + + function echoProblem($problem) { + $username = Auth::id(); + if (isBlogAllowedUser(Auth::user())) { + $state = 0; + $stateResult = DB::selectFirst("select state from collection_state where id = '{$problem['id']}' and username = '$username'"); + if(isset($stateResult)) $state = $stateResult['state']; + $color = ['fff','feb','dfb'][$state]; + echo ''; + echo ''; + echo $problem['id'], ''; + echo ''; + echo '
'; + echo ''; + echo '', $problem['title'], ''; + echo ''; + echo ''; + echo ' '; + echo ' '; + echo ''; + echo ''; + echo '
'; + echo ''; + echo ''; + echo $problem['note']; + echo ''; + echo ''; + } + } + + $cur_tab = isset($_GET['type']) ? $_GET['type'] : 'null'; + + $cond = "exists (select 1 from collection_tags where collection_tags.collection_id = collection.id and collection_tags.tag = '{$cur_tab}')"; + + $header = ''; + $header .= 'ID'; + $header .= '' . UOJLocale::get('problems::problem') . ''; + $header .= '' . UOJLocale::get('note') . ''; + $header .= ''; + + $tabs_info = array( + 'counting' => array( + 'name' => 'Counting', + 'url' => '/collection?type=counting' + ), + 'constructive' => array( + 'name' => 'Constructive', + 'url' => '/collection?type=constructive' + ), + 'interactive' => array( + 'name' => 'Interactive', + 'url' => '/collection?type=interactive' + ), + ); + + $pag_config = array('page_len' => 1000); + $pag_config['col_names'] = array('*'); + $username = Auth::id(); + $pag_config['table_name'] = "collection"; + $pag_config['cond'] = $cond; + $pag_config['tail'] = 'order by id asc'; + $pag_config['max_extend'] = 5; + $pag_config['timeout'] = 1000; + $pag = new Paginator($pag_config); + + $div_classes = array('table-responsive'); + $table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped'); +?> + + +
+
+ +
+ +
+ +
+ + +
+ +
+ +
+
+
+
+
+ pagination(); ?> +
+
+
+'; + echo ''; + echo ''; + echo $header; + echo ''; + echo ''; + + foreach ($pag->get() as $idx => $row) { + echoProblem($row); + } + + echo ''; + echo '
'; + echo ''; + + echo $pag->pagination(); +?> + diff --git a/app/controllers/collection_manage.php b/app/controllers/collection_manage.php new file mode 100644 index 0000000..339b64e --- /dev/null +++ b/app/controllers/collection_manage.php @@ -0,0 +1,182 @@ +'; + echo ''; + echo $problem['id'], ''; + echo ''; + echo '', $problem['title'], ''; + foreach (queryCollectionTags($problem['id']) as $tag) { + echo '', '', HTML::escape($tag), '', ''; + } + echo ''; + echo ''; + echo $problem['note']; + echo ''; + echo ''; + } + } + + $cur_tab = isset($_GET['type']) ? $_GET['type'] : 'null'; + + $cond = "1"; + + $header = ''; + $header .= 'ID'; + $header .= '' . UOJLocale::get('problems::problem') . ''; + $header .= '' . UOJLocale::get('note') . ''; + $header .= ''; + + $pag_config = array('page_len' => 10000); + $pag_config['col_names'] = array('*'); + $username = Auth::id(); + $pag_config['table_name'] = "collection"; + $pag_config['cond'] = $cond; + $pag_config['tail'] = 'order by id asc'; + $pag_config['max_extend'] = 5; + $pag_config['timeout'] = 1000; + $pag = new Paginator($pag_config); + + $div_classes = array('table-responsive'); + $table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped'); + + $proposal = DB::selectFirst("select * from collection_proposal"); + + $append_form = new UOJForm('append'); + + $append_form->addInput('id', 'text', 'id', $proposal['id'], + function($result) { + if(!preg_match('/^-?[a-z0-9]{1,12}$/', $result)) return '题号长度不能超过 12,且只能包含小写字母和数字'; + return ''; + }, + null + ); + $append_form->addInput('title', 'text', 'title', $proposal['title'], + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('url', 'text', 'url', $proposal['url'], + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('tag', 'text', 'tag', $proposal['tag'], + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('note', 'text', 'note', $proposal['note'], + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + + $append_form->handle = function() { + $id = $_POST['id']; + if ($id[0] == '-') { + $id = substr($id, 1); + DB::delete("delete from collection where id='$id'"); + DB::delete("delete from collection_tags where collection_id='$id'"); + return; + } + DB::delete("delete from collection_proposal where id='$id'"); + $title = DB::escape($_POST['title']); + $url = DB::escape($_POST['url']); + $tag = DB::escape(strtolower($_POST['tag'])); + $note = DB::escape($_POST['note']); + + $upd_cmd = ''; + if($title != '') $upd_cmd .= ", title='$title'"; + if($url != '') $upd_cmd .= ", url='$url'"; + if($note != '') { + if($note == '-') $note = ''; + $upd_cmd .= ", note='$note'"; + } + if($upd_cmd != '') { + DB::insert("insert into collection (id, title, url, note) values ('$id', '$title', '$url', '$note') on duplicate key update" . substr($upd_cmd, 1)); + } + if($tag == '-') { + DB::delete("delete from collection_tags where collection_id='$id'"); + } + else if($tag != '' && DB::selectCount("select count(*) from collection where id = '$id'")) { + DB::insert("insert ignore into collection_tags (collection_id, tag) values ('$id', '$tag')"); + } + }; + $append_form->runAtServer(); +?> + + +printHTML(); ?> +

约定

+选择一个题目所属的 OJ 时,按照以下顺序选择: +
    +
  1. 原出处,且有中文题面或英文题面
  2. +
  3. 有中文题面
  4. +
  5. 有英文题面
  6. +
  7. 原出处,但无中文题面或英文题面
  8. +
  9. 其他
  10. +
+有多个同一类的 OJ,按照以下优先级选择。 +

各 OJ 题号格式(按照优先级排序)

+ + + + + + + + + + +
OJ题号格式
AtCoderagc001a
Codeforces Problemsetcf1a
UOJuoj1
QOJqoj1
LOJloj1
SOJsoj1
Luogulg1001
Codeforces Gymcf123456a
+ +

列表

+'; + echo ''; + echo ''; + echo $header; + echo ''; + echo ''; + + foreach ($pag->get() as $idx => $row) { + echoProblem($row); + } + + echo ''; + echo '
'; + echo ''; + + echo $pag->pagination(); +?> + + diff --git a/app/controllers/collection_propose.php b/app/controllers/collection_propose.php new file mode 100644 index 0000000..ab08cf0 --- /dev/null +++ b/app/controllers/collection_propose.php @@ -0,0 +1,106 @@ +'; + echo ''; + echo $problem['id'], ''; + echo ''; + echo '', $problem['title'], ''; + foreach (queryCollectionTags($problem['id']) as $tag) { + echo '', '', HTML::escape($tag), '', ''; + } + echo ''; + echo ''; + echo $problem['note']; + echo ''; + echo ''; + } + } + + $cur_tab = isset($_GET['type']) ? $_GET['type'] : 'null'; + + $cond = "1"; + + $header = ''; + $header .= 'ID'; + $header .= '' . UOJLocale::get('problems::problem') . ''; + $header .= '' . UOJLocale::get('note') . ''; + $header .= ''; + + $pag_config = array('page_len' => 10000); + $pag_config['col_names'] = array('*'); + $username = Auth::id(); + $pag_config['table_name'] = "collection"; + $pag_config['cond'] = $cond; + $pag_config['tail'] = 'order by id asc'; + $pag_config['max_extend'] = 5; + $pag_config['timeout'] = 1000; + $pag = new Paginator($pag_config); + + $div_classes = array('table-responsive'); + $table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped'); + + $append_form = new UOJForm('append'); + + $append_form->addInput('id', 'text', 'id', '', + function($result) { + if(!preg_match('/^[a-z0-9]{1,12}$/', $result)) return '题号长度不能超过 12,且只能包含小写字母和数字'; + return ''; + }, + null + ); + $append_form->addInput('title', 'text', 'title', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('url', 'text', 'url', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('tag', 'text', 'tag', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('note', 'text', 'note', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + + $append_form->handle = function() { + $id = $_POST['id']; + $title = DB::escape($_POST['title']); + $url = DB::escape($_POST['url']); + $tag = DB::escape(strtolower($_POST['tag'])); + $note = DB::escape($_POST['note']); + DB::insert("insert into collection_proposal (id, title, url, tag, note) values ('$id', '$title', '$url', '$tag', '$note')"); + }; + $append_form->runAtServer(); +?> + +printHTML(); ?> + diff --git a/app/controllers/collection_user.php b/app/controllers/collection_user.php new file mode 100644 index 0000000..5031770 --- /dev/null +++ b/app/controllers/collection_user.php @@ -0,0 +1,135 @@ += 0 && $state <= 2)) die('invalid state'); + $username = Auth::id(); + DB::insert("insert into collection_state (id, username, state) values ('$id', '$username', $state) on duplicate key update state = $state"); + die('ok'); + } + + function echoProblem($problem) { + $username = Auth::id(); + if (isBlogAllowedUser(Auth::user())) { + $state = 0; + $stateResult = DB::selectFirst("select state from collection_state where id = '{$problem['id']}' and username = '$username'"); + if(isset($stateResult)) $state = $stateResult['state']; + $color = ['fff','feb','dfb'][$state]; + echo ''; + echo ''; + echo $problem['id'], ''; + echo ''; + echo '
'; + echo ''; + echo '', $problem['title'], ''; + echo ''; + echo ''; + echo ' '; + echo ' '; + echo ''; + echo ''; + echo '
'; + echo ''; + echo ''; + echo $problem['note']; + echo ''; + echo ''; + } + } + + $cur_tab = isset($_GET['type']) ? $_GET['type'] : 'null'; + + $cond = "exists (select 1 from collection_user_tags where collection_user_tags.collection_id = collection_user.id and collection_user_tags.tag = '{$cur_tab}') and owner='{$owner}'"; + + $header = ''; + $header .= 'ID'; + $header .= '' . UOJLocale::get('problems::problem') . ''; + $header .= '' . UOJLocale::get('note') . ''; + $header .= ''; + + $tabs_info = array(); + + $pag_config = array('page_len' => 1000); + $pag_config['col_names'] = array('*'); + $username = Auth::id(); + $pag_config['table_name'] = "collection_user"; + $pag_config['cond'] = $cond; + $pag_config['tail'] = 'order by id asc'; + $pag_config['max_extend'] = 5; + $pag_config['timeout'] = 1000; + $pag = new Paginator($pag_config); + + $div_classes = array('table-responsive'); + $table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped'); +?> + + +
+
+ +
+ +
+ +
+ +
+
+
+
+
+ pagination(); ?> +
+
+
+'; + echo ''; + echo ''; + echo $header; + echo ''; + echo ''; + + foreach ($pag->get() as $idx => $row) { + echoProblem($row); + } + + echo ''; + echo '
'; + echo ''; + + echo $pag->pagination(); +?> + diff --git a/app/controllers/collection_user_manage.php b/app/controllers/collection_user_manage.php new file mode 100644 index 0000000..3404a62 --- /dev/null +++ b/app/controllers/collection_user_manage.php @@ -0,0 +1,188 @@ +'; + echo ''; + echo $problem['id'], ''; + echo ''; + echo '', $problem['title'], ''; + foreach (queryUserCollectionTags($owner, $problem['id']) as $tag) { + echo '', '', HTML::escape($tag), '', ''; + } + echo ''; + echo ''; + echo $problem['note']; + echo ''; + echo ''; + } + } + + $cur_tab = isset($_GET['type']) ? $_GET['type'] : 'null'; + + $cond = "owner = '$owner'"; + + $header = ''; + $header .= 'ID'; + $header .= '' . UOJLocale::get('problems::problem') . ''; + $header .= '' . UOJLocale::get('note') . ''; + $header .= ''; + + $pag_config = array('page_len' => 10000); + $pag_config['col_names'] = array('*'); + $username = Auth::id(); + $pag_config['table_name'] = "collection_user"; + $pag_config['cond'] = $cond; + $pag_config['tail'] = 'order by id asc'; + $pag_config['max_extend'] = 5; + $pag_config['timeout'] = 1000; + $pag = new Paginator($pag_config); + + $div_classes = array('table-responsive'); + $table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped'); + + if($owner == Auth::id()) { + $append_form = new UOJForm('append'); + + $append_form->addInput('id', 'text', 'id', '', + function($result) { + if(!preg_match('/^-?[a-z0-9]{1,12}$/', $result)) return '题号长度不能超过 12,且只能包含小写字母和数字'; + return ''; + }, + null + ); + $append_form->addInput('title', 'text', 'title', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('url', 'text', 'url', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('tag', 'text', 'tag', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + $append_form->addInput('note', 'text', 'note', '', + function($result) { + if(strlen($result) > 100) return '长度不能超过 100 byte'; + return ''; + }, + null + ); + + $append_form->handle = function() { + global $owner; + $id = $_POST['id']; + if ($id[0] == '-') { + $id = substr($id, 1); + DB::delete("delete from collection_user where id='$id' and owner = '$owner'"); + DB::delete("delete from collection_user_tags where collection_id='$id' and owner = '$owner'"); + return; + } + $title = DB::escape($_POST['title']); + $url = DB::escape($_POST['url']); + $tag = DB::escape(strtolower($_POST['tag'])); + $note = DB::escape($_POST['note']); + + $upd_cmd = ''; + if($title != '') $upd_cmd .= ", title='$title'"; + if($url != '') $upd_cmd .= ", url='$url'"; + if($note != '') { + if($note == '-') $note = ''; + $upd_cmd .= ", note='$note'"; + } + if($upd_cmd != '') { + DB::insert("insert into collection_user (owner, id, title, url, note) values ('$owner', '$id', '$title', '$url', '$note') on duplicate key update" . substr($upd_cmd, 1)); + } + if($tag == '-') { + DB::delete("delete from collection_user_tags where collection_id='$id' and owner = '$owner'"); + } + else if($tag != '' && DB::selectCount("select count(*) from collection_user where id = '$id' and owner = '$owner'")) { + DB::insert("insert ignore into collection_user_tags (owner, collection_id, tag) values ('$owner', '$id', '$tag')"); + } + }; + $append_form->runAtServer(); + } +?> + +printHTML(); ?> +

约定

+选择一个题目所属的 OJ 时,按照以下顺序选择: +
    +
  1. 原出处,且有中文题面或英文题面
  2. +
  3. 有中文题面
  4. +
  5. 有英文题面
  6. +
  7. 原出处,但无中文题面或英文题面
  8. +
  9. 其他
  10. +
+有多个同一类的 OJ,按照以下优先级选择。 +

各 OJ 题号格式(按照优先级排序)

+ + + + + + + + + + +
OJ题号格式
AtCoderagc001a
Codeforces Problemsetcf1a
UOJuoj1
QOJqoj1
LOJloj1
SOJsoj1
Luogulg1001
Codeforces Gymcf123456a
+ +

列表

+'; + echo ''; + echo ''; + echo $header; + echo ''; + echo ''; + + foreach ($pag->get() as $idx => $row) { + echoProblem($row); + } + + echo ''; + echo '
'; + echo ''; + + echo $pag->pagination(); +?> + + diff --git a/app/controllers/file_upload.php b/app/controllers/file_upload.php index 2a46889..8197c5c 100644 --- a/app/controllers/file_upload.php +++ b/app/controllers/file_upload.php @@ -203,7 +203,7 @@ function Ls($path) { } if (move_uploaded_file($_FILES['upload']['tmp_name'], UOJContext::documentRoot() . '/' . $path)) { - becomeMsgPage("
上传成功!最终路径:{$url}/{$path}
" . '返回'); + becomeMsgPage("
上传成功!最终路径:/{$path}
" . '返回'); } else { becomeMsgPage('上传失败!'); } diff --git a/app/controllers/hack_list.php b/app/controllers/hack_list.php index 0031ff9..3832fc9 100644 --- a/app/controllers/hack_list.php +++ b/app/controllers/hack_list.php @@ -9,9 +9,10 @@ $common = 'class="form-control input-sm" '; $q_submission_id = $search_form->addText('submission_id', UOJLocale::get('problems::submission id').':', $common.'maxlength="6" style="width:5em"', 'validateUInt'); $q_problem_id = $search_form->addText('problem_id', UOJLocale::get('problems::problem id').':', $common.'maxlength="4" style="width:4em"', 'validateUInt'); - $q_hacker = $search_form->addText('hacker', UOJLocale::get('problems::hacker').':', $common.'maxlength="20" style="width:10em"', 'validateUsername'); - $q_owner = $search_form->addText('owner', UOJLocale::get('problems::owner').':', $common.'maxlength="20" style="width:10em"', 'validateUsername'); - $q_status = $search_form->addSelect('status', UOJLocale::get('problems::result').':', $common, [''=>'All', 1=>'Success!', 2=>'Failed.']); + $q_hacker = $search_form->addText('hacker', UOJLocale::get('problems::hacker').':', $common.'maxlength="20" style="width:8em"', 'validateUsername'); + $q_owner = $search_form->addText('owner', UOJLocale::get('problems::owner').':', $common.'maxlength="20" style="width:8em"', 'validateUsername'); + $q_status = $search_form->addSelect('status', UOJLocale::get('problems::result').':', $common.'style="width:7em"', [''=>'All', 1=>'Success!', 2=>'Failed.']); + $q_cut_off_time = $search_form->addText('cut_off_time', UOJLocale::get('cut off time').':', $common . 'maxlength="11" style="width:8em; font-family: monospace" placeholder="YYYY-MM-DD"', "validateTime"); $search_form->addSubmit(UOJLocale::get('search')); $conds = array(); @@ -33,6 +34,12 @@ if($q_status == 2) { $conds[] = 'success = 0'; } + if ($q_cut_off_time != null) { + if (strlen($q_cut_off_time) == 10) { + $q_cut_off_time = $q_cut_off_time . ' 23:59:59'; + } + $conds[] = 'submit_time <=\'' . DB::escape($q_cut_off_time) . '\''; + } if ($conds) { $cond = join($conds, ' and '); diff --git a/app/controllers/problem.php b/app/controllers/problem.php index 5417e81..6bf34a8 100644 --- a/app/controllers/problem.php +++ b/app/controllers/problem.php @@ -86,8 +86,24 @@ $memory_suf = 'MiB*'; } } - $tags['time limit'] = getUOJConfVal($problem_conf, 'time_limit', 1) . $time_suf; - $tags['memory limit'] = getUOJConfVal($problem_conf, 'memory_limit', 256) . $memory_suf; + $time_limit = getUOJConfVal($problem_conf, 'time_limit', 1); + $memory_limit = getUOJConfVal($problem_conf, 'memory_limit', 256); + $time_style = ""; + if($time_limit < 0.5) { + $time_style = "color:red"; + } + if($time_limit > 5) { + $time_style = "color:blue"; + } + $memory_style = ""; + if($memory_limit < 128) { + $memory_style = "color:red"; + } + if($memory_limit > 1024) { + $memory_style = "color:blue"; + } + $tags['time limit'] = "" . $time_limit . $time_suf . ""; + $tags['memory limit'] = "" . $memory_limit . $memory_suf . ""; } $tags['checker'] = getUOJConfVal($problem_conf, 'use_builtin_checker', UOJLocale::get('problems::custom checker')); if (getUOJConfVal($problem_conf, 'use_builtin_judger', null) !== 'on') { @@ -332,7 +348,47 @@ function(response_text) {custom_test_onsubmit(response_text, $('#div-custom_test
-
+
+ + +
diff --git a/app/controllers/problem_data_manage.php b/app/controllers/problem_data_manage.php index 682fbd4..52483f0 100644 --- a/app/controllers/problem_data_manage.php +++ b/app/controllers/problem_data_manage.php @@ -313,12 +313,12 @@ function getDataDisplayer() { return function() use($name, $allow_files) { $src_name = $name . '.cpp'; if (isset($allow_files[$src_name])) { - echoFilePre($src_name); + echoFilePre($src_name, 100 * 1024); } else { echoFileNotFound($src_name); } if (isset($allow_files[$name])) { - echoFilePre($name); + echoFilePre($name, 100 * 1024); } else { echoFileNotFound($name); } diff --git a/app/controllers/problem_set.php b/app/controllers/problem_set.php index e947ffb..927e300 100644 --- a/app/controllers/problem_set.php +++ b/app/controllers/problem_set.php @@ -179,10 +179,13 @@ function echoProblem($problem) { ?>
-
+
-
+
+ +
+
diff --git a/app/controllers/problem_statement_md.php b/app/controllers/problem_statement_md.php new file mode 100644 index 0000000..843a99b --- /dev/null +++ b/app/controllers/problem_statement_md.php @@ -0,0 +1,68 @@ + diff --git a/app/controllers/problem_statistics.php b/app/controllers/problem_statistics.php index 0d078b7..ad88c05 100644 --- a/app/controllers/problem_statistics.php +++ b/app/controllers/problem_statistics.php @@ -31,24 +31,24 @@ function scoreDistributionData() { } else if ($row[0] == 100) { $has_score_100 = true; } - $score = $row[0] * 100; + $score = round($row[0] * 1000000); $data[] = array('score' => $score, 'count' => $row[1]); } if (!$has_score_0) { array_unshift($data, array('score' => 0, 'count' => 0)); } if (!$has_score_100) { - $data[] = array('score' => 10000, 'count' => 0); + $data[] = array('score' => 100 * 1000000, 'count' => 0); } return $data; } $data = scoreDistributionData(); - $pre_data = $data; - $suf_data = $data; for ($i = 0; $i < count($data); $i++) { - $data[$i]['score'] /= 100; + $data[$i]['score'] /= 1000000; } + $pre_data = $data; + $suf_data = $data; for ($i = 1; $i < count($data); $i++) { $pre_data[$i]['count'] += $pre_data[$i - 1]['count']; } @@ -134,7 +134,7 @@ function scoreDistributionData() { return (x.getTime() / 100).toString(); }, hoverCallback: function(index, options, content, row) { - var scr = row.score / 100; + var scr = row.score; return '
' + 'score: ≤' + scr + '
' + ''; }, @@ -161,7 +161,7 @@ function scoreDistributionData() { return (x.getTime() / 100).toString(); }, hoverCallback: function(index, options, content, row) { - var scr = row.score / 100; + var scr = row.score; return '
' + 'score: ≥' + scr + '
' + ''; }, diff --git a/app/controllers/submissions_list.php b/app/controllers/submissions_list.php index cc4f565..900480d 100644 --- a/app/controllers/submissions_list.php +++ b/app/controllers/submissions_list.php @@ -5,18 +5,14 @@ requirePHPLib('form'); - function validateTime($time) { - return is_string($time) && (preg_match('/^(19|20)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])( (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/', $time)); - } - $search_form = new SOJForm(); $common = 'class="form-control input-sm" '; $q_problem_id = $search_form->addText('problem_id', UOJLocale::get('problems::problem id').':', $common . 'maxlength="4" style="width:4em"', 'validateUInt'); $q_submitter = $search_form->addText('submitter', UOJLocale::get('username').':', $common . 'maxlength="20" style="width:10em"', 'validateUsername'); - $q_min_score = $search_form->addText('min_score', UOJLocale::get('score range').':', $common . 'maxlength="5" style="width:4em" placeholder="-∞"', 'validateInt'); - $q_max_score = $search_form->addText('max_score', '~', $common . 'maxlength="5" style="width:4em" placeholder="+∞"', 'validateInt'); + $q_min_score = $search_form->addText('min_score', UOJLocale::get('score range').':', $common . 'maxlength="5" style="width:4em" placeholder="-∞"', 'validateReal'); + $q_max_score = $search_form->addText('max_score', '~', $common . 'maxlength="5" style="width:4em" placeholder="+∞"', 'validateReal'); $q_language = $search_form->addText('language', UOJLocale::get('problems::language').':', $common . 'maxlength="10" style="width:8em"', validateLength(10)); - $q_cut_off_time = $search_form->addText('cut_off_time', UOJLocale::get('cut off time').':', $common . 'maxlength="20" style="width:14em; font-family: monospace" placeholder="YYYY-MM-DD[ hh:mm:ss]"', validateTime); + $q_cut_off_time = $search_form->addText('cut_off_time', UOJLocale::get('cut off time').':', $common . 'maxlength="20" style="width:14em; font-family: monospace" placeholder="YYYY-MM-DD[ hh:mm:ss]"', "validateTime"); $search_form->addSubmit(UOJLocale::get('search')); $conds = array(); diff --git a/app/locale/basic/en.php b/app/locale/basic/en.php index 0ef6edf..4ee35b0 100644 --- a/app/locale/basic/en.php +++ b/app/locale/basic/en.php @@ -173,4 +173,8 @@ 'manageable' => 'Manageable', 'log out all' => 'Log out on all devices', 'cut off time' => 'Cut-off time', + 'problem collection' => 'Collection', + 'manage' => 'Manage', + 'propose' => 'Propose', + 'note' => 'Note', ]; diff --git a/app/locale/basic/zh-cn.php b/app/locale/basic/zh-cn.php index 32137b6..32a2829 100644 --- a/app/locale/basic/zh-cn.php +++ b/app/locale/basic/zh-cn.php @@ -173,4 +173,8 @@ 'manageable' => '有权限', 'log out all' => '在所有设备登出', 'cut off time' => '截止时间', + 'problem collection' => '题单', + 'manage' => '管理', + 'propose' => '提议', + 'note' => '备注', ]; diff --git a/app/models/UOJBlogEditor.php b/app/models/UOJBlogEditor.php index c032032..bb3d8c5 100644 --- a/app/models/UOJBlogEditor.php +++ b/app/models/UOJBlogEditor.php @@ -41,6 +41,11 @@ function __construct() { if (strlen($content_md) > 1000000) { return '内容过长'; } + foreach (UOJConfig::$data['profile']['oj-address'] as $address) { + if (strpos($content_md, $address) !== false) { + return 'Warning: 使用相对链接'; + } + } return ''; }, 'tags' => function(&$tags) { @@ -78,10 +83,15 @@ public function validate($name) { } private function receivePostData() { $errors = array(); + $warnings = array(); foreach (array('title', 'content_md', 'tags') as $name) { $cur_err = $this->validate($name); if ($cur_err) { - $errors[$name] = $cur_err; + if (substr($cur_err, 0, 7) == 'Warning') { + $warnings[$name] = $cur_err; + } else { + $errors[$name] = $cur_err; + } } } if ($errors) { @@ -188,11 +198,12 @@ private function receivePostData() { } $this->post_data['content'] = json_encode($config) . "\n" . $this->post_data['content']; } + return $warnings; } public function handleSave() { $save = $this->save; - $this->receivePostData(); + $warnings = $this->receivePostData(); $ret = $save($this->post_data); if (!$ret) { $ret = array(); @@ -218,6 +229,8 @@ public function handleSave() { $ret['html'] = ob_get_contents(); ob_end_clean(); } + + $ret['warnings'] = $warnings; die(json_encode($ret)); } diff --git a/app/route.php b/app/route.php index 9d0d949..f631a39 100644 --- a/app/route.php +++ b/app/route.php @@ -17,11 +17,18 @@ Route::any('/problems/template', '/problem_set.php?tab=template'); Route::any('/problems/contested', '/problem_set.php?tab=contested'); Route::any('/problem/{id}', '/problem.php'); + Route::any('/problem/{id}/statement.md', '/problem_statement_md.php'); Route::any('/problem/{id}/statistics', '/problem_statistics.php'); Route::any('/problem/{id}/manage/statement', '/problem_statement_manage.php'); Route::any('/problem/{id}/manage/managers', '/problem_managers_manage.php'); Route::any('/problem/{id}/manage/data', '/problem_data_manage.php'); + Route::any('/collection', '/collection.php'); + Route::any('/collection/propose', '/collection_propose.php'); + Route::any('/collection/manage', '/collection_manage.php'); + Route::any('/collection/{username}', '/collection_user.php'); + Route::any('/collection/{username}/manage', '/collection_user_manage.php'); + Route::any('/contests', '/contests.php'); Route::any('/contest/new', '/add_contest.php'); Route::any('/contest/{id}', '/contest_inside.php'); diff --git a/app/uoj-form-lib.php b/app/uoj-form-lib.php index 7aaa827..0a43404 100644 --- a/app/uoj-form-lib.php +++ b/app/uoj-form-lib.php @@ -998,3 +998,7 @@ public function printHTML($url) { function validateLength($len) { return function($x) use ($len) {return mb_strlen($x) <= $len;}; } + + function validateTime($time) { + return is_string($time) && (preg_match('/^(19|20)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])( (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/', $time)); + } diff --git a/app/uoj-html-lib.php b/app/uoj-html-lib.php index c1c28c4..e4043f6 100644 --- a/app/uoj-html-lib.php +++ b/app/uoj-html-lib.php @@ -168,6 +168,8 @@ function getSubmissionJudgedStatusStr($result_error, $score, $uri = null) { $html_type = 'span'; $head = $html_type; } + $score = rtrim($score, '0'); + $score = rtrim($score, '.'); if (isset($result_error)) return '<' . $head . " class=\"small\">$result_error'; else @@ -321,7 +323,7 @@ function echoSubmission($submission, $config, $user) { $limitLevel = querySubmissionDetailPermission($user, $submission); $problem = queryProblemBrief($submission['problem_id']); $status = explode(', ', $submission['status'])[0]; - $show_status_details = isOurSubmission($user, $submission) && $status !== 'Judged'; + $show_status_details = (isOurSubmission($user, $submission) || isSuperUser(Auth::user())) && $status !== 'Judged'; $submission_uri = getSubmissionUri($submission['id']); $submission_id_str = getSubmissionLink($submission['id']); @@ -1266,6 +1268,14 @@ private function _print($node) { $this->_print_c($node); echo ' '; + } elseif ($node->nodeName == 'err') { + if(count($node->childNodes)) { + echo '

error (stderr):

+';
+				$this->_print_c($node);
+				echo '
+
'; + } } else { echo '<', $node->nodeName; foreach ($node->attributes as $attr) { diff --git a/app/uoj-query-lib.php b/app/uoj-query-lib.php index 47eb374..c5b9753 100644 --- a/app/uoj-query-lib.php +++ b/app/uoj-query-lib.php @@ -94,6 +94,18 @@ function queryProblemTags($id) { return $tags; } +function queryCollectionTags($id) { + $result = DB::select("select tag from collection_tags where collection_id = '$id' order by id"); + for ($tags = array(); $row = DB::fetch($result, MYSQLI_NUM); $tags[] = $row[0]); + return $tags; +} + +function queryUserCollectionTags($owner, $id) { + $result = DB::select("select tag from collection_user_tags where collection_id = '$id' and owner='$owner' order by id"); + for ($tags = array(); $row = DB::fetch($result, MYSQLI_NUM); $tags[] = $row[0]); + return $tags; +} + function queryContestProblemRank($contest, $problem) { if (!DB::selectFirst("select * from contests_problems where contest_id = {$contest['id']} and problem_id = {$problem['id']}")) { return null; @@ -405,12 +417,14 @@ function getHacksAuditLog($config = array()) { 'actor' => $his['hacker'], 'details' => $his ); - $audit_log[] = array( - 'time' => $his['judge_time'], - 'type' => 'hack judgement, auto', - 'hack_id' => $his['hack_id'], - 'details' => $his - ); + if($his['success'] != null) { + $audit_log[] = array( + 'time' => $his['judge_time'], + 'type' => 'hack judgement, auto', + 'hack_id' => $his['hack_id'], + 'details' => $his + ); + } } sortAuditLogByTime($audit_log); return $audit_log; diff --git a/app/uoj-validate-lib.php b/app/uoj-validate-lib.php index 17eadb5..4cbff04 100644 --- a/app/uoj-validate-lib.php +++ b/app/uoj-validate-lib.php @@ -32,6 +32,14 @@ function validateInt($x) { return is_string($x) and validateUInt($x[0] === '-' ? substr($x, 1) : $x); } +function validateUReal($x) { + return is_string($x) and preg_match('/^(0|[1-9][0-9]{0,8})(\.[0-9]{0,6})?$/', $x); +} + +function validateReal($x) { + return is_string($x) and validateUReal($x[0] === '-' ? substr($x, 1) : $x); +} + function validateUploadedFile($name) { return isset($_FILES[$name]) && is_uploaded_file($_FILES[$name]['tmp_name']); } diff --git a/app/views/blog-preview.php b/app/views/blog-preview.php index fc426e7..6e36cf1 100644 --- a/app/views/blog-preview.php +++ b/app/views/blog-preview.php @@ -23,6 +23,17 @@
+
diff --git a/css/soj-theme.css b/css/soj-theme.css index 0ff2a2a..6c9ad3f 100644 --- a/css/soj-theme.css +++ b/css/soj-theme.css @@ -401,6 +401,10 @@ pre { margin-left: 5px; margin-right: 5px; } +.uoj-collection-tag { + margin-left: 5px; + margin-right: 5px; +} .uoj-blog-tag { margin-left: 5px; margin-right: 5px; diff --git a/js/blog-editor/blog-editor.js b/js/blog-editor/blog-editor.js index 747fa52..310d0ed 100644 --- a/js/blog-editor/blog-editor.js +++ b/js/blog-editor/blog-editor.js @@ -184,6 +184,9 @@ function blog_editor_init(name, editor_config) { $(['title', 'content_md', 'tags']).each(function() { ok &= showErrorHelp(name + '_' + this, data[this]); }); + $(['title', 'content_md', 'tags']).each(function() { + showWarningHelp(name + '_' + this, data['warnings'][this]); + }); if (data.extra !== undefined) { alert(data.extra); ok = false; diff --git a/js/marked.js b/js/marked.js index f2c231e..0c29643 100644 --- a/js/marked.js +++ b/js/marked.js @@ -1,5 +1,5 @@ /** - * marked - a markdown parser + * Modified from marked - a markdown parser * Copyright (c) 2011-2013, Christopher Jeffrey. (MIT Licensed) * https://github.com/chjj/marked */ @@ -363,7 +363,8 @@ Lexer.prototype.token = function(src, top) { src = src.substring(cap[0].length); this.tokens.links[cap[1].toLowerCase()] = { href: cap[2], - title: cap[3] + title: cap[3], + rest: '' }; continue; } @@ -452,11 +453,14 @@ var inline = { math: /^\$((?:[^\\]|\\\\|\\[^\\]+?)+?)\$|^\$\$((?:[^\\]|\\\\|\\[^\\]+?)+?)\$\$|^\\begin{[^}]+}((?:[^\\]|\\\\|\\[^\\]+?)+?)\\end{[^}]+}/, br: /^ {2,}\n(?!\s*$)/, del: noop, - text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; +inline._href = /\s*?(?:\s+['"]([^)]*?)['"])?(\s*(?:\s\.[\w=]+\s*)*)/; inline.link = replace(inline.link) ('inside', inline._inside) @@ -618,7 +622,8 @@ InlineLexer.prototype.output = function(src) { src = src.substring(cap[0].length); out += this.outputLink(cap, { href: cap[2], - title: cap[3] + title: cap[3], + rest: cap[4] }); continue; } @@ -703,6 +708,31 @@ InlineLexer.prototype.output = function(src) { InlineLexer.prototype.outputLink = function(cap, link) { if (cap[0][0] !== '!') { + + if (link.href[0] == '%') { + link.href = link.href.substring(1); + return '' + + this.output(cap[1]) + + ''; + } + + if (link.href[0] == ':') { + var ret = this.extractProperties(link.rest); + if (link.href == ':user' && ret[1].rating !== undefined) { + return '' + escape(cap[1]) + ''; + } else { + return '{errors occur in processing ' + escape(link.href) + '}'; + } + } + return ''; } else { + var ret = this.extractProperties(link.rest) + , style = '' + , cls; + + if (ret[1].h !== undefined) { + style += "height:" + ret[1].h + "px;"; + } + if (ret[1].w !== undefined) { + style += "width:" + ret[1].w + "px;"; + } + if (ret[1].left) { + cls = 'img-responsive'; + } else if (ret[1].right) { + cls = 'img-responsive'; + style += "margin-left: auto;"; + } else if (ret[1].inline) { + cls = ''; + } else { + cls = 'img-responsive center-block'; + } + return ''
       + escape(cap[1])
       + ''; } }; +/** + * Extract Properties + */ + +InlineLexer.prototype.extractProperties = function(src) { + var props = {} + , cap; + + if (!src) { + return ['', props]; + } + while (src) { + // width, height, rating + if (cap = this.rules.property_num.exec(src)) { + src = src.substring(0, src.length - cap[0].length); + if (!(cap[1] in props)) { + props[cap[1]] = cap[2]; + } + continue; + } + + // keep, left, right, center + if (cap = this.rules.property_bool.exec(src)) { + src = src.substring(0, src.length - cap[0].length); + props[cap[1]] = true; + continue; + } + + break; + } + + return [src, props]; +}; + /** * Mangle Links */ @@ -820,8 +903,8 @@ Parser.prototype.parseText = function() { */ Parser.prototype.tok = function() { - var tok_class = this.options.getElementClass == null ? null : this.options.getElementClass(this.token); - tok_class = tok_class == null ? '' : ' class="' + tok_class + '"'; + var inner_tok_class = this.options.getElementClass == null ? null : this.options.getElementClass(this.token); + tok_class = inner_tok_class == null ? '' : ' class="' + inner_tok_class + '"'; switch (this.token.type) { case 'space': { return ''; @@ -851,16 +934,18 @@ Parser.prototype.tok = function() { if (!this.token.escaped) { this.token.text = escape(this.token.text, true); } - - return '
' + this.token.text + '
'; + } else if (this.options.getLangClass && this.token.lang != undefined) { + return '
'
+        + '">'
         + this.token.text
         + '
\n'; + } else { + return '
' + this.token.text + '
'; + } } case 'table': { var body = '' @@ -868,15 +953,32 @@ Parser.prototype.tok = function() { , i , row , cell + , attr , j; + var rowspan = false; + // header body += '\n\n'; for (i = 0; i < this.token.header.length; i++) { - heading = this.inline.output(this.token.header[i]); - body += this.token.align[i] - ? '' + heading + '\n' - : '' + heading + '\n'; + if (!this.token.header[i]) { + continue; + } + + heading = this.inline.extractProperties(this.token.header[i]); + heading[0] = this.inline.output(heading[0]); + attr = ''; + if (this.token.align[i]) { + attr += ' style="text-align:' + this.token.align[i] + '"'; + } + if (heading[1].w !== undefined) { + attr += ' colspan="' + cell[1].w + '"'; + } + if (heading[1].h !== undefined) { + attr += ' rowspan="' + cell[1].h + '"'; + rowspan = true; + } + body += '' + heading[0] + '\n'; } body += '\n\n'; @@ -886,18 +988,39 @@ Parser.prototype.tok = function() { row = this.token.cells[i]; body += '\n'; for (j = 0; j < row.length; j++) { - cell = this.inline.output(row[j]); - body += this.token.align[j] - ? '' + cell + '\n' - : '' + cell + '\n'; + if (!row[j]) { + continue; + } + + cell = this.inline.extractProperties(row[j]); + cell[0] = this.inline.output(cell[0]); + attr = ''; + if (this.token.align[j]) { + attr += ' style="text-align:' + this.token.align[j] + '"'; + } + if (cell[1].w !== undefined) { + attr += ' colspan="' + cell[1].w + '"'; + } + if (cell[1].h !== undefined) { + attr += ' rowspan="' + cell[1].h + '"'; + rowspan = true; + } + body += '' + cell[0] + '\n'; } body += '\n'; } body += '\n'; - return '\n' + if (!rowspan) { + if (inner_tok_class != null) { + inner_tok_class += ' table-striped'; + } + tok_class = inner_tok_class == null ? '' : ' class="' + inner_tok_class + '"'; + } + + return '
\n' + '\n' + body - + '\n'; + + '\n
\n'; } case 'blockquote_start': { var body = ''; @@ -953,6 +1076,9 @@ Parser.prototype.tok = function() { } case 'html': { return this.token.text; + /*return !this.token.pre && !this.options.pedantic + ? this.inline.output(this.token.text) + : this.token.text;*/ } case 'paragraph': { return '' @@ -1051,18 +1177,21 @@ marked.defaults = { silent: false, highlight: null, getLangClass: function(lang) { - lang = lang.toLowerCase(); - switch (lang) { - case 'c': return 'sh_c'; - case 'c++': return 'sh_cpp'; - case 'pascal': return 'sh_pascal'; - default: return 'sh_' + lang; - } + lang = lang.toLowerCase(); + switch (lang) { + case 'c': return 'sh_c'; + case 'c++': return 'sh_cpp'; + case 'pascal': return 'sh_pascal'; + default: return 'sh_' + lang; + } }, - getElementClass: function (token) { - if (token.type === "table") - return "table table-bordered table-hover table-striped table-text-center"; - return null; + getElementClass: function(token) { + switch (token.type) { + case 'table': + return 'table table-bordered table-text-center table-vertical-middle'; + default: + return null; + } } }; diff --git a/js/soj.js b/js/soj.js index a0af683..ac58982 100644 --- a/js/soj.js +++ b/js/soj.js @@ -183,7 +183,7 @@ function getColOfScore(score) { } else if (score == 100) { return ColorConverter.toStr(ColorConverter.toRGB(new HSV(120, 100, 80))); } else { - return ColorConverter.toStr(ColorConverter.toRGB(new HSV(30 + score * 0.6, 100, 90))); + return ColorConverter.toStr(ColorConverter.toRGB(new HSV(30 + score * 0.6, 100, 90 - Math.max(Math.min(score - 30, 90 - score), 0) * 0.5))); } } @@ -284,6 +284,16 @@ function showErrorHelp(name, err) { } } +function showWarningHelp(name, err) { + if (err) { + $('#div-' + name).addClass('has-warning'); + $('#help-' + name).text(err); + } else { + $('#div-' + name).removeClass('has-warning'); + $('#help-' + name).text(''); + } +} + function getFormErrorAndShowHelp(name, val) { var err = val($('#input-' + name).val()); return showErrorHelp(name, err); @@ -385,6 +395,15 @@ $.fn.uoj_blog_tag = function() { }); } +$.fn.uoj_collection_tag = function() { + return this.each(function() { + var owner = $(this).data('owner'); + var path_owner = ''; + if(owner) path_owner = '/' + owner; + $(this).attr('href', '/collection' + path_owner + '?type=' + encodeURIComponent($(this).text())); + }); +} + // click zan function click_zan(zan_id, zan_type, zan_delta, node) { var loading_node = $('
loading...
'); @@ -451,8 +470,21 @@ function getCountdownStr(t) { var mm = toFilledStr(x % 60, '0', 2); x = Math.floor(x / 60); var hh = x.toString(); + if (x >= 24) { + hh = toFilledStr(x % 24, '0', 2); + } + x = Math.floor(x / 24); + var dd = x.toString(); var res = ''; + if (x > 0) { + res += '' + dd + ''; + res += ' day'; + if (dd > 1) { + res += 's'; + } + res += ' + '; + } res += '' + hh + ''; res += ':'; res += '' + mm + ''; @@ -519,7 +551,7 @@ $.fn.uoj_highlight = function() { $(this).find("span.uoj-groupname").each(replaceWithHighlightUsername); $(this).find(".uoj-honor").uoj_honor(); $(this).find(".uoj-score").each(function() { - var score_str = $(this).text(), score = parseInt(score_str), maxscore = parseInt($(this).data('max')); + var score_str = $(this).text(), score = parseFloat(score_str), maxscore = parseFloat($(this).data('max')); if (score_str === '?') $(this).css("color", "rgb(0, 204, 204)"); else if (score_str.startsWith('+')) $(this).css("color", getColOfScore(100)); else if (score_str.startsWith('-')) $(this).css("color", getColOfScore(0)); @@ -539,6 +571,7 @@ $.fn.uoj_highlight = function() { }); $(this).find(".uoj-problem-tag").uoj_problem_tag(); $(this).find(".uoj-blog-tag").uoj_blog_tag(); + $(this).find(".uoj-collection-tag").uoj_collection_tag(); $(this).find(".uoj-click-zan-block").click_zan_block(); $(this).find(".countdown").countdown(); $(this).find(".uoj-readmore").readmore({ diff --git a/js/soj_collection_id2url.js b/js/soj_collection_id2url.js new file mode 100644 index 0000000..d4e503b --- /dev/null +++ b/js/soj_collection_id2url.js @@ -0,0 +1,90 @@ +document.getElementById('input-id').addEventListener('input', function(e) { + const input = e.target.value.trim(); + const outputElement = document.getElementById('input-url'); + + if (!input) { + outputElement.value = ''; + return; + } + + // 1. Codeforces 常规题目 (cf1a 或 1a) + if (input.startsWith('cf') || /^\d{1,5}[a-z]\d{0,1}$/.test(input)) { + let problemId = input.startsWith('cf') ? input.substring(2) : input; + + const cfMatch = problemId.match(/^(\d{1,5})([a-z]\d{0,1})$/); + if (cfMatch) { + const contestId = cfMatch[1]; + const problemLetter = cfMatch[2].toUpperCase(); // 注意:大写字母 + outputElement.value = `https://codeforces.com/problemset/problem/${contestId}/${problemLetter}`; + return; + } + } + + // 2. AtCoder 格式 (abc/arc/agc + 数字 + 字母) + const atcoderMatch = input.match(/^(a[brg]c)(\d+)([a-z])$/i); + if (atcoderMatch) { + const contestType = atcoderMatch[1].toLowerCase(); + const contestNumber = atcoderMatch[2]; + const problemLetter = atcoderMatch[3].toLowerCase(); + const contestId = contestType + contestNumber; + + outputElement.value = `https://atcoder.jp/contests/${contestId}/tasks/${contestId}_${problemLetter}`; + return; + } + + // 3. UOJ 格式 (uoj78) + const uojMatch = input.match(/^uoj(\d+)$/i); + if (uojMatch) { + const problemId = uojMatch[1]; + outputElement.value = `https://uoj.ac/problem/${problemId}`; + return; + } + + // 4. QOJ 格式 (qoj789) + const qojMatch = input.match(/^qoj(\d+)$/i); + if (qojMatch) { + const problemId = qojMatch[1]; + outputElement.value = `https://qoj.ac/problem/${problemId}`; + return; + } + + // 5. LOJ 格式 (loj456) + const lojMatch = input.match(/^loj(\d+)$/i); + if (lojMatch) { + const problemId = lojMatch[1]; + outputElement.value = `https://loj.ac/p/${problemId}`; + return; + } + + // 6. SOJ 格式 (soj567) - 相对路径 + const sojMatch = input.match(/^soj(\d+)$/i); + if (sojMatch) { + const problemId = sojMatch[1]; + outputElement.value = `/problem/${problemId}`; + return; + } + + // 7. 洛谷格式 (lg1001 或 p1001) + const lgMatch = input.match(/^(?:lg|p)(\d+)$/i); + if (lgMatch) { + const problemId = lgMatch[1]; + outputElement.value = `https://www.luogu.com.cn/problem/P${problemId}`; + return; + } + + // 8. Codeforces Gym 题目 (cf123456a 或 123456a,6位以上数字) + if (input.startsWith('cf') || /^\d{6,}[a-z]$/.test(input)) { + let problemId = input.startsWith('cf') ? input.substring(2) : input; + + const cfGymMatch = problemId.match(/^(\d{6,})([a-z])$/); + if (cfGymMatch) { + const contestId = cfGymMatch[1]; + const problemLetter = cfGymMatch[2].toUpperCase(); // 注意:大写字母 + outputElement.value = `https://codeforces.com/problemset/gymProblem/${contestId}/${problemLetter}`; + return; + } + } + + // 如果不是已知格式,不改变输出 + // outputElement.value = ''; +});