Compare commits
26 Commits
09fdb97b6f
...
master
Author | SHA1 | Date | |
---|---|---|---|
3f4c542265 | |||
3d4dae9515 | |||
88d47ef535 | |||
da32c3bdfa | |||
9778a49c64 | |||
79e0e5651a | |||
a7e6488e72 | |||
9c74f41022 | |||
98711948b6 | |||
f60d5f2114 | |||
c8e91ba949 | |||
78af91e971 | |||
af219e69bb | |||
f3360b6041 | |||
1e2503ef44 | |||
ae718e97f5 | |||
2c1cccf25a | |||
288c127520 | |||
93d491445a | |||
2f008730f9 | |||
5b95ce2e33 | |||
6cd275994e | |||
f046d9cc68 | |||
0e585ed1cd | |||
b2c034df5f | |||
c2c8b348e6 |
32
README.md
Normal file
32
README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Paste
|
||||||
|
|
||||||
|
A simple pastebin service. Running at [paste.fascinated.cc](https://paste.fascinated.cc).
|
||||||
|
|
||||||
|
## Javascript Utility
|
||||||
|
|
||||||
|
```js
|
||||||
|
/**
|
||||||
|
* Uploads a paste to paste.fascinated.cc
|
||||||
|
*
|
||||||
|
* @param content the content of the paste
|
||||||
|
* @returns the paste key and the URL
|
||||||
|
*/
|
||||||
|
async function uploadPaste(content: string) {
|
||||||
|
const response = await fetch("https://paste.fascinated.cc/api/upload", {
|
||||||
|
method: "POST",
|
||||||
|
body: content,
|
||||||
|
});
|
||||||
|
const json = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(json.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...json,
|
||||||
|
url: `https://paste.fascinated.cc/${json.key}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(await uploadPaste("Hello, World!"));
|
||||||
|
```
|
7
pom.xml
7
pom.xml
@ -77,12 +77,17 @@
|
|||||||
<version>3.14.0</version>
|
<version>3.14.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.googlecode.htmlcompressor</groupId>
|
||||||
|
<artifactId>htmlcompressor</artifactId>
|
||||||
|
<version>1.5.2</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Sentry -->
|
<!-- Sentry -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.sentry</groupId>
|
<groupId>io.sentry</groupId>
|
||||||
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
|
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
|
||||||
<version>7.9.0</version>
|
<version>7.10.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
package cc.fascinated.backend.common;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
public class FileHeaderChecker {
|
||||||
|
// Define known file headers
|
||||||
|
private static final Map<String, byte[]> FILE_HEADERS = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
FILE_HEADERS.put("PNG", new byte[]{(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A});
|
||||||
|
FILE_HEADERS.put("JPEG", new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF});
|
||||||
|
FILE_HEADERS.put("GIF", new byte[]{0x47, 0x49, 0x46, 0x38});
|
||||||
|
FILE_HEADERS.put("PDF", new byte[]{0x25, 0x50, 0x44, 0x46});
|
||||||
|
FILE_HEADERS.put("ZIP", new byte[]{0x50, 0x4B, 0x03, 0x04});
|
||||||
|
FILE_HEADERS.put("RAR", new byte[]{0x52, 0x61, 0x72, 0x21});
|
||||||
|
FILE_HEADERS.put("7Z", new byte[]{0x37, 0x7A, (byte) 0xBC, (byte) 0xAF, (byte) 0x27, 0x1C});
|
||||||
|
FILE_HEADERS.put("TAR", new byte[]{0x75, 0x73, 0x74, 0x61, 0x72});
|
||||||
|
FILE_HEADERS.put("BZ2", new byte[]{0x42, 0x5A, 0x68});
|
||||||
|
FILE_HEADERS.put("GZ", new byte[]{0x1F, (byte) 0x8B});
|
||||||
|
FILE_HEADERS.put("EXE", new byte[]{0x4D, 0x5A});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a string to a byte array using UTF-8 encoding.
|
||||||
|
*
|
||||||
|
* @param input the string to convert
|
||||||
|
* @return the byte array representation of the string
|
||||||
|
*/
|
||||||
|
private static byte[] stringToByteArray(String input) {
|
||||||
|
return input.getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the byte array starts with the specified header.
|
||||||
|
*
|
||||||
|
* @param byteArray the byte array to check
|
||||||
|
* @param header the header to look for
|
||||||
|
* @return true if the byte array starts with the header, false otherwise
|
||||||
|
*/
|
||||||
|
private static boolean startsWith(byte[] byteArray, byte[] header) {
|
||||||
|
if (byteArray.length < header.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < header.length; i++) {
|
||||||
|
if (byteArray[i] != header[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given string contains a known file header.
|
||||||
|
*
|
||||||
|
* @param input the string to check
|
||||||
|
* @return true if the string contains a known file header, false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean containsFileHeader(String input) {
|
||||||
|
byte[] byteArray = stringToByteArray(input);
|
||||||
|
|
||||||
|
for (byte[] header : FILE_HEADERS.values()) {
|
||||||
|
if (startsWith(byteArray, header)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -4,10 +4,18 @@ import lombok.NonNull;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
@Configuration
|
@Configuration @EnableWebMvc
|
||||||
public class Config {
|
public class Config implements WebMvcConfigurer {
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
registry
|
||||||
|
.addResourceHandler("/static/**")
|
||||||
|
.addResourceLocations("classpath:/static/");
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public WebMvcConfigurer configureCors() {
|
public WebMvcConfigurer configureCors() {
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
package cc.fascinated.backend.controller;
|
package cc.fascinated.backend.controller;
|
||||||
|
|
||||||
|
import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
|
||||||
import cc.fascinated.backend.model.Paste;
|
import cc.fascinated.backend.model.Paste;
|
||||||
import cc.fascinated.backend.service.PasteService;
|
import cc.fascinated.backend.service.PasteService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.util.MimeTypeUtils;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
|
import java.awt.datatransfer.MimeTypeParseException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Fascinated (fascinated7)
|
* @author Fascinated (fascinated7)
|
||||||
@ -28,20 +33,38 @@ public class IndexController {
|
|||||||
return "index";
|
return "index";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/{id}")
|
/**
|
||||||
|
* This is to allow for Hastebin compatibility.
|
||||||
|
*/
|
||||||
|
@PostMapping(value = "/documents")
|
||||||
|
public ResponseEntity<?> uploadPaste(@RequestBody String content, @RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType) {
|
||||||
|
String id = pasteService.createPaste(content, contentType);
|
||||||
|
return ResponseEntity.ok(Map.of("key", id));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@GetMapping(value = "/{id}", produces = MimeTypeUtils.TEXT_HTML_VALUE)
|
||||||
public String paste(@PathVariable String id, Model model) {
|
public String paste(@PathVariable String id, Model model) {
|
||||||
|
try {
|
||||||
Paste paste = pasteService.getPaste(id);
|
Paste paste = pasteService.getPaste(id);
|
||||||
model.addAttribute("paste", paste);
|
model.addAttribute("paste", paste);
|
||||||
model.addAttribute("title", "Paste - " + paste.getId());
|
model.addAttribute("title", "Paste - " + paste.getId());
|
||||||
model.addAttribute("rawUrl", "/raw/" + paste.getId());
|
model.addAttribute("rawUrl", "/raw/" + paste.getId());
|
||||||
return "paste";
|
return "paste";
|
||||||
|
} catch (ResourceNotFoundException ex) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/raw/{id}")
|
@GetMapping(value = "/raw/{id}", produces = MimeTypeUtils.TEXT_HTML_VALUE)
|
||||||
public String pasteRaw(@PathVariable String id, Model model) {
|
public String pasteRaw(@PathVariable String id, Model model) {
|
||||||
|
try {
|
||||||
Paste paste = pasteService.getPaste(id);
|
Paste paste = pasteService.getPaste(id);
|
||||||
model.addAttribute("paste", paste);
|
model.addAttribute("paste", paste);
|
||||||
model.addAttribute("title", "Paste - " + paste.getId() + " (Raw)");
|
model.addAttribute("title", "Paste - " + paste.getId() + " (Raw)");
|
||||||
return "paste-raw";
|
return "paste-raw";
|
||||||
|
} catch (ResourceNotFoundException ex) {
|
||||||
|
return "redirect:/";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package cc.fascinated.backend.controller;
|
|||||||
import cc.fascinated.backend.model.Paste;
|
import cc.fascinated.backend.model.Paste;
|
||||||
import cc.fascinated.backend.service.PasteService;
|
import cc.fascinated.backend.service.PasteService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@ -20,10 +20,10 @@ public class PasteController {
|
|||||||
this.pasteService = pasteService;
|
this.pasteService = pasteService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/upload", consumes = MediaType.TEXT_PLAIN_VALUE)
|
@PostMapping(value = "/upload")
|
||||||
public ResponseEntity<?> uploadPaste(@RequestBody String content) {
|
public ResponseEntity<?> uploadPaste(@RequestBody String content, @RequestHeader(value = HttpHeaders.CONTENT_TYPE, required = false) String contentType) {
|
||||||
String id = pasteService.createPaste(content);
|
String id = pasteService.createPaste(content, contentType);
|
||||||
return ResponseEntity.ok(Map.of("id", id));
|
return ResponseEntity.ok(Map.of("key", id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/paste/{id}")
|
@GetMapping(value = "/paste/{id}")
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
package cc.fascinated.backend.filter;
|
||||||
|
|
||||||
|
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;
|
||||||
|
import jakarta.servlet.*;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.CharArrayWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter to compress HTML responses using HtmlCompressor.
|
||||||
|
* Author: Fascinated (fascinated7)
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class HtmlCompressionFilter implements Filter {
|
||||||
|
private final HtmlCompressor htmlCompressor = new HtmlCompressor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies the HTML compression filter.
|
||||||
|
*
|
||||||
|
* @param request the ServletRequest
|
||||||
|
* @param response the ServletResponse
|
||||||
|
* @param chain the FilterChain
|
||||||
|
* @throws ServletException if an error occurs during filtering
|
||||||
|
* @throws IOException if an input or output error occurs
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
ServletResponse newResponse = response;
|
||||||
|
|
||||||
|
// Wrap the response to capture the output
|
||||||
|
if (request instanceof HttpServletRequest) {
|
||||||
|
newResponse = new CharResponseWrapper((HttpServletResponse) response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass the request and response along the filter chain
|
||||||
|
chain.doFilter(request, newResponse);
|
||||||
|
|
||||||
|
// Compress the captured response if it was wrapped
|
||||||
|
if (newResponse instanceof CharResponseWrapper) {
|
||||||
|
String responseContent = newResponse.toString();
|
||||||
|
if (responseContent != null) {
|
||||||
|
response.getWriter().write(htmlCompressor.compress(responseContent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for HttpServletResponse to capture the output for compression.
|
||||||
|
*/
|
||||||
|
private static class CharResponseWrapper extends HttpServletResponseWrapper {
|
||||||
|
private final CharArrayWriter charWriter = new CharArrayWriter();
|
||||||
|
private PrintWriter writer;
|
||||||
|
private boolean getOutputStreamCalled;
|
||||||
|
private boolean getWriterCalled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to wrap the response.
|
||||||
|
* @param response the original HttpServletResponse
|
||||||
|
*/
|
||||||
|
public CharResponseWrapper(HttpServletResponse response) {
|
||||||
|
super(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ServletOutputStream getOutputStream() throws IOException {
|
||||||
|
if (getWriterCalled) {
|
||||||
|
throw new IllegalStateException("getWriter already called");
|
||||||
|
}
|
||||||
|
getOutputStreamCalled = true;
|
||||||
|
return super.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PrintWriter getWriter() {
|
||||||
|
if (writer != null) {
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
if (getOutputStreamCalled) {
|
||||||
|
throw new IllegalStateException("getOutputStream already called");
|
||||||
|
}
|
||||||
|
getWriterCalled = true;
|
||||||
|
writer = new PrintWriter(charWriter);
|
||||||
|
return writer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return (writer != null) ? charWriter.toString() : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package cc.fascinated.backend.service;
|
package cc.fascinated.backend.service;
|
||||||
|
|
||||||
|
import cc.fascinated.backend.common.FileHeaderChecker;
|
||||||
import cc.fascinated.backend.exception.impl.BadRequestException;
|
import cc.fascinated.backend.exception.impl.BadRequestException;
|
||||||
import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
|
import cc.fascinated.backend.exception.impl.ResourceNotFoundException;
|
||||||
import cc.fascinated.backend.model.Paste;
|
import cc.fascinated.backend.model.Paste;
|
||||||
@ -43,14 +44,27 @@ public class PasteService {
|
|||||||
* @param content The content of the paste.
|
* @param content The content of the paste.
|
||||||
* @return The id of the paste.
|
* @return The id of the paste.
|
||||||
*/
|
*/
|
||||||
public String createPaste(String content) {
|
public String createPaste(String content, String contentType) {
|
||||||
int length = content.length();
|
int length = content.length();
|
||||||
|
long before = System.currentTimeMillis();
|
||||||
log.info("Creating a new paste. (characters: {})", length);
|
log.info("Creating a new paste. (characters: {})", length);
|
||||||
|
|
||||||
// Check if the content is too large.
|
// Check if the content is too large.
|
||||||
if (length > uploadSizeLimit && uploadSizeLimit != -1) {
|
if (length > uploadSizeLimit && uploadSizeLimit != -1) {
|
||||||
log.info("Paste didn't meet the size requirements. (characters: {})", length);
|
log.info("Paste didn't meet the size requirements. (characters: {})", length);
|
||||||
throw new BadRequestException("The paste content is too large, the limit is " + uploadSizeLimit + " characters");
|
throw new BadRequestException("The paste content is too large, the limit is " + uploadSizeLimit + " characters, not uploading...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the paste content type is valid.
|
||||||
|
if (contentType != null && (contentType.contains("image") || contentType.contains("video") || contentType.contains("audio"))) {
|
||||||
|
log.info("Paste content type is not supported. (content type: {})", contentType);
|
||||||
|
throw new BadRequestException("The paste content type is not supported, not uploading...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the paste content does not contain a file header.
|
||||||
|
if (FileHeaderChecker.containsFileHeader(content)) {
|
||||||
|
log.info("Paste content contains a file header, not uploading...");
|
||||||
|
throw new BadRequestException("The paste content contains a file header, not uploading...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the paste to the database.
|
// Save the paste to the database.
|
||||||
@ -59,7 +73,7 @@ public class PasteService {
|
|||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
content
|
content
|
||||||
));
|
));
|
||||||
log.info("Created a new paste with the id '{}'", paste.getId());
|
log.info("Created a new paste with the id '{}' (took: {}ms)", paste.getId(), System.currentTimeMillis() - before);
|
||||||
return paste.getId();
|
return paste.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +84,9 @@ public class PasteService {
|
|||||||
* @return The content of the paste.
|
* @return The content of the paste.
|
||||||
*/
|
*/
|
||||||
public Paste getPaste(String id) {
|
public Paste getPaste(String id) {
|
||||||
|
id = id.contains(".") ? id.split("\\.")[0] : id; // Remove file extensions (if any)
|
||||||
log.info("Getting paste with the id '{}'", id);
|
log.info("Getting paste with the id '{}'", id);
|
||||||
|
long before = System.currentTimeMillis();
|
||||||
Optional<Paste> paste = pasteRepository.findById(id);
|
Optional<Paste> paste = pasteRepository.findById(id);
|
||||||
|
|
||||||
// The paste does not exist.
|
// The paste does not exist.
|
||||||
@ -78,7 +94,7 @@ public class PasteService {
|
|||||||
log.info("Paste with the id '{}' not found", id);
|
log.info("Paste with the id '{}' not found", id);
|
||||||
throw new ResourceNotFoundException("Paste '%s' not found".formatted(id));
|
throw new ResourceNotFoundException("Paste '%s' not found".formatted(id));
|
||||||
}
|
}
|
||||||
log.info("Got paste with the id '{}'", id);
|
log.info("Got paste with the id '{}' (took: {}ms)", id, System.currentTimeMillis() - before);
|
||||||
return paste.get();
|
return paste.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
49
src/main/resources/static/assets/script.js
Normal file
49
src/main/resources/static/assets/script.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Handle custom key binds behavior
|
||||||
|
document.addEventListener("keydown", function (event) {
|
||||||
|
// Upload the paste when Ctrl + Enter is pressed
|
||||||
|
if (event.ctrlKey && event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
upload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upload the paste when the paste button is clicked
|
||||||
|
document.getElementById("paste-button").addEventListener("click", () => upload());
|
||||||
|
|
||||||
|
// Upload the paste to the server
|
||||||
|
const upload = async () => {
|
||||||
|
var pasteInput = document.getElementById("paste-input");
|
||||||
|
var paste = pasteInput.value;
|
||||||
|
|
||||||
|
if (!paste || paste.trim() === "") {
|
||||||
|
pasteInput.focus();
|
||||||
|
toast("Please enter a paste to upload.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Uploading paste...");
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/upload", {
|
||||||
|
method: "POST",
|
||||||
|
body: paste,
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = "/" + data.key;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
toast(`${error.message || "An error occurred while uploading the paste."}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toast = (message, duration = 3000) => {
|
||||||
|
Toastify({
|
||||||
|
text: message,
|
||||||
|
duration: duration,
|
||||||
|
gravity: "bottom",
|
||||||
|
position: "right"
|
||||||
|
}).showToast();
|
||||||
|
};
|
62
src/main/resources/static/tailwindcss.js
Normal file
62
src/main/resources/static/tailwindcss.js
Normal file
File diff suppressed because one or more lines are too long
@ -8,12 +8,16 @@
|
|||||||
<title>Paste</title>
|
<title>Paste</title>
|
||||||
|
|
||||||
<!-- TailwindCSS -->
|
<!-- TailwindCSS -->
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="/static/tailwindcss.js"></script>
|
||||||
|
|
||||||
|
<!-- Toastify -->
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-black text-white">
|
<body class="bg-neutral-900 text-white">
|
||||||
<div class="p-3 h-screen w-screen relative">
|
<div class="p-3 h-screen w-screen relative">
|
||||||
<div class="flex gap-2 h-full w-full text-xs">
|
<div class="flex gap-2 h-full w-full text-sm">
|
||||||
<p class="hidden md:block">></p>
|
<p class="hidden md:block select-none pointer-events-none">></p>
|
||||||
<textarea
|
<textarea
|
||||||
id="paste-input"
|
id="paste-input"
|
||||||
class="w-full h-full bg-background outline-none resize-none bg-transparent"
|
class="w-full h-full bg-background outline-none resize-none bg-transparent"
|
||||||
@ -29,22 +33,5 @@
|
|||||||
</body>
|
</body>
|
||||||
|
|
||||||
<!-- Paste Script -->
|
<!-- Paste Script -->
|
||||||
<script>
|
<script src="/static/assets/script.js"></script>
|
||||||
document.getElementById('paste-button').addEventListener('click', function() {
|
|
||||||
var pasteInput = document.getElementById('paste-input');
|
|
||||||
var paste = pasteInput.value;
|
|
||||||
|
|
||||||
fetch('/api/upload', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
},
|
|
||||||
body: paste
|
|
||||||
}).then(function(response) {
|
|
||||||
return response.json();
|
|
||||||
}).then(function(data) {
|
|
||||||
window.location.href = '/' + data.id;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</html>
|
</html>
|
@ -7,14 +7,18 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
<title th:text="${title}"></title>
|
<title th:text="${title}"></title>
|
||||||
|
|
||||||
|
<!-- Meta tags for social media sharing -->
|
||||||
|
<meta th:content="${title}" property="og:title" />
|
||||||
|
<meta content="Click to view the Paste." property="og:description" />
|
||||||
|
|
||||||
<!-- TailwindCSS -->
|
<!-- TailwindCSS -->
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="/static/tailwindcss.js"></script>
|
||||||
|
|
||||||
<!-- Highlight.js -->
|
<!-- Highlight.js -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-black text-white h-screen relative">
|
<body class="bg-neutral-900 text-white h-screen relative">
|
||||||
<pre><code class="bg-transparent text-sm" th:text="${paste.content}"></code></pre>
|
<pre><code class="bg-transparent text-sm" th:text="${paste.content}"></code></pre>
|
||||||
|
|
||||||
<div class="absolute top-0 right-0 p-3">
|
<div class="absolute top-0 right-0 p-3">
|
||||||
|
Reference in New Issue
Block a user