aboutsummaryrefslogtreecommitdiffstats
path: root/js/src
diff options
context:
space:
mode:
Diffstat (limited to 'js/src')
-rw-r--r--js/src/countdown.js79
-rw-r--r--js/src/errors.js49
-rw-r--r--js/src/fouc.js51
-rw-r--r--js/src/jams.js147
-rw-r--r--js/src/revision_diff.js83
-rw-r--r--js/src/wiki.js17
6 files changed, 426 insertions, 0 deletions
diff --git a/js/src/countdown.js b/js/src/countdown.js
new file mode 100644
index 00000000..7eaa650c
--- /dev/null
+++ b/js/src/countdown.js
@@ -0,0 +1,79 @@
+"use strict";
+
+(function(){ // Use a closure to avoid polluting global scope
+ // TODO: This needs to be built into the jams system
+ const startjam = new Date(Date.UTC(2018, 2, 23));
+ const endjam = new Date(Date.UTC(2018, 2, 26));
+
+ const now = Date.now();
+ let goal;
+
+ if (now + 1000 < endjam.getTime()) { // Only do anything if the jam hasn't ended
+ UIkit.notification( // Spawn the notification
+ {
+ "message": ""
+ + "<div class='uk-text-center'>"
+ + " <span id=\"countdown-title\" class=\"uk-text-center\">"
+ + " <a href=\"/info/jams\">Code Jam</a> Countdown"
+ + " </span>"
+ + " <p class='uk-text-large' id=\"countdown-remaining\">...</p>"
+ + "<small style='font-size: 0.6em;'>(Tap/click to dismiss)</small>"
+ + "</div>",
+ "pos": "bottom-right",
+ "timeout": endjam - now
+ }
+ );
+
+ const heading = document.getElementById("countdown-title");
+
+ if (now > startjam.getTime()) { // Jam's already started
+ heading.innerHTML = "Current <a href=\"/info/jams\">code jam</a> ends in...";
+ goal = endjam.getTime();
+ } else {
+ heading.innerHTML = "Next <a href=\"/info/jams\">code jam</a> starts in...";
+ goal = startjam.getTime();
+ }
+
+ const refreshCountdown = setInterval(() => { // Create a repeating task
+ let delta = goal - Date.now(); // Time until the goal is met
+
+ if (delta <= 1000) { // Goal has been met, best reload
+ clearInterval(refreshCountdown);
+ return location.reload();
+ }
+
+ let days = Math.floor(delta / (24 * 60 * 60 * 1000));
+ delta -= days * (24 * 60 * 60 * 1000);
+
+ let hours = Math.floor(delta / (60 * 60 * 1000));
+ delta -= hours * (60 * 60 * 1000);
+
+ let minutes = Math.floor(delta / (60 * 1000));
+ delta -= minutes * (60 * 1000);
+
+ let seconds = Math.floor(delta / 1000);
+
+ if (days < 10) {
+ days = `0${days}`;
+ }
+
+ if (hours < 10) {
+ hours = `0${hours}`;
+ }
+
+ if (minutes < 10) {
+ minutes = `0${minutes}`;
+ }
+
+ if (seconds < 10) {
+ seconds = `0${seconds}`;
+ }
+
+ try {
+ document.getElementById("countdown-remaining").innerHTML = `${days}:${hours}:${minutes}:${seconds}`;
+ } catch (e) { // Notification was probably closed, so we can stop counting
+ return clearInterval(refreshCountdown);
+ }
+ }, 500);
+ }
+})();
diff --git a/js/src/errors.js b/js/src/errors.js
new file mode 100644
index 00000000..492285d4
--- /dev/null
+++ b/js/src/errors.js
@@ -0,0 +1,49 @@
+"use strict";
+
+/* exported error_typewriter */
+
+function error_typewriter() {
+ const app = document.getElementById("error");
+
+ const typewriter = new Typewriter(app, {
+ "loop": false,
+ "deleteSpeed": 40,
+ "typingSpeed": "natural",
+ "devMode": false
+ });
+
+ function closeWindow() {
+ const app = document.getElementById("win");
+ const current_class = app.getAttribute("class");
+ app.setAttribute("class", `${current_class } uk-animation-scale-up uk-animation-reverse`);
+ typewriter.stop();
+ }
+
+ document.getElementById("terminal-close").onclick = closeWindow;
+
+ typewriter.appendText("Python 3.6.4 (default, Jan 5 2018, 02:35:40)\n")
+ .appendText("[GCC 7.2.1 20171224] on darwin\n")
+ .appendText("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n")
+ .appendText(">>> ")
+ .pauseFor(1000)
+ .typeString("impor requests")
+ .deleteChars(9)
+ .typeString("t requests\n")
+ .appendText(">>> ")
+ .pauseFor(750)
+ .changeSettings({"typingSpeed": "natural"})
+ .typeString(`response = requests.${ window._RequestMethod }('https://pythim`)
+ .deleteChars(2)
+ .typeString("ondiscord.con/")
+ .deleteChars(2)
+ .typeString(`m${ window._Path }')\n`)
+ .pauseFor(1000)
+ .appendText(`&lt;Response [${ window._Code }]&gt;\n>>> `)
+ .typeString("# hmmmm")
+ .pauseFor(1000)
+ .deleteChars(7)
+ .pauseFor(1000)
+ .typeString("response.text\n")
+ .appendText(`${ window._ErrorMsg }\n>>> `)
+ .start();
+}
diff --git a/js/src/fouc.js b/js/src/fouc.js
new file mode 100644
index 00000000..3611ec27
--- /dev/null
+++ b/js/src/fouc.js
@@ -0,0 +1,51 @@
+"use strict";
+
+function getScript(url, integrity, cross_origin) {
+ const script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = url;
+ script.defer = true;
+
+ if (integrity !== undefined) {
+ script.integrity = integrity;
+ }
+
+ if (cross_origin !== undefined) {
+ script.crossOrigin = cross_origin;
+ }
+
+ document.getElementsByTagName("head")[0].appendChild(script);
+}
+
+function setClass(selector, my_class) {
+ const element = document.querySelector(selector);
+ // console.log(element);
+ element.className = my_class;
+}
+
+function removeClass(selector, my_class) {
+ const element = document.querySelector(selector);
+ const reg = new RegExp(`(^| )${my_class}($| )`, "g");
+ element.className = element.className.replace(reg, " ");
+}
+
+// hide the html when the page loads, but only if js is turned on.
+setClass("html", "prevent-fouc");
+
+// when the DOM has finished loading, unhide the html
+document.onreadystatechange = function () {
+ if (document.readyState === "interactive") {
+ removeClass("html", "prevent-fouc");
+ getScript(
+ "https://pro.fontawesome.com/releases/v5.0.13/js/all.js", // URL
+ "sha384-d84LGg2pm9KhR4mCAs3N29GQ4OYNy+K+FBHX8WhimHpPm86c839++MDABegrZ3gn", // Integrity
+ "anonymous" // Cross-origin
+ );
+ getScript(
+ "https://cdnjs.cloudflare.com/ajax/libs/ace/1.3.3/ace.js"
+ );
+ getScript(
+ "https://cdn.jsdelivr.net/npm/flatpickr"
+ );
+ }
+};
diff --git a/js/src/jams.js b/js/src/jams.js
new file mode 100644
index 00000000..ee2ee2ea
--- /dev/null
+++ b/js/src/jams.js
@@ -0,0 +1,147 @@
+"use strict";
+
+/* exported JamActions */
+
+class JamActions {
+ constructor(url, csrf_token) {
+ this.url = url;
+ this.csrf_token = csrf_token;
+ }
+
+ send(action, method, data, callback) {
+ data["action"] = action;
+
+ $.ajax(this.url, {
+ "data": data,
+ "dataType": "json",
+ "headers": {"X-CSRFToken": this.csrf_token},
+ "method": method,
+ }).done(data => {
+ if ("error_code" in data) {
+ return callback(false, data);
+ }
+
+ return callback(true, data);
+ }).fail(() => callback(false));
+ }
+
+ send_json(action, method, data, callback) {
+ data["action"] = action;
+
+ $.ajax(this.url, {
+ "data": JSON.stringify(data),
+ "dataType": "json",
+ "headers": {"X-CSRFToken": this.csrf_token},
+ "method": method
+ }).done(data => {
+ if ("error_code" in data) {
+ return callback(false, data);
+ }
+
+ return callback(true, data);
+ }).fail(() => callback(false));
+ }
+
+ set_state(jam, state, callback) {
+ this.send(
+ "state",
+ "POST",
+ {
+ "jam": jam,
+ "state": state
+ },
+ callback
+ );
+ }
+
+ get_questions(callback) {
+ this.send(
+ "questions",
+ "GET",
+ {},
+ callback
+ );
+ }
+
+ create_question(data, callback) {
+ this.send_json(
+ "questions",
+ "POST",
+ data,
+ callback
+ );
+ }
+
+ delete_question(id, callback) {
+ this.send(
+ "question",
+ "DELETE",
+ {"id": id},
+ callback
+ );
+ }
+
+ associate_question(form, question, callback) {
+ this.send(
+ "associate_question",
+ "POST",
+ {
+ "form": form,
+ "question": question,
+ },
+ callback
+ );
+ }
+
+ disassociate_question(form, question, callback) {
+ this.send(
+ "disassociate_question",
+ "POST",
+ {
+ "form": form,
+ "question": question,
+ },
+ callback
+ );
+ }
+
+ create_infraction(id, reason, number, callback) {
+ this.send(
+ "infraction",
+ "POST",
+ {
+ "participant": id,
+ "reason": reason,
+ "number": number
+ },
+ callback
+ );
+ }
+
+ delete_infraction(id, callback) {
+ this.send(
+ "infraction",
+ "DELETE",
+ {"id": id},
+ callback
+ );
+ }
+
+ approve_application(id, callback) {
+ this.send(
+ "approve_application",
+ "POST",
+ {"id": id},
+ callback
+ );
+ }
+
+ unapprove_application(id, callback) {
+ this.send(
+ "unapprove_application",
+ "POST",
+ {"id": id},
+ callback
+ );
+ }
+}
diff --git a/js/src/revision_diff.js b/js/src/revision_diff.js
new file mode 100644
index 00000000..f124fbec
--- /dev/null
+++ b/js/src/revision_diff.js
@@ -0,0 +1,83 @@
+"use strict";
+
+/* exported revision_diff */
+
+function revision_diff(revisions) {
+ const buttons = document.querySelectorAll("td input"); // Fetch all radio buttons
+ const id_reg = /compare-(before|after)-([\w|-]+)/; // Matches compare-after/before-ID
+
+
+ function getRevisionId(element){
+ const e = element.id.match(id_reg); // Match ID with RegExp
+ return [e[1], e[2]]; // e is in format of [full id, after/before, ID] we only want ID & mode
+ }
+
+ function getRevision(id) {
+ const e = revisions.filter((x) => {
+ // Filter through all revisions to find the selected one (revisions in declared in the template)
+ return x.id === id;
+ });
+ return e[0];
+ }
+
+ function radioButtonChecked(element) {
+ const id = getRevisionId(element);
+ const rev = getRevision(id[1]);
+ if (id[0] === "after"){
+ /*
+ * Deselect the opposite checkbox to the one which has been checked
+ * because we don't want checking of the same revision
+ */
+
+ document.querySelector(`#compare-before-${id[1]}`).checked = false;
+
+ buttons.forEach((e) => {
+ if (getRevisionId(e)[0] === "after" && e.id !== element.id) { // Deselect all checkboxes in the same row
+ e.checked = false;
+ }
+ });
+ } else { // This else does the same as above but for the before column
+ document.querySelector(`#compare-after-${id[1]}`).checked = false;
+ buttons.forEach((e) => {
+ if (getRevisionId(e)[0] === "before" && e.id !== element.id) {
+ e.checked = false;
+ }
+
+ // This makes sure that you do not compare a new revision with an old one
+ if (getRevisionId(e)[0] === "after") {
+ const tmprev = getRevision(getRevisionId(e)[1]);
+ // console.log(tmprev);
+ if (tmprev.date <= rev.date) {
+ document.querySelector(`#${e.id}`).setAttribute("disabled", "");
+ } else {
+ document.querySelector(`#${e.id}`).removeAttribute("disabled");
+ }
+ }
+ });
+ }
+
+ let aft, bef;
+
+ buttons.forEach((button) => { // Find the selected posts
+ const id = getRevisionId(button);
+ if (button.checked && id[0] === "before") {
+ bef = id[1];
+ }
+
+ if (button.checked && id[0] === "after") {
+ aft = id[1];
+ }
+ });
+
+ // Switch the buttons HREF to point to the correct compare URL
+ document.getElementById("compare-submit").href = `/history/compare/${bef}/${aft}`;
+
+ }
+
+ buttons.forEach((button) => {
+ button.checked = false; // Some browsers remember if a button is checked.
+ button.onchange = function() {
+ radioButtonChecked(button);
+ };
+ });
+}
diff --git a/js/src/wiki.js b/js/src/wiki.js
new file mode 100644
index 00000000..f4bc18e8
--- /dev/null
+++ b/js/src/wiki.js
@@ -0,0 +1,17 @@
+"use strict";
+
+/* exported wiki_sidebar */
+
+function wiki_sidebar(){
+ const visible_class = "uk-visible@s";
+ const sidebar = document.getElementById("wiki-sidebar");
+ const display_button = document.getElementById("wiki-sidebar-button");
+
+ display_button.onclick = function() {
+ if (sidebar.classList.contains(visible_class)) {
+ sidebar.classList.remove(visible_class);
+ } else {
+ sidebar.classList.add(visible_class);
+ }
+ };
+}