uoj.js 34 KB

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