Files
CTFd/assets/js/challenges.js
Kevin Chung a64e7d51ef Squashed 'CTFd/themes/core-beta/' changes from 9126d77d..5ce3003b
5ce3003b Merge pull request #47 from aCursedComrade/patch-1
c9887cb1 Fix team template

git-subtree-dir: CTFd/themes/core-beta
git-subtree-split: 5ce3003b4d68352e629ee2d390bc999e7d6b071e
2023-06-11 15:56:28 -04:00

254 lines
6.2 KiB
JavaScript

import Alpine from "alpinejs";
import dayjs from "dayjs";
import CTFd from "./index";
import { Modal, Tab } from "bootstrap";
import highlight from "./theme/highlight";
function addTargetBlank(html) {
let dom = new DOMParser();
let view = dom.parseFromString(html, "text/html");
let links = view.querySelectorAll('a[href*="://"]');
links.forEach(link => {
link.setAttribute("target", "_blank");
});
return view.documentElement.outerHTML;
}
window.Alpine = Alpine;
Alpine.store("challenge", {
data: {
view: "",
},
});
Alpine.data("Hint", () => ({
id: null,
html: null,
async showHint(event) {
if (event.target.open) {
let response = await CTFd.pages.challenge.loadHint(this.id);
let hint = response.data;
if (hint.content) {
this.html = addTargetBlank(hint.html);
} else {
let answer = await CTFd.pages.challenge.displayUnlock(this.id);
if (answer) {
let unlock = await CTFd.pages.challenge.loadUnlock(this.id);
if (unlock.success) {
let response = await CTFd.pages.challenge.loadHint(this.id);
let hint = response.data;
this.html = addTargetBlank(hint.html);
} else {
event.target.open = false;
CTFd._functions.challenge.displayUnlockError(unlock);
}
} else {
event.target.open = false;
}
}
}
},
}));
Alpine.data("Challenge", () => ({
id: null,
next_id: null,
submission: "",
tab: null,
solves: [],
response: null,
async init() {
highlight();
},
getStyles() {
let styles = {
"modal-dialog": true,
};
try {
let size = CTFd.config.themeSettings.challenge_window_size;
switch (size) {
case "sm":
styles["modal-sm"] = true;
break;
case "lg":
styles["modal-lg"] = true;
break;
case "xl":
styles["modal-xl"] = true;
break;
default:
break;
}
} catch (error) {
// Ignore errors with challenge window size
console.log("Error processing challenge_window_size");
console.log(error);
}
return styles;
},
async init() {
highlight();
},
async showChallenge() {
new Tab(this.$el).show();
},
async showSolves() {
this.solves = await CTFd.pages.challenge.loadSolves(this.id);
this.solves.forEach(solve => {
solve.date = dayjs(solve.date).format("MMMM Do, h:mm:ss A");
return solve;
});
new Tab(this.$el).show();
},
getNextId() {
let data = Alpine.store("challenge").data;
return data.next_id;
},
async nextChallenge() {
let modal = Modal.getOrCreateInstance("[x-ref='challengeWindow']");
// TODO: Get rid of this private attribute access
// See https://github.com/twbs/bootstrap/issues/31266
modal._element.addEventListener(
"hidden.bs.modal",
event => {
// Dispatch load-challenge event to call loadChallenge in the ChallengeBoard
Alpine.nextTick(() => {
this.$dispatch("load-challenge", this.getNextId());
});
},
{ once: true }
);
modal.hide();
},
async submitChallenge() {
this.response = await CTFd.pages.challenge.submitChallenge(
this.id,
this.submission
);
await this.renderSubmissionResponse();
},
async renderSubmissionResponse() {
if (this.response.data.status === "correct") {
this.submission = "";
}
// Dispatch load-challenges event to call loadChallenges in the ChallengeBoard
this.$dispatch("load-challenges");
},
}));
Alpine.data("ChallengeBoard", () => ({
loaded: false,
challenges: [],
challenge: null,
async init() {
this.challenges = await CTFd.pages.challenges.getChallenges();
this.loaded = true;
if (window.location.hash) {
let chalHash = decodeURIComponent(window.location.hash.substring(1));
let idx = chalHash.lastIndexOf("-");
if (idx >= 0) {
let pieces = [chalHash.slice(0, idx), chalHash.slice(idx + 1)];
let id = pieces[1];
await this.loadChallenge(id);
}
}
},
getCategories() {
const categories = [];
this.challenges.forEach(challenge => {
const { category } = challenge;
if (!categories.includes(category)) {
categories.push(category);
}
});
try {
const f = CTFd.config.themeSettings.challenge_category_order;
if (f) {
const getSort = new Function(`return (${f})`);
categories.sort(getSort());
}
} catch (error) {
// Ignore errors with theme category sorting
console.log("Error running challenge_category_order function");
console.log(error);
}
return categories;
},
getChallenges(category) {
let challenges = this.challenges;
if (category) {
challenges = this.challenges.filter(challenge => challenge.category === category);
}
try {
const f = CTFd.config.themeSettings.challenge_order;
if (f) {
const getSort = new Function(`return (${f})`);
challenges.sort(getSort());
}
} catch (error) {
// Ignore errors with theme challenge sorting
console.log("Error running challenge_order function");
console.log(error);
}
return challenges;
},
async loadChallenges() {
this.challenges = await CTFd.pages.challenges.getChallenges();
},
async loadChallenge(challengeId) {
await CTFd.pages.challenge.displayChallenge(challengeId, challenge => {
challenge.data.view = addTargetBlank(challenge.data.view);
Alpine.store("challenge").data = challenge.data;
// nextTick is required here because we're working in a callback
Alpine.nextTick(() => {
let modal = Modal.getOrCreateInstance("[x-ref='challengeWindow']");
// TODO: Get rid of this private attribute access
// See https://github.com/twbs/bootstrap/issues/31266
modal._element.addEventListener(
"hidden.bs.modal",
event => {
// Remove location hash
history.replaceState(null, null, " ");
},
{ once: true }
);
modal.show();
history.replaceState(null, null, `#${challenge.data.name}-${challengeId}`);
});
});
},
}));
Alpine.start();