AtCoder Easy Test v2

Make testing sample cases easy

2022-02-15 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

  1. // ==UserScript==
  2. // @name AtCoder Easy Test v2
  3. // @namespace https://atcoder.jp/
  4. // @version 2.10.0
  5. // @description Make testing sample cases easy
  6. // @author magurofly
  7. // @license MIT
  8. // @supportURL https://github.com/magurofly/atcoder-easy-test/
  9. // @match https://atcoder.jp/contests/*/tasks/*
  10. // @match https://atcoder.jp/contests/*/submit*
  11. // @match https://yukicoder.me/problems/no/*
  12. // @match https://yukicoder.me/problems/*
  13. // @match http://codeforces.com/contest/*/problem/*
  14. // @match http://codeforces.com/gym/*/problem/*
  15. // @match http://codeforces.com/problemset/problem/*
  16. // @match http://codeforces.com/group/*/contest/*/problem/*
  17. // @match http://*.contest.codeforces.com/group/*/contest/*/problem/*
  18. // @match https://codeforces.com/contest/*/problem/*
  19. // @match https://codeforces.com/gym/*/problem/*
  20. // @match https://codeforces.com/problemset/problem/*
  21. // @match https://codeforces.com/group/*/contest/*/problem/*
  22. // @match https://*.contest.codeforces.com/group/*/contest/*/problem/*
  23. // @match https://m1.codeforces.com/contest/*/problem/*
  24. // @match https://m2.codeforces.com/contest/*/problem/*
  25. // @match https://m3.codeforces.com/contest/*/problem/*
  26. // @match https://greatest.deepsurf.us/*/scripts/433152-atcoder-easy-test-v2
  27. // @grant unsafeWindow
  28. // @grant GM_getValue
  29. // @grant GM_setValue
  30. // ==/UserScript==
  31. (function() {
  32. function buildParams(data) {
  33. return Object.entries(data).map(([key, value]) => encodeURIComponent(key) + "=" + encodeURIComponent(value)).join("&");
  34. }
  35. function sleep(ms) {
  36. return new Promise(done => setTimeout(done, ms));
  37. }
  38. function doneOrFail(p) {
  39. return p.then(() => Promise.resolve(), () => Promise.resolve());
  40. }
  41. function html2element(html) {
  42. const template = document.createElement("template");
  43. template.innerHTML = html;
  44. return template.content.firstChild;
  45. }
  46. function newElement(tagName, attrs = {}, children = []) {
  47. const e = document.createElement(tagName);
  48. for (const [key, value] of Object.entries(attrs)) {
  49. if (key == "style") {
  50. for (const [propKey, propValue] of Object.entries(value)) {
  51. e.style[propKey] = propValue;
  52. }
  53. }
  54. else {
  55. e[key] = value;
  56. }
  57. }
  58. for (const child of children) {
  59. e.appendChild(child);
  60. }
  61. return e;
  62. }
  63. function uuid() {
  64. return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".
  65. replace(/x/g, () => "0123456789abcdef"[Math.random() * 16 | 0]).
  66. replace(/y/g, () => "89ab"[Math.random() * 4 | 0]);
  67. }
  68. async function loadScript(src, ctx = null, env = {}) {
  69. const js = await fetch(src).then(res => res.text());
  70. const keys = [];
  71. const values = [];
  72. for (const [key, value] of Object.entries(env)) {
  73. keys.push(key);
  74. values.push(value);
  75. }
  76. unsafeWindow["Function"](keys.join(), js).apply(ctx, values);
  77. }
  78. const eventListeners = {};
  79. const events = {
  80. on(name, listener) {
  81. const listeners = (name in eventListeners ? eventListeners[name] : eventListeners[name] = []);
  82. listeners.push(listener);
  83. },
  84. trig(name) {
  85. if (name in eventListeners) {
  86. for (const listener of eventListeners[name])
  87. listener();
  88. }
  89. },
  90. };
  91. class ObservableValue {
  92. _value;
  93. _listeners;
  94. constructor(value) {
  95. this._value = value;
  96. this._listeners = new Set();
  97. }
  98. get value() {
  99. return this._value;
  100. }
  101. set value(value) {
  102. this._value = value;
  103. for (const listener of this._listeners)
  104. listener(value);
  105. }
  106. addListener(listener) {
  107. this._listeners.add(listener);
  108. listener(this._value);
  109. }
  110. removeListener(listener) {
  111. this._listeners.delete(listener);
  112. }
  113. map(f) {
  114. const y = new ObservableValue(f(this.value));
  115. this.addListener(x => {
  116. y.value = f(x);
  117. });
  118. return y;
  119. }
  120. }
  121.  
  122. var hPage = "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n <title>AtCoder Easy Test</title>\n <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css\" rel=\"stylesheet\">\n </head>\n <body>\n <div class=\"container\" id=\"root\">\n </div>\n <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js\"></script>\n <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js\"></script>\n </body>\n</html>";
  123.  
  124. const components = [];
  125. const settings = {
  126. add(title, generator) {
  127. components.push({ title, generator });
  128. },
  129. open() {
  130. const win = window.open("about:blank");
  131. const doc = win.document;
  132. doc.open();
  133. doc.write(hPage);
  134. doc.close();
  135. const root = doc.getElementById("root");
  136. for (const { title, generator } of components) {
  137. const panel = newElement("div", { className: "panel panel-default" }, [
  138. newElement("div", { className: "panel-heading", textContent: title }),
  139. newElement("div", { className: "panel-body" }, [generator(win)]),
  140. ]);
  141. root.appendChild(panel);
  142. }
  143. },
  144. };
  145.  
  146. const options = [];
  147. let data = {};
  148. function toString() {
  149. return JSON.stringify(data);
  150. }
  151. function save() {
  152. GM_setValue("config", toString());
  153. }
  154. function load() {
  155. data = JSON.parse(GM_getValue("config") || "{}");
  156. }
  157. function reset() {
  158. data = {};
  159. save();
  160. }
  161. load();
  162. // 設定ページ
  163. settings.add("config", (win) => {
  164. const root = newElement("form", { className: "form-horizontal" });
  165. options.sort((a, b) => {
  166. const x = a.key.split(".");
  167. const y = b.key.split(".");
  168. return x < y ? -1 : x > y ? 1 : 0;
  169. });
  170. for (const { type, key, defaultValue, description } of options) {
  171. const id = uuid();
  172. const control = newElement("div", { className: "col-sm-3 text-center" });
  173. const group = newElement("div", { className: "form-group" }, [
  174. control,
  175. newElement("label", {
  176. className: "col-sm-3",
  177. htmlFor: id,
  178. textContent: key,
  179. style: {
  180. fontFamily: "monospace",
  181. },
  182. }),
  183. newElement("label", {
  184. className: "col-sm-6",
  185. htmlFor: id,
  186. textContent: description,
  187. }),
  188. ]);
  189. root.appendChild(group);
  190. switch (type) {
  191. case "flag": {
  192. control.appendChild(newElement("input", {
  193. id,
  194. type: "checkbox",
  195. checked: config.get(key, defaultValue),
  196. onchange() {
  197. config.set(key, this.checked);
  198. },
  199. }));
  200. break;
  201. }
  202. case "count": {
  203. control.appendChild(newElement("input", {
  204. id,
  205. type: "number",
  206. min: "0",
  207. value: config.get(key, defaultValue),
  208. onchange() {
  209. config.set(key, +this.value);
  210. },
  211. }));
  212. break;
  213. }
  214. default:
  215. throw new TypeError(`AtCoderEasyTest.setting: undefined option type ${type} for ${key}`);
  216. }
  217. }
  218. root.appendChild(newElement("button", {
  219. className: "btn btn-danger",
  220. textContent: "Reset",
  221. type: "button",
  222. onclick() {
  223. if (win.confirm("Configuration data will be cleared. Are you sure?")) {
  224. config.reset();
  225. }
  226. },
  227. }));
  228. return root;
  229. });
  230. const config = {
  231. getString(key, defaultValue = "") {
  232. if (!(key in data))
  233. config.setString(key, defaultValue);
  234. return data[key];
  235. },
  236. setString(key, value) {
  237. data[key] = value;
  238. save();
  239. },
  240. has(key) {
  241. return key in data;
  242. },
  243. get(key, defaultValue = null) {
  244. if (!(key in data))
  245. config.set(key, defaultValue);
  246. return JSON.parse(data[key]);
  247. },
  248. set(key, value) {
  249. config.setString(key, JSON.stringify(value));
  250. },
  251. save,
  252. load,
  253. toString,
  254. reset,
  255. /** 設定項目を登録 */
  256. registerFlag(key, defaultValue, description) {
  257. options.push({
  258. type: "flag",
  259. key,
  260. defaultValue,
  261. description,
  262. });
  263. },
  264. registerCount(key, defaultValue, description) {
  265. options.push({
  266. type: "count",
  267. key,
  268. defaultValue,
  269. description,
  270. });
  271. },
  272. };
  273.  
  274. config.registerCount("codeSaver.limit", 10, "Max number to save codes");
  275. const codeSaver = {
  276. get() {
  277. // `json` は、ソースコード文字列またはJSON文字列
  278. let json = unsafeWindow.localStorage.AtCoderEasyTest$lastCode;
  279. let data = [];
  280. try {
  281. if (typeof json == "string") {
  282. data.push(...JSON.parse(json));
  283. }
  284. else {
  285. data = [];
  286. }
  287. }
  288. catch (e) {
  289. data.push({
  290. path: unsafeWindow.localStorage.AtCoderEasyTset$lastPage,
  291. code: json,
  292. });
  293. }
  294. return data;
  295. },
  296. set(data) {
  297. unsafeWindow.localStorage.AtCoderEasyTest$lastCode = JSON.stringify(data);
  298. },
  299. save(code) {
  300. let data = codeSaver.get();
  301. const idx = data.findIndex(({ path }) => path == location.pathname);
  302. if (idx != -1)
  303. data.splice(idx, idx + 1);
  304. data.push({
  305. path: location.pathname,
  306. code,
  307. });
  308. while (data.length > config.get("codeSaver.limit", 10))
  309. data.shift();
  310. codeSaver.set(data);
  311. },
  312. restore() {
  313. const data = codeSaver.get();
  314. const idx = data.findIndex(({ path }) => path == location.pathname);
  315. if (idx == -1 || !(data[idx] instanceof Object))
  316. return Promise.reject(`No saved code found for ${location.pathname}`);
  317. return Promise.resolve(data[idx].code);
  318. }
  319. };
  320. settings.add(`codeSaver (${location.host})`, (win) => {
  321. const root = newElement("table", { className: "table" }, [
  322. newElement("thead", {}, [
  323. newElement("tr", {}, [
  324. newElement("th", { textContent: "path" }),
  325. newElement("th", { textContent: "code" }),
  326. ]),
  327. ]),
  328. newElement("tbody"),
  329. ]);
  330. root.tBodies;
  331. for (const savedCode of codeSaver.get()) {
  332. root.tBodies[0].appendChild(newElement("tr", {}, [
  333. newElement("td", { textContent: savedCode.path }),
  334. newElement("td", {}, [
  335. newElement("textarea", {
  336. rows: 1,
  337. cols: 30,
  338. textContent: savedCode.code,
  339. }),
  340. ]),
  341. ]));
  342. }
  343. return root;
  344. });
  345.  
  346. function similarLangs(targetLang, candidateLangs) {
  347. const [targetName, targetDetail] = targetLang.split(" ", 2);
  348. const selectedLangs = candidateLangs.filter(candidateLang => {
  349. const [name, _] = candidateLang.split(" ", 2);
  350. return name == targetName;
  351. }).map(candidateLang => {
  352. const [_, detail] = candidateLang.split(" ", 2);
  353. return [candidateLang, similarity(detail, targetDetail)];
  354. });
  355. return selectedLangs.sort((a, b) => a[1] - b[1]).map(([lang, _]) => lang);
  356. }
  357. function similarity(s, t) {
  358. const n = s.length, m = t.length;
  359. let dp = new Array(m + 1).fill(0);
  360. for (let i = 0; i < n; i++) {
  361. const dp2 = new Array(m + 1).fill(0);
  362. for (let j = 0; j < m; j++) {
  363. const cost = (s.charCodeAt(i) - t.charCodeAt(j)) ** 2;
  364. dp2[j + 1] = Math.min(dp[j] + cost, dp[j + 1] + cost * 0.25, dp2[j] + cost * 0.25);
  365. }
  366. dp = dp2;
  367. }
  368. return dp[m];
  369. }
  370.  
  371. class CodeRunner {
  372. get label() {
  373. return this._label;
  374. }
  375. constructor(label, site) {
  376. this._label = `${label} [${site}]`;
  377. }
  378. async test(sourceCode, input, expectedOutput, options) {
  379. let result = { status: "IE", input };
  380. try {
  381. result = await this.run(sourceCode, input);
  382. }
  383. catch (e) {
  384. result.error = e.toString();
  385. return result;
  386. }
  387. if (expectedOutput != null)
  388. result.expectedOutput = expectedOutput;
  389. if (result.status != "OK" || typeof expectedOutput != "string")
  390. return result;
  391. let output = result.output || "";
  392. if (options.trim) {
  393. expectedOutput = expectedOutput.trim();
  394. output = output.trim();
  395. }
  396. let equals = (x, y) => x === y;
  397. if (options.allowableError) {
  398. const floatPattern = /^[-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?$/;
  399. const superEquals = equals;
  400. equals = (x, y) => {
  401. if (floatPattern.test(x) || floatPattern.test(y))
  402. return Math.abs(parseFloat(x) - parseFloat(y)) <= options.allowableError;
  403. return superEquals(x, y);
  404. };
  405. }
  406. if (options.split) {
  407. const superEquals = equals;
  408. equals = (x, y) => {
  409. const xs = x.split(/\s+/);
  410. const ys = y.split(/\s+/);
  411. if (xs.length != ys.length)
  412. return false;
  413. const len = xs.length;
  414. for (let i = 0; i < len; i++) {
  415. if (!superEquals(xs[i], ys[i]))
  416. return false;
  417. }
  418. return true;
  419. };
  420. }
  421. result.status = equals(output, expectedOutput) ? "AC" : "WA";
  422. return result;
  423. }
  424. }
  425.  
  426. class CustomRunner extends CodeRunner {
  427. run;
  428. constructor(label, run) {
  429. super(label, "Browser");
  430. this.run = run;
  431. }
  432. }
  433.  
  434. let waitAtCoderCustomTest = Promise.resolve();
  435. const AtCoderCustomTestBase = location.href.replace(/\/tasks\/.+$/, "/custom_test");
  436. const AtCoderCustomTestResultAPI = AtCoderCustomTestBase + "/json?reload=true";
  437. const AtCoderCustomTestSubmitAPI = AtCoderCustomTestBase + "/submit/json";
  438. class AtCoderRunner extends CodeRunner {
  439. languageId;
  440. constructor(languageId, label) {
  441. super(label, "AtCoder");
  442. this.languageId = languageId;
  443. }
  444. async run(sourceCode, input) {
  445. const promise = this.submit(sourceCode, input);
  446. waitAtCoderCustomTest = promise;
  447. return await promise;
  448. }
  449. async submit(sourceCode, input) {
  450. try {
  451. await waitAtCoderCustomTest;
  452. }
  453. catch (error) {
  454. console.error(error);
  455. }
  456. const error = await fetch(AtCoderCustomTestSubmitAPI, {
  457. method: "POST",
  458. credentials: "include",
  459. headers: {
  460. "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
  461. },
  462. body: buildParams({
  463. "data.LanguageId": String(this.languageId),
  464. sourceCode,
  465. input,
  466. csrf_token: unsafeWindow.csrfToken,
  467. }),
  468. }).then(r => r.text());
  469. if (error) {
  470. throw new Error(error);
  471. }
  472. await sleep(100);
  473. for (;;) {
  474. const data = await fetch(AtCoderCustomTestResultAPI, {
  475. method: "GET",
  476. credentials: "include",
  477. }).then(r => r.json());
  478. if (!("Result" in data))
  479. continue;
  480. const result = data.Result;
  481. if ("Interval" in data) {
  482. await sleep(data.Interval);
  483. continue;
  484. }
  485. return {
  486. status: (result.ExitCode == 0) ? "OK" : (result.TimeConsumption == -1) ? "CE" : "RE",
  487. exitCode: result.ExitCode,
  488. execTime: result.TimeConsumption,
  489. memory: result.MemoryConsumption,
  490. input,
  491. output: data.Stdout,
  492. error: data.Stderr,
  493. };
  494. }
  495. }
  496. }
  497.  
  498. class PaizaIORunner extends CodeRunner {
  499. name;
  500. constructor(name, label) {
  501. super(label, "PaizaIO");
  502. this.name = name;
  503. }
  504. async run(sourceCode, input) {
  505. let id, status, error;
  506. try {
  507. const res = await fetch("https://api.paiza.io/runners/create?" + buildParams({
  508. source_code: sourceCode,
  509. language: this.name,
  510. input,
  511. longpoll: "true",
  512. longpoll_timeout: "10",
  513. api_key: "guest",
  514. }), {
  515. method: "POST",
  516. mode: "cors",
  517. }).then(r => r.json());
  518. id = res.id;
  519. status = res.status;
  520. error = res.error;
  521. }
  522. catch (error) {
  523. return {
  524. status: "IE",
  525. input,
  526. error: String(error),
  527. };
  528. }
  529. while (status == "running") {
  530. const res = await fetch("https://api.paiza.io/runners/get_status?" + buildParams({
  531. id,
  532. api_key: "guest",
  533. }), {
  534. mode: "cors",
  535. }).then(res => res.json());
  536. status = res.status;
  537. error = res.error;
  538. }
  539. const res = await fetch("https://api.paiza.io/runners/get_details?" + buildParams({
  540. id,
  541. api_key: "guest",
  542. }), {
  543. mode: "cors",
  544. }).then(r => r.json());
  545. const result = {
  546. status: "OK",
  547. exitCode: String(res.exit_code),
  548. execTime: +res.time * 1e3,
  549. memory: +res.memory * 1e-3,
  550. input,
  551. };
  552. if (res.build_result == "failure") {
  553. result.status = "CE";
  554. result.exitCode = res.build_exit_code;
  555. result.output = res.build_stdout;
  556. result.error = res.build_stderr;
  557. }
  558. else {
  559. result.status = (res.result == "timeout") ? "TLE" : (res.result == "failure") ? "RE" : "OK";
  560. result.exitCode = res.exit_code;
  561. result.output = res.stdout;
  562. result.error = res.stderr;
  563. }
  564. return result;
  565. }
  566. }
  567.  
  568. class WandboxRunner extends CodeRunner {
  569. name;
  570. options;
  571. constructor(name, label, options = {}) {
  572. super(label, "Wandbox");
  573. this.name = name;
  574. this.options = options;
  575. }
  576. getOptions(sourceCode, input) {
  577. if (typeof this.options == "function")
  578. return this.options(sourceCode, input);
  579. return this.options;
  580. }
  581. run(sourceCode, input) {
  582. const options = this.getOptions(sourceCode, input);
  583. return this.request(Object.assign({
  584. compiler: this.name,
  585. code: sourceCode,
  586. stdin: input,
  587. }, options));
  588. }
  589. async request(body) {
  590. const startTime = Date.now();
  591. let res;
  592. try {
  593. res = await fetch("https://wandbox.org/api/compile.json", {
  594. method: "POST",
  595. mode: "cors",
  596. headers: {
  597. "Content-Type": "application/json",
  598. },
  599. body: JSON.stringify(body),
  600. }).then(r => r.json());
  601. }
  602. catch (error) {
  603. console.error(error);
  604. return {
  605. status: "IE",
  606. input: body.stdin,
  607. error: String(error),
  608. };
  609. }
  610. const endTime = Date.now();
  611. const result = {
  612. status: "OK",
  613. exitCode: String(res.status),
  614. execTime: endTime - startTime,
  615. input: body.stdin,
  616. output: String(res.program_output || ""),
  617. error: String(res.program_error || ""),
  618. };
  619. // 正常終了以外の場合
  620. if (res.status != 0) {
  621. if (res.signal) {
  622. result.exitCode += ` (${res.signal})`;
  623. }
  624. result.output = String(res.compiler_output || "") + String(result.output || "");
  625. result.error = String(res.compiler_error || "") + String(result.error || "");
  626. if (res.compiler_output || res.compiler_error) {
  627. result.status = "CE";
  628. }
  629. else {
  630. result.status = "RE";
  631. }
  632. }
  633. return result;
  634. }
  635. }
  636.  
  637. class WandboxCppRunner extends WandboxRunner {
  638. async run(sourceCode, input) {
  639. // ACL を結合する
  640. const ACLBase = "https://cdn.jsdelivr.net/gh/atcoder/ac-library/";
  641. const files = new Map();
  642. const includeHeader = async (source) => {
  643. const pattern = /^#\s*include\s*[<"]atcoder\/([^>"]+)[>"]/gm;
  644. const loaded = [];
  645. let match;
  646. while (match = pattern.exec(source)) {
  647. const file = "atcoder/" + match[1];
  648. if (files.has(file))
  649. continue;
  650. files.set(file, null);
  651. loaded.push([file, fetch(ACLBase + file, { mode: "cors", cache: "force-cache", }).then(r => r.text())]);
  652. }
  653. const included = await Promise.all(loaded.map(async ([file, r]) => {
  654. const source = await r;
  655. files.set(file, source);
  656. return source;
  657. }));
  658. for (const source of included) {
  659. await includeHeader(source);
  660. }
  661. };
  662. await includeHeader(sourceCode);
  663. const codes = [];
  664. for (const [file, code] of files) {
  665. codes.push({ file, code, });
  666. }
  667. const options = this.getOptions(sourceCode, input);
  668. return await this.request(Object.assign({
  669. compiler: this.name,
  670. code: sourceCode,
  671. stdin: input,
  672. codes,
  673. "compiler-option-raw": "-I.",
  674. }, options));
  675. }
  676. }
  677.  
  678. let brythonRunnerLoaded = false;
  679. const brythonRunner = new CustomRunner("Brython", async (sourceCode, input) => {
  680. if (!brythonRunnerLoaded) {
  681. // BrythonRunner を読み込む
  682. await new Promise((resolve) => {
  683. const script = document.createElement("script");
  684. script.src = "https://cdn.jsdelivr.net/gh/pythonpad/brython-runner/lib/brython-runner.bundle.js";
  685. script.onload = () => {
  686. brythonRunnerLoaded = true;
  687. resolve(null);
  688. };
  689. document.head.appendChild(script);
  690. });
  691. }
  692. let stdout = "";
  693. let stderr = "";
  694. let stdinOffset = 0;
  695. const BrythonRunner = unsafeWindow.BrythonRunner;
  696. const runner = new BrythonRunner({
  697. stdout: { write(content) { stdout += content; }, flush() { } },
  698. stderr: { write(content) { stderr += content; }, flush() { } },
  699. stdin: { async readline() {
  700. let index = input.indexOf("\n", stdinOffset) + 1;
  701. if (index == 0)
  702. index = input.length;
  703. const text = input.slice(stdinOffset, index);
  704. stdinOffset = index;
  705. return text;
  706. } },
  707. });
  708. const timeStart = Date.now();
  709. await runner.runCode(sourceCode);
  710. const timeEnd = Date.now();
  711. return {
  712. status: "OK",
  713. exitCode: "0",
  714. execTime: (timeEnd - timeStart),
  715. input,
  716. output: stdout,
  717. error: stderr,
  718. };
  719. });
  720.  
  721. async function loadPyodide() {
  722. const script = await fetch("https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js").then(res => res.text());
  723. unsafeWindow["Function"](script)();
  724. const pyodide = await unsafeWindow["loadPyodide"]({
  725. indexURL: "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/",
  726. });
  727. await pyodide.runPythonAsync(`
  728. import contextlib, io, platform
  729. class __redirect_stdin(contextlib._RedirectStream):
  730. _stream = "stdin"
  731. `);
  732. return pyodide;
  733. }
  734. let _pyodide = Promise.reject("Pyodide is not yet loaded");
  735. let _serial = Promise.resolve();
  736. const pyodideRunner = new CustomRunner("Pyodide", (sourceCode, input) => new Promise((resolve, reject) => {
  737. _serial = _serial.finally(async () => {
  738. const pyodide = await (_pyodide = _pyodide.catch(loadPyodide));
  739. const code = `
  740. def __run():
  741. global __stdout, __stderr, __stdin, __code
  742. with __redirect_stdin(io.StringIO(__stdin)):
  743. with contextlib.redirect_stdout(io.StringIO()) as __stdout:
  744. with contextlib.redirect_stderr(io.StringIO()) as __stderr:
  745. try:
  746. pass
  747. ` + sourceCode.split("\n").map(line => " " + line).join("\n") + `
  748. except SystemExit as e:
  749. __code = e.code
  750. `;
  751. let status = "OK";
  752. let exitCode = "0";
  753. let stdout = "";
  754. let stderr = "";
  755. let startTime = -Infinity;
  756. let endTime = Infinity;
  757. pyodide.globals.__stdin = input;
  758. try {
  759. pyodide.globals.__code = null;
  760. await pyodide.loadPackagesFromImports(code);
  761. await pyodide.runPythonAsync(code);
  762. startTime = Date.now();
  763. pyodide.runPython("__run()");
  764. endTime = Date.now();
  765. stdout += pyodide.globals.__stdout.getvalue();
  766. stderr += pyodide.globals.__stderr.getvalue();
  767. if (typeof pyodide.globals.__code == "number") {
  768. exitCode = String(pyodide.globals.__code);
  769. if (pyodide.globals.__code != 0)
  770. status = "RE";
  771. }
  772. }
  773. catch (error) {
  774. status = "RE";
  775. exitCode = "-1";
  776. stderr += error.toString();
  777. }
  778. resolve({
  779. status,
  780. exitCode,
  781. execTime: (endTime - startTime),
  782. input,
  783. output: stdout,
  784. error: stderr,
  785. });
  786. });
  787. }));
  788.  
  789. function pairs(list) {
  790. const pairs = [];
  791. const len = list.length >> 1;
  792. for (let i = 0; i < len; i++)
  793. pairs.push([list[i * 2], list[i * 2 + 1]]);
  794. return pairs;
  795. }
  796. async function init$5() {
  797. if (location.host != "atcoder.jp")
  798. throw "Not AtCoder";
  799. const doc = unsafeWindow.document;
  800. const langMap = {
  801. 4001: "C GCC 9.2.1",
  802. 4002: "C Clang 10.0.0",
  803. 4003: "C++ GCC 9.2.1",
  804. 4004: "C++ Clang 10.0.0",
  805. 4005: "Java OpenJDK 11.0.6",
  806. 4006: "Python3 CPython 3.8.2",
  807. 4007: "Bash 5.0.11",
  808. 4008: "bc 1.07.1",
  809. 4009: "Awk GNU Awk 4.1.4",
  810. 4010: "C# .NET Core 3.1.201",
  811. 4011: "C# Mono-mcs 6.8.0.105",
  812. 4012: "C# Mono-csc 3.5.0",
  813. 4013: "Clojure 1.10.1.536",
  814. 4014: "Crystal 0.33.0",
  815. 4015: "D DMD 2.091.0",
  816. 4016: "D GDC 9.2.1",
  817. 4017: "D LDC 1.20.1",
  818. 4018: "Dart 2.7.2",
  819. 4019: "dc 1.4.1",
  820. 4020: "Erlang 22.3",
  821. 4021: "Elixir 1.10.2",
  822. 4022: "F# .NET Core 3.1.201",
  823. 4023: "F# Mono 10.2.3",
  824. 4024: "Forth gforth 0.7.3",
  825. 4025: "Fortran GNU Fortran 9.2.1",
  826. 4026: "Go 1.14.1",
  827. 4027: "Haskell GHC 8.8.3",
  828. 4028: "Haxe 4.0.3",
  829. 4029: "Haxe 4.0.3",
  830. 4030: "JavaScript Node.js 12.16.1",
  831. 4031: "Julia 1.4.0",
  832. 4032: "Kotlin 1.3.71",
  833. 4033: "Lua Lua 5.3.5",
  834. 4034: "Lua LuaJIT 2.1.0",
  835. 4035: "Dash 0.5.8",
  836. 4036: "Nim 1.0.6",
  837. 4037: "Objective-C Clang 10.0.0",
  838. 4038: "Lisp SBCL 2.0.3",
  839. 4039: "OCaml 4.10.0",
  840. 4040: "Octave 5.2.0",
  841. 4041: "Pascal FPC 3.0.4",
  842. 4042: "Perl 5.26.1",
  843. 4043: "Raku Rakudo 2020.02.1",
  844. 4044: "PHP 7.4.4",
  845. 4045: "Prolog SWI-Prolog 8.0.3",
  846. 4046: "Python PyPy2 7.3.0",
  847. 4047: "Python3 PyPy3 7.3.0",
  848. 4048: "Racket 7.6",
  849. 4049: "Ruby 2.7.1",
  850. 4050: "Rust 1.42.0",
  851. 4051: "Scala 2.13.1",
  852. 4052: "Java OpenJDK 1.8.0",
  853. 4053: "Scheme Gauche 0.9.9",
  854. 4054: "ML MLton 20130715",
  855. 4055: "Swift 5.2.1",
  856. 4056: "Text cat 8.28",
  857. 4057: "TypeScript 3.8",
  858. 4058: "Basic .NET Core 3.1.101",
  859. 4059: "Zsh 5.4.2",
  860. 4060: "COBOL Fixed OpenCOBOL 1.1.0",
  861. 4061: "COBOL Free OpenCOBOL 1.1.0",
  862. 4062: "Brainfuck bf 20041219",
  863. 4063: "Ada Ada2012 GNAT 9.2.1",
  864. 4064: "Unlambda 2.0.0",
  865. 4065: "Cython 0.29.16",
  866. 4066: "Sed 4.4",
  867. 4067: "Vim 8.2.0460",
  868. };
  869. const languageId = new ObservableValue(unsafeWindow.$("#select-lang select.current").val());
  870. unsafeWindow.$("#select-lang select").change(() => {
  871. languageId.value = unsafeWindow.$("#select-lang select.current").val();
  872. });
  873. const language = languageId.map(lang => langMap[lang]);
  874. const isTestCasesHere = /^\/contests\/[^\/]+\/tasks\//.test(location.pathname);
  875. const taskSelector = doc.querySelector("#select-task");
  876. const testcasesCache = {};
  877. if (taskSelector) {
  878. const doFetchTestCases = async () => {
  879. const taskName = taskSelector.value;
  880. const load = !("taskName" in testcasesCache) || testcasesCache[taskName].state == "error";
  881. if (!load)
  882. return;
  883. try {
  884. testcasesCache[taskName] = { state: "loading" };
  885. const testcases = await fetchTestCases(`/contests/${unsafeWindow.contestScreenName}/tasks/${taskName}`);
  886. testcasesCache[taskName] = { testcases, state: "loaded" };
  887. }
  888. catch (e) {
  889. testcasesCache[taskName] = { state: "error" };
  890. }
  891. };
  892. taskSelector.addEventListener("change", doFetchTestCases);
  893. doFetchTestCases();
  894. }
  895. async function fetchTestCases(taskUrl) {
  896. const html = await fetch(taskUrl).then(res => res.text());
  897. const taskDoc = new DOMParser().parseFromString(html, "text/html");
  898. return getTestCases(taskDoc);
  899. }
  900. function getTestCases(doc) {
  901. const selectors = [
  902. ["#task-statement p+pre.literal-block", ".section"],
  903. ["#task-statement pre.source-code-for-copy", ".part"],
  904. ["#task-statement .lang>*:nth-child(1) .div-btn-copy+pre", ".part"],
  905. ["#task-statement .div-btn-copy+pre", ".part"],
  906. ["#task-statement>.part pre.linenums", ".part"],
  907. ["#task-statement>.part section>pre", ".part"],
  908. ["#task-statement>.part:not(.io-style)>h3+section>pre", ".part"],
  909. ["#task-statement pre", ".part"],
  910. ];
  911. for (const [selector, closestSelector] of selectors) {
  912. let e = [...doc.querySelectorAll(selector)];
  913. e = e.filter(e => {
  914. if (e.closest(".io-style"))
  915. return false; // practice2
  916. if (e.querySelector("var"))
  917. return false;
  918. return true;
  919. });
  920. if (e.length == 0)
  921. continue;
  922. return pairs(e).map(([input, output], index) => {
  923. const container = input.closest(closestSelector) || input.parentElement;
  924. return {
  925. selector,
  926. title: `Sample ${index + 1}`,
  927. input: input.textContent,
  928. output: output.textContent,
  929. anchor: container.querySelector(".btn-copy") || container.querySelector("h1,h2,h3,h4,h5,h6"),
  930. };
  931. });
  932. }
  933. { // maximum_cup_2018_d
  934. let e = [...doc.querySelectorAll("#task-statement .div-btn-copy+pre")];
  935. e = e.filter(f => !f.childElementCount);
  936. if (e.length) {
  937. return pairs(e).map(([input, output], index) => ({
  938. selector: "#task-statement .div-btn-copy+pre",
  939. title: `Sample ${index + 1}`,
  940. input: input.textContent,
  941. output: output.textContent,
  942. anchor: (input.closest(".part") || input.parentElement).querySelector(".btn-copy"),
  943. }));
  944. }
  945. }
  946. return [];
  947. }
  948. return {
  949. name: "AtCoder",
  950. language,
  951. get sourceCode() {
  952. return unsafeWindow.getSourceCode();
  953. },
  954. set sourceCode(sourceCode) {
  955. doc.querySelector(".plain-textarea").value = sourceCode;
  956. unsafeWindow.$(".editor").data("editor").doc.setValue(sourceCode);
  957. },
  958. submit() {
  959. doc.querySelector("#submit").click();
  960. },
  961. get testButtonContainer() {
  962. return doc.querySelector("#submit").parentElement;
  963. },
  964. get sideButtonContainer() {
  965. return doc.querySelector(".editor-buttons");
  966. },
  967. get bottomMenuContainer() {
  968. return doc.getElementById("main-div");
  969. },
  970. get resultListContainer() {
  971. return doc.querySelector(".form-code-submit");
  972. },
  973. get testCases() {
  974. if (isTestCasesHere) {
  975. return getTestCases(doc);
  976. }
  977. else {
  978. const taskSelection = doc.querySelector("#select-task");
  979. if (!taskSelection) {
  980. console.error("AtCoder Easy Test v2: Couldn't find task name");
  981. return [];
  982. }
  983. const taskName = taskSelection.value;
  984. if (!(taskName in testcasesCache) || testcasesCache[taskName].state == "error") {
  985. console.error("AtCoder Easy Test v2: Test cases are still not loaded");
  986. return [];
  987. }
  988. return testcasesCache[taskName].testcases || [];
  989. }
  990. },
  991. get jQuery() {
  992. return unsafeWindow["jQuery"];
  993. },
  994. };
  995. }
  996.  
  997. async function init$4() {
  998. if (location.host != "yukicoder.me")
  999. throw "Not yukicoder";
  1000. const $ = unsafeWindow.$;
  1001. const doc = unsafeWindow.document;
  1002. const editor = unsafeWindow.ace.edit("rich_source");
  1003. const eSourceObject = $("#source");
  1004. const eLang = $("#lang");
  1005. const eSamples = $(".sample");
  1006. const langMap = {
  1007. "cpp14": "C++ C++14 GCC 11.1.0 + Boost 1.77.0",
  1008. "cpp17": "C++ C++17 GCC 11.1.0 + Boost 1.77.0",
  1009. "cpp-clang": "C++ C++17 Clang 10.0.0 + Boost 1.76.0",
  1010. "cpp23": "C++ C++11 GCC 8.4.1",
  1011. "c11": "C++ C++11 GCC 11.1.0",
  1012. "c": "C C90 GCC 8.4.1",
  1013. "java8": "Java Java16 OpenJDK 16.0.1",
  1014. "csharp": "C# CSC 3.9.0",
  1015. "csharp_mono": "C# Mono 6.12.0.147",
  1016. "csharp_dotnet": "C# .NET 5.0",
  1017. "perl": "Perl 5.26.3",
  1018. "raku": "Raku Rakudo v2021-07-2-g74d7ff771",
  1019. "php": "PHP 7.2.24",
  1020. "php7": "PHP 8.0.8",
  1021. "python3": "Python3 3.9.6 + numpy 1.14.5 + scipy 1.1.0",
  1022. "pypy2": "Python PyPy2 7.3.5",
  1023. "pypy3": "Python3 PyPy3 7.3.5",
  1024. "ruby": "Ruby 3.0.2p107",
  1025. "d": "D DMD 2.097.1",
  1026. "go": "Go 1.16.6",
  1027. "haskell": "Haskell 8.10.5",
  1028. "scala": "Scala 2.13.6",
  1029. "nim": "Nim 1.4.8",
  1030. "rust": "Rust 1.53.0",
  1031. "kotlin": "Kotlin 1.5.21",
  1032. "scheme": "Scheme Gauche 0.9.10",
  1033. "crystal": "Crystal 1.1.1",
  1034. "swift": "Swift 5.4.2",
  1035. "ocaml": "OCaml 4.12.0",
  1036. "clojure": "Clojure 1.10.2.790",
  1037. "fsharp": "F# 5.0",
  1038. "elixir": "Elixir 1.7.4",
  1039. "lua": "Lua LuaJIT 2.0.5",
  1040. "fortran": "Fortran gFortran 8.4.1",
  1041. "node": "JavaScript Node.js 15.5.0",
  1042. "typescript": "TypeScript 4.3.5",
  1043. "lisp": "Lisp Common Lisp sbcl 2.1.6",
  1044. "sml": "ML Standard ML MLton 20180207-6",
  1045. "kuin": "Kuin KuinC++ v.2021.7.17",
  1046. "vim": "Vim v8.2",
  1047. "sh": "Bash 4.4.19",
  1048. "nasm": "Assembler nasm 2.13.03",
  1049. "clay": "cLay 20210917-1",
  1050. "bf": "Brainfuck BFI 1.1",
  1051. "Whitespace": "Whitespace 0.3",
  1052. "text": "Text cat 8.3",
  1053. };
  1054. // place anchor elements
  1055. for (const btnCopyInput of doc.querySelectorAll(".copy-sample-input")) {
  1056. btnCopyInput.parentElement.insertBefore(newElement("span", { className: "atcoder-easy-test-anchor" }), btnCopyInput);
  1057. }
  1058. const language = new ObservableValue(langMap[eLang.val()]);
  1059. eLang.on("change", () => {
  1060. language.value = langMap[eLang.val()];
  1061. });
  1062. return {
  1063. name: "yukicoder",
  1064. language,
  1065. get sourceCode() {
  1066. if (eSourceObject.is(":visible"))
  1067. return eSourceObject.val();
  1068. return editor.getSession().getValue();
  1069. },
  1070. set sourceCode(sourceCode) {
  1071. eSourceObject.val(sourceCode);
  1072. editor.getSession().setValue(sourceCode);
  1073. },
  1074. submit() {
  1075. doc.querySelector(`#submit_form input[type="submit"]`).click();
  1076. },
  1077. get testButtonContainer() {
  1078. return doc.querySelector("#submit_form");
  1079. },
  1080. get sideButtonContainer() {
  1081. return doc.querySelector("#toggle_source_editor").parentElement;
  1082. },
  1083. get bottomMenuContainer() {
  1084. return doc.body;
  1085. },
  1086. get resultListContainer() {
  1087. return doc.querySelector("#content");
  1088. },
  1089. get testCases() {
  1090. const testCases = [];
  1091. let sampleId = 1;
  1092. for (let i = 0; i < eSamples.length; i++) {
  1093. const eSample = eSamples.eq(i);
  1094. const [eInput, eOutput] = eSample.find("pre");
  1095. testCases.push({
  1096. title: `Sample ${sampleId++}`,
  1097. input: eInput.textContent,
  1098. output: eOutput.textContent,
  1099. anchor: eSample.find(".atcoder-easy-test-anchor")[0],
  1100. });
  1101. }
  1102. return testCases;
  1103. },
  1104. get jQuery() {
  1105. return $;
  1106. },
  1107. };
  1108. }
  1109.  
  1110. class Editor {
  1111. _element;
  1112. constructor(lang) {
  1113. this._element = document.createElement("textarea");
  1114. this._element.style.fontFamily = "monospace";
  1115. this._element.style.width = "100%";
  1116. this._element.style.minHeight = "5em";
  1117. }
  1118. get element() {
  1119. return this._element;
  1120. }
  1121. get sourceCode() {
  1122. return this._element.value;
  1123. }
  1124. set sourceCode(sourceCode) {
  1125. this._element.value = sourceCode;
  1126. }
  1127. setLanguage(lang) {
  1128. }
  1129. }
  1130.  
  1131. var langMap = {
  1132. 3: "Delphi 7",
  1133. 4: "Pascal Free Pascal 3.0.2",
  1134. 6: "PHP 7.2.13",
  1135. 7: "Python 2.7.18",
  1136. 9: "C# Mono 6.8",
  1137. 12: "Haskell GHC 8.10.1",
  1138. 13: "Perl 5.20.1",
  1139. 19: "OCaml 4.02.1",
  1140. 20: "Scala 2.12.8",
  1141. 28: "D DMD32 v2.091.0",
  1142. 31: "Python3 3.8.10",
  1143. 32: "Go 1.15.6",
  1144. 34: "JavaScript V8 4.8.0",
  1145. 36: "Java 1.8.0_241",
  1146. 40: "Python PyPy2 2.7 (7.3.0)",
  1147. 41: "Python3 PyPy3 3.7 (7.3.0)",
  1148. 43: "C C11 GCC 5.1.0",
  1149. 48: "Kotlin 1.5.31",
  1150. 49: "Rust 1.49.0",
  1151. 50: "C++ C++14 G++ 6.4.0",
  1152. 51: "Pascal PascalABC.NET 3.4.1",
  1153. 52: "C++ C++17 Clang++",
  1154. 54: "C++ C++17 G++ 7.3.0",
  1155. 55: "JavaScript Node.js 12.6.3",
  1156. 59: "C++ Microsoft Visual C++ 2017",
  1157. 60: "Java 11.0.6",
  1158. 61: "C++ C++17 9.2.0 (64 bit, msys 2)",
  1159. 65: "C# 8, .NET Core 3.1",
  1160. 67: "Ruby 3.0.0",
  1161. 70: "Python3 PyPy 3.7 (7.3.5, 64bit)",
  1162. 72: "Kotlin 1.5.31",
  1163. 73: "C++ GNU G++ 11.2.0 (64 bit, winlibs)",
  1164. };
  1165.  
  1166. config.registerFlag("site.codeforces.showEditor", true, "Show Editor in Codeforces Problem Page");
  1167. async function init$3() {
  1168. if (location.host != "codeforces.com")
  1169. throw "not Codeforces";
  1170. //TODO: m1.codeforces.com, m2.codeforces.com, m3.codeforces.com に対応する
  1171. const doc = unsafeWindow.document;
  1172. const eLang = doc.querySelector("select[name='programTypeId']");
  1173. doc.head.appendChild(newElement("link", {
  1174. rel: "stylesheet",
  1175. href: "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
  1176. }));
  1177. doc.head.appendChild(newElement("style", {
  1178. textContent: `
  1179. .atcoder-easy-test-btn-run-case {
  1180. float: right;
  1181. line-height: 1.1rem;
  1182. }
  1183. `,
  1184. }));
  1185. const eButtons = newElement("span");
  1186. doc.querySelector(".submitForm").appendChild(eButtons);
  1187. await loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js");
  1188. const jQuery = unsafeWindow["jQuery"].noConflict();
  1189. unsafeWindow["jQuery"] = unsafeWindow["$"];
  1190. unsafeWindow["jQuery11"] = jQuery;
  1191. await loadScript("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js", null, { jQuery, $: jQuery });
  1192. const language = new ObservableValue(langMap[eLang.value]);
  1193. eLang.addEventListener("change", () => {
  1194. language.value = langMap[eLang.value];
  1195. });
  1196. let _sourceCode = "";
  1197. const eFile = doc.querySelector(".submitForm").elements["sourceFile"];
  1198. eFile.addEventListener("change", async () => {
  1199. if (eFile.files[0]) {
  1200. _sourceCode = await eFile.files[0].text();
  1201. if (editor)
  1202. editor.sourceCode = _sourceCode;
  1203. }
  1204. });
  1205. let editor = null;
  1206. let waitCfFastSubmitCount = 0;
  1207. const waitCfFastSubmit = setInterval(() => {
  1208. if (document.getElementById("editor")) {
  1209. // cf-fast-submit
  1210. if (editor && editor.element)
  1211. editor.element.style.display = "none";
  1212. // 言語セレクトを同期させる
  1213. const eLang2 = doc.querySelector(".submit-form select[name='programTypeId']");
  1214. if (eLang2) {
  1215. eLang.addEventListener("change", () => {
  1216. eLang2.value = eLang.value;
  1217. });
  1218. eLang2.addEventListener("change", () => {
  1219. eLang.value = eLang2.value;
  1220. language.value = langMap[eLang.value];
  1221. });
  1222. }
  1223. // TODO: 選択されたファイルをどうかする
  1224. // エディタを使う
  1225. const aceEditor = unsafeWindow["ace"].edit("editor");
  1226. editor = {
  1227. get sourceCode() {
  1228. return aceEditor.getValue();
  1229. },
  1230. set sourceCode(sourceCode) {
  1231. aceEditor.setValue(sourceCode);
  1232. },
  1233. setLanguage(lang) { },
  1234. };
  1235. // ボタンを追加する
  1236. const buttonContainer = doc.querySelector(".submit-form .submit").parentElement;
  1237. buttonContainer.appendChild(newElement("button", {
  1238. type: "button",
  1239. className: "btn btn-info",
  1240. textContent: "Test & Submit",
  1241. onclick: () => events.trig("testAndSubmit"),
  1242. }));
  1243. buttonContainer.appendChild(newElement("button", {
  1244. type: "button",
  1245. className: "btn btn-default",
  1246. textContent: "Test All Samples",
  1247. onclick: () => events.trig("testAllSamples"),
  1248. }));
  1249. clearInterval(waitCfFastSubmit);
  1250. }
  1251. else {
  1252. waitCfFastSubmitCount++;
  1253. if (waitCfFastSubmitCount >= 100)
  1254. clearInterval(waitCfFastSubmit);
  1255. }
  1256. }, 100);
  1257. if (config.get("site.codeforces.showEditor", true)) {
  1258. editor = new Editor(langMap[eLang.value].split(" ")[0]);
  1259. doc.getElementById("pageContent").appendChild(editor.element);
  1260. language.addListener(lang => {
  1261. editor.setLanguage(lang);
  1262. });
  1263. }
  1264. return {
  1265. name: "Codeforces",
  1266. language,
  1267. get sourceCode() {
  1268. if (editor)
  1269. return editor.sourceCode;
  1270. return _sourceCode;
  1271. },
  1272. set sourceCode(sourceCode) {
  1273. const container = new DataTransfer();
  1274. container.items.add(new File([sourceCode], "prog.txt", { type: "text/plain" }));
  1275. const eFile = doc.querySelector(".submitForm").elements["sourceFile"];
  1276. eFile.files = container.files;
  1277. _sourceCode = sourceCode;
  1278. if (editor)
  1279. editor.sourceCode = sourceCode;
  1280. },
  1281. submit() {
  1282. if (editor)
  1283. _sourceCode = editor.sourceCode;
  1284. this.sourceCode = _sourceCode;
  1285. doc.querySelector(`.submitForm .submit`).click();
  1286. },
  1287. get testButtonContainer() {
  1288. return eButtons;
  1289. },
  1290. get sideButtonContainer() {
  1291. return eButtons;
  1292. },
  1293. get bottomMenuContainer() {
  1294. return doc.body;
  1295. },
  1296. get resultListContainer() {
  1297. return doc.querySelector("#pageContent");
  1298. },
  1299. get testCases() {
  1300. const testcases = [];
  1301. let num = 1;
  1302. for (const eSampleTest of doc.querySelectorAll(".sample-test")) {
  1303. const inputs = eSampleTest.querySelectorAll(".input pre");
  1304. const outputs = eSampleTest.querySelectorAll(".output pre");
  1305. const anchors = eSampleTest.querySelectorAll(".input .title .input-output-copier");
  1306. const count = Math.min(inputs.length, outputs.length, anchors.length);
  1307. for (let i = 0; i < count; i++) {
  1308. testcases.push({
  1309. title: `Sample ${num++}`,
  1310. input: inputs[i].textContent,
  1311. output: outputs[i].textContent,
  1312. anchor: anchors[i],
  1313. });
  1314. }
  1315. }
  1316. return testcases;
  1317. },
  1318. get jQuery() {
  1319. return jQuery;
  1320. },
  1321. };
  1322. }
  1323.  
  1324. config.registerFlag("site.codeforcesMobile.showEditor", true, "Show Editor in Mobile Codeforces (m[1-3].codeforces.com) Problem Page");
  1325. async function init$2() {
  1326. if (!/^m[1-3]\.codeforces\.com$/.test(location.host))
  1327. throw "not Codeforces Mobile";
  1328. const url = /\/contest\/(\d+)\/problem\/([^/]+)/.exec(location.pathname);
  1329. const contestId = url[1];
  1330. const problemId = url[2];
  1331. const doc = unsafeWindow.document;
  1332. const main = doc.querySelector("main");
  1333. doc.head.appendChild(newElement("link", {
  1334. rel: "stylesheet",
  1335. href: "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css",
  1336. }));
  1337. await loadScript("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js");
  1338. const language = new ObservableValue("");
  1339. let submit = () => { };
  1340. let getSourceCode = () => "";
  1341. let setSourceCode = (_) => { };
  1342. // make Editor
  1343. if (config.get("site.codeforcesMobile.showEditor", true)) {
  1344. const frame = newElement("iframe", {
  1345. src: `/contest/${contestId}/submit`,
  1346. style: {
  1347. display: "none",
  1348. },
  1349. });
  1350. doc.body.appendChild(frame);
  1351. await new Promise(done => frame.onload = done);
  1352. const fdoc = frame.contentDocument;
  1353. const form = fdoc.querySelector("._SubmitPage_submitForm");
  1354. form.elements["problemIndex"].value = problemId;
  1355. form.elements["problemIndex"].readonly = true;
  1356. form.elements["programTypeId"].addEventListener("change", function () {
  1357. language.value = langMap[this.value];
  1358. });
  1359. for (const row of form.children) {
  1360. if (row.tagName != "DIV")
  1361. continue;
  1362. row.classList.add("form-group");
  1363. const control = row.querySelector("*[name]");
  1364. if (control)
  1365. control.classList.add("form-control");
  1366. }
  1367. form.parentElement.removeChild(form);
  1368. main.appendChild(form);
  1369. submit = () => form.submit();
  1370. getSourceCode = () => form.elements["source"].value;
  1371. setSourceCode = sourceCode => {
  1372. form.elements["source"].value = sourceCode;
  1373. };
  1374. }
  1375. return {
  1376. name: "Codeforces",
  1377. language,
  1378. get sourceCode() {
  1379. return getSourceCode();
  1380. },
  1381. set sourceCode(sourceCode) {
  1382. setSourceCode(sourceCode);
  1383. },
  1384. submit,
  1385. get testButtonContainer() {
  1386. return main;
  1387. },
  1388. get sideButtonContainer() {
  1389. return main;
  1390. },
  1391. get bottomMenuContainer() {
  1392. return doc.body;
  1393. },
  1394. get resultListContainer() {
  1395. return main;
  1396. },
  1397. get testCases() {
  1398. const testcases = [];
  1399. let index = 1;
  1400. for (const container of doc.querySelectorAll(".sample-test")) {
  1401. const input = container.querySelector(".input pre.content").textContent;
  1402. const output = container.querySelector(".output pre.content").textContent;
  1403. const anchor = container.querySelector(".input .title");
  1404. testcases.push({
  1405. input, output, anchor,
  1406. title: `Sample ${index++}`,
  1407. });
  1408. }
  1409. return testcases;
  1410. },
  1411. get jQuery() {
  1412. return unsafeWindow["jQuery"];
  1413. },
  1414. };
  1415. }
  1416.  
  1417. async function init$1() {
  1418. if (location.host != "greatest.deepsurf.us" && !location.href.match(/433152-atcoder-easy-test-v2/))
  1419. throw "Not about page";
  1420. const doc = unsafeWindow.document;
  1421. await loadScript("https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js");
  1422. const jQuery = unsafeWindow["jQuery"];
  1423. await loadScript("https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js", null, { jQuery, $: jQuery });
  1424. const e = newElement("div");
  1425. doc.getElementById("install-area").appendChild(newElement("button", {
  1426. type: "button",
  1427. textContent: "Open config",
  1428. onclick: () => settings.open(),
  1429. }));
  1430. return {
  1431. name: "About Page",
  1432. language: new ObservableValue(""),
  1433. get sourceCode() { return ""; },
  1434. set sourceCode(sourceCode) { },
  1435. submit() { },
  1436. get testButtonContainer() { return e; },
  1437. get sideButtonContainer() { return e; },
  1438. get bottomMenuContainer() { return e; },
  1439. get resultListContainer() { return e; },
  1440. get testCases() { return []; },
  1441. get jQuery() { return jQuery; },
  1442. };
  1443. }
  1444.  
  1445. // 設定ページが開けなくなるのを避ける
  1446. const inits = [init$1()];
  1447. config.registerFlag("site.atcoder", true, "Use AtCoder Easy Test in AtCoder");
  1448. if (config.get("site.atcoder", true))
  1449. inits.push(init$5());
  1450. config.registerFlag("site.yukicoder", true, "Use AtCoder Easy Test in yukicoder");
  1451. if (config.get("site.yukicoder", true))
  1452. inits.push(init$4());
  1453. config.registerFlag("site.codeforces", true, "Use AtCoder Easy Test in Codeforces");
  1454. if (config.get("site.codeforces", true))
  1455. inits.push(init$3());
  1456. config.registerFlag("site.codeforcesMobile", true, "Use AtCoder Easy Test in Codeforces Mobile (m[1-3].codeforces.com)");
  1457. if (config.get("site.codeforcesMobile", true))
  1458. inits.push(init$2());
  1459. const site = Promise.any(inits);
  1460. site.catch(() => {
  1461. for (const promise of inits) {
  1462. promise.catch(console.error);
  1463. }
  1464. });
  1465.  
  1466. const runners = {
  1467. "C GCC 10.2.0 Wandbox": new WandboxRunner("gcc-10.2.0-c", "C (GCC 10.2.0)"),
  1468. "C C17 Clang 10.0.0 paiza.io": new PaizaIORunner("c", "C (C17 / Clang 10.0.0)"),
  1469. "C++ GCC 10.2.0 + Boost 1.73.0 + ACL Wandbox": new WandboxCppRunner("gcc-10.2.0", "C++ (GCC 10.2.0) + ACL", { options: "warning,boost-1.73.0-gcc-9.2.0,gnu++17" }),
  1470. "C++ Clang 10.0.1 + ACL Wandbox": new WandboxCppRunner("clang-10.0.1", "C++ (Clang 10.0.1) + ACL", { options: "warning,boost-nothing-clang-10.0.0,c++17" }),
  1471. "Python3 CPython 3.8.2 paiza.io": new PaizaIORunner("python3", "Python (3.8.2)"),
  1472. "Python3 Brython": brythonRunner,
  1473. "Python3 Pyodide": pyodideRunner,
  1474. "Bash 5.0.17 paiza.io": new PaizaIORunner("bash", "Bash (5.0.17)"),
  1475. "Bash 5.0.17 Wandbox": new WandboxRunner("bash", "Bash (5.0.17(1)-release)"),
  1476. "C# .NET Core 3.1.407 Wandbox": new WandboxRunner("dotnetcore-3.1.407", "C# (.NET Core 3.1.407)"),
  1477. "C# Mono-mcs 6.12.0.122 Wandbox": new WandboxRunner("mono-6.12.0.122", "C# (Mono-mcs 6.12.0.122)"),
  1478. "Clojure 1.10.1-1 paiza.io": new PaizaIORunner("clojure", "Clojure (1.10.1-1)"),
  1479. "Crystal 0.36.1 Wandbox": new WandboxRunner("crystal-0.36.1", "Crystal (0.36.1)"),
  1480. "D LDC 1.23.0 paiza.io": new PaizaIORunner("d", "D (LDC 1.23.0)"),
  1481. "D DMD 2.093.1": new WandboxRunner("dmd-2.093.1", "D (DMD 2.093.1)"),
  1482. "D LDC 1.23.0": new WandboxRunner("ldc-1.23.0", "D (LDC 1.23.0)"),
  1483. "Erlang 10.6.4 paiza.io": new PaizaIORunner("erlang", "Erlang (10.6.4)"),
  1484. "Erlang 22.3.4.16": new WandboxRunner("erlang-22.3.4.16", "Erlang (22.3.4.16)"),
  1485. "Elixir 1.10.4": new WandboxRunner("elixir-1.10.4", "Elixir (1.10.4)"),
  1486. "Elixir 1.10.4 paiza.io": new PaizaIORunner("elixir", "Elixir (1.10.4)"),
  1487. "F# Interactive 4.0 paiza.io": new PaizaIORunner("fsharp", "F# (Interactive 4.0)"),
  1488. "Go 1.14.15 Wandbox": new WandboxRunner("go-1.14.15", "Go (1.14.15)"),
  1489. "Haskell GHC 8.8.4 Wandbox": new WandboxRunner("ghc-8.8.4", "Haskell (GHC 8.8.4)"),
  1490. "Haskell GHC 8.6.5 paiza.io": new PaizaIORunner("haskell", "Haskell (GHC 8.6.5)"),
  1491. "Java openjdk-jdk-15.0.3+2 Wandbox": new WandboxRunner("openjdk-jdk-15.0.3+2", "Java (JDK 15.0.3+2)"),
  1492. "Java openjdk-jdk-14.0.2+12 Wandbox": new WandboxRunner("openjdk-jdk-14.0.2+12", "Java (JDK 14.0.2+12)"),
  1493. "JavaScript Node.js paiza.io": new PaizaIORunner("javascript", "JavaScript (Node.js 12.18.3)"),
  1494. "JavaScript Node.js 12.22.1 Wandbox": new WandboxRunner("nodejs-12.22.1", "JavaScript (Node.js 12.22.1)"),
  1495. "Julia 1.6.1 Wandbox": new WandboxRunner("julia-1.6.1", "Julia (1.6.1)"),
  1496. "Kotlin 1.4.0 paiza.io": new PaizaIORunner("kotlin", "Kotlin (1.4.0)"),
  1497. "Lua 5.3.6 Wandbox": new WandboxRunner("lua-5.3.6", "Lua (Lua 5.3.6)"),
  1498. "Lua LuaJIT 2.0.5 Wandbox": new WandboxRunner("luajit-2.0.5", "Lua (LuaJIT 2.0.5)"),
  1499. "Nim 1.0.10 Wandbox": new WandboxRunner("nim-1.0.10", "Nim (1.0.10)"),
  1500. "Objective-C Clang 10.0.0 paiza.io": new PaizaIORunner("objective-c", "Objective-C (Clang 10.0.0)"),
  1501. "OCaml 4.10.2 Wandbox": new WandboxRunner("ocaml-4.10.2", "OCaml (4.10.2)"),
  1502. "Pascal FPC 3.0.4 Wandbox": new WandboxRunner("fpc-3.0.4", "Pascal (FPC 3.0.4)"),
  1503. "Perl 5.30.0 paiza.io": new PaizaIORunner("perl", "Perl (5.30.0)"),
  1504. "Perl 5.30.3 Wandbox": new WandboxRunner("perl-5.30.3", "Perl (5.30.3)"),
  1505. "PHP 7.4.10 paiza.io": new PaizaIORunner("php", "PHP (7.4.10)"),
  1506. "PHP 7.4.16 Wandbox": new WandboxRunner("php-7.4.16", "PHP (7.4.16)"),
  1507. "Python PyPy 7.3.4 Wandbox": new WandboxRunner("pypy-2.7-v7.3.4", "PyPy2 (7.3.4)"),
  1508. "Python3 PyPy3 7.3.4 Wandbox": new WandboxRunner("pypy-3.7-v7.3.4", "PyPy3 (7.3.4)"),
  1509. "Ruby 2.7.1 paiza.io": new PaizaIORunner("ruby", "Ruby (2.7.1)"),
  1510. "Ruby 3.1.0 Wandbox": new WandboxRunner("ruby-3.1.0", "Ruby (3.1.0)"),
  1511. "Ruby 2.7.3 Wandbox": new WandboxRunner("ruby-2.7.3", "Ruby (2.7.1)"),
  1512. "Rust 1.42.0 AtCoder": new AtCoderRunner("4050", "Rust (1.42.0)"),
  1513. "Rust 1.50.0 Wandbox": new WandboxRunner("rust-1.50.0", "Rust (1.50.0)"),
  1514. "Rust 1.43.0 paiza.io": new PaizaIORunner("rust", "Rust (1.43.0)"),
  1515. "Scala 2.13.3 paiza": new PaizaIORunner("scala", "Scala (2.13.3)"),
  1516. "Scala 2.13.5 Wandbox": new WandboxRunner("scala-2.13.5", "Scala (2.13.5)"),
  1517. "Scheme Gauche 0.9.6 paiza.io": new PaizaIORunner("scheme", "Scheme (Gauche 0.9.6)"),
  1518. "Swift 5.2.5 paiza.io": new PaizaIORunner("swift", "Swift (5.2.5)"),
  1519. "Swift 5.3.3 Wandbox": new WandboxRunner("swift-5.3.3", "Swift (5.3.3)"),
  1520. "TypeScript typescript-3.9.9 nodejs 14.16.1 Wandbox": new WandboxRunner("typescript-3.9.9 nodejs 14.16.1", "TypeScript (3.9.9)"),
  1521. "Text local": new CustomRunner("Text", async (sourceCode, input) => {
  1522. return {
  1523. status: "OK",
  1524. exitCode: "0",
  1525. input,
  1526. output: sourceCode,
  1527. };
  1528. }),
  1529. "Basic Visual Basic .NET Core 4.0.1 paiza.io": new PaizaIORunner("vb", "Visual Basic (.NET Core 4.0.1)"),
  1530. "COBOL Free OpenCOBOL 2.2.0 paiza.io": new PaizaIORunner("cobol", "COBOL - Free (OpenCOBOL 2.2.0)"),
  1531. "COBOL Fixed OpenCOBOL 1.1.0 AtCoder": new AtCoderRunner("4060", "COBOL - Fixed (OpenCOBOL 1.1.0)"),
  1532. "COBOL Free OpenCOBOL 1.1.0 AtCoder": new AtCoderRunner("4061", "COBOL - Free (OpenCOBOL 1.1.0)"),
  1533. "C++ GCC 9.3.0 + ACL Wandbox": new WandboxCppRunner("gcc-9.3.0", "C++ (GCC 9.3.0) + ACL"),
  1534. };
  1535. site.then(site => {
  1536. if (site.name == "AtCoder") {
  1537. // AtCoderRunner がない場合は、追加する
  1538. for (const e of document.querySelectorAll("#select-lang option[value]")) {
  1539. const m = e.textContent.match(/([^ ]+) \(([^)]+)\)/);
  1540. if (m) {
  1541. const name = `${m[1]} ${m[2]} AtCoder`;
  1542. const languageId = e.value;
  1543. runners[name] = new AtCoderRunner(languageId, e.textContent);
  1544. }
  1545. }
  1546. }
  1547. });
  1548. console.info("AtCoder Easy Test: codeRunner OK");
  1549. config.registerCount("codeRunner.maxRetry", 3, "Max count of retry when IE (Internal Error)");
  1550. var codeRunner = {
  1551. // 指定した環境でコードを実行する
  1552. async run(runnerId, sourceCode, input, expectedOutput, options = { trim: true, split: true }) {
  1553. // CodeRunner が存在しない言語ID
  1554. if (!(runnerId in runners))
  1555. return Promise.reject("Language not supported");
  1556. // 最後に実行したコードを保存
  1557. if (sourceCode.length > 0)
  1558. codeSaver.save(sourceCode);
  1559. // 実行
  1560. const maxRetry = config.get("codeRunner.maxRetry", 3);
  1561. for (let retry = 0; retry < maxRetry; retry++) {
  1562. try {
  1563. const result = await runners[runnerId].test(sourceCode, input, expectedOutput, options);
  1564. const lang = runnerId.split(" ")[0];
  1565. if (result.status == "IE") {
  1566. console.error(result);
  1567. const runnerIds = Object.keys(runners).filter(runnerId => runnerId.split(" ")[0] == lang);
  1568. const index = runnerIds.indexOf(runnerId);
  1569. runnerId = runnerIds[(index + 1) % runnerIds.length];
  1570. continue;
  1571. }
  1572. return result;
  1573. }
  1574. catch (e) {
  1575. console.error(e);
  1576. }
  1577. }
  1578. },
  1579. // 環境の名前の一覧を取得する
  1580. // @return runnerIdとラベルのペアの配列
  1581. async getEnvironment(languageId) {
  1582. const langs = similarLangs(languageId, Object.keys(runners));
  1583. if (langs.length == 0)
  1584. throw `Undefined language: ${languageId}`;
  1585. return langs.map(runnerId => [runnerId, runners[runnerId].label]);
  1586. },
  1587. };
  1588.  
  1589. var hBottomMenu = "<div id=\"bottom-menu-wrapper\" class=\"navbar navbar-default navbar-fixed-bottom\">\n <div class=\"container\">\n <div class=\"navbar-header\">\n <button id=\"bottom-menu-key\" type=\"button\" class=\"navbar-toggle collapsed glyphicon glyphicon-menu-down\" data-toggle=\"collapse\" data-target=\"#bottom-menu\"></button>\n </div>\n <div id=\"bottom-menu\" class=\"collapse navbar-collapse\">\n <ul id=\"bottom-menu-tabs\" class=\"nav nav-tabs\"></ul>\n <div id=\"bottom-menu-contents\" class=\"tab-content\"></div>\n </div>\n </div>\n</div>";
  1590.  
  1591. var hStyle$1 = "<style>\n#bottom-menu-wrapper {\n background: transparent !important;\n border: none !important;\n pointer-events: none;\n padding: 0;\n}\n\n#bottom-menu-wrapper>.container {\n position: absolute;\n bottom: 0;\n width: 100%;\n padding: 0;\n}\n\n#bottom-menu-wrapper>.container>.navbar-header {\n float: none;\n}\n\n#bottom-menu-key {\n display: block;\n float: none;\n margin: 0 auto;\n padding: 10px 3em;\n border-radius: 5px 5px 0 0;\n background: #000;\n opacity: 0.5;\n color: #FFF;\n cursor: pointer;\n pointer-events: auto;\n text-align: center;\n}\n\n@media screen and (max-width: 767px) {\n #bottom-menu-key {\n opacity: 0.25;\n }\n}\n\n#bottom-menu-key.collapsed:before {\n content: \"\\e260\";\n}\n\n#bottom-menu-tabs {\n padding: 3px 0 0 10px;\n cursor: n-resize;\n}\n\n#bottom-menu-tabs a {\n pointer-events: auto;\n}\n\n#bottom-menu {\n pointer-events: auto;\n background: rgba(0, 0, 0, 0.8);\n color: #fff;\n max-height: unset;\n}\n\n#bottom-menu.collapse:not(.in) {\n display: none !important;\n}\n\n#bottom-menu-tabs>li>a {\n background: rgba(150, 150, 150, 0.5);\n color: #000;\n border: solid 1px #ccc;\n filter: brightness(0.75);\n}\n\n#bottom-menu-tabs>li>a:hover {\n background: rgba(150, 150, 150, 0.5);\n border: solid 1px #ccc;\n color: #111;\n filter: brightness(0.9);\n}\n\n#bottom-menu-tabs>li.active>a {\n background: #eee;\n border: solid 1px #ccc;\n color: #333;\n filter: none;\n}\n\n.bottom-menu-btn-close {\n font-size: 8pt;\n vertical-align: baseline;\n padding: 0 0 0 6px;\n margin-right: -6px;\n}\n\n#bottom-menu-contents {\n padding: 5px 15px;\n max-height: 50vh;\n overflow-y: auto;\n}\n\n#bottom-menu-contents .panel {\n color: #333;\n}\n</style>";
  1592.  
  1593. async function init() {
  1594. const site$1 = await site;
  1595. const style = html2element(hStyle$1);
  1596. const bottomMenu = html2element(hBottomMenu);
  1597. unsafeWindow.document.head.appendChild(style);
  1598. site$1.bottomMenuContainer.appendChild(bottomMenu);
  1599. const bottomMenuKey = bottomMenu.querySelector("#bottom-menu-key");
  1600. const bottomMenuTabs = bottomMenu.querySelector("#bottom-menu-tabs");
  1601. const bottomMenuContents = bottomMenu.querySelector("#bottom-menu-contents");
  1602. // メニューのリサイズ
  1603. {
  1604. let resizeStart = null;
  1605. const onStart = (event) => {
  1606. const target = event.target;
  1607. const pageY = event.pageY;
  1608. if (target.id != "bottom-menu-tabs")
  1609. return;
  1610. resizeStart = { y: pageY, height: bottomMenuContents.getBoundingClientRect().height };
  1611. };
  1612. const onMove = (event) => {
  1613. if (!resizeStart)
  1614. return;
  1615. event.preventDefault();
  1616. bottomMenuContents.style.height = `${resizeStart.height - (event.pageY - resizeStart.y)}px`;
  1617. };
  1618. const onEnd = () => {
  1619. resizeStart = null;
  1620. };
  1621. bottomMenuTabs.addEventListener("mousedown", onStart);
  1622. bottomMenuTabs.addEventListener("mousemove", onMove);
  1623. bottomMenuTabs.addEventListener("mouseup", onEnd);
  1624. bottomMenuTabs.addEventListener("mouseleave", onEnd);
  1625. }
  1626. let tabs = new Set();
  1627. let selectedTab = null;
  1628. /** 下メニューの操作 */
  1629. const menuController = {
  1630. /** タブを選択 */
  1631. selectTab(tabId) {
  1632. const tab = site$1.jQuery(`#bottom-menu-tab-${tabId}`);
  1633. if (tab && tab[0]) {
  1634. tab.tab("show"); // Bootstrap 3
  1635. selectedTab = tabId;
  1636. }
  1637. },
  1638. /** 下メニューにタブを追加する */
  1639. addTab(tabId, tabLabel, paneContent, options = {}) {
  1640. console.log(`AtCoder Easy Test: addTab: ${tabLabel} (${tabId})`, paneContent);
  1641. // タブを追加
  1642. const tab = document.createElement("a");
  1643. tab.textContent = tabLabel;
  1644. tab.id = `bottom-menu-tab-${tabId}`;
  1645. tab.href = "#";
  1646. tab.dataset.target = `#bottom-menu-pane-${tabId}`;
  1647. tab.dataset.toggle = "tab";
  1648. tab.addEventListener("click", event => {
  1649. event.preventDefault();
  1650. menuController.selectTab(tabId);
  1651. });
  1652. const tabLi = document.createElement("li");
  1653. tabLi.appendChild(tab);
  1654. bottomMenuTabs.appendChild(tabLi);
  1655. // 内容を追加
  1656. const pane = document.createElement("div");
  1657. pane.className = "tab-pane";
  1658. pane.id = `bottom-menu-pane-${tabId}`;
  1659. pane.appendChild(paneContent);
  1660. bottomMenuContents.appendChild(pane);
  1661. const controller = {
  1662. get id() {
  1663. return tabId;
  1664. },
  1665. close() {
  1666. bottomMenuTabs.removeChild(tabLi);
  1667. bottomMenuContents.removeChild(pane);
  1668. tabs.delete(tab);
  1669. if (selectedTab == tabId) {
  1670. selectedTab = null;
  1671. if (tabs.size > 0) {
  1672. menuController.selectTab(tabs.values().next().value.id);
  1673. }
  1674. }
  1675. },
  1676. show() {
  1677. menuController.show();
  1678. menuController.selectTab(tabId);
  1679. },
  1680. set color(color) {
  1681. tab.style.backgroundColor = color;
  1682. },
  1683. };
  1684. // 閉じるボタン
  1685. if (options.closeButton) {
  1686. const btn = document.createElement("a");
  1687. btn.className = "bottom-menu-btn-close btn btn-link glyphicon glyphicon-remove";
  1688. btn.addEventListener("click", () => {
  1689. controller.close();
  1690. });
  1691. tab.appendChild(btn);
  1692. }
  1693. // 選択されているタブがなければ選択
  1694. if (!selectedTab)
  1695. menuController.selectTab(tabId);
  1696. return controller;
  1697. },
  1698. /** 下メニューを表示する */
  1699. show() {
  1700. if (bottomMenuKey.classList.contains("collapsed"))
  1701. bottomMenuKey.click();
  1702. },
  1703. /** 下メニューの表示/非表示を切り替える */
  1704. toggle() {
  1705. bottomMenuKey.click();
  1706. },
  1707. };
  1708. console.info("AtCoder Easy Test: bottomMenu OK");
  1709. return menuController;
  1710. }
  1711.  
  1712. var hRowTemplate = "<div class=\"atcoder-easy-test-cases-row alert alert-dismissible\">\n <button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n <div class=\"progress\">\n <div class=\"progress-bar\" style=\"width: 0%;\">0 / 0</div>\n </div>\n <div class=\"atcoder-easy-test-cases-row-date\" style=\"font-family: monospace; text-align: right; position: absolute; right: 1em;\"></div>\n</div>";
  1713.  
  1714. class ResultRow {
  1715. _tabs;
  1716. _element;
  1717. _promise;
  1718. constructor(pairs) {
  1719. this._tabs = pairs.map(([_, tab]) => tab);
  1720. this._element = html2element(hRowTemplate);
  1721. this._element.querySelector(".close").addEventListener("click", () => this.remove());
  1722. {
  1723. const date = new Date();
  1724. const h = date.getHours().toString().padStart(2, "0");
  1725. const m = date.getMinutes().toString().padStart(2, "0");
  1726. const s = date.getSeconds().toString().padStart(2, "0");
  1727. this._element.querySelector(".atcoder-easy-test-cases-row-date").textContent = `${h}:${m}:${s}`;
  1728. }
  1729. const numCases = pairs.length;
  1730. let numFinished = 0;
  1731. let numAccepted = 0;
  1732. const progressBar = this._element.querySelector(".progress-bar");
  1733. progressBar.textContent = `${numFinished} / ${numCases}`;
  1734. this._promise = Promise.all(pairs.map(([pResult, tab]) => {
  1735. const button = html2element(`<div class="label label-default" style="margin: 3px; cursor: pointer;">WJ</div>`);
  1736. button.addEventListener("click", async () => {
  1737. (await tab).show();
  1738. });
  1739. this._element.appendChild(button);
  1740. return pResult.then(result => {
  1741. button.textContent = result.status;
  1742. if (result.status == "AC") {
  1743. button.classList.add("label-success");
  1744. }
  1745. else if (result.status != "OK") {
  1746. button.classList.add("label-warning");
  1747. }
  1748. numFinished++;
  1749. if (result.status == "AC")
  1750. numAccepted++;
  1751. progressBar.textContent = `${numFinished} / ${numCases}`;
  1752. progressBar.style.width = `${100 * numFinished / numCases}%`;
  1753. if (numFinished == numCases) {
  1754. if (numAccepted == numCases)
  1755. this._element.classList.add("alert-success");
  1756. else
  1757. this._element.classList.add("alert-warning");
  1758. }
  1759. }).catch(reason => {
  1760. button.textContent = "IE";
  1761. button.classList.add("label-danger");
  1762. console.error(reason);
  1763. });
  1764. }));
  1765. }
  1766. get element() {
  1767. return this._element;
  1768. }
  1769. onFinish(listener) {
  1770. this._promise.then(listener);
  1771. }
  1772. remove() {
  1773. for (const pTab of this._tabs)
  1774. pTab.then(tab => tab.close());
  1775. const parent = this._element.parentElement;
  1776. if (parent)
  1777. parent.removeChild(this._element);
  1778. }
  1779. }
  1780.  
  1781. var hResultList = "<div class=\"row\"></div>";
  1782.  
  1783. const eResultList = html2element(hResultList);
  1784. site.then(site => site.resultListContainer.appendChild(eResultList));
  1785. const resultList = {
  1786. addResult(pairs) {
  1787. const result = new ResultRow(pairs);
  1788. eResultList.insertBefore(result.element, eResultList.firstChild);
  1789. return result;
  1790. },
  1791. };
  1792.  
  1793. const version = {
  1794. currentProperty: new ObservableValue("2.10.0"),
  1795. get current() {
  1796. return this.currentProperty.value;
  1797. },
  1798. latestProperty: new ObservableValue(config.get("version.latest", "2.10.0")),
  1799. get latest() {
  1800. return this.latestProperty.value;
  1801. },
  1802. lastCheckProperty: new ObservableValue(config.get("version.lastCheck", 0)),
  1803. get lastCheck() {
  1804. return this.lastCheckProperty.value;
  1805. },
  1806. get hasUpdate() {
  1807. return this.compare(this.current, this.latest) < 0;
  1808. },
  1809. compare(a, b) {
  1810. const x = a.split(".").map((s) => parseInt(s, 10));
  1811. const y = b.split(".").map((s) => parseInt(s, 10));
  1812. for (let i = 0; i < 3; i++) {
  1813. if (x[i] < y[i]) {
  1814. return -1;
  1815. }
  1816. else if (x[i] > y[i]) {
  1817. return 1;
  1818. }
  1819. }
  1820. return 0;
  1821. },
  1822. async checkUpdate(force = false) {
  1823. const now = Date.now();
  1824. if (!force && now - version.lastCheck < config.get("version.checkInterval", aDay)) {
  1825. return this.current;
  1826. }
  1827. const packageJson = await fetch("https://raw.githubusercontent.com/magurofly/atcoder-easy-test/main/v2/package.json").then(r => r.json());
  1828. console.log(packageJson);
  1829. const latest = packageJson["version"];
  1830. this.latestProperty.value = latest;
  1831. config.set("version.latest", latest);
  1832. this.lastCheckProperty.value = now;
  1833. config.set("version.lastCheck", now);
  1834. return latest;
  1835. },
  1836. };
  1837. // 更新チェック
  1838. const aDay = 24 * 60 * 60 * 1e3;
  1839. config.registerCount("version.checkInterval", aDay, "Interval [ms] of checking for new version");
  1840. config.get("version.checkInterval", aDay);
  1841. setInterval(() => {
  1842. version.checkUpdate(false);
  1843. }, 60e3);
  1844. settings.add("version", (win) => {
  1845. const root = newElement("div");
  1846. const text = win.document.createTextNode.bind(win.document);
  1847. const textAuto = (property) => {
  1848. const t = text(property.value);
  1849. property.addListener(value => {
  1850. t.textContent = value;
  1851. });
  1852. return t;
  1853. };
  1854. const tCurrent = textAuto(version.currentProperty);
  1855. const tLatest = textAuto(version.latestProperty);
  1856. const tLastCheck = textAuto(version.lastCheckProperty.map(time => new Date(time).toLocaleString()));
  1857. root.appendChild(newElement("p", {}, [
  1858. text("AtCoder Easy Test v"),
  1859. tCurrent,
  1860. ]));
  1861. const updateButton = newElement("a", {
  1862. className: "btn btn-info",
  1863. textContent: "Install",
  1864. href: "https://github.com/magurofly/atcoder-easy-test/raw/main/v2/atcoder-easy-test.user.js",
  1865. target: "_blank",
  1866. });
  1867. const showButton = () => {
  1868. if (version.hasUpdate)
  1869. updateButton.style.display = "inline";
  1870. else
  1871. updateButton.style.display = "none";
  1872. };
  1873. showButton();
  1874. version.lastCheckProperty.addListener(showButton);
  1875. root.appendChild(newElement("p", {}, [
  1876. text("Latest: v"),
  1877. tLatest,
  1878. text(" (Last Check: "),
  1879. tLastCheck,
  1880. text(") "),
  1881. updateButton,
  1882. ]));
  1883. root.appendChild(newElement("p", {}, [
  1884. newElement("a", {
  1885. className: "btn btn-primary",
  1886. textContent: "Check Update",
  1887. onclick() {
  1888. version.checkUpdate(true);
  1889. },
  1890. }),
  1891. ]));
  1892. return root;
  1893. });
  1894.  
  1895. var hTabTemplate = "<div class=\"atcoder-easy-test-result container\">\n <div class=\"row\">\n <div class=\"atcoder-easy-test-result-col-input col-xs-12\" data-if-expected-output=\"col-sm-6 col-sm-push-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Input\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-input form-control\" rows=\"3\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n <div class=\"atcoder-easy-test-result-col-expected-output col-xs-12 col-sm-6 hidden\" data-if-expected-output=\"!hidden col-sm-pull-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Expected Output\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-expected-output form-control\" rows=\"3\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n </div>\n <div class=\"row\"><div class=\"col-sm-6 col-sm-offset-3\">\n <div class=\"panel panel-default\">\n <table class=\"table table-condensed\">\n <tbody>\n <tr>\n <th class=\"text-center\">Exit Code</th>\n <th class=\"text-center\">Exec Time</th>\n <th class=\"text-center\">Memory</th>\n </tr>\n <tr>\n <td class=\"atcoder-easy-test-result-exit-code text-center\"></td>\n <td class=\"atcoder-easy-test-result-exec-time text-center\"></td>\n <td class=\"atcoder-easy-test-result-memory text-center\"></td>\n </tr>\n </tbody>\n </table>\n </div>\n </div></div>\n <div class=\"row\">\n <div class=\"atcoder-easy-test-result-col-output col-xs-12\" data-if-error=\"col-md-6\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Output\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-output form-control\" rows=\"5\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n <div class=\"atcoder-easy-test-result-col-error col-xs-12 col-md-6 hidden\" data-if-error=\"!hidden\">\n <div class=\"form-group\">\n <label class=\"control-label col-xs-12\">\n Standard Error\n <div class=\"col-xs-12\">\n <textarea class=\"atcoder-easy-test-result-error form-control\" rows=\"5\" readonly=\"readonly\"></textarea>\n </div>\n </label>\n </div>\n </div>\n </div>\n</div>";
  1896.  
  1897. function setClassFromData(element, name) {
  1898. const classes = element.dataset[name].split(/\s+/);
  1899. for (let className of classes) {
  1900. let flag = true;
  1901. if (className[0] == "!") {
  1902. className = className.slice(1);
  1903. flag = false;
  1904. }
  1905. element.classList.toggle(className, flag);
  1906. }
  1907. }
  1908. class ResultTabContent {
  1909. _title;
  1910. _uid;
  1911. _element;
  1912. _result;
  1913. constructor() {
  1914. this._uid = Date.now().toString(16);
  1915. this._result = null;
  1916. this._element = html2element(hTabTemplate);
  1917. this._element.id = `atcoder-easy-test-result-${this._uid}`;
  1918. }
  1919. set result(result) {
  1920. this._result = result;
  1921. if (result.status == "AC") {
  1922. this.outputStyle.backgroundColor = "#dff0d8";
  1923. }
  1924. else if (result.status != "OK") {
  1925. this.outputStyle.backgroundColor = "#fcf8e3";
  1926. }
  1927. this.input = result.input;
  1928. if ("expectedOutput" in result)
  1929. this.expectedOutput = result.expectedOutput;
  1930. this.exitCode = result.exitCode;
  1931. if ("execTime" in result)
  1932. this.execTime = `${result.execTime} ms`;
  1933. if ("memory" in result)
  1934. this.memory = `${result.memory} KB`;
  1935. if ("output" in result)
  1936. this.output = result.output;
  1937. if (result.error)
  1938. this.error = result.error;
  1939. }
  1940. get result() {
  1941. return this._result;
  1942. }
  1943. get uid() {
  1944. return this._uid;
  1945. }
  1946. get element() {
  1947. return this._element;
  1948. }
  1949. set title(title) {
  1950. this._title = title;
  1951. }
  1952. get title() {
  1953. return this._title;
  1954. }
  1955. set input(input) {
  1956. this._get("input").value = input;
  1957. }
  1958. get inputStyle() {
  1959. return this._get("input").style;
  1960. }
  1961. set expectedOutput(output) {
  1962. this._get("expected-output").value = output;
  1963. setClassFromData(this._get("col-input"), "ifExpectedOutput");
  1964. setClassFromData(this._get("col-expected-output"), "ifExpectedOutput");
  1965. }
  1966. get expectedOutputStyle() {
  1967. return this._get("expected-output").style;
  1968. }
  1969. set output(output) {
  1970. this._get("output").value = output;
  1971. }
  1972. get outputStyle() {
  1973. return this._get("output").style;
  1974. }
  1975. set error(error) {
  1976. this._get("error").value = error;
  1977. setClassFromData(this._get("col-output"), "ifError");
  1978. setClassFromData(this._get("col-error"), "ifError");
  1979. }
  1980. set exitCode(code) {
  1981. const element = this._get("exit-code");
  1982. element.textContent = code;
  1983. const isSuccess = code == "0";
  1984. element.classList.toggle("bg-success", isSuccess);
  1985. element.classList.toggle("bg-danger", !isSuccess);
  1986. }
  1987. set execTime(time) {
  1988. this._get("exec-time").textContent = time;
  1989. }
  1990. set memory(memory) {
  1991. this._get("memory").textContent = memory;
  1992. }
  1993. _get(name) {
  1994. return this._element.querySelector(`.atcoder-easy-test-result-${name}`);
  1995. }
  1996. }
  1997.  
  1998. var hRoot = "<form id=\"atcoder-easy-test-container\" class=\"form-horizontal\">\n <div class=\"row\">\n <div class=\"col-xs-12 col-lg-8\">\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\">Test Environment</label>\n <div class=\"col-sm-10\">\n <select class=\"form-control\" id=\"atcoder-easy-test-language\"></select>\n </div>\n </div>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-input\">Standard Input</label>\n <div class=\"col-sm-10\">\n <textarea id=\"atcoder-easy-test-input\" name=\"input\" class=\"form-control\" rows=\"3\"></textarea>\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-lg-4\">\n <details close>\n <summary>Expected Output</summary>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-allowable-error-check\">Allowable Error</label>\n <div class=\"col-sm-10\">\n <div class=\"input-group\">\n <span class=\"input-group-addon\">\n <input id=\"atcoder-easy-test-allowable-error-check\" type=\"checkbox\" checked=\"checked\">\n </span>\n <input id=\"atcoder-easy-test-allowable-error\" type=\"text\" class=\"form-control\" value=\"1e-6\">\n </div>\n </div>\n </div>\n <div class=\"form-group\">\n <label class=\"control-label col-sm-2\" for=\"atcoder-easy-test-output\">Expected Output</label>\n <div class=\"col-sm-10\">\n <textarea id=\"atcoder-easy-test-output\" name=\"output\" class=\"form-control\" rows=\"3\"></textarea>\n </div>\n </div>\n </details>\n </div>\n <div class=\"col-xs-12 col-md-6\">\n <div class=\"col-xs-11 col-xs-offset=1\">\n <div class=\"form-group\">\n <a id=\"atcoder-easy-test-run\" class=\"btn btn-primary\">Run</a>\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-md-6\">\n <div class=\"col-xs-11 col-xs-offset=1\">\n <div class=\"form-group text-right\">\n <small>AtCoder Easy Test v<span id=\"atcoder-easy-test-version\"></span></small>\n <a id=\"atcoder-easy-test-setting\" class=\"btn btn-xs btn-default\">Setting</a>\n </div>\n </div>\n </div>\n </div>\n <style>\n #atcoder-easy-test-language {\n border: none;\n background: transparent;\n font: inherit;\n color: #fff;\n }\n #atcoder-easy-test-language option {\n border: none;\n color: #333;\n font: inherit;\n }\n </style>\n</form>";
  1999.  
  2000. var hStyle = "<style>\n.atcoder-easy-test-result textarea {\n font-family: monospace;\n font-weight: normal;\n}\n</style>";
  2001.  
  2002. var hRunButton = "<button type=\"button\" class=\"btn btn-primary btn-sm atcoder-easy-test-btn-run-case\" style=\"vertical-align: top; margin-left: 0.5em\">Run</button>";
  2003.  
  2004. var hTestAndSubmit = "<button type=\"button\" id=\"atcoder-easy-test-btn-test-and-submit\" class=\"btn btn-info btn\" style=\"margin-left: 1rem\" title=\"Ctrl+Enter\" data-toggle=\"tooltip\">Test &amp; Submit</button>";
  2005.  
  2006. var hTestAllSamples = "<button type=\"button\" id=\"atcoder-easy-test-btn-test-all\" class=\"btn btn-default btn-sm\" style=\"margin-left: 1rem\" title=\"Alt+Enter\" data-toggle=\"tooltip\">Test All Samples</button>";
  2007.  
  2008. (async () => {
  2009. const site$1 = await site;
  2010. const doc = unsafeWindow.document;
  2011. // init bottomMenu
  2012. const pBottomMenu = init();
  2013. pBottomMenu.then(bottomMenu => {
  2014. unsafeWindow.bottomMenu = bottomMenu;
  2015. });
  2016. await doneOrFail(pBottomMenu);
  2017. // external interfaces
  2018. unsafeWindow.codeRunner = codeRunner;
  2019. doc.head.appendChild(html2element(hStyle));
  2020. // interface
  2021. const atCoderEasyTest = {
  2022. version,
  2023. site: site$1,
  2024. config,
  2025. codeSaver,
  2026. enableButtons() {
  2027. events.trig("enable");
  2028. },
  2029. disableButtons() {
  2030. events.trig("disable");
  2031. },
  2032. runCount: 0,
  2033. runTest(title, language, sourceCode, input, output = null, options = { trim: true, split: true, }) {
  2034. this.disableButtons();
  2035. const content = new ResultTabContent();
  2036. const pTab = pBottomMenu.then(bottomMenu => bottomMenu.addTab("easy-test-result-" + content.uid, `#${++this.runCount} ${title}`, content.element, { active: true, closeButton: true }));
  2037. const pResult = codeRunner.run(language, sourceCode, input, output, options);
  2038. pResult.then(result => {
  2039. content.result = result;
  2040. if (result.status == "AC") {
  2041. pTab.then(tab => tab.color = "#dff0d8");
  2042. }
  2043. else if (result.status != "OK") {
  2044. pTab.then(tab => tab.color = "#fcf8e3");
  2045. }
  2046. }).finally(() => {
  2047. this.enableButtons();
  2048. });
  2049. return [pResult, pTab];
  2050. }
  2051. };
  2052. unsafeWindow.atCoderEasyTest = atCoderEasyTest;
  2053. // place "Easy Test" tab
  2054. {
  2055. // declare const hRoot: string;
  2056. const root = html2element(hRoot);
  2057. const E = (id) => root.querySelector(`#atcoder-easy-test-${id}`);
  2058. const eLanguage = E("language");
  2059. const eInput = E("input");
  2060. const eAllowableErrorCheck = E("allowable-error-check");
  2061. const eAllowableError = E("allowable-error");
  2062. const eOutput = E("output");
  2063. const eRun = E("run");
  2064. const eSetting = E("setting");
  2065. const eVersion = E("version");
  2066. eVersion.textContent = atCoderEasyTest.version.current;
  2067. events.on("enable", () => {
  2068. eRun.classList.remove("disabled");
  2069. });
  2070. events.on("disable", () => {
  2071. eRun.classList.add("disabled");
  2072. });
  2073. eSetting.addEventListener("click", () => {
  2074. settings.open();
  2075. });
  2076. // バージョン確認
  2077. {
  2078. let button = null;
  2079. const showButton = () => {
  2080. if (!version.hasUpdate)
  2081. return;
  2082. if (button) {
  2083. button.textContent = `Update to v${version.latest}`;
  2084. return;
  2085. }
  2086. console.info(`AtCoder Easy Test: New version available: v${version}`);
  2087. button = newElement("a", {
  2088. href: "https://github.com/magurofly/atcoder-easy-test/raw/main/v2/atcoder-easy-test.user.js",
  2089. target: "_blank",
  2090. className: "btn btn-xs btn-info",
  2091. textContent: `Update to v${version.latest}`,
  2092. });
  2093. eVersion.insertAdjacentElement("afterend", button);
  2094. };
  2095. version.latestProperty.addListener(showButton);
  2096. showButton();
  2097. }
  2098. // 言語選択関係
  2099. {
  2100. eLanguage.addEventListener("change", async () => {
  2101. const langSelection = config.get("langSelection", {});
  2102. langSelection[site$1.language.value] = eLanguage.value;
  2103. config.set("langSelection", langSelection);
  2104. });
  2105. async function setLanguage() {
  2106. const languageId = site$1.language.value;
  2107. while (eLanguage.firstChild)
  2108. eLanguage.removeChild(eLanguage.firstChild);
  2109. try {
  2110. const langs = await codeRunner.getEnvironment(languageId);
  2111. console.log(`language: ${langs[1]} (${langs[0]})`);
  2112. // add <option>
  2113. for (const [languageId, label] of langs) {
  2114. const option = document.createElement("option");
  2115. option.value = languageId;
  2116. option.textContent = label;
  2117. eLanguage.appendChild(option);
  2118. }
  2119. // load
  2120. const langSelection = config.get("langSelection", {});
  2121. if (languageId in langSelection) {
  2122. const prev = langSelection[languageId];
  2123. const [lang, _] = langs.find(([lang, label]) => lang == prev);
  2124. if (lang)
  2125. eLanguage.value = lang;
  2126. }
  2127. events.trig("enable");
  2128. }
  2129. catch (error) {
  2130. console.log(`language: ? (${languageId})`);
  2131. console.error(error);
  2132. const option = document.createElement("option");
  2133. option.className = "fg-danger";
  2134. option.textContent = error;
  2135. eLanguage.appendChild(option);
  2136. events.trig("disable");
  2137. }
  2138. }
  2139. site$1.language.addListener(() => setLanguage());
  2140. eAllowableError.disabled = !eAllowableErrorCheck.checked;
  2141. eAllowableErrorCheck.addEventListener("change", event => {
  2142. eAllowableError.disabled = !eAllowableErrorCheck.checked;
  2143. });
  2144. }
  2145. // テスト実行
  2146. function runTest(title, input, output = null) {
  2147. const options = { trim: true, split: true, };
  2148. if (eAllowableErrorCheck.checked) {
  2149. options.allowableError = parseFloat(eAllowableError.value);
  2150. }
  2151. return atCoderEasyTest.runTest(title, eLanguage.value, site$1.sourceCode, input, output, options);
  2152. }
  2153. function runAllCases(testcases) {
  2154. const pairs = testcases.map(testcase => runTest(testcase.title, testcase.input, testcase.output));
  2155. resultList.addResult(pairs);
  2156. return Promise.all(pairs.map(([pResult, _]) => pResult.then(result => {
  2157. if (result.status == "AC")
  2158. return Promise.resolve(result);
  2159. else
  2160. return Promise.reject(result);
  2161. })));
  2162. }
  2163. eRun.addEventListener("click", _ => {
  2164. const title = "Run";
  2165. const input = eInput.value;
  2166. const output = eOutput.value;
  2167. runTest(title, input, output || null);
  2168. });
  2169. await doneOrFail(pBottomMenu.then(bottomMenu => bottomMenu.addTab("easy-test", "Easy Test", root)));
  2170. // place "Run" button on each sample
  2171. for (const testCase of site$1.testCases) {
  2172. const eRunButton = html2element(hRunButton);
  2173. eRunButton.addEventListener("click", async () => {
  2174. const [pResult, pTab] = runTest(testCase.title, testCase.input, testCase.output);
  2175. await pResult;
  2176. (await pTab).show();
  2177. });
  2178. testCase.anchor.insertAdjacentElement("afterend", eRunButton);
  2179. events.on("disable", () => {
  2180. eRunButton.classList.add("disabled");
  2181. });
  2182. events.on("enable", () => {
  2183. eRunButton.classList.remove("disabled");
  2184. });
  2185. }
  2186. // place "Test & Submit" button
  2187. {
  2188. const button = html2element(hTestAndSubmit);
  2189. site$1.testButtonContainer.appendChild(button);
  2190. const testAndSubmit = async () => {
  2191. await runAllCases(site$1.testCases);
  2192. site$1.submit();
  2193. };
  2194. button.addEventListener("click", testAndSubmit);
  2195. events.on("testAndSubmit", testAndSubmit);
  2196. events.on("disable", () => button.classList.add("disabled"));
  2197. events.on("enable", () => button.classList.remove("disabled"));
  2198. }
  2199. // place "Test All Samples" button
  2200. {
  2201. const button = html2element(hTestAllSamples);
  2202. site$1.testButtonContainer.appendChild(button);
  2203. const testAllSamples = () => runAllCases(site$1.testCases);
  2204. button.addEventListener("click", testAllSamples);
  2205. events.on("testAllSamples", testAllSamples);
  2206. events.on("disable", () => button.classList.add("disabled"));
  2207. events.on("enable", () => button.classList.remove("disabled"));
  2208. }
  2209. }
  2210. // place "Restore Last Play" button
  2211. try {
  2212. const restoreButton = doc.createElement("a");
  2213. restoreButton.className = "btn btn-danger btn-sm";
  2214. restoreButton.textContent = "Restore Last Play";
  2215. restoreButton.addEventListener("click", async () => {
  2216. try {
  2217. const lastCode = await codeSaver.restore();
  2218. if (site$1.sourceCode.length == 0 || confirm("Your current code will be replaced. Are you sure?")) {
  2219. site$1.sourceCode = lastCode;
  2220. }
  2221. }
  2222. catch (reason) {
  2223. alert(reason);
  2224. }
  2225. });
  2226. site$1.sideButtonContainer.appendChild(restoreButton);
  2227. }
  2228. catch (e) {
  2229. console.error(e);
  2230. }
  2231. // キーボードショートカット
  2232. config.registerFlag("ui.useKeyboardShortcut", true, "Use Keyboard Shortcuts");
  2233. unsafeWindow.addEventListener("keydown", (event) => {
  2234. if (config.get("ui.useKeyboardShortcut", true)) {
  2235. if (event.key == "Enter" && event.ctrlKey) {
  2236. events.trig("testAndSubmit");
  2237. }
  2238. else if (event.key == "Enter" && event.altKey) {
  2239. events.trig("testAllSamples");
  2240. }
  2241. else if (event.key == "Escape" && event.altKey) {
  2242. pBottomMenu.then(bottomMenu => bottomMenu.toggle());
  2243. }
  2244. }
  2245. });
  2246. })();
  2247. })();