pastebin/js/views/editor/editor.ts
2022-05-06 07:46:15 +02:00

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);
}