diff --git a/db/app_uoj233.sql b/db/app_uoj233.sql
index 13fad39..00460ec 100644
--- a/db/app_uoj233.sql
+++ b/db/app_uoj233.sql
@@ -591,6 +591,49 @@ LOCK TABLES `problems_tags` WRITE;
/*!40000 ALTER TABLE `problems_tags` ENABLE KEYS */;
UNLOCK TABLES;
+--
+-- Table structure for table `lists`
+--
+
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8mb4 */;
+CREATE TABLE `lists` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `title` text NOT NULL,
+ `is_hidden` tinyint(1) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `lists_problems`
+--
+
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8mb4 */;
+CREATE TABLE `lists_problems` (
+ `list_id` int(11) NOT NULL,
+ `problem_id` int(11) NOT NULL,
+ PRIMARY KEY (`list_id`, `problem_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Table structure for table `lists_tags`
+--
+
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8mb4 */;
+CREATE TABLE `lists_tags` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `list_id` int(11) NOT NULL,
+ `tag` varchar(30) NOT NULL,
+ PRIMARY KEY (`id`),
+ KEY `list_id` (`list_id`),
+ KEY `tag` (`tag`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
--
-- Table structure for table `search_requests`
--
diff --git a/web/app/controllers/problem_list.php b/web/app/controllers/problem_list.php
new file mode 100644
index 0000000..6935b48
--- /dev/null
+++ b/web/app/controllers/problem_list.php
@@ -0,0 +1,279 @@
+name = 'list';
+ $list_editor->blog_url = null;
+ $list_editor->cur_data = array(
+ 'title' => $list['title'],
+ 'tags' => $list_tags,
+ 'is_hidden' => $list['is_hidden']
+ );
+ $list_editor->label_text = array_merge($list_editor->label_text, array(
+ 'view blog' => '保存题单信息',
+ 'blog visibility' => '题单可见性'
+ ));
+ $list_editor->show_editor = false;
+
+ $list_editor->save = function($data) {
+ global $list_id, $list;
+ DB::update("update lists set title = '" . DB::escape($data['title']) . "' where id = {$list_id}");
+
+ if ($data['tags'] !== $list_tags) {
+ DB::delete("delete from lists_tags where list_id = {$list_id}");
+ foreach ($data['tags'] as $tag) {
+ DB::insert("insert into lists_tags (list_id, tag) values ({$list_id}, '" . DB::escape($tag) . "')");
+ }
+ }
+
+ if ($data['is_hidden'] != $list['is_hidden'] ) {
+ DB::update("update lists set is_hidden = {$data['is_hidden']} where id = {$list_id}");
+ }
+ };
+
+ $list_editor->runAtServer();
+ }
+
+ function removeFromProblemListForm($problem_id) {
+ $res_form = new UOJForm("remove_problem_{$problem_id}");
+ $input_name = "problem_id_delete_{$problem_id}";
+ $res_form->addHidden($input_name, $problem_id, function($problem_id) {
+ global $myUser;
+ if (!isSuperUser($myUser)) {
+ return '只有超级用户可以编辑题单';
+ }
+ }, null);
+ $res_form->handle = function() use ($input_name) {
+ global $list_id;
+ $problem_id = $_POST[$input_name];
+ DB::query("delete from lists_problems where problem_id={$problem_id} and list_id={$list_id}");
+ };
+ $res_form->submit_button_config['class_str'] = 'btn btn-danger';
+ $res_form->submit_button_config['text'] = '删除';
+ $res_form->submit_button_config['align'] = 'inline';
+ return $res_form;
+ }
+
+ $removeProblemForms = array();
+ if (isSuperUser($myUser)) {
+ $problem_ids = DB::query("select problem_id from lists_problems where list_id = {$list_id}");
+ while ($row = DB::fetch($problem_ids)) {
+ $problem_id = $row['problem_id'];
+ $removeForm = removeFromProblemListForm($problem_id);
+ $removeForm->runAtServer();
+ $removeProblemForms[$problem_id] = $removeForm;
+ }
+ }
+
+ if (isSuperUser($myUser)) {
+ $add_new_problem_form = new UOJForm('add_new_problem');
+ $add_new_problem_form->addInput('problem_id', 'text', '题目 ID', '',
+ function ($x) {
+ global $myUser, $list_id;
+
+ if (!isSuperUser($myUser)) {
+ return '只有超级用户可以编辑题单';
+ }
+
+ if (!validateUInt($x)) {
+ return 'ID 不合法';
+ }
+ $problem = queryProblemBrief($x);
+ if (!$problem) {
+ return '题目不存在';
+ }
+
+ if (queryProblemInList($list_id, $x)) {
+ return '该题目已经在题单中';
+ }
+
+ return '';
+ },
+ null
+ );
+ $add_new_problem_form->submit_button_config['align'] = 'compressed';
+ $add_new_problem_form->submit_button_config['text'] = '添加到题单';
+ $add_new_problem_form->handle = function() {
+ global $list_id, $myUser;
+ $problem_id = $_POST['problem_id'];
+
+ DB::insert("insert into lists_problems (list_id, problem_id) values ({$list_id}, {$problem_id})");
+ };
+ $add_new_problem_form->runAtServer();
+ }
+
+ function echoProblem($problem) {
+ global $myUser, $removeProblemForms;
+
+ if (isProblemVisibleToUser($problem, $myUser)) {
+ echo '
';
+ if ($problem['submission_id']) {
+ echo '';
+ } else {
+ echo ' | ';
+ }
+ echo '#', $problem['id'], ' | ';
+ echo '';
+ if (isSuperUser($myUser)) {
+ $form = $removeProblemForms[$problem['id']];
+ $form->printHTML();
+ }
+ if ($problem['is_hidden']) {
+ echo ' [隐藏] ';
+ }
+ if ($problem['uploader'] == $myUser['username']) {
+ echo ' [我的题目] ';
+ }
+ echo '', $problem['title'], '';
+
+ if (isset($_COOKIE['show_tags_mode'])) {
+ echo ' ' . $problem["uploader"] . ' ';
+
+ foreach (queryProblemTags($problem['id']) as $tag) {
+ echo '', '', HTML::escape($tag), '', '';
+ }
+ }
+ echo ' | ';
+ if (isset($_COOKIE['show_submit_mode'])) {
+ $perc = $problem['submit_num'] > 0 ? round(100 * $problem['ac_num'] / $problem['submit_num']) : 0;
+ echo <<×{$problem['ac_num']}
+ ×{$problem['submit_num']} |
+
+
+ |
+EOD;
+ }
+ if (isset($_COOKIE['show_difficulty'])) {
+ $extra_config = getProblemExtraConfig($problem);
+ if ($extra_config['difficulty'] == 0) {
+ echo " | ";
+ } else {
+ echo "{$extra_config['difficulty']} | ";
+ }
+ }
+ echo '', getClickZanBlock('P', $problem['id'], $problem['zan']), ' | ';
+ echo '
';
+ }
+ }
+
+ $header = '';
+ $header .= 'ID | ';
+ $header .= ''.UOJLocale::get('problems::problem').' | ';
+ if (isset($_COOKIE['show_submit_mode'])) {
+ $header .= ''.UOJLocale::get('problems::ac').' | ';
+ $header .= ''.UOJLocale::get('problems::submit').' | ';
+ $header .= ''.UOJLocale::get('problems::ac ratio').' | ';
+ }
+ if (isset($_COOKIE['show_difficulty'])) {
+ $header .= ''.UOJLocale::get('problems::difficulty').' | ';
+ }
+ $header .= ''.UOJLocale::get('appraisal').' | ';
+ $header .= '
';
+
+ $pag_config = array('page_len' => 40);
+ $pag_config['col_names'] = array('best_ac_submissions.submission_id as submission_id', 'problems.id as id', 'problems.is_hidden as is_hidden', 'problems.title as title', 'problems.submit_num as submit_num', 'problems.ac_num as ac_num', 'problems.zan as zan', 'problems.extra_config as extra_config', 'problems.uploader as uploader');
+
+ $pag_config['table_name'] = "problems left join best_ac_submissions on best_ac_submissions.submitter = '{$myUser['username']}' and problems.id = best_ac_submissions.problem_id inner join lists_problems lp on lp.list_id = {$list_id} and lp.problem_id = problems.id";
+
+ $pag_config['cond'] = '1';
+ $pag_config['tail'] = "order by id asc";
+ $pag = new Paginator($pag_config);
+
+ $div_classes = array('table-responsive');
+ $table_classes = array('table', 'table-bordered', 'table-hover', 'table-striped');
+ ?>
+
+编辑题单信息';
+ echo '';
+ $list_editor->printHTML();
+ echo '
';
+
+ echo '添加题目到题单
';
+ $add_new_problem_form->printHTML();
+ }
+ ?>
+
+
+
"= $list['title'] ?>" 中的题目:
+
(题单 ID: #= $list['id'] ?>)
+
+
+
+
+
+
+ pagination(); ?>
+
+
+
+
+
+';
+ echo '';
+ echo '';
+ echo $header;
+ echo '';
+ echo '';
+
+ foreach ($pag->get() as $idx => $row) {
+ echoProblem($row);
+ echo "\n";
+ }
+ if ($pag->isEmpty()) {
+ echo ''.UOJLocale::get('none').' |
';
+ }
+
+ echo '';
+ echo '
';
+ echo '';
+
+ echo $pag->pagination();
+ ?>
+
+
diff --git a/web/app/controllers/problem_lists.php b/web/app/controllers/problem_lists.php
new file mode 100644
index 0000000..e5140d2
--- /dev/null
+++ b/web/app/controllers/problem_lists.php
@@ -0,0 +1,107 @@
+handle = function() {
+ DB::query("insert into lists (title, is_hidden) values ('未命名题单', 1)");
+ };
+ $new_list_form->submit_button_config['align'] = 'right';
+ $new_list_form->submit_button_config['class_str'] = 'btn btn-primary';
+ $new_list_form->submit_button_config['text'] = UOJLocale::get('problems::add new list');
+ $new_list_form->submit_button_config['smart_confirm'] = '';
+
+ $new_list_form->runAtServer();
+ }
+
+ function echoList($list) {
+ global $myUser;
+
+ echo '';
+ if ($list['problem_count'] == $list['accepted'] && $list['problem_count'] > 0) {
+ echo '';
+ } else {
+ echo ' | ';
+ }
+ echo '#', $list['list_id'], ' | ';
+
+ echo '';
+ if ($list['is_hidden']) {
+ echo ' [隐藏] ';
+ }
+ echo '', $list['title'], '';
+ foreach (queryProblemListTags($list['list_id']) as $tag) {
+ echo '', '', HTML::escape($tag), '', '';
+ }
+ echo ' | ';
+
+ echo "{$list['accepted']} | ";
+ echo "{$list['problem_count']} | ";
+
+ echo '
';
+ }
+ ?>
+
+
+
+printHTML();
+ }
+
+ $problem_list_caption = UOJLocale::get('problems::problem list');
+ $ac_caption = UOJLocale::get('problems::ac');
+ $total_caption = UOJLocale::get('problems::total');
+ $header = <<
+ ID |
+ {$problem_list_caption} |
+ {$ac_caption} |
+ {$total_caption} |
+
+EOD;
+
+ $cond = array();
+
+ $search_tag = null;
+ if (isset($_GET['tag'])) {
+ $search_tag = $_GET['tag'];
+ }
+ if ($search_tag) {
+ $cond[] = "'" . DB::escape($search_tag) . "' in (select tag from lists_tags where lists_tags.list_id = a.id)";
+ }
+ if (!isSuperUser($myUser)) {
+ $cond[] = "is_hidden = 0";
+ }
+
+ if ($cond) {
+ $cond = join($cond, ' and ');
+ } else {
+ $cond = '1';
+ }
+
+ $from = "lists a left join lists_problems b on a.id = b.list_id left join best_ac_submissions c on (b.problem_id = c.problem_id and c.submitter = '{$myUser['username']}')";
+
+ echoLongTable(
+ array('a.id as list_id', 'a.title as title', 'a.is_hidden as is_hidden', 'count(b.problem_id) as problem_count', 'count(c.submitter) as accepted'),
+ $from, $cond, 'group by a.id order by a.id desc',
+ $header,
+ 'echoList',
+ array('page_len' => 40,
+ 'table_classes' => array('table', 'table-bordered', 'table-hover', 'table-striped'),
+ 'print_after_table' => function() {
+ global $myUser;
+ },
+ 'head_pagination' => true
+ )
+ );
+ ?>
+
+
diff --git a/web/app/libs/uoj-query-lib.php b/web/app/libs/uoj-query-lib.php
index 3364df8..5d357a1 100644
--- a/web/app/libs/uoj-query-lib.php
+++ b/web/app/libs/uoj-query-lib.php
@@ -63,6 +63,25 @@ function queryProblemTags($id) {
}
return $tags;
}
+
+function queryProblemList($id) {
+ return DB::selectFirst("select * from lists where id = $id", MYSQLI_ASSOC);
+}
+function queryProblemListTags($id) {
+ $tags = array();
+ $result = DB::query("select tag from lists_tags where list_id = $id order by id");
+ if (!$result) {
+ return $tags;
+ }
+ while ($row = DB::fetch($result, MYSQLI_NUM)) {
+ $tags[] = $row[0];
+ }
+ return $tags;
+}
+function queryProblemInList($list_id, $problem_id) {
+ return DB::selectFirst("select * from lists_problems where list_id='$blog_id' and problem_id='$problem_id'", MYSQLI_ASSOC);
+}
+
function queryContestProblemRank($contest, $problem) {
if (!DB::selectFirst("select * from contests_problems where contest_id = {$contest['id']} and problem_id = {$problem['id']}")) {
return null;
diff --git a/web/app/locale/basic/en.php b/web/app/locale/basic/en.php
index e9d1479..7c17bd5 100644
--- a/web/app/locale/basic/en.php
+++ b/web/app/locale/basic/en.php
@@ -11,6 +11,7 @@ return [
'system manage' => 'System Manage',
'contests' => 'Contests',
'problems' => 'Problems',
+ 'problems lists' => 'Problems Lists',
'groups' => 'Groups',
'add new group' => 'Add new group',
'users count' => 'Users',
diff --git a/web/app/locale/basic/zh-cn.php b/web/app/locale/basic/zh-cn.php
index 032d5fc..abc9a40 100644
--- a/web/app/locale/basic/zh-cn.php
+++ b/web/app/locale/basic/zh-cn.php
@@ -11,6 +11,7 @@ return [
'system manage' => '系统管理',
'contests' => '比赛',
'problems' => '题库',
+ 'problems lists' => '题单',
'groups' => '小组',
'add new group' => '添加新小组',
'users count' => '用户数量',
diff --git a/web/app/locale/problems/en.php b/web/app/locale/problems/en.php
index 9df005f..0e1edcc 100644
--- a/web/app/locale/problems/en.php
+++ b/web/app/locale/problems/en.php
@@ -1,15 +1,19 @@
'Problem',
+ 'problem list' => 'Problem List',
'all problems' => 'All Problems',
'template problems' => 'Template Problems',
'add new' => 'Add new problem',
+ 'add new list' => 'Add new problem list',
'title' => 'Title',
+ 'total' => 'Total',
'ac' => 'AC',
'submit' => 'Submit',
'ac ratio' => 'AC Ratio',
'show tags' => 'Show tags',
'show statistics' => 'Show statistics',
+ 'submissions statistics' => 'Submissions statistics',
'statement' => 'Statement',
'custom test' => 'Custom Test',
'manage' => 'Manage',
diff --git a/web/app/locale/problems/zh-cn.php b/web/app/locale/problems/zh-cn.php
index 0523cb5..7e3f5e0 100644
--- a/web/app/locale/problems/zh-cn.php
+++ b/web/app/locale/problems/zh-cn.php
@@ -1,9 +1,13 @@
'题目',
+ 'problem list' => '题单',
'all problems' => '总题库',
'template problems' => '模板题库',
'add new' => '添加新题',
+ 'add new list' => '添加新题单',
+ 'title' => '标题',
+ 'total' => '总题数',
'ac' => 'AC',
'submit' => '提交',
'ac ratio' => 'AC 率',
diff --git a/web/app/route.php b/web/app/route.php
index d045857..e12bfc4 100644
--- a/web/app/route.php
+++ b/web/app/route.php
@@ -18,6 +18,9 @@ Route::group([
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('/problem_lists', '/problem_lists.php');
+ Route::any('/problem_list/{id}', '/problem_list.php');
Route::any('/contests', '/contests.php');
Route::any('/contest/new', '/add_contest.php');
diff --git a/web/app/views/main-nav.php b/web/app/views/main-nav.php
index 871545f..c51f7fa 100644
--- a/web/app/views/main-nav.php
+++ b/web/app/views/main-nav.php
@@ -6,8 +6,9 @@
- = UOJLocale::get('contests') ?>
- - = UOJLocale::get('problems') ?>
+ - = UOJLocale::get('problems') ?>
- = UOJLocale::get('groups') ?>
+ - = UOJLocale::get('problems lists') ?>
- = UOJLocale::get('submissions') ?>
- = UOJLocale::get('hacks') ?>
- = UOJLocale::get('blogs') ?>
diff --git a/web/js/blog-editor/blog-editor.js b/web/js/blog-editor/blog-editor.js
index e55d818..7748fdb 100644
--- a/web/js/blog-editor/blog-editor.js
+++ b/web/js/blog-editor/blog-editor.js
@@ -11,7 +11,7 @@ function blog_editor_init(name, editor_config) {
var input_tags = $("#input-" + name + "_tags");
var input_content_md = $("#input-" + name + "_content_md");
var input_is_hidden = $("#input-" + name + "_is_hidden");
- var this_form = input_content_md[0].form;
+ var this_form = input_is_hidden[0].form;
var is_saved;
var last_save_done = true;
@@ -23,7 +23,7 @@ function blog_editor_init(name, editor_config) {
var italic_btn = $('');
save_btn.tooltip({ container: 'body', title: '保存 (Ctrl-S)' });
- preview_btn.tooltip({ container: 'body', title: '预览 (Ctrl-D)' });
+ preview_btn.tooltip({ container: 'body', title: '预览 (Ctrl-D)' });
bold_btn.tooltip({ container: 'body', title: '粗体 (Ctrl-B)' });
italic_btn.tooltip({ container: 'body', title: '斜体 (Ctrl-I)' });
@@ -78,34 +78,36 @@ function blog_editor_init(name, editor_config) {
set_saved(true);
// init codemirror
- input_content_md.wrap('');
- var blog_contend_md_editor = input_content_md.parent();
- input_content_md.before($('')
- .append(toolbar)
- );
- input_content_md.wrap('');
-
- var codeeditor;
- if (editor_config.type == 'blog') {
- codeeditor = CodeMirror.fromTextArea(input_content_md[0], {
- mode: 'gfm',
- lineNumbers: true,
- matchBrackets: true,
- lineWrapping: true,
- styleActiveLine: true,
- theme: 'default'
- });
- } else if (editor_config.type == 'slide') {
- codeeditor = CodeMirror.fromTextArea(input_content_md[0], {
- mode: 'plain',
- lineNumbers: true,
- matchBrackets: true,
- lineWrapping: true,
- styleActiveLine: true,
- theme: 'default'
- });
+ if (input_content_md[0]) {
+ input_content_md.wrap('');
+ var blog_contend_md_editor = input_content_md.parent();
+ input_content_md.before($('')
+ .append(toolbar)
+ );
+ input_content_md.wrap('');
+
+ var codeeditor;
+ if (editor_config.type == 'blog') {
+ codeeditor = CodeMirror.fromTextArea(input_content_md[0], {
+ mode: 'gfm',
+ lineNumbers: true,
+ matchBrackets: true,
+ lineWrapping: true,
+ styleActiveLine: true,
+ theme: 'default'
+ });
+ } else if (editor_config.type == 'slide') {
+ codeeditor = CodeMirror.fromTextArea(input_content_md[0], {
+ mode: 'plain',
+ lineNumbers: true,
+ matchBrackets: true,
+ lineWrapping: true,
+ styleActiveLine: true,
+ theme: 'default'
+ });
+ }
}
-
+
function preview(html) {
var iframe = $('');
blog_contend_md_editor.append(
@@ -219,13 +221,23 @@ function blog_editor_init(name, editor_config) {
}
// event
- codeeditor.on('change', function() {
- codeeditor.save();
- set_saved(false);
- });
+ if (input_content_md[0]) {
+ codeeditor.on('change', function() {
+ codeeditor.save();
+ set_saved(false);
+ });
+ }
$.merge(input_title, input_tags).on('input', function() {
set_saved(false);
});
+ $('#a-' + name + '_save').click(function (e) {
+ e.preventDefault();
+ save({
+ done: function () {
+ location.reload();
+ }
+ });
+ });
save_btn.click(function() {
save();
});
@@ -274,20 +286,22 @@ function blog_editor_init(name, editor_config) {
});
// init hot keys
- codeeditor.setOption("extraKeys", {
- "Ctrl-S": function(cm) {
- save_btn.click();
- },
- "Ctrl-B": function(cm) {
- bold_btn.click();
- },
- "Ctrl-D": function(cm) {
- preview_btn.click();
- },
- "Ctrl-I": function(cm) {
- italic_btn.click();
- }
- });
+ if (input_content_md[0]) {
+ codeeditor.setOption("extraKeys", {
+ "Ctrl-S": function(cm) {
+ save_btn.click();
+ },
+ "Ctrl-B": function(cm) {
+ bold_btn.click();
+ },
+ "Ctrl-D": function(cm) {
+ preview_btn.click();
+ },
+ "Ctrl-I": function(cm) {
+ italic_btn.click();
+ }
+ });
+ }
$(document).bind('keydown', 'ctrl+d', function() {
preview_btn.click();
return false;
diff --git a/web/js/uoj.js b/web/js/uoj.js
index 4ecb91c..1e59f12 100644
--- a/web/js/uoj.js
+++ b/web/js/uoj.js
@@ -245,6 +245,11 @@ $.fn.uoj_problem_tag = function() {
$(this).attr('href', uojHome + '/problems?tag=' + encodeURIComponent($(this).text()));
});
}
+$.fn.uoj_list_tag = function() {
+ return this.each(function() {
+ $(this).attr('href', uojHome + '/problem_lists?tag=' + encodeURIComponent($(this).text()));
+ });
+}
$.fn.uoj_blog_tag = function() {
return this.each(function() {
$(this).attr('href', uojBlogUrl + '/archive?tag=' + encodeURIComponent($(this).text()));
@@ -408,6 +413,7 @@ $.fn.uoj_highlight = function() {
}
});
$(this).find(".uoj-problem-tag").uoj_problem_tag();
+ $(this).find(".uoj-list-tag").uoj_list_tag();
$(this).find(".uoj-blog-tag").uoj_blog_tag();
$(this).find(".uoj-click-zan-block").click_zan_block();
$(this).find(".countdown").countdown();