232 lines
7.1 KiB
TypeScript
232 lines
7.1 KiB
TypeScript
// pastebin.run
|
|
// Copyright (C) 2020 Konrad Borowski
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import createTextareaEditor from "../editor-types/textarea";
|
|
import getLanguage from "./get-language";
|
|
import Output from "./output";
|
|
import WrapperButtons from "./wrapper-buttons";
|
|
import {
|
|
EditorType,
|
|
types,
|
|
getCurrentEditor,
|
|
onChange,
|
|
} from "../../editor-types";
|
|
import { OutputWrapper, Wrapper } from "./types";
|
|
|
|
class Editor {
|
|
languageSelector: HTMLSelectElement;
|
|
wrapperButtons: WrapperButtons;
|
|
codeElement: HTMLTextAreaElement;
|
|
output: Output;
|
|
autodeleteText: NodeListOf<HTMLElement>;
|
|
submitButtons: NodeListOf<HTMLInputElement>;
|
|
detailsElement: HTMLDetailsElement;
|
|
stdinElement: HTMLTextAreaElement;
|
|
editor: EditorType;
|
|
currentLanguage: string | null = null;
|
|
abortEval: AbortController | null = null;
|
|
isHelloWorld: boolean = false;
|
|
|
|
initialize(form: HTMLElement) {
|
|
this.languageSelector = form.querySelector("#language");
|
|
this.wrapperButtons = new WrapperButtons(
|
|
form.querySelector("#wrapper-buttons"),
|
|
this.run.bind(this)
|
|
);
|
|
this.codeElement = form.querySelector("#code");
|
|
this.initializeEditor(createTextareaEditor);
|
|
onChange((editor) => this.changeEditor(editor));
|
|
this.initConfiguredEditor();
|
|
this.output = Output.addTo(form.querySelector("#split"));
|
|
const output = document.querySelector<HTMLInputElement>("#dboutput");
|
|
if (output) {
|
|
this.displayOutput(
|
|
{},
|
|
{
|
|
output: output.value,
|
|
status: +document.querySelector<HTMLInputElement>("#dbstatus")?.value,
|
|
}
|
|
);
|
|
}
|
|
this.autodeleteText = form.querySelectorAll(".autodelete-text");
|
|
this.submitButtons = form.querySelectorAll("[type=submit]");
|
|
for (const submit of this.submitButtons) {
|
|
submit.disabled = true;
|
|
}
|
|
form.addEventListener("submit", () => {
|
|
if (this.output.json && !this.output.wrapper.isFormatter) {
|
|
const types: ("output" | "status")[] = ["output", "status"];
|
|
for (const name of types) {
|
|
const elem =
|
|
form.querySelector<HTMLInputElement>(`[name=${name}]`) ||
|
|
document.createElement("input");
|
|
elem.type = "hidden";
|
|
elem.name = name;
|
|
elem.value = this.output.json[name].toString();
|
|
form.append(elem);
|
|
}
|
|
} else {
|
|
this.stdinElement.value = "";
|
|
}
|
|
});
|
|
this.detailsElement = document.createElement("details");
|
|
const summary = document.createElement("summary");
|
|
summary.textContent = "Standard input";
|
|
this.stdinElement = document.createElement("textarea");
|
|
this.stdinElement.name = "stdin";
|
|
this.stdinElement.addEventListener("change", () => {
|
|
this.isHelloWorld = false;
|
|
this.changeToLookLikeNewPaste();
|
|
});
|
|
this.detailsElement.append(summary, this.stdinElement);
|
|
const dbStdin = document.querySelector<HTMLInputElement>("#dbstdin")?.value;
|
|
if (dbStdin) {
|
|
this.stdinElement.value = dbStdin;
|
|
this.detailsElement.open = true;
|
|
} else {
|
|
this.detailsElement.style.display = "none";
|
|
}
|
|
form.querySelector("#extrafields").append(this.detailsElement);
|
|
this.assignEvents();
|
|
this.updateLanguage();
|
|
}
|
|
|
|
async initConfiguredEditor() {
|
|
this.changeEditor(await types[getCurrentEditor()].createView());
|
|
}
|
|
|
|
changeEditor(
|
|
createEditor: (
|
|
textArea: HTMLTextAreaElement,
|
|
onChange: () => void
|
|
) => EditorType
|
|
) {
|
|
this.editor.unload();
|
|
this.initializeEditor(createEditor);
|
|
}
|
|
|
|
initializeEditor(
|
|
createEditor: (
|
|
textArea: HTMLTextAreaElement,
|
|
onChange: () => void
|
|
) => EditorType
|
|
) {
|
|
this.editor = createEditor(this.codeElement, () => {
|
|
this.changeToLookLikeNewPaste();
|
|
this.isHelloWorld = false;
|
|
});
|
|
if (this.currentLanguage) {
|
|
this.editor.setLanguage(this.currentLanguage);
|
|
}
|
|
}
|
|
|
|
setLanguage(language: string) {
|
|
this.currentLanguage = language;
|
|
this.editor.setLanguage(language);
|
|
}
|
|
|
|
changeToLookLikeNewPaste() {
|
|
this.output.clear();
|
|
this.editor.update();
|
|
for (const element of this.autodeleteText) {
|
|
element.style.display = "none";
|
|
}
|
|
for (const submit of this.submitButtons) {
|
|
submit.disabled = false;
|
|
}
|
|
}
|
|
|
|
assignEvents() {
|
|
this.languageSelector.addEventListener("change", () => {
|
|
this.updateLanguage();
|
|
this.changeToLookLikeNewPaste();
|
|
});
|
|
}
|
|
|
|
async updateLanguage() {
|
|
this.wrapperButtons.clear();
|
|
const identifier = this.getLanguageIdentifier();
|
|
this.setLanguage(identifier);
|
|
const isStillValid = () => identifier === this.getLanguageIdentifier();
|
|
const language = await getLanguage(identifier, isStillValid);
|
|
// This deals with user changing the language after asynchronous event
|
|
if (isStillValid()) {
|
|
this.detailsElement.style.display = language.implementations.length
|
|
? "block"
|
|
: "none";
|
|
this.wrapperButtons.update(language.implementations);
|
|
if (this.isHelloWorld || this.editor.getValue() === "") {
|
|
this.editor.setValue(language.helloWorld);
|
|
this.isHelloWorld = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
getLanguageIdentifier() {
|
|
return this.languageSelector.selectedOptions[0].value;
|
|
}
|
|
|
|
async run(wrapper: Wrapper, compilerOptions: string) {
|
|
this.output.spin();
|
|
this.editor.update();
|
|
if (this.abortEval) {
|
|
this.abortEval.abort();
|
|
}
|
|
this.abortEval = new AbortController();
|
|
const body = new URLSearchParams();
|
|
body.append("compilerOptions", compilerOptions);
|
|
body.append("code", this.editor.getValue());
|
|
body.append("stdin", this.stdinElement.value);
|
|
const parameters = {
|
|
method: "POST",
|
|
body,
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
},
|
|
signal: this.abortEval.signal,
|
|
};
|
|
const path = `/api/v0/run/${wrapper.identifier}`;
|
|
let response;
|
|
try {
|
|
response = await (await fetch(path, parameters)).json();
|
|
} catch (e) {
|
|
if (e.name === "AbortError") {
|
|
return;
|
|
}
|
|
this.output.error();
|
|
throw e;
|
|
}
|
|
if (wrapper.isFormatter) {
|
|
this.editor.setValue(
|
|
response.output.replace(/\x7FE[^]*?(?:\x7FO|$)/g, "")
|
|
);
|
|
}
|
|
this.displayOutput(wrapper, response);
|
|
}
|
|
|
|
displayOutput(
|
|
wrapper: OutputWrapper,
|
|
response: { output: string; status: number }
|
|
) {
|
|
this.output.display(wrapper, response);
|
|
this.editor.update();
|
|
}
|
|
}
|
|
|
|
export default function createEditor(form: HTMLElement) {
|
|
new Editor().initialize(form);
|
|
}
|