uoj.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  1. // locale
  2. uojLocaleData = {
  3. "username": {
  4. "en": "Username",
  5. "zh-cn": "用户名"
  6. },
  7. "contests::total score": {
  8. "en": "Score",
  9. "zh-cn": "总分"
  10. },
  11. "contests::n participants": {
  12. "en": function(n) {
  13. return n + " participant" + (n <= 1 ? '' : 's');
  14. },
  15. "zh-cn": function(n) {
  16. return "共 " + n + " 名参赛者";
  17. }
  18. },
  19. "click-zan::good": {
  20. "en": "Good",
  21. "zh-cn": "好评"
  22. },
  23. "click-zan::bad": {
  24. "en": "Bad",
  25. "zh-cn": "差评"
  26. },
  27. "editor::use advanced editor": {
  28. "en": "use advanced editor",
  29. "zh-cn": "使用高级编辑器"
  30. },
  31. "editor::language": {
  32. "en": "Language",
  33. "zh-cn": "语言"
  34. },
  35. "editor::browse": {
  36. "en": "Browse",
  37. "zh-cn": "浏览"
  38. },
  39. "editor::upload by editor": {
  40. "en": "Upload by editor",
  41. "zh-cn": "使用编辑器上传"
  42. },
  43. "editor::upload from local": {
  44. "en": "Upload from local",
  45. "zh-cn": "从本地文件上传"
  46. }
  47. };
  48. function uojLocale(name) {
  49. locale = $.cookie('uoj_locale');
  50. if (uojLocaleData[name] === undefined) {
  51. return '';
  52. }
  53. if (uojLocaleData[name][locale] === undefined) {
  54. locale = 'zh-cn';
  55. }
  56. val = uojLocaleData[name][locale];
  57. if (!$.isFunction(val)) {
  58. return val;
  59. } else {
  60. var args = [];
  61. for (var i = 1; i < arguments.length; i++) {
  62. args.push(arguments[i]);
  63. }
  64. return val.apply(this, args);
  65. }
  66. }
  67. // utility
  68. function strToDate(str) {
  69. var a = str.split(/[^0-9]/);
  70. return new Date(
  71. parseInt(a[0]),
  72. parseInt(a[1]) - 1,
  73. parseInt(a[2]),
  74. parseInt(a[3]),
  75. parseInt(a[4]),
  76. parseInt(a[5]),
  77. 0);
  78. }
  79. function dateToStr(date) {
  80. return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + ' ' + date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
  81. }
  82. function toFilledStr(o, f, l) {
  83. var s = o.toString();
  84. while (s.length < l) {
  85. s = f.toString() + s;
  86. }
  87. return s;
  88. }
  89. function getPenaltyTimeStr(x) {
  90. var ss = toFilledStr(x % 60, '0', 2);
  91. x = Math.floor(x / 60);
  92. var mm = toFilledStr(x % 60, '0', 2);
  93. x = Math.floor(x / 60);
  94. var hh = x.toString();
  95. return hh + ':' + mm + ':' + ss;
  96. }
  97. function htmlspecialchars(str)
  98. {
  99. var s = "";
  100. if (str.length == 0) return "";
  101. s = str.replace(/&/g, "&amp;");
  102. s = s.replace(/</g, "&lt;");
  103. s = s.replace(/>/g, "&gt;");
  104. s = s.replace(/"/g, "&quot;");
  105. return s;
  106. }
  107. function getColOfRating(rating) {
  108. if (rating < 1500) {
  109. var H = 300 - (1500 - 850) * 300 / 1650, S = 30 + (1500 - 850) * 70 / 1650, V = 50 + (1500 - 850) * 50 / 1650;
  110. if (rating < 300) rating = 300;
  111. var k = (rating - 300) / 1200;
  112. return ColorConverter.toStr(ColorConverter.toRGB(new HSV(H + (300 - H) * (1 - k), 30 + (S - 30) * k, 50 + (V - 50) * k)));
  113. }
  114. if (rating > 2500) {
  115. rating = 2500;
  116. }
  117. return ColorConverter.toStr(ColorConverter.toRGB(new HSV(300 - (rating - 850) * 300 / 1650, 30 + (rating - 850) * 70 / 1650, 50 + (rating - 850) * 50 / 1650)));
  118. }
  119. function getColOfScore(score) {
  120. if (score == 0) {
  121. return ColorConverter.toStr(ColorConverter.toRGB(new HSV(0, 100, 80)));
  122. } else if (score == 100) {
  123. return ColorConverter.toStr(ColorConverter.toRGB(new HSV(120, 100, 80)));
  124. } else {
  125. return ColorConverter.toStr(ColorConverter.toRGB(new HSV(30 + score * 60 / 100, 100, 90)));
  126. }
  127. }
  128. function getUserLink(username, rating, addSymbol, certs) {
  129. if (!username) {
  130. return '';
  131. }
  132. if (addSymbol == undefined) {
  133. addSymbol = true;
  134. }
  135. var text = username;
  136. if (username.charAt(0) == '@') {
  137. username = username.substr(1);
  138. }
  139. if (addSymbol) {
  140. if (rating >= 2500) {
  141. text += '<sup>';
  142. for (var i = 2500; i <= rating; i += 200) {
  143. text += "&equiv;"
  144. }
  145. text += "</sup>";
  146. }
  147. }
  148. if (certs) {
  149. var certcolor = certs.split ('#')[0];
  150. var certhonor = certs.split ('#')[1];
  151. }
  152. var html = '<a class="uoj-username" href="' + uojHome + '/user/profile/' + username + '" style="color:' + getColOfRating(rating) + '">' + text + '</a>';
  153. if (certhonor != undefined)
  154. html += '<span class="uoj-certs uoj-certs-' + certcolor + '">' + certhonor + '</span>';
  155. return html;
  156. }
  157. function getUserSpan(username, rating, addSymbol, certs) {
  158. if (!username) {
  159. return '';
  160. }
  161. if (addSymbol == undefined) {
  162. addSymbol = true;
  163. }
  164. var text = username;
  165. if (username.charAt(0) == '@') {
  166. username = username.substr(1);
  167. }
  168. if (addSymbol) {
  169. if (rating >= 2500) {
  170. text += '<sup>';
  171. for (var i = 2500; i <= rating; i += 200) {
  172. text += "&equiv;"
  173. }
  174. text += "</sup>";
  175. }
  176. }
  177. if (certs) {
  178. var certcolor = certs.split ('#')[0];
  179. var certhonor = certs.split ('#')[1];
  180. }
  181. var html = '<span class="uoj-username" style="color:' + getColOfRating(rating) + '">' + text + '</span>';
  182. if (certhonor != undefined)
  183. html += '<span class="uoj-certs uoj-certs-' + certcolor + '">' + certhonor + '</span>';
  184. return html;
  185. }
  186. function replaceWithHighlightUsername() {
  187. var username = $(this).text();
  188. var rating = $(this).data("rating");
  189. var certs = $(this).data("certs");
  190. if (isNaN(rating)) {
  191. return;
  192. }
  193. if ($(this).data("link") != 0) {
  194. $(this).replaceWith(getUserLink(username, rating, false, certs));
  195. } else {
  196. $(this).replaceWith(getUserSpan(username, rating, false, certs));
  197. }
  198. }
  199. $.fn.uoj_honor = function() {
  200. return this.each(function() {
  201. var honor = $(this).text();
  202. var rating = $(this).data("rating");
  203. var certs = $(this).data("certs");
  204. if (isNaN(rating)) {
  205. return;
  206. }
  207. if (rating >= 2500) {
  208. honor += '<sup>';
  209. for (var i = 2500; i <= rating; i += 200) {
  210. honor += "&equiv;"
  211. }
  212. honor += "</sup>";
  213. }
  214. if (certs) {
  215. var certcolor = certs.split ('#')[0];
  216. var certhonor = certs.split ('#')[1];
  217. if (certhonor != undefined)
  218. honor += '<span class="uoj-certs uoj-certs-' + certcolor + '">' + certhonor + '</span>';
  219. }
  220. $(this).css("color", getColOfRating(rating)).html(honor);
  221. });
  222. }
  223. function showErrorHelp(name, err) {
  224. if (err) {
  225. $('#div-' + name).addClass('has-error');
  226. $('#help-' + name).text(err);
  227. return false;
  228. } else {
  229. $('#div-' + name).removeClass('has-error');
  230. $('#help-' + name).text('');
  231. return true;
  232. }
  233. }
  234. function getFormErrorAndShowHelp(name, val) {
  235. var err = val($('#input-' + name).val());
  236. return showErrorHelp(name, err);
  237. }
  238. function validateSettingPassword(str) {
  239. if (str.length < 6) {
  240. return '密码长度不应小于6。';
  241. } else if (! /^[!-~]+$/.test(str)) {
  242. return '密码应只包含可见ASCII字符。';
  243. } else if (str != $('#input-confirm_password').val()) {
  244. return '两次输入的密码不一致。';
  245. } else {
  246. return '';
  247. }
  248. }
  249. function validatePassword(str) {
  250. if (str.length < 6) {
  251. return '密码长度不应小于6。';
  252. } else if (! /^[!-~]+$/.test(str)) {
  253. return '密码应只包含可见ASCII字符。';
  254. } else {
  255. return '';
  256. }
  257. }
  258. function validateEmail(str) {
  259. if (str.length > 50) {
  260. return '电子邮箱地址太长。';
  261. } else if (! /^(.+)@(.+)$/.test(str)) {
  262. return '电子邮箱地址非法。';
  263. } else {
  264. return '';
  265. }
  266. }
  267. function validateUsername(str) {
  268. if (str.length == 0) {
  269. return '用户名不能为空。';
  270. } else if (! /^[a-zA-Z0-9_]+$/.test(str)) {
  271. return '用户名应只包含大小写英文字母、数字和下划线。';
  272. } else {
  273. return '';
  274. }
  275. }
  276. function validateQQ(str) {
  277. if (str.length < 5) {
  278. return 'QQ的长度不应小于5。';
  279. } else if (str.length > 15) {
  280. return 'QQ的长度不应大于15。';
  281. } else if (/\D/.test(str)) {
  282. return 'QQ应只包含0~9的数字。';
  283. } else {
  284. return '';
  285. }
  286. }
  287. function validateMotto(str) {
  288. if (str.length > 50) {
  289. return '不能超过50字';
  290. } else {
  291. return '';
  292. }
  293. }
  294. // tags
  295. $.fn.uoj_problem_tag = function() {
  296. return this.each(function() {
  297. $(this).attr('href', uojHome + '/problems?tag=' + encodeURIComponent($(this).text()));
  298. });
  299. }
  300. $.fn.uoj_blog_tag = function() {
  301. return this.each(function() {
  302. $(this).attr('href', uojBlogUrl + '/archive?tag=' + encodeURIComponent($(this).text()));
  303. });
  304. }
  305. // click zan
  306. function click_zan(zan_id, zan_type, zan_delta, node) {
  307. var loading_node = $('<div class="text-muted">loading...</div>');
  308. $(node).replaceWith(loading_node);
  309. $.post(zan_link + '/click-zan', {
  310. id : zan_id,
  311. delta : zan_delta,
  312. type : zan_type
  313. }, function(ret) {
  314. $(loading_node).replaceWith($(ret).click_zan_block());
  315. }).fail(function() {
  316. $(loading_node).replaceWith('<div class="text-danger">failed</div>');
  317. });
  318. }
  319. $.fn.click_zan_block = function() {
  320. return this.each(function() {
  321. var id = $(this).data('id');
  322. var type = $(this).data('type');
  323. var val = parseInt($(this).data('val'));
  324. var cnt = parseInt($(this).data('cnt'));
  325. if (isNaN(cnt)) {
  326. return;
  327. }
  328. if (val == 1) {
  329. $(this).addClass('uoj-click-zan-block-cur-up');
  330. } else if (val == 0) {
  331. $(this).addClass('uoj-click-zan-block-cur-zero');
  332. } else if (val == -1) {
  333. $(this).addClass('uoj-click-zan-block-cur-down');
  334. } else {
  335. return;
  336. }
  337. if (cnt > 0) {
  338. $(this).addClass('uoj-click-zan-block-positive');
  339. } else if (cnt == 0) {
  340. $(this).addClass('uoj-click-zan-block-neutral');
  341. } else {
  342. $(this).addClass('uoj-click-zan-block-negative');
  343. }
  344. var node = this;
  345. var up_node = $('<a href="#" class="uoj-click-zan-up"><span class="glyphicon glyphicon-thumbs-up"></span>'+uojLocale('click-zan::good')+'</a>').click(function(e) {
  346. e.preventDefault();
  347. click_zan(id, type, 1, node);
  348. });
  349. var down_node = $('<a href="#" class="uoj-click-zan-down"><span class="glyphicon glyphicon-thumbs-down"></span>'+uojLocale('click-zan::bad')+'</a>').click(function(e) {
  350. e.preventDefault();
  351. click_zan(id, type, -1, node);
  352. });
  353. $(this)
  354. .append(up_node)
  355. .append(down_node)
  356. .append($('<span class="uoj-click-zan-cnt">[<strong>' + (cnt > 0 ? '+' + cnt : cnt) + '</strong>]</span>'));
  357. });
  358. }
  359. // count down
  360. function getCountdownStr(t) {
  361. var x = Math.floor(t);
  362. var ss = toFilledStr(x % 60, '0', 2);
  363. x = Math.floor(x / 60);
  364. var mm = toFilledStr(x % 60, '0', 2);
  365. x = Math.floor(x / 60);
  366. var hh = x.toString();
  367. var res = '<span style="font-size:30px">';
  368. res += '<span style="color:' + getColOfScore(Math.min(t / 10800 * 100, 100)) + '">' + hh + '</span>';
  369. res += ':';
  370. res += '<span style="color:' + getColOfScore(mm / 60 * 100) + '">' + mm + '</span>';
  371. res += ':';
  372. res += '<span style="color:' + getColOfScore(ss / 60 * 100) + '">' + ss + '</span>';
  373. res += '</span>'
  374. return res;
  375. }
  376. $.fn.countdown = function(rest, callback) {
  377. return this.each(function() {
  378. var start = new Date().getTime();
  379. var cur_rest = rest != undefined ? rest : parseInt($(this).data('rest'));
  380. var cur = this;
  381. var countdown = function() {
  382. var passed = Math.floor((new Date().getTime() - start) / 1000);
  383. if (passed >= cur_rest) {
  384. $(cur).html(getCountdownStr(0));
  385. if (callback != undefined) {
  386. callback();
  387. }
  388. } else {
  389. $(cur).html(getCountdownStr(cur_rest - passed));
  390. setTimeout(countdown, 1000);
  391. }
  392. }
  393. countdown();
  394. });
  395. };
  396. // update_judgement_status
  397. update_judgement_status_list = []
  398. function update_judgement_status_details(id) {
  399. update_judgement_status_list.push(id);
  400. };
  401. $(document).ready(function() {
  402. function update() {
  403. $.get("/submission-status-details", {
  404. get: update_judgement_status_list
  405. },
  406. function(data) {
  407. for (var i = 0; i < update_judgement_status_list.length; i++) {
  408. $("#status_details_" + update_judgement_status_list[i]).html(data[i].html);
  409. if (data[i].judged) {
  410. location.reload();
  411. }
  412. }
  413. }, 'json').always(
  414. function() {
  415. setTimeout(update, 500);
  416. }
  417. );
  418. }
  419. if (update_judgement_status_list.length > 0) {
  420. setTimeout(update, 500);
  421. }
  422. });
  423. // highlight
  424. $.fn.uoj_highlight = function() {
  425. return $(this).each(function() {
  426. $(this).find("span.uoj-username").each(replaceWithHighlightUsername);
  427. $(this).find(".uoj-honor").uoj_honor();
  428. $(this).find(".uoj-score").each(function() {
  429. var score = parseInt($(this).text());
  430. var maxscore = parseInt($(this).data('max'));
  431. if (isNaN(score)) {
  432. return;
  433. }
  434. if (isNaN(maxscore)) {
  435. $(this).css("color", getColOfScore(score));
  436. } else {
  437. $(this).css("color", getColOfScore(score / maxscore * 100));
  438. }
  439. });
  440. $(this).find(".uoj-status").each(function() {
  441. var success = parseInt($(this).data("success"));
  442. if(isNaN(success)){
  443. return;
  444. }
  445. if (success == 1) {
  446. $(this).css("color", ColorConverter.toStr(ColorConverter.toRGB(new HSV(120, 100, 80))));
  447. }
  448. else {
  449. $(this).css("color", ColorConverter.toStr(ColorConverter.toRGB(new HSV(0, 100, 100))));
  450. }
  451. });
  452. $(this).find(".uoj-problem-tag").uoj_problem_tag();
  453. $(this).find(".uoj-blog-tag").uoj_blog_tag();
  454. $(this).find(".uoj-click-zan-block").click_zan_block();
  455. $(this).find(".countdown").countdown();
  456. $(this).find(".uoj-readmore").readmore({
  457. moreLink: '<a href="#" class="text-right">more...</a>',
  458. lessLink: '<a href="#" class="text-right">close</a>',
  459. });
  460. });
  461. };
  462. $(document).ready(function() {
  463. $('body').uoj_highlight();
  464. });
  465. // contest notice
  466. function checkContestNotice(id, lastTime) {
  467. $.post('/contest/' + id.toString(), {
  468. check_notice : '',
  469. last_time : lastTime
  470. },
  471. function(data) {
  472. setTimeout(function() {
  473. checkContestNotice(id, data.time);
  474. }, 60000);
  475. if (data.msg != undefined) {
  476. var len=data.msg.length;
  477. for (var i=0;i<len;i++) alert(data.msg[i]);
  478. }
  479. },
  480. 'json'
  481. ).fail(function() {
  482. setTimeout(function() {
  483. checkContestNotice(id, lastTime);
  484. }, 60000);
  485. });
  486. }
  487. // long table
  488. $.fn.long_table = function(data, cur_page, header_row, get_row_str, config) {
  489. return this.each(function() {
  490. var table_div = this;
  491. $(table_div).html('');
  492. var page_len = config.page_len != undefined ? config.page_len : 10;
  493. if (!config.echo_full) {
  494. var n_rows = data.length;
  495. var n_pages = Math.max(Math.ceil(n_rows / page_len), 1);
  496. if (cur_page == undefined) {
  497. cur_page = 1;
  498. }
  499. if (cur_page < 1) {
  500. cur_page = 1;
  501. } else if (cur_page > n_pages) {
  502. cur_page = n_pages;
  503. }
  504. var cur_start = (cur_page - 1) * page_len;
  505. } else {
  506. var n_rows = data.length;
  507. var n_pages = 1;
  508. cur_page = 1;
  509. var cur_start = (cur_page - 1) * page_len;
  510. }
  511. var div_classes = config.div_classes != undefined ? config.div_classes : ['table-responsive'];
  512. var table_classes = config.table_classes != undefined ? config.table_classes : ['table', 'table-bordered', 'table-hover', 'table-striped', 'table-text-center'];
  513. var now_cnt = 0;
  514. var tbody = $('<tbody />')
  515. for (var i = 0; i < page_len && cur_start + i < n_rows; i++) {
  516. now_cnt++;
  517. if (config.get_row_index) {
  518. tbody.append(get_row_str(data[cur_start + i], cur_start + i));
  519. } else {
  520. tbody.append(get_row_str(data[cur_start + i]));
  521. }
  522. }
  523. if (now_cnt == 0) {
  524. tbody.append('<tr><td colspan="233">无</td></tr>');
  525. }
  526. $(table_div).append(
  527. $('<div class="' + div_classes.join(' ') + '" />').append(
  528. $('<table class="' + table_classes.join(' ') + '" />').append(
  529. $('<thead>' + header_row + '</thead>')
  530. ).append(
  531. tbody
  532. )
  533. )
  534. );
  535. if (config.print_after_table != undefined) {
  536. $(table_div).append(config.print_after_table());
  537. }
  538. var get_page_li = function(p, h) {
  539. if (p == -1) {
  540. return $('<li class="page-item"></li>').addClass('disabled').append($('<a class="page-link"></a>').append(h));
  541. }
  542. var li = $('<li class="page-item"></li>');
  543. if (p == cur_page) {
  544. li.addClass('active');
  545. }
  546. li.append(
  547. $('<a class="page-link"></a>').attr('href', '#' + table_div.id).append(h).click(function(e) {
  548. if (config.prevent_focus_on_click) {
  549. e.preventDefault();
  550. }
  551. $(table_div).long_table(data, p, header_row, get_row_str, config);
  552. })
  553. );
  554. return li;
  555. };
  556. if (n_pages > 1) {
  557. var pagination = $('<ul class="pagination top-buffer-no bot-buffer-sm justify-content-center"></ul>');
  558. if (cur_page > 1) {
  559. pagination.append(get_page_li(cur_page - 1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
  560. } else {
  561. pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
  562. }
  563. var max_extend = config.max_extend != undefined ? config.max_extend : 5;
  564. for (var i = Math.max(cur_page - max_extend, 1); i <= Math.min(cur_page + max_extend, n_pages); i++) {
  565. pagination.append(get_page_li(i, i.toString()));
  566. }
  567. if (cur_page < n_pages) {
  568. pagination.append(get_page_li(cur_page + 1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
  569. } else {
  570. pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
  571. }
  572. $(table_div).append($('<div class="text-center"></div>').append(pagination));
  573. }
  574. });
  575. };
  576. // code mirror
  577. function require_codemirror(config, callback) {
  578. if ($('link[href="' + uojHome + '/js/codemirror/lib/codemirror.css' + '"]').length == 0) {
  579. $('<link type="text/css" rel="stylesheet" href="' + uojHome + '/js/codemirror/lib/codemirror.css' + '" />').appendTo('head');
  580. }
  581. $LAB.script(uojHome + '/js/codemirror/lib/codemirror.js')
  582. .wait()
  583. .script(uojHome + '/js/codemirror/addon/mode/overlay.js')
  584. .script(uojHome + '/js/codemirror/addon/selection/active-line.js')
  585. .wait(callback)
  586. }
  587. function get_codemirror_mode(lang) {
  588. switch (lang) {
  589. case 'C++':
  590. case 'C++11':
  591. case 'C++14':
  592. case 'C++17':
  593. case 'C++17ubsan':
  594. return 'text/x-c++src';
  595. case 'C':
  596. return 'text/x-csrc';
  597. case 'Python2':
  598. case 'Python3':
  599. return 'text/x-python';
  600. case 'Java8':
  601. case 'Java11':
  602. return 'text/x-java';
  603. case 'Pascal':
  604. return 'text/x-pascal';
  605. case 'text':
  606. return 'text/plain';
  607. default:
  608. return 'text/plain';
  609. }
  610. };
  611. function require_codemirror_mode(mode, callback) {
  612. var name = 'none';
  613. switch (mode) {
  614. case 'text/x-c++src':
  615. case 'text/x-csrc':
  616. case 'text/x-java':
  617. name = 'clike';
  618. break;
  619. case 'text/x-python':
  620. name = 'python';
  621. break;
  622. case 'text/x-pascal':
  623. name = 'pascal';
  624. break;
  625. }
  626. if (name !== 'none') {
  627. $LAB.script(uojHome + '/js/codemirror/mode/' + name + '/' + name + '.js')
  628. .wait(callback);
  629. } else {
  630. setTimeout(callback, 0);
  631. }
  632. };
  633. // auto save
  634. function autosave_locally(interval, name, target) {
  635. if (typeof(Storage) === "undefined") {
  636. console.log('autosave_locally: Sorry! No Web Storage support..');
  637. return;
  638. }
  639. var url = window.location.href;
  640. var hp = url.indexOf('#');
  641. var uri = hp == -1 ? url : url.substr(0, hp);
  642. var full_name = name + '@' + uri;
  643. target.val(localStorage.getItem(full_name));
  644. var save = function() {
  645. localStorage.setItem(full_name, target.val());
  646. setTimeout(save, interval);
  647. };
  648. setTimeout(save, interval);
  649. }
  650. // source code form group
  651. $.fn.source_code_form_group = function(name, text, langs_options_html) {
  652. return this.each(function() {
  653. var input_language_id = 'input-' + name + '_language';
  654. var input_language_name = name + '_language';
  655. var input_upload_type_name = name + '_upload_type';
  656. var input_editor_id = 'input-' + name + '_editor';
  657. var input_editor_name = name + '_editor';
  658. var input_file_id = 'input-' + name + '_file';
  659. var input_file_name = name + '_file';
  660. var div_help_language_id = 'div-help-' + name + '_language';
  661. var div_editor_id = 'div-' + name + '_editor';
  662. var div_file_id = 'div-' + name + '_file';
  663. var help_file_id = 'help-' + name + '_file';
  664. var input_language =
  665. $('<select id="' + input_language_id + '" name="' + input_language_name + '" class="form-control input-sm"/>')
  666. .html(langs_options_html);
  667. var input_upload_type_editor = $('<input type="radio" name="' + input_upload_type_name + '" value="editor" />');
  668. var input_upload_type_file = $('<input type="radio" name="' + input_upload_type_name + '" value="file" />');
  669. var input_file = $('<input type="file" id="' + input_file_id + '" name="' + input_file_name + '" style="display: none" />');
  670. var input_file_path = $('<input class="form-control" type="text" readonly="readonly" />');
  671. var input_editor = $('<textarea class="form-control" id="' + input_editor_id + '" name="' + input_editor_name + '"></textarea>');
  672. var input_use_advanced_editor = $('<input type="checkbox">');
  673. var div_editor =
  674. $('<div id="' + div_editor_id + '" class="col-sm-12"/>')
  675. .append(input_editor)
  676. .append($('<div class="checkbox text-right" />')
  677. .append($('<label />')
  678. .append(input_use_advanced_editor)
  679. .append(' ' + uojLocale('editor::use advanced editor'))
  680. )
  681. )
  682. var div_file =
  683. $('<div id="' + div_file_id + '" class="col-sm-12"/>')
  684. .append(input_file)
  685. .append($('<div class="input-group"/>')
  686. .append(input_file_path)
  687. .append($('<span class="input-group-append"/>')
  688. .append($('<button type="button" class="btn btn-primary">'+'<span class="glyphicon glyphicon-folder-open"></span> '+uojLocale('editor::browse')+'</button>')
  689. .css('width', '100px')
  690. .click(function() {
  691. input_file.click();
  692. })
  693. )
  694. )
  695. )
  696. .append($('<span class="help-block" id="' + help_file_id + '"></span>'))
  697. var div_help_language = $('<div id="' + div_help_language_id + '" class="col-sm-12 text-warning top-buffer-sm">');
  698. var show_help_lang = function() {
  699. if ($(this).val() == 'Java8' || $(this).val() == 'Java11') {
  700. div_help_language.text('注意:Java 程序源代码中不应指定所在的 package。我们会在源代码中找到第一个被定义的类并以它的 main 函数为程序入口点。');
  701. } else {
  702. div_help_language.text('');
  703. }
  704. };
  705. var advanced_editor = null;
  706. var advanced_editor_init = function() {
  707. require_codemirror({}, function() {
  708. var mode = get_codemirror_mode(input_language.val());
  709. require_codemirror_mode(mode, function() {
  710. if (advanced_editor != null) {
  711. return;
  712. }
  713. advanced_editor = CodeMirror.fromTextArea(input_editor[0], {
  714. mode: mode,
  715. lineNumbers: true,
  716. matchBrackets: true,
  717. lineWrapping: true,
  718. styleActiveLine: true,
  719. indentUnit: 4,
  720. indentWithTabs: true,
  721. theme: 'default'
  722. });
  723. advanced_editor.on('change', function() {
  724. advanced_editor.save();
  725. });
  726. $(advanced_editor.getWrapperElement()).css('box-shadow', '0 2px 10px rgba(0,0,0,0.2)');
  727. advanced_editor.focus();
  728. });
  729. });
  730. }
  731. var save_prefer_upload_type = function(type) {
  732. $.cookie('uoj_source_code_form_group_preferred_upload_type', type, { expires: 7, path: '/' });
  733. };
  734. autosave_locally(2000, name, input_editor);
  735. var prefer_upload_type = $.cookie('uoj_source_code_form_group_preferred_upload_type');
  736. if (prefer_upload_type === null) {
  737. prefer_upload_type = 'editor';
  738. }
  739. if (prefer_upload_type == 'file') {
  740. input_upload_type_file[0].checked = true;
  741. div_editor.css('display', 'none');
  742. } else {
  743. input_upload_type_editor[0].checked = true;
  744. div_file.css('display', 'none');
  745. if (prefer_upload_type == 'advanced') {
  746. input_use_advanced_editor[0].checked = true;
  747. }
  748. }
  749. input_language.each(show_help_lang);
  750. input_language.change(show_help_lang);
  751. input_language.change(function() {
  752. if (advanced_editor != null) {
  753. var mode = get_codemirror_mode(input_language.val());
  754. require_codemirror_mode(mode, function() {
  755. if (mode != get_codemirror_mode(input_language.val())) {
  756. return;
  757. }
  758. advanced_editor.setOption('mode', mode);
  759. });
  760. }
  761. })
  762. input_upload_type_editor.click(function() {
  763. div_editor.show('fast');
  764. div_file.hide('fast');
  765. save_prefer_upload_type('editor');
  766. });
  767. input_upload_type_file.click(function() {
  768. div_file.show('fast');
  769. div_editor.hide('fast');
  770. save_prefer_upload_type('file');
  771. });
  772. input_file.change(function() {
  773. input_file_path.val(input_file.val());
  774. });
  775. input_use_advanced_editor.click(function() {
  776. if (this.checked) {
  777. advanced_editor_init();
  778. save_prefer_upload_type('advanced');
  779. } else {
  780. if (advanced_editor != null) {
  781. advanced_editor.toTextArea();
  782. advanced_editor = null;
  783. input_editor.focus();
  784. }
  785. save_prefer_upload_type('editor');
  786. }
  787. });
  788. $(this)
  789. .append($('<div class="row col-sm-12"/>')
  790. .append($('<label class="col-sm-2 control-label"><div class="text-left">' + text + '</div></label>'))
  791. .append($('<label class="col-sm-1 control-label" for="' + input_language_name + '">'+uojLocale('editor::language')+'</label>'))
  792. .append($('<div class="col-sm-2"/>')
  793. .append(input_language)
  794. )
  795. .append($('<div class="col-sm-2 offset-sm-3 radio"/>')
  796. .append($('<label/>')
  797. .append(input_upload_type_editor)
  798. .append(' '+uojLocale('editor::upload by editor'))
  799. )
  800. )
  801. .append($('<div class="col-sm-2 radio"/>')
  802. .append($('<label/>')
  803. .append(input_upload_type_file)
  804. .append(' '+uojLocale('editor::upload from local'))
  805. )
  806. ))
  807. .append(div_help_language)
  808. .append(div_editor)
  809. .append(div_file);
  810. if (prefer_upload_type == 'advanced') {
  811. var check_advanced_init = function() {
  812. if (div_editor.is(':visible')) {
  813. advanced_editor_init();
  814. } else {
  815. setTimeout(check_advanced_init, 1);
  816. }
  817. }
  818. check_advanced_init();
  819. }
  820. });
  821. }
  822. // text file form group
  823. $.fn.text_file_form_group = function(name, text) {
  824. return this.each(function() {
  825. var input_upload_type_name = name + '_upload_type';
  826. var input_editor_id = 'input-' + name + '_editor';
  827. var input_editor_name = name + '_editor';
  828. var input_file_id = 'input-' + name + '_file';
  829. var input_file_name = name + '_file';
  830. var div_editor_id = 'div-' + name + '_editor';
  831. var div_file_id = 'div-' + name + '_file';
  832. var help_file_id = 'help-' + name + '_file';
  833. var input_upload_type_editor = $('<input type="radio" name="' + input_upload_type_name + '" value="editor" />');
  834. var input_upload_type_file = $('<input type="radio" name="' + input_upload_type_name + '" value="file" />');
  835. var input_file = $('<input type="file" id="' + input_file_id + '" name="' + input_file_name + '" style="display: none" />');
  836. var input_file_path = $('<input class="form-control" type="text" readonly="readonly" />');
  837. var input_editor = $('<textarea class="form-control" id="' + input_editor_id + '" name="' + input_editor_name + '"></textarea>');
  838. var input_use_advanced_editor = $('<input type="checkbox">');
  839. var div_editor =
  840. $('<div id="' + div_editor_id + '" class="col-sm-12"/>')
  841. .append(input_editor)
  842. .append($('<div class="checkbox text-right" />')
  843. .append($('<label />')
  844. .append(input_use_advanced_editor)
  845. .append(' ' + uojLocale('editor::use advanced editor'))
  846. )
  847. )
  848. var div_file =
  849. $('<div id="' + div_file_id + '" class="col-sm-12"/>')
  850. .append(input_file)
  851. .append($('<div class="input-group"/>')
  852. .append(input_file_path)
  853. .append($('<div class="input-group-append"/>')
  854. .append($('<button type="button" class="btn btn-primary">'+'<span class="glyphicon glyphicon-folder-open"></span> '+uojLocale('editor::browse')+'</button>')
  855. .css('width', '100px')
  856. .click(function() {
  857. input_file.click();
  858. })
  859. )
  860. )
  861. )
  862. .append($('<span class="help-block" id="' + help_file_id + '"></span>'))
  863. var advanced_editor = null;
  864. var advanced_editor_init = function() {
  865. require_codemirror({}, function() {
  866. var mode = get_codemirror_mode('text');
  867. require_codemirror_mode(mode, function() {
  868. if (advanced_editor != null) {
  869. return;
  870. }
  871. advanced_editor = CodeMirror.fromTextArea(input_editor[0], {
  872. mode: mode,
  873. lineNumbers: true,
  874. matchBrackets: true,
  875. lineWrapping: true,
  876. styleActiveLine: true,
  877. indentUnit: 4,
  878. indentWithTabs: true,
  879. theme: 'default'
  880. });
  881. advanced_editor.on('change', function() {
  882. advanced_editor.save();
  883. });
  884. $(advanced_editor.getWrapperElement()).css('box-shadow', '0 2px 10px rgba(0,0,0,0.2)');
  885. advanced_editor.focus();
  886. });
  887. });
  888. }
  889. var save_prefer_upload_type = function(type) {
  890. $.cookie('uoj_text_file_form_group_preferred_upload_type', type, { expires: 7, path: '/' });
  891. };
  892. autosave_locally(2000, name, input_editor);
  893. var prefer_upload_type = $.cookie('uoj_text_file_form_group_preferred_upload_type');
  894. if (prefer_upload_type === null) {
  895. prefer_upload_type = 'editor';
  896. }
  897. if (prefer_upload_type == 'file') {
  898. input_upload_type_file[0].checked = true;
  899. div_editor.css('display', 'none');
  900. } else {
  901. input_upload_type_editor[0].checked = true;
  902. div_file.css('display', 'none');
  903. if (prefer_upload_type == 'advanced') {
  904. input_use_advanced_editor[0].checked = true;
  905. }
  906. }
  907. input_upload_type_editor.click(function() {
  908. div_editor.show('fast');
  909. div_file.hide('fast');
  910. save_prefer_upload_type('editor');
  911. });
  912. input_upload_type_file.click(function() {
  913. div_file.show('fast');
  914. div_editor.hide('fast');
  915. save_prefer_upload_type('file');
  916. });
  917. input_file.change(function() {
  918. input_file_path.val(input_file.val());
  919. });
  920. input_use_advanced_editor.click(function() {
  921. if (this.checked) {
  922. advanced_editor_init();
  923. save_prefer_upload_type('advanced');
  924. } else {
  925. if (advanced_editor != null) {
  926. advanced_editor.toTextArea();
  927. advanced_editor = null;
  928. input_editor.focus();
  929. }
  930. save_prefer_upload_type('editor');
  931. }
  932. });
  933. $(this)
  934. .append($('<div class="row"/>')
  935. .append($('<label class="col-sm-2 control-label"><div class="text-left">' + text + '</div></label>'))
  936. .append($('<div class="top-buffer-sm" />'))
  937. .append($('<div class="col-sm-2 offset-sm-6 radio"/>')
  938. .append($('<label/>')
  939. .append(input_upload_type_editor)
  940. .append(' '+uojLocale('editor::upload by editor'))
  941. )
  942. )
  943. .append($('<div class="col-sm-2 radio"/>')
  944. .append($('<label/>')
  945. .append(input_upload_type_file)
  946. .append(' '+uojLocale('editor::upload from local'))
  947. )
  948. ))
  949. .append(div_editor)
  950. .append(div_file);
  951. if (prefer_upload_type == 'advanced') {
  952. var check_advanced_init = function() {
  953. if (div_editor.is(':visible')) {
  954. advanced_editor_init();
  955. } else {
  956. setTimeout(check_advanced_init, 1);
  957. }
  958. }
  959. check_advanced_init();
  960. }
  961. });
  962. }
  963. // custom test
  964. function custom_test_onsubmit(response_text, div_result, url) {
  965. if (response_text != '') {
  966. $(div_result).html('<div class="text-danger">' + response_text + '</div>');
  967. return;
  968. }
  969. var update = function() {
  970. var can_next = true;
  971. $.get(url,
  972. function(data) {
  973. if (data.judged === undefined) {
  974. $(div_result).html('<div class="text-danger">error</div>');
  975. } else {
  976. var judge_status = $('<table class="table table-bordered table-text-center"><tr class="info">' + data.html + '</tr></table>');
  977. $(div_result).empty();
  978. $(div_result).append(judge_status);
  979. if (data.judged) {
  980. var judge_result = $(data.result);
  981. judge_result.css('display', 'none');
  982. $(div_result).append(judge_result);
  983. judge_status.hide(500);
  984. judge_result.slideDown(500);
  985. can_next = false;
  986. }
  987. }
  988. }, 'json')
  989. .always(function() {
  990. if (can_next) {
  991. setTimeout(update, 500);
  992. }
  993. });
  994. };
  995. setTimeout(update, 500);
  996. }
  997. // comment
  998. function showCommentReplies(id, replies) {
  999. var toggleFormReply = function(from, text) {
  1000. if (text == undefined) {
  1001. text = '';
  1002. }
  1003. var p = '#comment-body-' + id;
  1004. var q = '#div-form-reply';
  1005. var r = '#input-reply_comment';
  1006. var t = '#input-reply_id';
  1007. if ($(q).data('from') != from) {
  1008. $(q).data('from', from);
  1009. $(q).hide('fast', function() {
  1010. $(this).appendTo(p).show('fast', function() {
  1011. $(t).val(id);
  1012. $(r).val(text).focus();
  1013. });
  1014. });
  1015. } else if ($(q).css('display') != 'none') {
  1016. $(q).appendTo(p).hide('fast');
  1017. } else {
  1018. $(q).appendTo(p).show('fast', function() {
  1019. $(t).val(id);
  1020. $(r).val(text).focus();
  1021. });
  1022. }
  1023. }
  1024. $('#reply-to-' + id).click(function(e) {
  1025. e.preventDefault();
  1026. toggleFormReply(id);
  1027. });
  1028. if (replies.length == 0) {
  1029. return;
  1030. }
  1031. $("#replies-" + id).long_table(
  1032. replies,
  1033. 1,
  1034. '<tr>' +
  1035. '<th>评论回复</th>' +
  1036. '</tr>',
  1037. function(reply) {
  1038. return $('<tr id="' + 'comment-' + reply.id + '" />').append(
  1039. $('<td />').append(
  1040. $('<div class="comtbox6">' + getUserLink(reply.poster, reply.poster_rating) + ':' + reply.content + '</div>')
  1041. ).append(
  1042. $('<ul class="text-right list-inline bot-buffer-no" />').append(
  1043. '<li>' + '<small class="text-muted">' + reply.post_time + '</small>' + '</li>'
  1044. ).append(
  1045. $('<li />').append(
  1046. $('<a href="#">回复</a>').click(function (e) {
  1047. e.preventDefault();
  1048. toggleFormReply(reply.id, '回复 @' + reply.poster + ':');
  1049. })
  1050. )
  1051. )
  1052. )
  1053. ).uoj_highlight();
  1054. }, {
  1055. table_classes: ['table', 'table-condensed'],
  1056. page_len: 5,
  1057. prevent_focus_on_click: true
  1058. }
  1059. );
  1060. }
  1061. // standings
  1062. function showStandings() {
  1063. $("#standings").long_table(
  1064. standings,
  1065. 1,
  1066. '<tr>' +
  1067. '<th style="width:5em">#</th>' +
  1068. '<th style="width:14em">'+uojLocale('username')+'</th>' +
  1069. '<th style="width:5em">'+uojLocale('contests::total score')+'</th>' +
  1070. $.map(problems, function(col, idx) {
  1071. return '<th style="width:8em;">' + '<a href="/contest/' + contest_id + '/problem/' + col + '">' + String.fromCharCode('A'.charCodeAt(0) + idx) + '</a>' + '</th>';
  1072. }).join('') +
  1073. '</tr>',
  1074. function(row) {
  1075. var col_tr = '<tr>';
  1076. col_tr += '<td>' + row[3] + '</td>';
  1077. col_tr += '<td>' + getUserLink(row[2][0], row[2][1]) + '</td>';
  1078. col_tr += '<td>' + '<div><span class="uoj-score" data-max="' + problems.length * 100 + '" style="color:' + getColOfScore(row[0] / problems.length) + '">' + row[0] + '</span></div>' + '<div>' + getPenaltyTimeStr(row[1]) + '</div></td>';
  1079. for (var i = 0; i < problems.length; i++) {
  1080. col_tr += '<td>';
  1081. col = score[row[2][0]][i];
  1082. if (col != undefined) {
  1083. col_tr += '<div><a href="/submission/' + col[2] + '" class="uoj-score" style="color:' + getColOfScore(col[0]) + '">' + col[0] + '</a></div>';
  1084. if (standings_version < 2) {
  1085. col_tr += '<div>' + getPenaltyTimeStr(col[1]) + '</div>';
  1086. } else {
  1087. if (col[0] > 0) {
  1088. col_tr += '<div>' + getPenaltyTimeStr(col[1]) + '</div>';
  1089. }
  1090. }
  1091. }
  1092. col_tr += '</td>';
  1093. }
  1094. col_tr += '</tr>';
  1095. return col_tr;
  1096. }, {
  1097. table_classes: ['table', 'table-bordered', 'table-striped', 'table-text-center', 'table-vertical-middle', 'table-condensed'],
  1098. page_len: 100,
  1099. print_after_table: function() {
  1100. return '<div class="text-right text-muted">' + uojLocale("contests::n participants", standings.length) + '</div>';
  1101. }
  1102. }
  1103. );
  1104. }