mirror of
https://github.com/aljazceru/CTFd.git
synced 2026-02-01 12:24:31 +01:00
5ce3003b Merge pull request #47 from aCursedComrade/patch-1 c9887cb1 Fix team template git-subtree-dir: CTFd/themes/core-beta git-subtree-split: 5ce3003b4d68352e629ee2d390bc999e7d6b071e
254 lines
6.2 KiB
JavaScript
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();
|