Merge branch 'redesign' into 'master'
Redesign pastebin.run See merge request pastebin.run/server!91
This commit is contained in:
commit
3fcf4b29e0
|
@ -2,6 +2,7 @@ export interface EditorType {
|
|||
setLanguage(identifier: string): void
|
||||
getValue(): string
|
||||
setValue(text: string): void
|
||||
update(): void
|
||||
unload(): void
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.CodeMirror {
|
||||
border: 1px solid #bbbdbe;
|
||||
height: 372px;
|
||||
height: 100%;
|
||||
font-size: 14.4px;
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ class CodeMirrorEditor {
|
|||
this.editor.setValue(value)
|
||||
}
|
||||
|
||||
update() {}
|
||||
|
||||
unload() {
|
||||
this.editor.toTextArea()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
.monaco {
|
||||
border: 1px solid #bbbdbe;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
font-size: 14.4px;
|
||||
height: 372px;
|
||||
}
|
||||
|
|
|
@ -75,6 +75,14 @@ class MonacoEditor {
|
|||
this.editor.setValue(value)
|
||||
}
|
||||
|
||||
update() {
|
||||
// Monaco has no idea how to reflow, so let's force it to reflow twice
|
||||
this.container.style.width = '0'
|
||||
this.editor.layout()
|
||||
this.container.style.width = ''
|
||||
this.editor.layout()
|
||||
}
|
||||
|
||||
unload() {
|
||||
this.textarea.value = this.getValue()
|
||||
this.editor.dispose()
|
||||
|
|
|
@ -17,6 +17,8 @@ class TextAreaEditor {
|
|||
this.textarea.value = value
|
||||
}
|
||||
|
||||
update() {}
|
||||
|
||||
unload() {
|
||||
this.textarea.removeEventListener('input', this.onChange)
|
||||
}
|
||||
|
|
|
@ -10,14 +10,13 @@ class Editor {
|
|||
codeElement: HTMLTextAreaElement
|
||||
output: Output
|
||||
autodeleteText: HTMLSpanElement
|
||||
autodeleteCheckbox: HTMLLabelElement
|
||||
helloWorldLink: HTMLSpanElement
|
||||
submit: HTMLInputElement
|
||||
submitButtons: HTMLInputElement[]
|
||||
detailsElement: HTMLDetailsElement
|
||||
stdinElement: HTMLTextAreaElement
|
||||
editor: EditorType
|
||||
currentLanguage: string | null = null
|
||||
abortEval: AbortController | null = null
|
||||
isHelloWorld: boolean = false
|
||||
|
||||
initialize(form) {
|
||||
this.languageSelector = form.querySelector('#language')
|
||||
|
@ -26,20 +25,20 @@ class Editor {
|
|||
this.initializeEditor(createTextareaEditor)
|
||||
onChange(editor => this.changeEditor(editor))
|
||||
this.initConfiguredEditor()
|
||||
this.output = new Output(form.querySelector('#output'))
|
||||
this.output = Output.addTo(form.querySelector('#split'))
|
||||
const stdout = document.querySelector<HTMLInputElement>('#dbstdout')
|
||||
if (stdout) {
|
||||
this.output.display({}, {
|
||||
this.displayOutput({}, {
|
||||
stdout: stdout.value,
|
||||
stderr: document.querySelector<HTMLInputElement>('#dbstderr').value,
|
||||
status: +document.querySelector<HTMLInputElement>('#dbstatus') ?.value,
|
||||
})
|
||||
}
|
||||
this.autodeleteText = form.querySelector('#autodelete-text')
|
||||
this.autodeleteCheckbox = form.querySelector('#automatically-hidden-label')
|
||||
this.helloWorldLink = form.querySelector('#hello-world')
|
||||
this.submit = form.querySelector('[type=submit]')
|
||||
this.submit.disabled = true
|
||||
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) {
|
||||
for (const name of ['stdout', 'stderr', 'status']) {
|
||||
|
@ -58,7 +57,10 @@ class Editor {
|
|||
summary.textContent = 'Standard input'
|
||||
this.stdinElement = document.createElement('textarea')
|
||||
this.stdinElement.name = 'stdin'
|
||||
this.stdinElement.addEventListener('change', () => this.changeToLookLikeNewPaste())
|
||||
this.stdinElement.addEventListener('change', () => {
|
||||
this.isHelloWorld = false
|
||||
this.changeToLookLikeNewPaste()
|
||||
})
|
||||
this.detailsElement.append(summary, this.stdinElement)
|
||||
const dbStdin = document.querySelector<HTMLInputElement>('#dbstdin') ?.value
|
||||
if (dbStdin) {
|
||||
|
@ -67,12 +69,10 @@ class Editor {
|
|||
} else {
|
||||
this.detailsElement.style.display = 'none'
|
||||
}
|
||||
form.querySelector('#buttons').append(this.detailsElement)
|
||||
if (this.autodeleteText) {
|
||||
this.autodeleteCheckbox.style.display = 'none'
|
||||
}
|
||||
form.querySelector('#extrafields').append(this.detailsElement)
|
||||
this.assignEvents()
|
||||
this.updateLanguage()
|
||||
addEventListener('resize', () => this.editor.update())
|
||||
}
|
||||
|
||||
async initConfiguredEditor() {
|
||||
|
@ -85,7 +85,10 @@ class Editor {
|
|||
}
|
||||
|
||||
initializeEditor(createEditor) {
|
||||
this.editor = createEditor(this.codeElement, () => this.changeToLookLikeNewPaste())
|
||||
this.editor = createEditor(this.codeElement, () => {
|
||||
this.changeToLookLikeNewPaste()
|
||||
this.isHelloWorld = false
|
||||
})
|
||||
if (this.currentLanguage) {
|
||||
this.editor.setLanguage(this.currentLanguage)
|
||||
}
|
||||
|
@ -99,10 +102,10 @@ class Editor {
|
|||
changeToLookLikeNewPaste() {
|
||||
if (this.autodeleteText) {
|
||||
this.autodeleteText.style.display = 'none'
|
||||
this.autodeleteCheckbox.style.display = ''
|
||||
}
|
||||
this.submit.disabled = false
|
||||
this.output.clear()
|
||||
for (const submit of this.submitButtons) {
|
||||
submit.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
assignEvents() {
|
||||
|
@ -114,21 +117,22 @@ class Editor {
|
|||
|
||||
async updateLanguage() {
|
||||
this.wrapperButtons.clear()
|
||||
this.helloWorldLink.textContent = ''
|
||||
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()) {
|
||||
if (language.helloWorldPaste) {
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = '/' + language.helloWorldPaste
|
||||
anchor.textContent = 'Hello world program'
|
||||
this.helloWorldLink.append(' | ', anchor)
|
||||
}
|
||||
this.detailsElement.style.display = language.implementations.length ? 'block' : 'none'
|
||||
this.wrapperButtons.update(language.implementations)
|
||||
const isStillHelloWorld = () => this.isHelloWorld || this.editor.getValue() === ''
|
||||
if (isStillHelloWorld()) {
|
||||
const helloWorldText = language.helloWorldPaste ? await (await fetch(`/${language.helloWorldPaste}.txt`)).text() : ""
|
||||
if (isStillHelloWorld()) {
|
||||
this.editor.setValue(helloWorldText)
|
||||
this.isHelloWorld = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,6 +142,7 @@ class Editor {
|
|||
|
||||
async run(wrapper, compilerOptions) {
|
||||
this.output.clear()
|
||||
this.editor.update()
|
||||
if (this.abortEval) {
|
||||
this.abortEval.abort()
|
||||
}
|
||||
|
@ -168,7 +173,12 @@ class Editor {
|
|||
if (wrapper.isFormatter) {
|
||||
this.editor.setValue(response.stdout)
|
||||
}
|
||||
this.displayOutput(wrapper, response)
|
||||
}
|
||||
|
||||
displayOutput(wrapper, response) {
|
||||
this.output.display(wrapper, response)
|
||||
this.editor.update()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
import { Wrapper } from './types'
|
||||
import { SplitChunksPlugin } from 'webpack'
|
||||
|
||||
const filterRegex = /(?:\t\.(?:text|file|section|globl|p2align|type|cfi_.*|size|section)\b|.Lfunc_end).*\n?/g
|
||||
|
||||
export default class Output {
|
||||
split: HTMLDivElement
|
||||
outputContainer: HTMLDivElement
|
||||
output: HTMLDivElement
|
||||
filterAsm = document.createElement('label')
|
||||
filterAsmCheckbox = document.createElement('input')
|
||||
wrapper: Wrapper | null = null
|
||||
json: { stdout: string, stderr: string, status: number | null } | null = null
|
||||
|
||||
constructor(output) {
|
||||
static addTo(split: HTMLDivElement) {
|
||||
const outputContainer = document.createElement('div')
|
||||
outputContainer.id = 'outputcontainer'
|
||||
const output = document.createElement('div')
|
||||
output.id = 'output'
|
||||
outputContainer.append(output)
|
||||
return new Output(split, outputContainer, output)
|
||||
}
|
||||
|
||||
private constructor(split: HTMLDivElement, outputContainer: HTMLDivElement, output: HTMLDivElement) {
|
||||
this.split = split
|
||||
this.outputContainer = outputContainer
|
||||
this.output = output
|
||||
this.filterAsmCheckbox.type = 'checkbox'
|
||||
this.filterAsmCheckbox.checked = true
|
||||
|
@ -18,8 +32,8 @@ export default class Output {
|
|||
}
|
||||
|
||||
clear() {
|
||||
this.json = null
|
||||
this.output.textContent = ''
|
||||
this.outputContainer.remove()
|
||||
}
|
||||
|
||||
error() {
|
||||
|
@ -34,7 +48,8 @@ export default class Output {
|
|||
|
||||
update() {
|
||||
const { stdout, stderr, status } = this.json
|
||||
this.output.textContent = ''
|
||||
this.clear()
|
||||
this.split.append(this.outputContainer)
|
||||
if (stderr) {
|
||||
const stderrHeader = document.createElement('h2')
|
||||
stderrHeader.textContent = 'Standard error'
|
||||
|
|
|
@ -29,12 +29,13 @@ export default class WrapperButtons {
|
|||
this.select.append(option)
|
||||
}
|
||||
this.buttonsContainer.textContent = ''
|
||||
if (implementations.length > 1) {
|
||||
this.buttonsContainer.append(this.select)
|
||||
this.select.addEventListener('change', () => this.updateButtons())
|
||||
}
|
||||
if (implementations.length !== 0) {
|
||||
this.buttonsContainer.append(this.compilerOptions, this.buttons)
|
||||
this.buttonsContainer.append(this.buttons)
|
||||
if (implementations.length > 1) {
|
||||
this.buttonsContainer.append(this.select)
|
||||
this.select.addEventListener('change', () => this.updateButtons())
|
||||
}
|
||||
this.buttonsContainer.append(this.compilerOptions)
|
||||
}
|
||||
this.updateButtons()
|
||||
}
|
||||
|
@ -71,7 +72,7 @@ export default class WrapperButtons {
|
|||
first = false
|
||||
}
|
||||
button.addEventListener('click', event)
|
||||
this.buttons.append(button)
|
||||
this.buttons.append(button, ' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ pub fn insert(
|
|||
Ok(insert_paste.identifier)
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExternPaste {
|
||||
pub paste: String,
|
||||
pub language_id: i32,
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::Connection;
|
|||
use chrono::{Duration, Utc};
|
||||
use futures::Future;
|
||||
use futures03::TryFutureExt;
|
||||
use serde::de::IgnoredAny;
|
||||
use serde::Deserialize;
|
||||
use tokio_executor::blocking;
|
||||
use warp::http::header::LOCATION;
|
||||
|
@ -15,7 +14,7 @@ use warp::{reply, Rejection, Reply};
|
|||
pub struct PasteForm {
|
||||
language: String,
|
||||
code: String,
|
||||
autodelete: Option<IgnoredAny>,
|
||||
share: Share,
|
||||
#[serde(default)]
|
||||
stdin: String,
|
||||
stdout: Option<String>,
|
||||
|
@ -23,11 +22,18 @@ pub struct PasteForm {
|
|||
status: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Share {
|
||||
Share,
|
||||
Share24,
|
||||
}
|
||||
|
||||
pub fn insert_paste(
|
||||
PasteForm {
|
||||
language,
|
||||
code,
|
||||
autodelete,
|
||||
share,
|
||||
stdin,
|
||||
stdout,
|
||||
stderr,
|
||||
|
@ -36,7 +42,10 @@ pub fn insert_paste(
|
|||
connection: Connection,
|
||||
) -> impl Future<Item = impl Reply, Error = Rejection> {
|
||||
blocking::run(move || {
|
||||
let delete_at = autodelete.map(|_| Utc::now() + Duration::hours(24));
|
||||
let delete_at = match share {
|
||||
Share::Share => None,
|
||||
Share::Share24 => Some(Utc::now() + Duration::hours(24)),
|
||||
};
|
||||
let identifier = paste::insert(
|
||||
&connection,
|
||||
delete_at,
|
||||
|
|
|
@ -203,8 +203,8 @@ fn not_found(pool: PgPool) -> impl Clone + Fn(Rejection) -> NotFoundFuture {
|
|||
Err(rejection)
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
.compat()
|
||||
.boxed()
|
||||
.compat()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -310,7 +310,7 @@ mod test {
|
|||
#[test]
|
||||
#[cfg_attr(not(feature = "database_tests"), ignore)]
|
||||
fn test_raw_pastes() {
|
||||
let body = format!("language={}&code=abc", get_sh_id());
|
||||
let body = format!("language={}&code=abc&share=share24", get_sh_id());
|
||||
let reply = warp::test::request()
|
||||
.method("POST")
|
||||
.header(CONTENT_LENGTH, body.len())
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.6;
|
||||
color: black;
|
||||
text-align: justify;
|
||||
|
@ -13,9 +19,7 @@ body {
|
|||
box-shadow: 0 -5px 2px rgba(0, 0, 0, 0.1) inset;
|
||||
}
|
||||
#header div {
|
||||
margin: 0 auto;
|
||||
padding: 0 1em;
|
||||
max-width: 900px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
@ -40,17 +44,41 @@ body {
|
|||
border: none;
|
||||
}
|
||||
#article {
|
||||
margin: 0 auto;
|
||||
padding: 0 1em;
|
||||
max-width: 980px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
padding: 0 1em 1em;
|
||||
position: relative;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
#toolbar {
|
||||
padding: 6px 0;
|
||||
}
|
||||
#split {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
#extrafieldsplit {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
#textarea {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 372px;
|
||||
resize: vertical;
|
||||
height: 100%;
|
||||
resize: none;
|
||||
}
|
||||
[name=stdin] {
|
||||
height: 124px;
|
||||
|
@ -58,6 +86,28 @@ textarea {
|
|||
#right-buttons {
|
||||
float: right;
|
||||
}
|
||||
#outputcontainer {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
#output {
|
||||
padding: 0 1em;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
#split {
|
||||
flex-direction: column;
|
||||
flex: auto;
|
||||
}
|
||||
#output {
|
||||
padding: 0;
|
||||
max-height: 40vh;
|
||||
max-width: unset;
|
||||
}
|
||||
}
|
||||
table {
|
||||
overflow: auto;
|
||||
border-collapse: collapse;
|
|
@ -1,8 +1,6 @@
|
|||
@()
|
||||
<div id="buttons">
|
||||
<div id="right-buttons">
|
||||
<span id="wrapper-buttons"></span>
|
||||
<input type=submit value=Share>
|
||||
</div>
|
||||
</div>
|
||||
<div id="output"></div>
|
||||
<span id="wrapper-buttons"></span>
|
||||
<span id="right-buttons">
|
||||
<button type=submit name=share value=share24>Share (delete after 24 hours)</button>
|
||||
<button type=submit name=share value=share>Share</button>
|
||||
</span>
|
||||
|
|
|
@ -8,20 +8,21 @@
|
|||
|
||||
@Html(paste.markdown)
|
||||
<form method="post" action="/" id="editor">
|
||||
<p>
|
||||
@if let Some(delete_at) = paste.delete_at {
|
||||
<div id="autodelete-text">
|
||||
This paste will be automatically deleted on @delete_at.format("%Y-%m-%d %H:%M") UTC.
|
||||
</div>
|
||||
}
|
||||
<div id="toolbar">
|
||||
@:language_selection(selection)
|
||||
@if let Some(delete_at) = paste.delete_at {
|
||||
<span id="autodelete-text">
|
||||
This paste will be automatically deleted on @delete_at.format("%Y-%m-%d %H:%M") UTC.
|
||||
</span>
|
||||
}
|
||||
<span id="automatically-hidden-label">
|
||||
<label><input type=checkbox name=autodelete checked> Automatically delete after 24 hours</label>
|
||||
<span id=hello-world></span>
|
||||
</span>
|
||||
</p>
|
||||
<p><textarea id=code name=code>@('\n')@paste.paste</textarea></p>
|
||||
@:buttons()
|
||||
@:buttons()
|
||||
</div>
|
||||
<div id="split">
|
||||
<div id="extrafieldsplit">
|
||||
<div id="textarea"><textarea id=code name=code>@('\n')@paste.paste</textarea></div>
|
||||
<div id="extrafields"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<input type=hidden id=dbstdin value="@paste.stdin">
|
||||
@if let Some(exit_code) = paste.exit_code {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<meta charset=utf-8>
|
||||
<meta name=viewport content="width=device-width, initial-scale=1">
|
||||
<title>pastebin.run</title>
|
||||
<link rel=stylesheet href="/static/style.css">
|
||||
<link rel=stylesheet href="/static/style-v2.css">
|
||||
<meta name=description content="@session.description">
|
||||
<header id=header>
|
||||
<div>
|
||||
|
|
|
@ -1,19 +1,8 @@
|
|||
@use crate::models::language::Selection;
|
||||
@use crate::models::session::Session;
|
||||
@use crate::templates::{buttons, header, footer, language_selection};
|
||||
@use crate::models::paste::ExternPaste;
|
||||
@use crate::templates::display_paste;
|
||||
|
||||
@(session: &Session, selection: Selection)
|
||||
|
||||
@:header(session)
|
||||
<form method="post" action="/" id="editor">
|
||||
<p>
|
||||
@:language_selection(selection)
|
||||
<label>
|
||||
<input type=checkbox name=autodelete checked> Automatically delete after 24 hours
|
||||
</label>
|
||||
<span id=hello-world></span>
|
||||
</p>
|
||||
<p><textarea id=code name=code></textarea></p>
|
||||
@:buttons()
|
||||
</form>
|
||||
@:footer(session)
|
||||
@:display_paste(session, ExternPaste::default(), selection)
|
||||
|
|
Loading…
Reference in New Issue
Block a user