updates!
1. Removed all codes which adds ability to execute programs and display results 2. Added more options for the save duration
This commit is contained in:
parent
ba55a222f5
commit
be4da1823f
|
@ -46,19 +46,6 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ammonia"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170"
|
||||
dependencies = [
|
||||
"html5ever",
|
||||
"maplit",
|
||||
"once_cell",
|
||||
"tendril",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
|
@ -257,7 +244,7 @@ checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde"
|
|||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf 0.11.1",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -267,8 +254,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6f509c3a87b33437b05e2458750a0700e5bdd6956176773e6c7d6dd15a283a0c"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf 0.11.1",
|
||||
"phf_codegen 0.11.1",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -674,16 +661,6 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
|
||||
dependencies = [
|
||||
"mac",
|
||||
"new_debug_unreachable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.1.31"
|
||||
|
@ -920,20 +897,6 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "html5ever"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
|
@ -1271,32 +1234,6 @@ dependencies = [
|
|||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf 0.10.1",
|
||||
"phf_codegen 0.10.0",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
|
@ -1460,12 +1397,6 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "5.1.2"
|
||||
|
@ -1668,7 +1599,6 @@ dependencies = [
|
|||
name = "pastebinrun"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"chrono",
|
||||
"diesel",
|
||||
"diesel_migrations",
|
||||
|
@ -1765,32 +1695,13 @@ dependencies = [
|
|||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1799,18 +1710,8 @@ version = "0.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56ac890c5e3ca598bbdeaa99964edb5b0258a583a9eb6ef4e89fc85d9224770"
|
||||
dependencies = [
|
||||
"phf_generator 0.11.1",
|
||||
"phf_shared 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
"rand",
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1819,19 +1720,10 @@ version = "0.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
|
||||
dependencies = [
|
||||
"phf_shared 0.11.1",
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.1"
|
||||
|
@ -1887,12 +1779,6 @@ dependencies = [
|
|||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.51"
|
||||
|
@ -2562,32 +2448,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"phf_shared 0.10.0",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -2625,17 +2485,6 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tendril"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
|
||||
dependencies = [
|
||||
"futf",
|
||||
"mac",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "1.17.1"
|
||||
|
@ -2771,7 +2620,6 @@ dependencies = [
|
|||
"memchr",
|
||||
"mio 0.8.5",
|
||||
"num_cpus",
|
||||
"parking_lot 0.12.1",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
|
@ -3119,12 +2967,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -7,7 +7,6 @@ license = "AGPL-3.0-or-later"
|
|||
build = "buildSrc/build.rs"
|
||||
|
||||
[dependencies]
|
||||
ammonia = "3.3.0"
|
||||
chrono = "0.4.23"
|
||||
diesel = { version = "1.4.6", features = ["chrono"] }
|
||||
diesel_migrations = "1.4.0"
|
||||
|
@ -31,7 +30,7 @@ serde = { version = "1.0.152", features = ["derive"] }
|
|||
serde_json = "1.0.93"
|
||||
serde_with = "2.2.0"
|
||||
time-parse = "0.2.0"
|
||||
tokio = { version = "1.25.0", features = ["full"] }
|
||||
tokio = { version = "1.25.0", features = ["rt"] }
|
||||
tokio-signal = "0.2.9"
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 { createSignal } from "solid-js";
|
||||
import { render, screen } from "solid-testing-library";
|
||||
import { expect, test } from "vitest";
|
||||
import StandardInput from "../js/views/StandardInput";
|
||||
|
||||
test("StandardInput can become visible", async () => {
|
||||
const [visible, setVisible] = createSignal(false);
|
||||
const [, setStandardInput] = createSignal("");
|
||||
render(() => (
|
||||
<StandardInput visible={visible} setStandardInput={setStandardInput} />
|
||||
));
|
||||
expect(screen.queryByRole("textbox")).toBeNull();
|
||||
setVisible(true);
|
||||
expect(screen.getByRole("textbox")).toBeEmptyDOMElement();
|
||||
});
|
|
@ -18,8 +18,6 @@ import { render } from "solid-js/web";
|
|||
import App from "./views/App";
|
||||
|
||||
export default function createEditor(editor: Element) {
|
||||
const markdown = editor.querySelector("#markdown") as Element;
|
||||
const languages = editor.querySelector("select") as HTMLSelectElement;
|
||||
const autoDelete = editor.querySelector(".autodelete-text") as Element;
|
||||
const rawPasteElement = editor.querySelector(".rawpaste-text") as Element;
|
||||
const code = (editor.querySelector("textarea") as HTMLTextAreaElement).value;
|
||||
|
@ -27,8 +25,6 @@ export default function createEditor(editor: Element) {
|
|||
render(
|
||||
() => (
|
||||
<App
|
||||
markdown={markdown}
|
||||
languages={languages}
|
||||
autoDelete={autoDelete}
|
||||
rawPasteElement={rawPasteElement}
|
||||
code={code}
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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/>.
|
||||
|
||||
type Wrapper = {
|
||||
identifier: string;
|
||||
label: string;
|
||||
isAsm: boolean;
|
||||
isFormatter: boolean;
|
||||
};
|
||||
|
||||
export default Wrapper;
|
|
@ -1,23 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 Wrapper from "./Wrapper";
|
||||
|
||||
type WrapperOptions = {
|
||||
wrapper: Wrapper;
|
||||
compilerOptions: string;
|
||||
};
|
||||
export default WrapperOptions;
|
|
@ -39,5 +39,5 @@ export function getEditorTypeSignal(): Signal<string> {
|
|||
}
|
||||
|
||||
export function getTabIndentationSignal(): Signal<string> {
|
||||
return createLocalStorageState("tabIndentation", "false");
|
||||
return createLocalStorageState("tabIndentation", "true");
|
||||
}
|
||||
|
|
|
@ -15,60 +15,36 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import { createSignal, Show } from "solid-js";
|
||||
import WrapperOptions from "../models/WrapperOptions";
|
||||
import Editor from "./Editor";
|
||||
import LanguageSelector from "./LanguageSelector";
|
||||
import Output from "./Output";
|
||||
import StandardInput from "./StandardInput";
|
||||
import WrapperBar from "./WrapperBar";
|
||||
|
||||
export default function App({
|
||||
markdown,
|
||||
languages,
|
||||
autoDelete,
|
||||
rawPasteElement,
|
||||
code,
|
||||
}: {
|
||||
markdown: Element | null;
|
||||
languages: HTMLSelectElement;
|
||||
autoDelete: Element | null;
|
||||
rawPasteElement: Element | null;
|
||||
code: string;
|
||||
}) {
|
||||
let form: HTMLFormElement | undefined;
|
||||
const [isPaste, setIsPaste] = createSignal(true);
|
||||
const [currentLanguage, setCurrentLanguage] = createSignal(
|
||||
languages.selectedOptions[0]?.value
|
||||
);
|
||||
const [standardInputVisible, setStandardInputVisible] = createSignal(false);
|
||||
const [codeView, setCodeView] = createSignal({ code: "" });
|
||||
const [standardInput, setStandardInput] = createSignal("");
|
||||
const [wrapperOptions, setWrapperOptions] = createSignal<WrapperOptions>();
|
||||
const [_, setCodeView] = createSignal({ code: "" });
|
||||
const [label, setLabel] = createSignal<Element>();
|
||||
return (
|
||||
<form action="/" method="post" ref={(e) => (form = e)}>
|
||||
{markdown}
|
||||
<Show when={isPaste()}>{autoDelete}</Show>
|
||||
<div id="toolbar">
|
||||
<LanguageSelector
|
||||
setCurrentLanguage={setCurrentLanguage}
|
||||
languages={languages}
|
||||
/>
|
||||
<WrapperBar
|
||||
currentLanguage={currentLanguage}
|
||||
setStandardInputVisible={setStandardInputVisible}
|
||||
codeView={codeView}
|
||||
runEvaluation={(wrapper, compilerOptions) => {
|
||||
setWrapperOptions();
|
||||
setWrapperOptions({ wrapper, compilerOptions });
|
||||
}}
|
||||
/>
|
||||
<Show when={isPaste()}>{rawPasteElement}</Show>
|
||||
<span id="right-buttons">
|
||||
<button type="submit" name="share" value="share24">
|
||||
Share (delete after 24 hours)
|
||||
</button>{" "}
|
||||
<button type="submit" name="share" value="share">
|
||||
<select name="duration" id="duration">
|
||||
<option value="1h">1 Hour</option>
|
||||
<option value="3h">3 Hours</option>
|
||||
<option value="24h">24 Hours</option>
|
||||
<option value="3d">3 Days</option>
|
||||
<option value="7d">7 Days</option>
|
||||
<option value="90d">90 Days</option>
|
||||
</select>{" "}
|
||||
<button type="submit" name="share">
|
||||
Share
|
||||
</button>
|
||||
</span>
|
||||
|
@ -81,29 +57,13 @@ export default function App({
|
|||
code={code}
|
||||
onInput={() => {
|
||||
setIsPaste(false);
|
||||
setWrapperOptions();
|
||||
}}
|
||||
currentLanguage={currentLanguage}
|
||||
form={form as HTMLFormElement}
|
||||
setCodeView={setCodeView}
|
||||
setLabel={setLabel}
|
||||
/>
|
||||
</div>
|
||||
<StandardInput
|
||||
visible={standardInputVisible}
|
||||
setStandardInput={setStandardInput}
|
||||
/>
|
||||
</div>
|
||||
<Show when={wrapperOptions()} keyed>
|
||||
{(wrapperOptions) => (
|
||||
<Output
|
||||
codeView={codeView()}
|
||||
stdin={standardInput()}
|
||||
wrapperOptions={wrapperOptions}
|
||||
setWrapperOptions={setWrapperOptions}
|
||||
/>
|
||||
)}
|
||||
</Show>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
|
|
|
@ -14,29 +14,26 @@
|
|||
// 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 { EditorView, basicSetup } from "codemirror";
|
||||
import { indentWithTab } from "@codemirror/commands";
|
||||
import { indentUnit } from "@codemirror/language";
|
||||
import { Compartment } from "@codemirror/state";
|
||||
import { keymap } from "@codemirror/view";
|
||||
import { basicSetup, EditorView } from "codemirror";
|
||||
import {
|
||||
Accessor,
|
||||
createEffect,
|
||||
createUniqueId,
|
||||
JSXElement,
|
||||
onCleanup,
|
||||
Setter,
|
||||
Setter
|
||||
} from "solid-js";
|
||||
import CodeView from "../../models/CodeView";
|
||||
import { getTabIndentationSignal } from "../../options";
|
||||
import "./codemirror.css";
|
||||
import languagesMap from "./languages";
|
||||
|
||||
export default function CodeMirrorEditor({
|
||||
code,
|
||||
setCode,
|
||||
onInput,
|
||||
currentLanguage,
|
||||
form,
|
||||
setCodeView,
|
||||
setLabel,
|
||||
|
@ -44,7 +41,6 @@ export default function CodeMirrorEditor({
|
|||
code: Accessor<string>;
|
||||
setCode: Setter<string>;
|
||||
onInput(): void;
|
||||
currentLanguage: Accessor<string>;
|
||||
form: HTMLFormElement;
|
||||
setCodeView: Setter<CodeView>;
|
||||
setLabel: Setter<JSXElement>;
|
||||
|
@ -56,7 +52,6 @@ export default function CodeMirrorEditor({
|
|||
: [];
|
||||
}
|
||||
const tabIndentation = new Compartment();
|
||||
const language = new Compartment();
|
||||
let avoidChangeNotifications = false;
|
||||
const labelId = createUniqueId();
|
||||
const view = new EditorView({
|
||||
|
@ -72,8 +67,6 @@ export default function CodeMirrorEditor({
|
|||
}
|
||||
}),
|
||||
EditorView.lineWrapping,
|
||||
indentUnit.of(" ".repeat(4)),
|
||||
language.of([]),
|
||||
],
|
||||
});
|
||||
createEffect(() => {
|
||||
|
@ -81,13 +74,6 @@ export default function CodeMirrorEditor({
|
|||
effects: tabIndentation.reconfigure(getTabIndentationExtension()),
|
||||
});
|
||||
});
|
||||
createEffect(async () => {
|
||||
const callback = languagesMap[currentLanguage()];
|
||||
const extension = callback ? await callback() : [];
|
||||
view.dispatch({
|
||||
effects: language.reconfigure(extension),
|
||||
});
|
||||
});
|
||||
const getValue = () => view.state.doc.toString();
|
||||
setCodeView({
|
||||
get code() {
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 { StreamLanguage } from "@codemirror/language";
|
||||
import { Extension } from "@codemirror/state";
|
||||
|
||||
const languagesMap: { [name: string]: () => Promise<Extension> } = {
|
||||
async c() {
|
||||
return (await import("@codemirror/lang-cpp")).cpp();
|
||||
},
|
||||
async cpp() {
|
||||
return (await import("@codemirror/lang-cpp")).cpp();
|
||||
},
|
||||
async csharp() {
|
||||
return StreamLanguage.define(
|
||||
(await import("@codemirror/legacy-modes/mode/clike")).csharp
|
||||
);
|
||||
},
|
||||
async go() {
|
||||
return StreamLanguage.define(
|
||||
(await import("@codemirror/legacy-modes/mode/go")).go
|
||||
);
|
||||
},
|
||||
async haskell() {
|
||||
return StreamLanguage.define(
|
||||
(await import("@codemirror/legacy-modes/mode/haskell")).haskell
|
||||
);
|
||||
},
|
||||
async html() {
|
||||
return (await import("@codemirror/lang-html")).html();
|
||||
},
|
||||
async java() {
|
||||
return (await import("@codemirror/lang-java")).java();
|
||||
},
|
||||
async javascript() {
|
||||
return (await import("@codemirror/lang-javascript")).javascript();
|
||||
},
|
||||
async jinja2() {
|
||||
return StreamLanguage.define(
|
||||
(await import("@codemirror/legacy-modes/mode/jinja2")).jinja2
|
||||
);
|
||||
},
|
||||
async jsx() {
|
||||
return (await import("@codemirror/lang-javascript")).javascript({
|
||||
jsx: true,
|
||||
});
|
||||
},
|
||||
async markdown() {
|
||||
return (await import("./markdown")).default;
|
||||
},
|
||||
async nix() {
|
||||
return (await import("@replit/codemirror-lang-nix")).nix();
|
||||
},
|
||||
async perl() {
|
||||
return StreamLanguage.define(
|
||||
(await import("@codemirror/legacy-modes/mode/perl")).perl
|
||||
);
|
||||
},
|
||||
async php() {
|
||||
return (await import("@codemirror/lang-php")).php();
|
||||
},
|
||||
async postgresql() {
|
||||
const { sql, PostgreSQL } = await import("@codemirror/lang-sql");
|
||||
return sql({ dialect: PostgreSQL });
|
||||
},
|
||||
async python() {
|
||||
return (await import("@codemirror/lang-python")).python();
|
||||
},
|
||||
async rust() {
|
||||
return (await import("@codemirror/lang-rust")).rust();
|
||||
},
|
||||
async sh() {
|
||||
return StreamLanguage.define(
|
||||
(await import("@codemirror/legacy-modes/mode/shell")).shell
|
||||
);
|
||||
},
|
||||
async sql() {
|
||||
return (await import("@codemirror/lang-sql")).sql();
|
||||
},
|
||||
async sqlite() {
|
||||
const { sql, SQLite } = await import("@codemirror/lang-sql");
|
||||
return sql({ dialect: SQLite });
|
||||
},
|
||||
async typescript() {
|
||||
return (await import("@codemirror/lang-javascript")).javascript({
|
||||
typescript: true,
|
||||
});
|
||||
},
|
||||
async tsx() {
|
||||
return (await import("@codemirror/lang-javascript")).javascript({
|
||||
jsx: true,
|
||||
typescript: true,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default languagesMap;
|
|
@ -1,15 +0,0 @@
|
|||
import { markdown } from "@codemirror/lang-markdown";
|
||||
import { LanguageDescription } from "@codemirror/language";
|
||||
import { languages } from "@codemirror/language-data";
|
||||
|
||||
export default markdown({
|
||||
codeLanguages: languages.concat(
|
||||
LanguageDescription.of({
|
||||
name: "Nix",
|
||||
extensions: ["nix"],
|
||||
async load() {
|
||||
return (await import("@replit/codemirror-lang-nix")).nix();
|
||||
},
|
||||
})
|
||||
),
|
||||
});
|
|
@ -15,11 +15,10 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import {
|
||||
Accessor,
|
||||
createResource,
|
||||
createSignal,
|
||||
JSXElement,
|
||||
Setter,
|
||||
Setter
|
||||
} from "solid-js";
|
||||
import CodeView from "../models/CodeView";
|
||||
import { getEditorTypeSignal } from "../options";
|
||||
|
@ -28,14 +27,12 @@ import TextAreaEditor from "./TextAreaEditor";
|
|||
export default function Editor({
|
||||
code: initialCode,
|
||||
onInput,
|
||||
currentLanguage,
|
||||
form,
|
||||
setCodeView: setCodeView,
|
||||
setLabel,
|
||||
}: {
|
||||
code: string;
|
||||
onInput(): void;
|
||||
currentLanguage: Accessor<string>;
|
||||
form: HTMLFormElement;
|
||||
setCodeView: Setter<CodeView>;
|
||||
setLabel: Setter<JSXElement>;
|
||||
|
@ -56,7 +53,6 @@ export default function Editor({
|
|||
code,
|
||||
setCode,
|
||||
onInput,
|
||||
currentLanguage,
|
||||
form,
|
||||
setCodeView,
|
||||
setLabel,
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 { For, Setter } from "solid-js";
|
||||
|
||||
export default function LanguageSelector({
|
||||
setCurrentLanguage,
|
||||
languages,
|
||||
}: {
|
||||
setCurrentLanguage: Setter<string>;
|
||||
languages: HTMLSelectElement;
|
||||
}) {
|
||||
return (
|
||||
<div class="group">
|
||||
<label>
|
||||
{"Language: "}
|
||||
<select
|
||||
id="language"
|
||||
name="language"
|
||||
onChange={(e) =>
|
||||
setCurrentLanguage(e.currentTarget.selectedOptions[0].value)
|
||||
}
|
||||
>
|
||||
<For each={[...languages.options]}>
|
||||
{(option) => (
|
||||
<option value={option.value} selected={option.selected}>
|
||||
{option.textContent}
|
||||
</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 {
|
||||
createEffect,
|
||||
createResource,
|
||||
Match,
|
||||
onCleanup,
|
||||
Setter,
|
||||
Switch,
|
||||
} from "solid-js";
|
||||
import CodeView from "../models/CodeView";
|
||||
import WrapperOptions from "../models/WrapperOptions";
|
||||
import OutputBox from "./OutputBox";
|
||||
import Spinner from "./Spinner";
|
||||
|
||||
export default function Output(props: {
|
||||
codeView: CodeView;
|
||||
stdin: string;
|
||||
wrapperOptions: WrapperOptions;
|
||||
setWrapperOptions: Setter<WrapperOptions | undefined>;
|
||||
}) {
|
||||
const abortController = new AbortController();
|
||||
const [output, { refetch }] = createResource(async () => {
|
||||
const body = new URLSearchParams();
|
||||
body.append("compilerOptions", props.wrapperOptions.compilerOptions);
|
||||
body.append("code", props.codeView.code);
|
||||
body.append("stdin", props.stdin);
|
||||
return (
|
||||
await fetch(`/api/v0/run/${props.wrapperOptions.wrapper.identifier}`, {
|
||||
method: "POST",
|
||||
body,
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
signal: abortController.signal,
|
||||
})
|
||||
).json();
|
||||
});
|
||||
createEffect(() => {
|
||||
if (props.wrapperOptions.wrapper.isFormatter && output()?.status === 0) {
|
||||
props.codeView.code = output().output.replace(
|
||||
/\x7F(?:E[^\x7F]*|O)?/g,
|
||||
""
|
||||
);
|
||||
if (!output().output.includes("\x7F")) {
|
||||
props.setWrapperOptions();
|
||||
}
|
||||
}
|
||||
});
|
||||
onCleanup(() => abortController.abort());
|
||||
return (
|
||||
<div id="outputcontainer">
|
||||
<div id="output">
|
||||
<Switch
|
||||
fallback={
|
||||
<OutputBox
|
||||
output={output()}
|
||||
wrapper={props.wrapperOptions.wrapper}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Match when={output.loading}>
|
||||
<Spinner />
|
||||
</Match>
|
||||
<Match when={output.error}>
|
||||
{"An error occured while running the code. "}
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
Try again
|
||||
</a>
|
||||
.
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 { For, Show } from "solid-js";
|
||||
import Wrapper from "../models/Wrapper";
|
||||
import { createLocalStorageState } from "../options";
|
||||
|
||||
function parseOutput(wrapper: Wrapper, output: string) {
|
||||
return (
|
||||
<For
|
||||
each={("O" + output)
|
||||
.split("\x7F")
|
||||
.filter(
|
||||
(x) => x.length > 1 && (!wrapper.isFormatter || !x.startsWith("O"))
|
||||
)}
|
||||
fallback={<i>(no output)</i>}
|
||||
>
|
||||
{(item) => {
|
||||
const firstChar = item[0];
|
||||
if (firstChar === "O") {
|
||||
return item.substring(1);
|
||||
}
|
||||
if (firstChar === "E") {
|
||||
return <span class="error">{item.substring(1)}</span>;
|
||||
}
|
||||
return item;
|
||||
}}
|
||||
</For>
|
||||
);
|
||||
}
|
||||
|
||||
function runAsmFilter(output: string) {
|
||||
return output.replace(
|
||||
/(?:\t\.(?:text|file|section|globl|p2align|type|cfi_.*|size|section)\b|.Lfunc_end).*\n?/g,
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
export default function OutputBox({
|
||||
output,
|
||||
wrapper,
|
||||
}: {
|
||||
output: { output: string; status: number | null };
|
||||
wrapper: Wrapper;
|
||||
}) {
|
||||
const [filterAsmDirectivesOrig, setFilterAsmDirectives] =
|
||||
createLocalStorageState("filterAsmDirectives", "true");
|
||||
const filterAsmDirectives = () => JSON.parse(filterAsmDirectivesOrig());
|
||||
return (
|
||||
<>
|
||||
<div class="stdout-header">
|
||||
<Show when={output.status}>
|
||||
<h2>{`Output (exit code ${output.status})`}</h2>
|
||||
</Show>
|
||||
<Show when={wrapper.isAsm}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={filterAsmDirectives()}
|
||||
onChange={(e) =>
|
||||
setFilterAsmDirectives(e.currentTarget.checked.toString())
|
||||
}
|
||||
/>
|
||||
{" Filter assembler directives"}
|
||||
</label>
|
||||
</Show>
|
||||
</div>
|
||||
<pre>
|
||||
{parseOutput(
|
||||
wrapper,
|
||||
wrapper.isAsm && filterAsmDirectives()
|
||||
? runAsmFilter(output.output)
|
||||
: output.output
|
||||
)}
|
||||
</pre>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 "./spinner.css";
|
||||
|
||||
export default function Spinner() {
|
||||
return <div class="spinner" />;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
@keyframes spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.spinner {
|
||||
animation: 1s linear infinite spinner;
|
||||
border: solid 3px #f5f2f0;
|
||||
border-bottom-color: #c7c75c;
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 { Accessor, Setter, Show } from "solid-js";
|
||||
|
||||
export default function StandardInput({
|
||||
visible,
|
||||
setStandardInput,
|
||||
}: {
|
||||
visible: Accessor<boolean>;
|
||||
setStandardInput: Setter<string>;
|
||||
}) {
|
||||
return (
|
||||
<Show when={visible()}>
|
||||
<details>
|
||||
<summary>Standard input</summary>
|
||||
<textarea
|
||||
name="stdin"
|
||||
onInput={(e) => setStandardInput(e.currentTarget.value)}
|
||||
/>
|
||||
</details>
|
||||
</Show>
|
||||
);
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 {
|
||||
Accessor,
|
||||
createEffect,
|
||||
createResource,
|
||||
createSignal,
|
||||
For,
|
||||
Setter,
|
||||
Show,
|
||||
} from "solid-js";
|
||||
import CodeView from "../models/CodeView";
|
||||
import Wrapper from "../models/Wrapper";
|
||||
import WrapperBarButton from "./WrapperBarButton";
|
||||
|
||||
type Language = {
|
||||
helloWorld: string;
|
||||
implementations: {
|
||||
label: string;
|
||||
wrappers: Wrapper[];
|
||||
}[];
|
||||
};
|
||||
|
||||
async function fetchLanguage(language: string): Promise<Language> {
|
||||
return (await fetch(`/api/v0/language/${language}`)).json();
|
||||
}
|
||||
|
||||
export default function WrapperBar({
|
||||
currentLanguage,
|
||||
setStandardInputVisible,
|
||||
codeView,
|
||||
runEvaluation,
|
||||
}: {
|
||||
currentLanguage: Accessor<string>;
|
||||
setStandardInputVisible: Setter<boolean>;
|
||||
codeView: Accessor<CodeView>;
|
||||
runEvaluation: (wrapper: Wrapper, compilerOptions: string) => void;
|
||||
}) {
|
||||
const [data] = createResource(currentLanguage, fetchLanguage);
|
||||
const [currentImplementationIndex, setCurrentImplementationIndex] =
|
||||
createSignal(0);
|
||||
let previousHelloWorld = "";
|
||||
createEffect(() => {
|
||||
const loadedData = data();
|
||||
if (loadedData) {
|
||||
setStandardInputVisible(loadedData.implementations.length > 0);
|
||||
const code = codeView().code;
|
||||
if (!code || code === previousHelloWorld) {
|
||||
previousHelloWorld = codeView().code = loadedData.helloWorld;
|
||||
} else {
|
||||
previousHelloWorld = "";
|
||||
}
|
||||
}
|
||||
});
|
||||
let compilerOptions: HTMLInputElement;
|
||||
return (
|
||||
<Show when={data()} keyed>
|
||||
{(data) => (
|
||||
<>
|
||||
<div class="group">
|
||||
<For
|
||||
each={
|
||||
data.implementations[currentImplementationIndex()]?.wrappers
|
||||
}
|
||||
>
|
||||
{(wrapper, index) => (
|
||||
<>
|
||||
{" "}
|
||||
<WrapperBarButton
|
||||
index={index()}
|
||||
runEvaluation={() => {
|
||||
runEvaluation(wrapper, compilerOptions.value);
|
||||
}}
|
||||
>
|
||||
{wrapper.label}
|
||||
</WrapperBarButton>
|
||||
</>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
<Show when={data.implementations.length > 1}>
|
||||
<div class="group">
|
||||
<label>
|
||||
{"Implementation: "}
|
||||
<select
|
||||
onChange={(e) =>
|
||||
setCurrentImplementationIndex(+e.currentTarget.value)
|
||||
}
|
||||
>
|
||||
<For each={data.implementations}>
|
||||
{({ label }, index) => (
|
||||
<option value={index()}>{label}</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={data.implementations.length}>
|
||||
<div class="group">
|
||||
<label>
|
||||
{"Compiler options: "}
|
||||
<input ref={(e) => (compilerOptions = e)} />
|
||||
</label>
|
||||
</div>
|
||||
</Show>
|
||||
</>
|
||||
)}
|
||||
</Show>
|
||||
);
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2022 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 { onCleanup } from "solid-js";
|
||||
|
||||
export default function WrapperBarButton({
|
||||
index,
|
||||
runEvaluation,
|
||||
children,
|
||||
}: {
|
||||
index: number;
|
||||
runEvaluation(): void;
|
||||
children: string;
|
||||
}) {
|
||||
if (index === 0) {
|
||||
const globalKeyEvent = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
runEvaluation();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", globalKeyEvent);
|
||||
onCleanup(() => document.removeEventListener("keydown", globalKeyEvent));
|
||||
}
|
||||
return (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
runEvaluation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE pastes DROP COLUMN output;
|
||||
ALTER TABLE pastes ADD COLUMN stdout text, ADD COLUMN stderr text;
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE pastes
|
||||
DROP COLUMN language_id,
|
||||
DROP COLUMN stdin,
|
||||
DROP COLUMN exit_code,
|
||||
DROP COLUMN output;
|
File diff suppressed because it is too large
Load Diff
13
package.json
13
package.json
|
@ -7,20 +7,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@codemirror/commands": "^6.2.0",
|
||||
"@codemirror/lang-cpp": "^6.0.2",
|
||||
"@codemirror/lang-html": "^6.4.2",
|
||||
"@codemirror/lang-java": "^6.0.1",
|
||||
"@codemirror/lang-javascript": "^6.1.3",
|
||||
"@codemirror/lang-markdown": "^6.0.5",
|
||||
"@codemirror/lang-php": "^6.0.1",
|
||||
"@codemirror/lang-python": "^6.1.1",
|
||||
"@codemirror/lang-rust": "^6.0.1",
|
||||
"@codemirror/lang-sql": "^6.4.0",
|
||||
"@codemirror/language": "^6.5.0",
|
||||
"@codemirror/language-data": "^6.1.0",
|
||||
"@codemirror/legacy-modes": "^6.3.1",
|
||||
"@codemirror/view": "^6.8.1",
|
||||
"@replit/codemirror-lang-nix": "^6.0.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"solid-js": "^1.6.10"
|
||||
},
|
||||
|
|
48
src/main.rs
48
src/main.rs
|
@ -19,15 +19,12 @@ extern crate diesel;
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
mod migration;
|
||||
mod models;
|
||||
mod routes;
|
||||
mod schema;
|
||||
|
||||
use crate::models::paste::ExtraPasteParameters;
|
||||
use crate::routes::{
|
||||
api_insert_paste, api_language, api_languages, config, display_paste, index, insert_paste,
|
||||
metrics, raw_paste, run,
|
||||
api_insert_paste, config, display_paste, index, insert_paste, metrics, raw_paste,
|
||||
};
|
||||
use chrono::{Duration, Utc};
|
||||
use diesel::prelude::*;
|
||||
|
@ -39,8 +36,9 @@ use rocket_dyn_templates::tera::{self, Value};
|
|||
use rocket_dyn_templates::Template;
|
||||
use rocket_sync_db_pools::database;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::signal;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
|
@ -131,31 +129,35 @@ async fn shutdown_monitor(s: Sender<i32>) {
|
|||
}
|
||||
|
||||
async fn cli() {
|
||||
println!("starting cli");
|
||||
let addr = "0.0.0.0:9000".parse::<SocketAddr>().unwrap();
|
||||
let listener = TcpListener::bind(addr).unwrap();
|
||||
let mut db = PgConnection::establish("postgres://postgres:ifd783farq@localhost/pastebin")
|
||||
let listener = TcpListener::bind(addr).await.unwrap();
|
||||
|
||||
let mut db = PgConnection::establish(&std::env::var("POSTGRES_URL").unwrap())
|
||||
.expect("error in establishing connection with postgresql");
|
||||
|
||||
let host = std::env::var("HOST").unwrap();
|
||||
|
||||
loop {
|
||||
let (socket, details) = listener.accept().unwrap();
|
||||
let (socket, details) = listener.accept().await.unwrap();
|
||||
|
||||
println!("received connection from {:?}", details);
|
||||
save_paste(&mut db, socket).unwrap();
|
||||
save_paste(&mut db, &host, socket).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
const BUFFER_SIZE: usize = 128;
|
||||
const HOST: &str = "https://p.ishan.pw";
|
||||
|
||||
fn save_paste(
|
||||
async fn save_paste(
|
||||
db: &mut PgConnection,
|
||||
host: &str,
|
||||
mut socket: TcpStream,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut paste = vec![];
|
||||
let mut buffer = [0; BUFFER_SIZE];
|
||||
|
||||
loop {
|
||||
let read = socket.read(&mut buffer)?;
|
||||
let read = socket.read(&mut buffer).await?;
|
||||
if read < BUFFER_SIZE {
|
||||
paste.extend(&buffer[..read]);
|
||||
break;
|
||||
|
@ -170,20 +172,20 @@ fn save_paste(
|
|||
db,
|
||||
// Terminal paste should stay for 3 days
|
||||
Some(Utc::now() + Duration::days(3)),
|
||||
"plaintext",
|
||||
&String::from_utf8_lossy(&paste),
|
||||
ExtraPasteParameters {
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
socket.write_fmt(format_args!("{HOST}/{v}\n"))?;
|
||||
socket.flush().unwrap();
|
||||
let output = format!("{}/{}\n", host, v);
|
||||
let output: Vec<u8> = output.into_bytes();
|
||||
|
||||
socket.write(&output).await?;
|
||||
socket.flush().await.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rocket() -> Rocket<Build> {
|
||||
println!("starting rocket");
|
||||
let mut rocket = rocket::build()
|
||||
.attach(Template::custom(|engines| {
|
||||
engines.tera.register_function("js_path", js_path);
|
||||
|
@ -196,10 +198,7 @@ fn rocket() -> Rocket<Build> {
|
|||
Db::get_one(&rocket)
|
||||
.await
|
||||
.expect("a database")
|
||||
.run(|conn| {
|
||||
diesel_migrations::run_pending_migrations(conn)?;
|
||||
migration::run(conn)
|
||||
})
|
||||
.run(|conn| diesel_migrations::run_pending_migrations(conn))
|
||||
.await
|
||||
.expect("database to be migrated");
|
||||
rocket
|
||||
|
@ -212,11 +211,8 @@ fn rocket() -> Rocket<Build> {
|
|||
.mount(
|
||||
"/",
|
||||
routes![
|
||||
api_language,
|
||||
api_languages,
|
||||
api_insert_paste,
|
||||
config,
|
||||
run,
|
||||
index,
|
||||
insert_paste,
|
||||
display_paste,
|
||||
|
|
160
src/migration.rs
160
src/migration.rs
|
@ -1,160 +0,0 @@
|
|||
// 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/>.
|
||||
|
||||
use crate::schema::{implementation_wrappers, implementations, languages};
|
||||
use diesel::prelude::*;
|
||||
use diesel::sql_types::{Bool, Integer, Text};
|
||||
use serde::Deserialize;
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct JsonLanguage {
|
||||
identifier: String,
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
helloworld: String,
|
||||
#[serde(default)]
|
||||
implementations: Vec<Implementation>,
|
||||
}
|
||||
|
||||
#[derive(Insertable)]
|
||||
struct Language<'a> {
|
||||
identifier: &'a str,
|
||||
name: String,
|
||||
priority: i32,
|
||||
hello_world: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Implementation {
|
||||
label: String,
|
||||
identifier: String,
|
||||
#[serde(default)]
|
||||
wrappers: Vec<Wrapper>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Wrapper {
|
||||
identifier: String,
|
||||
label: String,
|
||||
code: String,
|
||||
#[serde(default)]
|
||||
is_asm: bool,
|
||||
#[serde(default)]
|
||||
is_formatter: bool,
|
||||
}
|
||||
|
||||
pub fn run(connection: &PgConnection) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let languages: Vec<JsonLanguage> = serde_json::from_slice(&fs::read("languages.json")?)?;
|
||||
for JsonLanguage {
|
||||
identifier: languages_identifier,
|
||||
name,
|
||||
helloworld: hello_world,
|
||||
implementations,
|
||||
} in languages
|
||||
{
|
||||
diesel::insert_into(languages::table)
|
||||
.values(Language {
|
||||
identifier: &languages_identifier,
|
||||
name,
|
||||
priority: 10,
|
||||
hello_world: &hello_world,
|
||||
})
|
||||
.on_conflict(languages::identifier)
|
||||
.do_update()
|
||||
.set(languages::hello_world.eq(&hello_world))
|
||||
.execute(connection)?;
|
||||
for (
|
||||
i,
|
||||
Implementation {
|
||||
label,
|
||||
identifier: implementation_identifier,
|
||||
wrappers,
|
||||
},
|
||||
) in (1..).zip(implementations)
|
||||
{
|
||||
languages::table
|
||||
.filter(languages::identifier.eq(&languages_identifier))
|
||||
.select((
|
||||
languages::language_id,
|
||||
label.as_sql::<Text>(),
|
||||
implementation_identifier.as_sql::<Text>(),
|
||||
i.as_sql::<Integer>(),
|
||||
))
|
||||
.insert_into(implementations::table)
|
||||
.into_columns((
|
||||
implementations::language_id,
|
||||
implementations::label,
|
||||
implementations::identifier,
|
||||
implementations::ordering,
|
||||
))
|
||||
.on_conflict((implementations::language_id, implementations::identifier))
|
||||
.do_update()
|
||||
.set((
|
||||
implementations::label.eq(&label),
|
||||
implementations::ordering.eq(i),
|
||||
))
|
||||
.execute(connection)?;
|
||||
for (
|
||||
i,
|
||||
Wrapper {
|
||||
identifier,
|
||||
label,
|
||||
code,
|
||||
is_asm,
|
||||
is_formatter,
|
||||
},
|
||||
) in (1..).zip(wrappers)
|
||||
{
|
||||
languages::table
|
||||
.inner_join(implementations::table)
|
||||
.filter(languages::identifier.eq(&languages_identifier))
|
||||
.filter(implementations::identifier.eq(&implementation_identifier))
|
||||
.select((
|
||||
implementations::implementation_id,
|
||||
identifier.as_sql::<Text>(),
|
||||
label.as_sql::<Text>(),
|
||||
code.as_sql::<Text>(),
|
||||
is_asm.as_sql::<Bool>(),
|
||||
is_formatter.as_sql::<Bool>(),
|
||||
i.as_sql::<Integer>(),
|
||||
))
|
||||
.insert_into(implementation_wrappers::table)
|
||||
.into_columns((
|
||||
implementation_wrappers::implementation_id,
|
||||
implementation_wrappers::identifier,
|
||||
implementation_wrappers::label,
|
||||
implementation_wrappers::code,
|
||||
implementation_wrappers::is_asm,
|
||||
implementation_wrappers::is_formatter,
|
||||
implementation_wrappers::ordering,
|
||||
))
|
||||
.on_conflict(implementation_wrappers::identifier)
|
||||
.do_update()
|
||||
.set((
|
||||
implementation_wrappers::label.eq(&label),
|
||||
implementation_wrappers::code.eq(&code),
|
||||
implementation_wrappers::is_asm.eq(is_asm),
|
||||
implementation_wrappers::is_formatter.eq(is_formatter),
|
||||
implementation_wrappers::ordering.eq(i),
|
||||
))
|
||||
.execute(connection)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2020-2021 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/>.
|
||||
|
||||
use crate::schema::languages::dsl::*;
|
||||
use diesel::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Queryable, Serialize)]
|
||||
pub struct Language {
|
||||
pub id: i32,
|
||||
pub identifier: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
pub fn fetch(connection: &PgConnection) -> Result<Vec<Language>, diesel::result::Error> {
|
||||
languages
|
||||
.select((language_id, identifier, name))
|
||||
.order((priority.asc(), name.asc()))
|
||||
.load(connection)
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
mod cors;
|
||||
pub mod language;
|
||||
pub mod paste;
|
||||
|
||||
pub use cors::Cors;
|
||||
|
|
|
@ -14,32 +14,21 @@
|
|||
// 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/>.
|
||||
|
||||
use crate::schema::{languages, pastes};
|
||||
use ammonia::Builder;
|
||||
use crate::schema::pastes;
|
||||
use chrono::{DateTime, Utc};
|
||||
use diesel::prelude::*;
|
||||
use itertools::Itertools;
|
||||
use log::info;
|
||||
use once_cell::sync::Lazy;
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
use rand::seq::SliceRandom;
|
||||
use rocket::http::Status;
|
||||
use rocket::response::{self, Debug, Responder};
|
||||
use rocket::Request;
|
||||
use serde::Serialize;
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::iter;
|
||||
|
||||
#[derive(Queryable)]
|
||||
pub struct Paste {
|
||||
pub identifier: String,
|
||||
pub paste: String,
|
||||
pub language_id: i32,
|
||||
pub delete_at: Option<DateTime<Utc>>,
|
||||
pub language_identifier: String,
|
||||
pub stdin: String,
|
||||
pub exit_code: Option<i32>,
|
||||
pub output: Option<String>,
|
||||
}
|
||||
|
||||
impl Paste {
|
||||
|
@ -61,24 +50,12 @@ const CHARACTERS: &[u8] = b"23456789bcdfghjkmnpqrstvwxz-";
|
|||
struct InsertPaste<'a> {
|
||||
identifier: &'a str,
|
||||
delete_at: Option<DateTime<Utc>>,
|
||||
language_id: i32,
|
||||
paste: &'a str,
|
||||
stdin: &'a str,
|
||||
output: Option<&'a str>,
|
||||
exit_code: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtraPasteParameters<'a> {
|
||||
pub stdin: &'a str,
|
||||
pub output: Option<&'a str>,
|
||||
pub exit_code: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum InsertionError {
|
||||
Diesel(diesel::result::Error),
|
||||
UnrecognizedLanguageIdentifier,
|
||||
}
|
||||
|
||||
impl From<diesel::result::Error> for InsertionError {
|
||||
|
@ -91,7 +68,6 @@ impl<'r> Responder<'r, 'static> for InsertionError {
|
|||
fn respond_to(self, request: &'r Request<'_>) -> response::Result<'static> {
|
||||
match self {
|
||||
Self::Diesel(e) => Debug(e).respond_to(request),
|
||||
Self::UnrecognizedLanguageIdentifier => Err(Status::BadRequest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,32 +75,17 @@ impl<'r> Responder<'r, 'static> for InsertionError {
|
|||
pub fn insert(
|
||||
connection: &PgConnection,
|
||||
delete_at: Option<DateTime<Utc>>,
|
||||
language: &str,
|
||||
paste: &str,
|
||||
ExtraPasteParameters {
|
||||
stdin,
|
||||
output,
|
||||
exit_code,
|
||||
}: ExtraPasteParameters,
|
||||
) -> Result<String, InsertionError> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let identifier: String = (0..12)
|
||||
.map(|_| char::from(*CHARACTERS.choose(&mut rng).expect("a random character")))
|
||||
.collect();
|
||||
let language_id = languages::table
|
||||
.select(languages::language_id)
|
||||
.filter(languages::identifier.eq(language))
|
||||
.get_result(connection)
|
||||
.optional()?
|
||||
.ok_or(InsertionError::UnrecognizedLanguageIdentifier)?;
|
||||
|
||||
let insert_paste = InsertPaste {
|
||||
identifier: &identifier,
|
||||
delete_at,
|
||||
language_id,
|
||||
paste,
|
||||
stdin,
|
||||
output,
|
||||
exit_code,
|
||||
};
|
||||
diesel::insert_into(pastes::table)
|
||||
.values(&insert_paste)
|
||||
|
@ -137,12 +98,7 @@ pub fn insert(
|
|||
pub struct ExternPaste {
|
||||
pub identifier: Option<String>,
|
||||
pub paste: String,
|
||||
pub language_id: i32,
|
||||
pub delete_at: Option<String>,
|
||||
pub markdown: String,
|
||||
pub stdin: String,
|
||||
pub exit_code: Option<i32>,
|
||||
pub output: Option<String>,
|
||||
}
|
||||
|
||||
impl ExternPaste {
|
||||
|
@ -150,98 +106,12 @@ impl ExternPaste {
|
|||
let Paste {
|
||||
identifier,
|
||||
paste,
|
||||
language_id,
|
||||
language_identifier,
|
||||
delete_at,
|
||||
stdin,
|
||||
exit_code,
|
||||
output,
|
||||
} = paste;
|
||||
let markdown = if language_identifier == "markdown" {
|
||||
render_markdown(&paste)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
Self {
|
||||
identifier: Some(identifier),
|
||||
paste,
|
||||
language_id,
|
||||
delete_at: delete_at.map(|delete_at| delete_at.format("%Y-%m-%d %H:%M").to_string()),
|
||||
markdown,
|
||||
stdin,
|
||||
exit_code,
|
||||
output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_markdown(markdown: &str) -> String {
|
||||
static FILTER: Lazy<Builder<'static>> = Lazy::new(|| {
|
||||
let mut builder = Builder::new();
|
||||
builder.link_rel(Some("noopener noreferrer nofollow"));
|
||||
builder.add_generic_attributes(iter::once("class"));
|
||||
builder.attribute_filter(|_, attribute, value| {
|
||||
if attribute == "class" {
|
||||
// class attribute must have a value that is a set of space-separate tokens
|
||||
// https://html.spec.whatwg.org/#global-attributes
|
||||
//
|
||||
// A set of space-separated tokens is a string containing zero or more words
|
||||
// (known as tokens) separated by one or more ASCII whitespace, where words
|
||||
// consist of any string of one or more characters, none of which are ASCII
|
||||
// whitespace.
|
||||
// https://html.spec.whatwg.org/#space-separated-tokens
|
||||
//
|
||||
// Rust uses the WhatWG Infra Standard’s definition of ASCII whitespace.
|
||||
// https://doc.rust-lang.org/stable/std/primitive.char.html#method.is_ascii_whitespace
|
||||
Some(
|
||||
value
|
||||
.split_ascii_whitespace()
|
||||
.filter(|value| value.starts_with("language-"))
|
||||
.join(" ")
|
||||
.into(),
|
||||
)
|
||||
} else {
|
||||
Some(value.into())
|
||||
}
|
||||
});
|
||||
builder
|
||||
});
|
||||
let mut output = String::new();
|
||||
let options = Options::ENABLE_TABLES | Options::ENABLE_STRIKETHROUGH;
|
||||
pulldown_cmark::html::push_html(&mut output, Parser::new_ext(markdown, options));
|
||||
FILTER.clean(&output).to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::render_markdown;
|
||||
|
||||
#[test]
|
||||
fn markdown_works() {
|
||||
assert_eq!(
|
||||
render_markdown("**bold**"),
|
||||
"<p><strong>bold</strong></p>\n"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn strikethrough_works() {
|
||||
assert_eq!(render_markdown("~~strike~~"), "<p><del>strike</del></p>\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn code_blocks_work() {
|
||||
assert_eq!(
|
||||
render_markdown("```rust\nfn main() {}\n```"),
|
||||
"<pre><code class=\"language-rust\">fn main() {}\n</code></pre>\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_language_classes_are_allowed() {
|
||||
assert_eq!(
|
||||
render_markdown(r#"<br class="language-a madoka language-b homura">"#),
|
||||
"<br class=\"language-a language-b\">",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
// 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/>.
|
||||
|
||||
use crate::models::paste::{self, ExtraPasteParameters, InsertionError};
|
||||
use crate::models::paste::{self, InsertionError};
|
||||
use crate::models::Cors;
|
||||
use crate::Db;
|
||||
use chrono::Duration;
|
||||
|
@ -26,8 +26,6 @@ use std::error::Error;
|
|||
pub struct PasteForm {
|
||||
#[field(default = Expiration(None))]
|
||||
expiration: Expiration,
|
||||
#[field(default = "plaintext")]
|
||||
language: String,
|
||||
code: String,
|
||||
}
|
||||
|
||||
|
@ -52,13 +50,7 @@ pub async fn api_insert_paste(
|
|||
paste::insert(
|
||||
conn,
|
||||
form.expiration.0.map(|expiration| Utc::now() + expiration),
|
||||
&form.language,
|
||||
&form.code,
|
||||
ExtraPasteParameters {
|
||||
stdin: "",
|
||||
output: None,
|
||||
exit_code: None,
|
||||
},
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2020-2021 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/>.
|
||||
|
||||
use crate::schema::{implementation_wrappers, implementations, languages};
|
||||
use crate::Db;
|
||||
use diesel::prelude::*;
|
||||
use rocket::response::Debug;
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Queryable)]
|
||||
struct Language {
|
||||
id: i32,
|
||||
hello_world: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Queryable)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Wrapper {
|
||||
identifier: String,
|
||||
label: String,
|
||||
is_asm: bool,
|
||||
is_formatter: bool,
|
||||
}
|
||||
|
||||
#[derive(Identifiable, Queryable)]
|
||||
struct Implementation {
|
||||
id: i32,
|
||||
label: String,
|
||||
}
|
||||
|
||||
#[derive(Associations, Identifiable, Queryable)]
|
||||
#[belongs_to(Implementation)]
|
||||
struct ImplementationWrapper {
|
||||
id: i32,
|
||||
implementation_id: i32,
|
||||
identifier: String,
|
||||
label: String,
|
||||
is_asm: bool,
|
||||
is_formatter: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct JsonLanguage {
|
||||
hello_world: Option<String>,
|
||||
implementations: Vec<JsonImplementation>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct JsonImplementation {
|
||||
label: String,
|
||||
wrappers: Vec<Wrapper>,
|
||||
}
|
||||
|
||||
#[get("/api/v0/language/<identifier>")]
|
||||
pub async fn api_language(
|
||||
db: Db,
|
||||
identifier: String,
|
||||
) -> Result<Option<Json<JsonLanguage>>, Debug<diesel::result::Error>> {
|
||||
db.run(|conn| {
|
||||
let language: Option<Language> = languages::table
|
||||
.filter(languages::identifier.eq(identifier))
|
||||
.select((languages::language_id, languages::hello_world))
|
||||
.get_result(conn)
|
||||
.optional()?;
|
||||
Ok(if let Some(language) = language {
|
||||
let implementations = implementations::table
|
||||
.select((implementations::implementation_id, implementations::label))
|
||||
.filter(implementations::language_id.eq(language.id))
|
||||
.order(implementations::ordering)
|
||||
.load(conn)?;
|
||||
let implementation_wrappers = ImplementationWrapper::belonging_to(&implementations)
|
||||
.select((
|
||||
implementation_wrappers::implementation_wrapper_id,
|
||||
implementation_wrappers::implementation_id,
|
||||
implementation_wrappers::identifier,
|
||||
implementation_wrappers::label,
|
||||
implementation_wrappers::is_asm,
|
||||
implementation_wrappers::is_formatter,
|
||||
))
|
||||
.order(implementation_wrappers::ordering)
|
||||
.load(conn)?;
|
||||
let implementations = implementation_wrappers
|
||||
.grouped_by(&implementations)
|
||||
.into_iter()
|
||||
.zip(implementations)
|
||||
.map(|(wrappers, implementation)| JsonImplementation {
|
||||
label: implementation.label,
|
||||
wrappers: wrappers
|
||||
.into_iter()
|
||||
.map(
|
||||
|ImplementationWrapper {
|
||||
identifier,
|
||||
label,
|
||||
is_asm,
|
||||
is_formatter,
|
||||
..
|
||||
}| {
|
||||
Wrapper {
|
||||
identifier,
|
||||
label,
|
||||
is_asm,
|
||||
is_formatter,
|
||||
}
|
||||
},
|
||||
)
|
||||
.collect(),
|
||||
})
|
||||
.collect();
|
||||
Some(Json(JsonLanguage {
|
||||
implementations,
|
||||
hello_world: language.hello_world,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2021 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/>.
|
||||
|
||||
use crate::models::Cors;
|
||||
use crate::schema::languages;
|
||||
use crate::Db;
|
||||
use diesel::prelude::*;
|
||||
use rocket::response::Debug;
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Queryable, Serialize)]
|
||||
pub struct Language {
|
||||
identifier: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[get("/api/v1/languages")]
|
||||
pub async fn api_languages(
|
||||
db: Db,
|
||||
) -> Result<Cors<Json<Vec<Language>>>, Debug<diesel::result::Error>> {
|
||||
let languages = db
|
||||
.run(|conn| {
|
||||
languages::table
|
||||
.select((languages::identifier, languages::name))
|
||||
.load(conn)
|
||||
})
|
||||
.await?;
|
||||
Ok(Cors(Json(languages)))
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "database_tests"))]
|
||||
mod test {
|
||||
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
use rocket::http::Status;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::uri;
|
||||
|
||||
#[test]
|
||||
fn test_cors() {
|
||||
let rocket = Client::untracked(crate::rocket()).unwrap();
|
||||
let response = rocket.get(uri!(super::api_languages)).dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get_one(ACCESS_CONTROL_ALLOW_ORIGIN.as_str()),
|
||||
Some("*"),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -14,10 +14,8 @@
|
|||
// 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/>.
|
||||
|
||||
use super::WithTxt;
|
||||
use crate::models::language::Language;
|
||||
use crate::models::paste::{ExternPaste, Paste};
|
||||
use crate::schema::{languages, pastes};
|
||||
use crate::schema::pastes;
|
||||
use crate::Db;
|
||||
use diesel::prelude::*;
|
||||
use rocket::http::uri::Origin;
|
||||
|
@ -27,7 +25,6 @@ use serde::Serialize;
|
|||
|
||||
#[derive(Serialize)]
|
||||
struct DisplayPaste {
|
||||
languages: Vec<Language>,
|
||||
description: String,
|
||||
paste: ExternPaste,
|
||||
raw_paste_url: Origin<'static>,
|
||||
|
@ -40,19 +37,9 @@ pub async fn display_paste(
|
|||
) -> Result<Option<Template>, Debug<diesel::result::Error>> {
|
||||
db.run(move |conn| {
|
||||
Paste::delete_old(conn)?;
|
||||
let languages = Language::fetch(conn)?;
|
||||
|
||||
let paste: Option<Paste> = pastes::table
|
||||
.inner_join(languages::table.on(pastes::language_id.eq(languages::language_id)))
|
||||
.select((
|
||||
pastes::identifier,
|
||||
pastes::paste,
|
||||
pastes::language_id,
|
||||
pastes::delete_at,
|
||||
languages::identifier,
|
||||
pastes::stdin,
|
||||
pastes::exit_code,
|
||||
pastes::output,
|
||||
))
|
||||
.select((pastes::identifier, pastes::paste, pastes::delete_at))
|
||||
.filter(pastes::identifier.eq(&identifier))
|
||||
.get_result(conn)
|
||||
.optional()?;
|
||||
|
@ -61,7 +48,6 @@ pub async fn display_paste(
|
|||
Ok(Some(Template::render(
|
||||
"display-paste",
|
||||
&DisplayPaste {
|
||||
languages,
|
||||
description,
|
||||
paste: ExternPaste::from_paste(paste),
|
||||
raw_paste_url: uri!(super::raw_paste(identifier)),
|
||||
|
|
|
@ -14,19 +14,15 @@
|
|||
// 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/>.
|
||||
|
||||
use crate::models::language::Language;
|
||||
use crate::Db;
|
||||
use rocket::response::Debug;
|
||||
use rocket_dyn_templates::Template;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Index {
|
||||
languages: Vec<Language>,
|
||||
}
|
||||
struct Index {}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index(db: Db) -> Result<Template, Debug<diesel::result::Error>> {
|
||||
let languages = db.run(|conn| Language::fetch(conn)).await?;
|
||||
Ok(Template::render("index", &Index { languages }))
|
||||
pub async fn index(_db: Db) -> Result<Template, Debug<diesel::result::Error>> {
|
||||
Ok(Template::render("index", &Index {}))
|
||||
}
|
||||
|
|
|
@ -14,49 +14,56 @@
|
|||
// 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/>.
|
||||
|
||||
use crate::models::paste::{self, ExtraPasteParameters, InsertionError};
|
||||
use crate::models::paste::{self, InsertionError};
|
||||
use crate::Db;
|
||||
use chrono::{Duration, Utc};
|
||||
use rocket::form::Form;
|
||||
use rocket::form::{self, Form, FromForm, FromFormField};
|
||||
use rocket::response::Redirect;
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct PasteForm {
|
||||
language: String,
|
||||
#[field(validate = len(1..))]
|
||||
code: String,
|
||||
share: Share,
|
||||
#[field(default = "")]
|
||||
stdin: String,
|
||||
output: Option<String>,
|
||||
status: Option<i32>,
|
||||
duration: ShareDuration,
|
||||
}
|
||||
|
||||
#[derive(FromFormField)]
|
||||
pub enum Share {
|
||||
Share,
|
||||
Share24,
|
||||
pub enum ShareDuration {
|
||||
OneHour,
|
||||
ThreeHour,
|
||||
TwentyFourHour,
|
||||
ThreeDays,
|
||||
SevenDays,
|
||||
NinetyDays,
|
||||
}
|
||||
|
||||
impl<'v> FromFormField<'v> for ShareDuration {
|
||||
fn from_value(field: rocket::form::ValueField<'v>) -> rocket::form::Result<'v, Self> {
|
||||
match field.value {
|
||||
"1h" => Ok(ShareDuration::OneHour),
|
||||
"3h" => Ok(ShareDuration::ThreeHour),
|
||||
"24h" => Ok(ShareDuration::TwentyFourHour),
|
||||
"3d" => Ok(ShareDuration::ThreeDays),
|
||||
"7d" => Ok(ShareDuration::SevenDays),
|
||||
"90d" => Ok(ShareDuration::NinetyDays),
|
||||
|
||||
_ => Err(form::Error::validation("invalid value for share duration"))?,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/", data = "<form>")]
|
||||
pub async fn insert_paste(db: Db, form: Form<PasteForm>) -> Result<Redirect, InsertionError> {
|
||||
let delete_at = match form.share {
|
||||
Share::Share => None,
|
||||
Share::Share24 => Some(Utc::now() + Duration::hours(24)),
|
||||
let delete_at = match form.duration {
|
||||
ShareDuration::OneHour => Some(Utc::now() + Duration::hours(1)),
|
||||
ShareDuration::ThreeHour => Some(Utc::now() + Duration::hours(3)),
|
||||
ShareDuration::TwentyFourHour => Some(Utc::now() + Duration::hours(24)),
|
||||
ShareDuration::ThreeDays => Some(Utc::now() + Duration::days(3)),
|
||||
ShareDuration::SevenDays => Some(Utc::now() + Duration::days(7)),
|
||||
ShareDuration::NinetyDays => Some(Utc::now() + Duration::days(90)),
|
||||
};
|
||||
|
||||
let identifier = db
|
||||
.run(move |conn| {
|
||||
paste::insert(
|
||||
conn,
|
||||
delete_at,
|
||||
&form.language,
|
||||
&form.code,
|
||||
ExtraPasteParameters {
|
||||
stdin: &form.stdin,
|
||||
output: form.output.as_deref(),
|
||||
exit_code: form.status,
|
||||
},
|
||||
)
|
||||
})
|
||||
.run(move |conn| paste::insert(conn, delete_at, &form.code))
|
||||
.await?;
|
||||
Ok(Redirect::to(uri!(super::display_paste(identifier))))
|
||||
}
|
||||
|
|
|
@ -15,23 +15,17 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
mod api_insert_paste_route;
|
||||
mod api_language_route;
|
||||
mod api_languages_route;
|
||||
mod config_route;
|
||||
mod display_paste_route;
|
||||
mod index_route;
|
||||
mod insert_paste_route;
|
||||
mod metrics_route;
|
||||
mod raw_paste_route;
|
||||
mod run_route;
|
||||
|
||||
pub use api_insert_paste_route::*;
|
||||
pub use api_language_route::*;
|
||||
pub use api_languages_route::*;
|
||||
pub use config_route::*;
|
||||
pub use display_paste_route::*;
|
||||
pub use index_route::*;
|
||||
pub use insert_paste_route::*;
|
||||
pub use metrics_route::*;
|
||||
pub use raw_paste_route::*;
|
||||
pub use run_route::*;
|
||||
|
|
|
@ -19,45 +19,10 @@ use crate::schema::pastes;
|
|||
use crate::Db;
|
||||
use diesel::prelude::*;
|
||||
use rocket::http::hyper::header::ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||
use rocket::http::uri::fmt::{Formatter, FromUriParam, Path, UriDisplay};
|
||||
use rocket::http::{impl_from_uri_param_identity, Header};
|
||||
use rocket::request::{FromParam, Request};
|
||||
use rocket::http::Header;
|
||||
use rocket::request::Request;
|
||||
use rocket::response::status::NotFound;
|
||||
use rocket::response::{self, Debug, Responder, Response};
|
||||
use std::fmt;
|
||||
|
||||
pub struct WithTxt(String);
|
||||
|
||||
impl<'a> FromParam<'a> for WithTxt {
|
||||
type Error = &'a str;
|
||||
|
||||
fn from_param(param: &str) -> Result<Self, &str> {
|
||||
if let Some(param) = param.strip_suffix(".txt") {
|
||||
Ok(WithTxt(
|
||||
String::from_param(param).unwrap_or_else(|e| match e {}),
|
||||
))
|
||||
} else {
|
||||
Err(param)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UriDisplay<Path> for WithTxt {
|
||||
fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
|
||||
self.0.fmt(f)?;
|
||||
f.write_raw(".txt")
|
||||
}
|
||||
}
|
||||
|
||||
impl_from_uri_param_identity!([Path] WithTxt);
|
||||
|
||||
impl FromUriParam<Path, String> for WithTxt {
|
||||
type Target = WithTxt;
|
||||
|
||||
fn from_uri_param(param: String) -> WithTxt {
|
||||
WithTxt(param)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RawPasteResponse {
|
||||
Found(String),
|
||||
|
@ -80,17 +45,17 @@ impl<'r> Responder<'r, 'static> for RawPasteResponse {
|
|||
}
|
||||
}
|
||||
|
||||
#[get("/<identifier>")]
|
||||
#[get("/raw/<identifier>")]
|
||||
pub async fn raw_paste(
|
||||
db: Db,
|
||||
identifier: WithTxt,
|
||||
identifier: String,
|
||||
) -> Result<RawPasteResponse, Debug<diesel::result::Error>> {
|
||||
Ok(db
|
||||
.run(move |conn| {
|
||||
Paste::delete_old(conn)?;
|
||||
pastes::table
|
||||
.select(pastes::paste)
|
||||
.filter(pastes::identifier.eq(&identifier.0))
|
||||
.filter(pastes::identifier.eq(&identifier))
|
||||
.get_result(conn)
|
||||
.optional()
|
||||
})
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
// pastebin.run
|
||||
// Copyright (C) 2021 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/>.
|
||||
|
||||
use crate::schema::implementation_wrappers;
|
||||
use crate::Db;
|
||||
use diesel::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
use reqwest::Client;
|
||||
use rocket::form::Form;
|
||||
use rocket::response::Debug;
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
|
||||
static CLIENT: Lazy<Client> = Lazy::new(Client::new);
|
||||
static SANDBOX_URL: Lazy<String> = Lazy::new(|| env::var("SANDBOX_URL").unwrap());
|
||||
|
||||
#[derive(FromForm)]
|
||||
pub struct RunForm {
|
||||
code: String,
|
||||
#[field(name = "compilerOptions")]
|
||||
compiler_options: String,
|
||||
stdin: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Request {
|
||||
files: Files,
|
||||
stdin: String,
|
||||
code: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Files {
|
||||
code: File,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct File {
|
||||
contents: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Output {
|
||||
status: Option<i32>,
|
||||
output: String,
|
||||
}
|
||||
|
||||
#[post("/api/v0/run/<identifier>", data = "<form>")]
|
||||
pub async fn run(
|
||||
db: Db,
|
||||
identifier: String,
|
||||
form: Form<RunForm>,
|
||||
) -> Result<Option<Json<Output>>, Debug<Box<dyn Error + Send + Sync>>> {
|
||||
let run = || async {
|
||||
let language_code = db
|
||||
.run(|conn| {
|
||||
implementation_wrappers::table
|
||||
.filter(implementation_wrappers::identifier.eq(identifier))
|
||||
.select(implementation_wrappers::code)
|
||||
.get_result(conn)
|
||||
.optional()
|
||||
})
|
||||
.await?;
|
||||
let language_code: String = if let Some(code) = language_code {
|
||||
code
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
let RunForm {
|
||||
code,
|
||||
compiler_options,
|
||||
stdin,
|
||||
} = form.into_inner();
|
||||
let json: Output = CLIENT
|
||||
.post(SANDBOX_URL.as_str())
|
||||
.json(&Request {
|
||||
files: Files {
|
||||
code: File { contents: code },
|
||||
},
|
||||
stdin,
|
||||
code: language_code.replace("%s", &compiler_options),
|
||||
})
|
||||
.send()
|
||||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
|
||||
Ok(Some(Json(json)))
|
||||
};
|
||||
run().await.map_err(Debug)
|
||||
}
|
|
@ -14,39 +14,22 @@ table! {
|
|||
table! {
|
||||
implementations (implementation_id) {
|
||||
implementation_id -> Int4,
|
||||
language_id -> Int4,
|
||||
identifier -> Text,
|
||||
label -> Text,
|
||||
ordering -> Int4,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
languages (language_id) {
|
||||
language_id -> Int4,
|
||||
priority -> Int4,
|
||||
name -> Text,
|
||||
identifier -> Text,
|
||||
hello_world -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
pastes (paste_id) {
|
||||
paste_id -> Int4,
|
||||
identifier -> Text,
|
||||
delete_at -> Nullable<Timestamptz>,
|
||||
created_at -> Timestamptz,
|
||||
language_id -> Int4,
|
||||
paste -> Text,
|
||||
stdin -> Text,
|
||||
exit_code -> Nullable<Int4>,
|
||||
output -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(implementation_wrappers -> implementations (implementation_id));
|
||||
joinable!(implementations -> languages (language_id));
|
||||
joinable!(pastes -> languages (language_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(implementation_wrappers, implementations, languages, pastes,);
|
||||
allow_tables_to_appear_in_same_query!(implementation_wrappers, implementations, pastes,);
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
<html lang=en>
|
||||
<meta charset=utf-8>
|
||||
<meta name=viewport content="width=device-width, initial-scale=1">
|
||||
<title>pastebin.run</title>
|
||||
<title>p.ishan.pw</title>
|
||||
{{ css_stylesheet() | safe }}
|
||||
<meta name=description content="{% block description %}Compile and share code in multiple programming languages{% endblock description %}">
|
||||
<meta name=description content="{% block description %}Compile and share code{% endblock description %}">
|
||||
<script type=module src="{{ js_path() }}"></script>
|
||||
<header id=header>
|
||||
<a href="/">
|
||||
|
@ -12,7 +12,8 @@
|
|||
</a>
|
||||
<nav id="menu-buttons">
|
||||
<ul>
|
||||
<li><a href="https://pastebin.run/api">API</a
|
||||
<!--TODO: do not hard code -->
|
||||
<li><a href="https://p.ishan.pw/api">API</a
|
||||
><li><a href="https://github.com/pastebinrun/pastebinrun/">Source code</a
|
||||
></ul>
|
||||
</nav>
|
||||
|
|
|
@ -3,33 +3,13 @@
|
|||
{% block description %}{{ description }}{% endblock %}
|
||||
|
||||
{% block paste_header %}
|
||||
<div id=markdown>
|
||||
{{ paste.markdown | safe }}
|
||||
</div>
|
||||
{% if paste.delete_at %}
|
||||
<div class="autodelete-text">
|
||||
This paste will be automatically deleted on {{ paste.delete_at }} UTC.
|
||||
This paste will be automatically deleted on {{ paste.delete_at }} UTC
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if paste.exit_code is defined %}
|
||||
<input type=hidden id=dbstatus value="{{ paste.exit_code }}">
|
||||
{% endif %}
|
||||
{% if paste.output is defined %}
|
||||
<input type=hidden id=dboutput value="{{ paste.output }}">
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block code %}{{ paste.paste }}{% endblock %}
|
||||
|
||||
{% block languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.identifier }}"
|
||||
{% if paste.language_id == language.id %}
|
||||
selected
|
||||
{% endif %}
|
||||
>{{ language.name }}</option>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
{% block rawpaste %}<div class="group rawpaste-text"><a href="{{ raw_paste_url }}">raw paste</a></div>{% endblock %}
|
||||
|
|
|
@ -4,13 +4,6 @@
|
|||
<form method="post" action="/">
|
||||
{% block paste_header %}{% endblock %}
|
||||
<div id="toolbar">
|
||||
<div class=group><label>Language: <select id=language name=language>
|
||||
{% block languages %}
|
||||
{% for language in languages %}
|
||||
<option value="{{ language.identifier }}">{{ language.name }}</option>
|
||||
{% endfor %}
|
||||
{% endblock languages %}
|
||||
</select></label></div>
|
||||
{% block rawpaste %}{% endblock %}
|
||||
<span id="right-buttons">
|
||||
<button type=submit name=share value=share24>Share (delete after 24 hours)</button>
|
||||
|
|
Loading…
Reference in New Issue