Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
7667c5064b | |||
454c411121 | |||
a031e9d2be | |||
26ea8020c3 | |||
25ce46a70b | |||
23c535d933 | |||
f9d9e0e3c0 | |||
92ca08c1ed | |||
c3a8994769 | |||
de1b826928 | |||
7f5c9202bb | |||
2e5fd2024f | |||
a33bf8f665 | |||
052acdb394 | |||
3e40c79b81 | |||
67bdc5c325 | |||
a2544633e6 | |||
740300edaf | |||
79d0adfcf8 | |||
333c7f13c0 | |||
29e26aa647 | |||
24cb6cd848 | |||
fa373d6b46 | |||
88099f5a54 | |||
cd2c8323d5 | |||
c76ff11c26 | |||
3edd810c36 | |||
d404490442 | |||
45699446e0 | |||
5c0d0413ef | |||
5751297239 | |||
4b0ab996ab | |||
0f444b7606 |
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
# Ignore SQLite database
|
||||
data.db
|
||||
|
||||
# Ignore uploads directory
|
||||
/uploads/
|
||||
|
||||
# Python-related files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Virtual environment
|
||||
venv/
|
||||
env/
|
||||
.env
|
||||
|
||||
# IDE-specific files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
32
README.md
32
README.md
@ -1,30 +1,2 @@
|
||||
## Text sharing, File sharing, link shortener.
|
||||
### frontend and backend, easily configurable (thru editing the html files and the .py)
|
||||
----------------
|
||||
|
||||
1. Introduction
|
||||
</br>
|
||||
</br>
|
||||
</br>
|
||||
As said above, this is a **simple & private** backend (app.py) and frontend (in /templates), which has these features:
|
||||
- File Sharing (get a short link to share files, and when the link is opened it will show some information)
|
||||
- Text Sharing (kind of a pastebin, it will show the text and when the text was made, again in a short link)
|
||||
- URL Shortener (when hosting this, it will short an url, and give you a link, and when you open it, it will automatically redirect you to that website)
|
||||
|
||||
2. This is 100% **anonymous**, no one can see who created the links, etc.
|
||||
|
||||
3. The UI is simple.
|
||||
|
||||
4. This is very easily self-hostable </br> </br>
|
||||
Rookie Mistake: when you self-host it, you can open it in localhost:7123, when making a file/paste/shortener don't share that localhost link, localhost is only accessible on YOUR network. - instead, u can host it with your domain.
|
||||
</br>
|
||||
</br>
|
||||
5. This project is **COMPLETLY** open source.
|
||||
</br>
|
||||
</br>
|
||||
6. You might need to install some pip packages, if you dont have them installed already download this repo and go into the folder and execute ```pip install -r requirements.txt```
|
||||
</br>
|
||||
</br>
|
||||
7. Demo:
|
||||
<video src="https://cgcristi.xyz/demo.mp4" controls></video>
|
||||
|
||||
this repo is deprecated
|
||||
https://github.com/spitkov/sxbin
|
19
schema.sql
19
schema.sql
@ -1,12 +1,17 @@
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
api_key TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS content (
|
||||
vanity TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
user_id INTEGER,
|
||||
is_private INTEGER DEFAULT 0,
|
||||
password TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
);
|
@ -66,7 +66,13 @@
|
||||
</br>
|
||||
</br>
|
||||
</br>
|
||||
<p>Source code on: </p><a href="https://github.com/realcgcristi/aCloud" target="_blank">GitHub</a> | <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a>
|
||||
<div class="footer">
|
||||
<p>
|
||||
Source code on: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
|
||||
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
|
||||
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
62
templates/api_docs.html
Normal file
62
templates/api_docs.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>API Documentation - sxbin</title>
|
||||
<style>
|
||||
/* Add your styles here */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>sxbin API Documentation</h1>
|
||||
|
||||
<h2>Authentication</h2>
|
||||
<p>All API requests require an API key to be sent in the X-API-Key header.</p>
|
||||
|
||||
<h2>Endpoints</h2>
|
||||
|
||||
<h3>Upload File</h3>
|
||||
<pre>
|
||||
POST /api/upload/file
|
||||
Headers:
|
||||
X-API-Key: your_api_key
|
||||
Body: multipart/form-data
|
||||
file: (binary)
|
||||
</pre>
|
||||
<p>Returns: JSON with file URL and deletion URL</p>
|
||||
|
||||
<h3>Upload Pastebin</h3>
|
||||
<pre>
|
||||
POST /api/upload/pastebin
|
||||
Headers:
|
||||
X-API-Key: your_api_key
|
||||
Content-Type: application/json
|
||||
Body:
|
||||
{
|
||||
"content": "Your pastebin content here"
|
||||
}
|
||||
</pre>
|
||||
<p>Returns: JSON with pastebin URL and deletion URL</p>
|
||||
|
||||
<h3>Shorten URL</h3>
|
||||
<pre>
|
||||
POST /api/shorten
|
||||
Headers:
|
||||
X-API-Key: your_api_key
|
||||
Content-Type: application/json
|
||||
Body:
|
||||
{
|
||||
"url": "https://example.com/your-long-url-here"
|
||||
}
|
||||
</pre>
|
||||
<p>Returns: JSON with shortened URL and deletion URL</p>
|
||||
|
||||
<h2>Error Handling</h2>
|
||||
<p>All errors are returned as JSON with an "error" field describing the issue.</p>
|
||||
|
||||
<footer>
|
||||
<p>For more information or support, please contact our team.</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
@ -86,6 +86,8 @@
|
||||
/* Ensure proper contrast for syntax highlighting */
|
||||
.highlight pre {
|
||||
color: var(--text-color);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Override Pygments styles for dark mode */
|
||||
@ -154,42 +156,65 @@
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Content</h1>
|
||||
{% if created_at %}
|
||||
<p>Created at: {{ created_at }}</p>
|
||||
{% endif %}
|
||||
{% if language %}
|
||||
<p>Detected Language: {{ language }}</p>
|
||||
{% endif %}
|
||||
<div class="button-container">
|
||||
<button id="copy-button" onclick="copyToClipboard()">Copy Raw</button>
|
||||
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" target="_blank"><button>View Raw</button></a>
|
||||
</div>
|
||||
<div class="highlight">
|
||||
<h2>Content</h2>
|
||||
{% if content %}
|
||||
<p>Uploaded by: {{ 'Anonymous' if content.username == 'None' else content.username }}</p>
|
||||
<p>Created at: {{ content.created_at }}</p>
|
||||
|
||||
{% if highlighted_content %}
|
||||
{{ highlighted_content|safe }}
|
||||
<style>{{ css|safe }}</style>
|
||||
<div class="highlight">
|
||||
{{ highlighted_content|safe }}
|
||||
</div>
|
||||
<button onclick="copyToClipboard()" class="btn">Copy</button>
|
||||
<a href="{{ url_for('raw_vanity', vanity=content.vanity) }}" class="btn">View Raw</a>
|
||||
{% if current_user.is_authenticated and current_user.id == content.user_id %}
|
||||
<a href="{{ url_for('edit_content', vanity=content.vanity) }}" class="btn">Edit</a>
|
||||
{% endif %}
|
||||
{% elif url %}
|
||||
<p>Shortened URL: <a href="{{ url }}">{{ url }}</a></p>
|
||||
<button onclick="copyToClipboard()" class="btn">Copy URL</button>
|
||||
{% elif raw_content %}
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word;">{{ raw_content }}</pre>
|
||||
<button onclick="copyToClipboard()" class="btn">Copy</button>
|
||||
<a href="{{ url_for('raw_vanity', vanity=content.vanity) }}" class="btn">View Raw</a>
|
||||
{% if current_user.is_authenticated and current_user.id == content.user_id %}
|
||||
<a href="{{ url_for('edit_content', vanity=content.vanity) }}" class="btn">Edit</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<pre>{{ content }}</pre>
|
||||
<p>No content available</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if current_user.is_authenticated and current_user.id == content.user_id %}
|
||||
<form action="{{ url_for('delete_content', vanity=content.vanity) }}" method="post" style="display: inline;">
|
||||
<button type="submit" class="btn">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>No content found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<button id="theme-toggle">Toggle Theme</button>
|
||||
|
||||
<script>
|
||||
const rawContent = {{ raw_content|tojson }};
|
||||
const rawContent = {{ raw_content|tojson|default('null') }};
|
||||
const url = {{ url|tojson|default('null') }};
|
||||
|
||||
function copyToClipboard() {
|
||||
navigator.clipboard.writeText(rawContent).then(() => {
|
||||
const copyButton = document.getElementById("copy-button");
|
||||
const originalText = copyButton.textContent;
|
||||
copyButton.textContent = "Copied!";
|
||||
setTimeout(() => {
|
||||
copyButton.textContent = originalText;
|
||||
}, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
let textToCopy = rawContent;
|
||||
if (url) {
|
||||
textToCopy = url;
|
||||
}
|
||||
if (textToCopy) {
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
alert('Copied to clipboard!');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
} else {
|
||||
console.error('No content to copy');
|
||||
}
|
||||
}
|
||||
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
|
71
templates/content_info.html
Normal file
71
templates/content_info.html
Normal file
@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Content Info</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #1a1a1a;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h2 {
|
||||
color: #4CAF50;
|
||||
}
|
||||
.info-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Content Information</h2>
|
||||
<div class="info-item"><strong>Type:</strong> {{ info.type }}</div>
|
||||
<div class="info-item"><strong>Vanity URL:</strong> {{ info.vanity }}</div>
|
||||
<div class="info-item"><strong>Created At:</strong> {{ info.created_at }}</div>
|
||||
<div class="info-item"><strong>Uploaded By:</strong> {{ info.username }}</div>
|
||||
{% if info.type == 'file' %}
|
||||
<div class="info-item"><strong>Filename:</strong> {{ info.data }}</div>
|
||||
{% if info.file_size %}
|
||||
<div class="info-item"><strong>File Size:</strong> {{ info.file_size }} bytes</div>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('redirect_vanity', vanity=info.vanity)|replace('/download', '') }}" class="btn">View/Embed</a>
|
||||
<a href="{{ url_for('redirect_vanity', vanity=info.vanity) ~ ('/download' if '/download' not in url_for('redirect_vanity', vanity=info.vanity) else '') }}" class="btn">Download</a>
|
||||
{% elif info.type == 'url' %}
|
||||
<div class="info-item"><strong>Target URL:</strong> {{ info.data }}</div>
|
||||
<a href="{{ url_for('redirect_vanity', vanity=info.vanity) }}" class="btn">Visit URL</a>
|
||||
{% elif info.type == 'pastebin' %}
|
||||
<a href="{{ url_for('redirect_vanity', vanity=info.vanity) }}" class="btn">View Pastebin</a>
|
||||
<a href="{{ url_for('raw_vanity', vanity=info.vanity) }}" class="btn">View Raw</a>
|
||||
{% endif %}
|
||||
{% if current_user.is_authenticated and current_user.username == info.username %}
|
||||
{% if info.type != 'file' or not info.is_media %}
|
||||
<a href="{{ url_for('edit_content', vanity=info.vanity) }}" class="btn">Edit</a>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('delete_content', vanity=info.vanity) }}" method="post" style="display: inline;">
|
||||
<button type="submit" class="btn">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
68
templates/edit_content.html
Normal file
68
templates/edit_content.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Edit Content</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #1a1a1a;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
h1 {
|
||||
color: #4CAF50;
|
||||
}
|
||||
#editor {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
background-color: #2a2a2a;
|
||||
color: #f0f0f0;
|
||||
border: 1px solid #4CAF50;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
input[type="submit"] {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
input[type="submit"]:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Edit Content</h1>
|
||||
<form method="post">
|
||||
<div id="editor">{{ content }}</div>
|
||||
<input type="hidden" name="content" id="hidden-content">
|
||||
<input type="submit" value="Save" onclick="updateContent()">
|
||||
</form>
|
||||
</div>
|
||||
<script>
|
||||
var editor = ace.edit("editor");
|
||||
editor.setTheme("ace/theme/monokai");
|
||||
editor.session.setMode("ace/mode/text");
|
||||
|
||||
function updateContent() {
|
||||
document.getElementById('hidden-content').value = editor.getValue();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
26
templates/edit_password.html
Normal file
26
templates/edit_password.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Edit Password</title>
|
||||
<style>
|
||||
/* Add your styles here */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>Edit Password</h2>
|
||||
<form method="POST">
|
||||
<input type="password" name="new_password" placeholder="Enter new password">
|
||||
<input type="hidden" name="action" value="update">
|
||||
<button type="submit">Update Password</button>
|
||||
</form>
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="remove">
|
||||
<button type="submit">Remove Password</button>
|
||||
</form>
|
||||
<a href="{{ url_for('user_files', username=current_user.username) }}">Cancel</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
165
templates/file_info.html
Normal file
165
templates/file_info.html
Normal file
@ -0,0 +1,165 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ filename }} - sxbin</title>
|
||||
<meta property="og:title" content="{{ filename }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ request.url }}">
|
||||
<meta property="og:description" content="File size: {{ file_size|filesizeformat }} | Uploaded by: {{ username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}{% if is_private %} | Password Protected{% endif %}">
|
||||
{% if is_embeddable %}
|
||||
<meta property="og:image" content="{{ raw_url }}">
|
||||
{% endif %}
|
||||
<meta property="og:site_name" content="sxbin">
|
||||
<meta property="theme-color" content="#4CAF50">
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #e0e0e0;
|
||||
background-color: #1e1e1e;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h2 {
|
||||
color: #4CAF50;
|
||||
}
|
||||
.info-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.embed-container {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.embed-container img, .embed-container embed {
|
||||
max-width: 100%;
|
||||
max-height: 600px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.btn-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
margin: 4px 2px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.home-button {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
font-size: 24px;
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
}
|
||||
.home-button:hover {
|
||||
color: #45a049;
|
||||
}
|
||||
#theme-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
background-color: #2a2a2a;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.edit-btn, .delete-btn {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.edit-btn:hover, .delete-btn:hover {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/" class="home-button">⌂</a>
|
||||
<div class="container">
|
||||
<h2>{{ filename }}</h2>
|
||||
<div class="info-item"><strong>File size:</strong> {{ file_size|filesizeformat }}</div>
|
||||
<div class="info-item"><strong>Uploaded by:</strong> {{ username }}</div>
|
||||
<div class="info-item"><strong>Date:</strong> {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</div>
|
||||
<div class="info-item"><strong>File type:</strong> {{ filename.split('.')[-1].upper() if '.' in filename else 'Unknown' }}</div>
|
||||
|
||||
{% if is_embeddable %}
|
||||
<div class="embed-container">
|
||||
{% if filename.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.svg')) %}
|
||||
<img src="{{ raw_url }}" alt="{{ filename }}">
|
||||
{% elif filename.lower().endswith('.pdf') %}
|
||||
<embed src="{{ raw_url }}" type="application/pdf" width="100%" height="600px">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="btn-container">
|
||||
<a href="{{ file_url }}/download{% if password %}/{{ password }}{% endif %}" class="btn">Download</a>
|
||||
<a href="{{ raw_url }}" class="btn">View Raw</a>
|
||||
{% if current_user.is_authenticated and current_user.id == user_id %}
|
||||
{% if filename.lower().endswith(('.txt', '.html', '.css', '.js', '.py', '.md')) or '.' not in filename %}
|
||||
<a href="{{ url_for('edit_content', vanity=vanity) }}" class="btn edit-btn">Edit</a>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('delete_content', vanity=vanity) }}" method="post" style="display: inline;">
|
||||
<button type="submit" class="btn delete-btn" onclick="return confirm('Are you sure you want to delete this file?')">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="theme-toggle">Toggle Theme</button>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
|
||||
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
|
||||
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const body = document.body;
|
||||
|
||||
themeToggle.addEventListener('click', () => {
|
||||
body.classList.toggle('light-mode');
|
||||
localStorage.setItem('theme', body.classList.contains('light-mode') ? 'light' : 'dark');
|
||||
});
|
||||
|
||||
// Check for saved theme preference
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'light') {
|
||||
body.classList.add('light-mode');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,9 +1,10 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>aCloud - File Sharing Service</title>
|
||||
<title>sxbin - File Sharing Service</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
@ -47,40 +48,54 @@
|
||||
margin: 4px 2px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.upload-form {
|
||||
display: none;
|
||||
background-color: #2a2a2a;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
.upload-options button:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
|
||||
}
|
||||
.upload-form.active {
|
||||
display: block;
|
||||
.upload-options button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.upload-form input[type="text"],
|
||||
.upload-form input[type="file"],
|
||||
.upload-form textarea {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #4CAF50;
|
||||
background-color: #333;
|
||||
color: #f0f0f0;
|
||||
.upload-options button::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: rgba(255, 255, 255, .5);
|
||||
opacity: 0;
|
||||
border-radius: 100%;
|
||||
transform: scale(1, 1) translate(-50%);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
.upload-form button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
@keyframes ripple {
|
||||
0% {
|
||||
transform: scale(0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
transform: scale(25, 25);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(40, 40);
|
||||
}
|
||||
}
|
||||
.result {
|
||||
margin-top: 10px;
|
||||
color: #4CAF50;
|
||||
.upload-options button:focus:not(:active)::after {
|
||||
animation: ripple 1s ease-out;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
.pulse {
|
||||
animation: pulse 0.5s;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
@ -94,6 +109,11 @@
|
||||
overflow: hidden;
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
@ -107,13 +127,209 @@
|
||||
50% { opacity: 1; }
|
||||
100% { opacity: 0; }
|
||||
}
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
.modal-content {
|
||||
background-color: #2a2a2a;
|
||||
margin: 15% auto;
|
||||
padding: 20px 30px;
|
||||
border: 1px solid #4CAF50;
|
||||
width: 50%;
|
||||
max-width: 500px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.upload-area {
|
||||
border: 2px dashed #4CAF50;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
width: calc(100% - 44px);
|
||||
}
|
||||
.upload-area:hover {
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
#fileInput {
|
||||
display: none;
|
||||
}
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
background-color: #333;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.progress {
|
||||
width: 0%;
|
||||
height: 20px;
|
||||
background-color: #4CAF50;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
color: white;
|
||||
}
|
||||
textarea, input[type="text"], input[type="file"], input[type="password"] {
|
||||
width: calc(100% - 20px);
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
background-color: #333;
|
||||
border: 1px solid #4CAF50;
|
||||
color: #f0f0f0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.file-group {
|
||||
border: 1px solid #4CAF50;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.file-group-header {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-group-header span:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.file-group-header span:nth-child(2) {
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.file-group-content {
|
||||
padding: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-group-content.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.other-links-btn {
|
||||
background-color: #45a049;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.other-links {
|
||||
margin-top: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.other-links.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.global-drop-area {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.global-drop-box {
|
||||
border: 3px dashed #4CAF50;
|
||||
border-radius: 20px;
|
||||
padding: 50px;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.instant-upload-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.instant-upload-result {
|
||||
background-color: #2a2a2a;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.close-modal {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 15px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.close-modal:hover {
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<nav>
|
||||
<a href="{{ url_for('login') }}">Login</a>
|
||||
<a href="{{ url_for('register') }}">Register</a>
|
||||
{% if user %}
|
||||
<a href="{{ url_for('user_files', username=user.username) }}">View Dashboard</a>
|
||||
<a href="{{ url_for('logout') }}">Logout</a>
|
||||
{% else %}
|
||||
<a href="{{ url_for('login') }}">Login</a>
|
||||
<a href="{{ url_for('register') }}">Register</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
|
||||
<div class="typewriter-container">
|
||||
@ -121,58 +337,98 @@
|
||||
</div>
|
||||
|
||||
<div class="upload-options">
|
||||
<button onclick="showForm('text')">Upload Text</button>
|
||||
<button onclick="showForm('file')">Upload File</button>
|
||||
<button onclick="showForm('folder')">Upload Folder</button>
|
||||
<button onclick="showForm('url')">Shorten URL</button>
|
||||
<button onclick="openModal('textModal')">Upload Text</button>
|
||||
<button onclick="openModal('fileModal')">Upload File</button>
|
||||
<button onclick="openModal('urlModal')">Shorten URL</button>
|
||||
</div>
|
||||
<div id="uploadFormContainer">
|
||||
<div id="textForm" class="upload-form">
|
||||
<h2>Upload Text</h2>
|
||||
<form>
|
||||
<textarea name="content" rows="4" placeholder="Enter text here..."></textarea>
|
||||
<button type="button" onclick="uploadText()">Upload Text</button>
|
||||
</form>
|
||||
<div id="textResult" class="result"></div>
|
||||
</div>
|
||||
|
||||
<div id="textModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" onclick="closeModal('textModal')">×</span>
|
||||
<h2>Upload Text</h2>
|
||||
<textarea id="textContent" rows="4" placeholder="Enter text here..."></textarea>
|
||||
<div>
|
||||
<input type="checkbox" id="isPrivate" name="isPrivate">
|
||||
<label for="isPrivate">Add password protection</label>
|
||||
</div>
|
||||
<div id="fileForm" class="upload-form">
|
||||
<h2>Upload File</h2>
|
||||
<form enctype="multipart/form-data">
|
||||
<input type="file" name="file" />
|
||||
<button type="button" onclick="uploadFile()">Upload File</button>
|
||||
</form>
|
||||
<div id="fileResult" class="result"></div>
|
||||
<div id="passwordField" style="display: none;">
|
||||
<input type="password" id="textPassword" placeholder="Enter password">
|
||||
</div>
|
||||
<div id="folderForm" class="upload-form">
|
||||
<h2>Upload Folder</h2>
|
||||
<form enctype="multipart/form-data">
|
||||
<input type="file" name="file" webkitdirectory directory />
|
||||
<button type="button" onclick="uploadFolder()">Upload Folder</button>
|
||||
</form>
|
||||
<div id="folderResult" class="result"></div>
|
||||
<button onclick="uploadText()">Upload Text</button>
|
||||
<div id="textResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fileModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" onclick="closeModal('fileModal')">×</span>
|
||||
<h2>Upload File</h2>
|
||||
<div id="uploadArea" class="upload-area">
|
||||
<p>Drag and drop files here or click to select files</p>
|
||||
<input type="file" id="fileInput" multiple>
|
||||
</div>
|
||||
<div id="urlForm" class="upload-form">
|
||||
<h2>Shorten URL</h2>
|
||||
<form>
|
||||
<input type="text" name="url" placeholder="Enter URL here..." />
|
||||
<button type="button" onclick="shortenUrl()">Shorten URL</button>
|
||||
</form>
|
||||
<div id="urlResult" class="result"></div>
|
||||
<div id="selectedFiles"></div>
|
||||
<div>
|
||||
<input type="checkbox" id="fileIsPrivate" name="fileIsPrivate">
|
||||
<label for="fileIsPrivate">Add password protection</label>
|
||||
</div>
|
||||
<div id="filePasswordField" style="display: none;">
|
||||
<input type="password" id="filePassword" placeholder="Enter password">
|
||||
</div>
|
||||
<button onclick="uploadSelectedFiles()">Upload</button>
|
||||
<div class="progress-bar">
|
||||
<div id="progressBar" class="progress"></div>
|
||||
</div>
|
||||
<div id="fileResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="urlModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" onclick="closeModal('urlModal')">×</span>
|
||||
<h2>Shorten URL</h2>
|
||||
<input type="text" id="urlInput" placeholder="Enter URL here...">
|
||||
<div>
|
||||
<input type="checkbox" id="urlIsPrivate" name="urlIsPrivate">
|
||||
<label for="urlIsPrivate">Add password protection</label>
|
||||
</div>
|
||||
<div id="urlPasswordField" style="display: none;">
|
||||
<input type="password" id="urlPassword" placeholder="Enter password">
|
||||
</div>
|
||||
<button onclick="shortenUrl()">Shorten URL</button>
|
||||
<div id="urlResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="global-drop-area">
|
||||
<div class="global-drop-box">
|
||||
<h2>Drop file to instantly upload</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="instant-upload-overlay">
|
||||
<div class="instant-upload-result">
|
||||
<span class="close-modal">×</span>
|
||||
<h3>File Uploaded</h3>
|
||||
<p>Direct download URL: <a id="directDownloadUrl" href="#" target="_blank"></a></p>
|
||||
<p>Normal URL: <a id="normalUrl" href="#" target="_blank"></a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<p>Source code available on:
|
||||
<a href="https://github.com/realcgcristi/order" target="_blank">GitHub (not up-to-date)</a> |
|
||||
<a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git (main)</a>
|
||||
|
||||
<a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git (main)</a> |
|
||||
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
|
||||
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const message1 = "Welcome to aCloud.";
|
||||
const message2 = "\n A simple toolbox for file uploading,\n URL shortening and pastebin.";
|
||||
const message1 = "Welcome to sxbin.gay";
|
||||
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
|
||||
const typewriterTextElement = document.getElementById('typewriter-text');
|
||||
const cursorElement = document.getElementById('cursor');
|
||||
const typingSpeed = 70;
|
||||
@ -188,7 +444,6 @@
|
||||
typewriterTextElement.innerHTML += message[index];
|
||||
}
|
||||
index++;
|
||||
updateCursorPosition();
|
||||
setTimeout(typeCharacter, typingSpeed);
|
||||
} else if (callback) {
|
||||
setTimeout(callback, typingSpeed);
|
||||
@ -198,115 +453,381 @@
|
||||
typeCharacter();
|
||||
}
|
||||
|
||||
function updateCursorPosition() {
|
||||
|
||||
const textRect = typewriterTextElement.getBoundingClientRect();
|
||||
cursorElement.style.left = (textRect.width + textRect.left - cursorElement.offsetWidth) + 'px';
|
||||
}
|
||||
|
||||
typeMessage(message1, function() {
|
||||
typeMessage(message2);
|
||||
});
|
||||
});;
|
||||
});
|
||||
|
||||
let isFileModalOpen = false;
|
||||
|
||||
document.getElementById('uploadForm').onsubmit = function(e) {
|
||||
function openModal(modalId) {
|
||||
const modal = document.getElementById(modalId);
|
||||
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
||||
|
||||
modal.style.display = "block";
|
||||
button.classList.add('pulse');
|
||||
|
||||
// Add ripple effect
|
||||
button.classList.add('ripple');
|
||||
setTimeout(() => {
|
||||
button.classList.remove('ripple');
|
||||
}, 1000);
|
||||
|
||||
// Add fade-in animation to modal
|
||||
modal.style.opacity = 0;
|
||||
let opacity = 0;
|
||||
const fadeIn = setInterval(() => {
|
||||
if (opacity < 1) {
|
||||
opacity += 0.1;
|
||||
modal.style.opacity = opacity;
|
||||
} else {
|
||||
clearInterval(fadeIn);
|
||||
}
|
||||
}, 30);
|
||||
|
||||
if (modalId === 'fileModal') {
|
||||
isFileModalOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(modalId) {
|
||||
const modal = document.getElementById(modalId);
|
||||
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
||||
|
||||
button.classList.remove('pulse');
|
||||
|
||||
// Add fade-out animation to modal
|
||||
let opacity = 1;
|
||||
const fadeOut = setInterval(() => {
|
||||
if (opacity > 0) {
|
||||
opacity -= 0.1;
|
||||
modal.style.opacity = opacity;
|
||||
} else {
|
||||
clearInterval(fadeOut);
|
||||
modal.style.display = "none";
|
||||
}
|
||||
}, 30);
|
||||
|
||||
if (modalId === 'fileModal') {
|
||||
isFileModalOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.className === "modal") {
|
||||
event.target.style.display = "none";
|
||||
if (event.target.id === 'fileModal') {
|
||||
isFileModalOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('isPrivate').addEventListener('change', function() {
|
||||
document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
document.getElementById('fileIsPrivate').addEventListener('change', function() {
|
||||
document.getElementById('filePasswordField').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
document.getElementById('urlIsPrivate').addEventListener('change', function() {
|
||||
document.getElementById('urlPasswordField').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
function uploadText() {
|
||||
const content = document.getElementById('textContent').value;
|
||||
const isPrivate = document.getElementById('isPrivate').checked;
|
||||
const password = isPrivate ? document.getElementById('textPassword').value : null;
|
||||
|
||||
const data = {
|
||||
content: content,
|
||||
password: password
|
||||
};
|
||||
|
||||
fetch('/upload/pastebin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const simpleUrl = `${window.location.origin}/${data.vanity}`;
|
||||
document.getElementById('textResult').innerHTML = `Pastebin created. Access it <a href="${simpleUrl}" target="_blank">${simpleUrl}</a>`;
|
||||
} else {
|
||||
document.getElementById('textResult').innerHTML = `Error: ${data.error}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
document.getElementById('textResult').innerHTML = `An error occurred: ${error.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
const uploadArea = document.getElementById('uploadArea');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const selectedFiles = document.getElementById('selectedFiles');
|
||||
let filesToUpload = [];
|
||||
|
||||
uploadArea.addEventListener('click', () => fileInput.click());
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
var formData = new FormData(this);
|
||||
var xhr = new XMLHttpRequest();
|
||||
uploadArea.style.backgroundColor = '#2d2d2d';
|
||||
});
|
||||
uploadArea.addEventListener('dragleave', () => {
|
||||
uploadArea.style.backgroundColor = '';
|
||||
});
|
||||
uploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
uploadArea.style.backgroundColor = '';
|
||||
handleFiles(e.dataTransfer.files);
|
||||
});
|
||||
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
|
||||
|
||||
function handleFiles(files) {
|
||||
filesToUpload = Array.from(files);
|
||||
updateSelectedFilesList();
|
||||
}
|
||||
|
||||
function updateSelectedFilesList() {
|
||||
selectedFiles.innerHTML = filesToUpload.map(file => `<div>${file.name}</div>`).join('');
|
||||
}
|
||||
|
||||
function uploadSelectedFiles() {
|
||||
const isPrivate = document.getElementById('fileIsPrivate').checked;
|
||||
const password = isPrivate ? document.getElementById('filePassword').value : null;
|
||||
|
||||
filesToUpload.forEach(file => uploadFile(file, isPrivate, password));
|
||||
}
|
||||
|
||||
function uploadFile(file, isPrivate, password) {
|
||||
if (isPrivate && (password === 'info' || password === 'download')) {
|
||||
document.getElementById('fileResult').innerHTML += `Error: Password cannot be 'info' or 'download'<br>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
if (isPrivate && password) {
|
||||
formData.append('password', password);
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/upload/file', true);
|
||||
|
||||
xhr.upload.onprogress = function(e) {
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable) {
|
||||
var percentComplete = (e.loaded / e.total) * 100;
|
||||
document.getElementById('progressBar').style.display = 'block';
|
||||
document.getElementById('progressBar').value = percentComplete;
|
||||
const percentComplete = (e.loaded / e.total) * 100;
|
||||
progressBar.style.width = percentComplete + '%';
|
||||
progressBar.textContent = percentComplete.toFixed(2) + '%';
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
alert('Upload complete!');
|
||||
document.getElementById('progressBar').style.display = 'none';
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
let resultHtml = `
|
||||
<div class="file-group">
|
||||
<div class="file-group-header" onclick="toggleFileGroup(this)">
|
||||
<span>▼</span>
|
||||
<span>${file.name}</span>
|
||||
<button class="other-links-btn" onclick="toggleOtherLinks(event, this)">Other Links</button>
|
||||
</div>
|
||||
<div class="file-group-content show">
|
||||
<p>File uploaded: <a href="${response.url}" target="_blank">${response.url}</a></p>
|
||||
<div class="other-links">
|
||||
`;
|
||||
|
||||
if (isPrivate) {
|
||||
resultHtml += `
|
||||
<p>Password-protected link: <a href="${response.url}/${password}" target="_blank">${response.url}/${password}</a></p>
|
||||
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
|
||||
<p>Password-protected direct download link: <a href="${response.download_url}/${password}" target="_blank">${response.download_url}/${password}</a></p>
|
||||
`;
|
||||
} else {
|
||||
resultHtml += `
|
||||
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
|
||||
`;
|
||||
}
|
||||
|
||||
resultHtml += `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.getElementById('fileResult').innerHTML += resultHtml;
|
||||
} else {
|
||||
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
|
||||
}
|
||||
} else {
|
||||
alert('Upload failed.');
|
||||
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
|
||||
}
|
||||
progressBar.style.width = '0%';
|
||||
progressBar.textContent = '';
|
||||
};
|
||||
|
||||
xhr.onerror = function() {
|
||||
console.error('Error:', xhr.statusText);
|
||||
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
|
||||
progressBar.style.width = '0%';
|
||||
progressBar.textContent = '';
|
||||
};
|
||||
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
function showForm(type) {
|
||||
document.querySelectorAll('.upload-form').forEach(form => {
|
||||
form.classList.remove('active');
|
||||
});
|
||||
document.getElementById(type + 'Form').classList.add('active');
|
||||
}
|
||||
|
||||
function uploadText() {
|
||||
const content = document.querySelector('#textForm textarea').value;
|
||||
fetch('/upload/pastebin', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: new URLSearchParams({ content }),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('textResult').innerHTML = `Text uploaded. Access it <a href="/${data.vanity}">here</a>.`;
|
||||
});
|
||||
}
|
||||
|
||||
function uploadFile() {
|
||||
const formData = new FormData();
|
||||
formData.append('file', document.querySelector('#fileForm input[type="file"]').files[0]);
|
||||
fetch('/upload/file', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('fileResult').innerHTML = `File uploaded. Download it <a href="/download/${data.vanity}">here</a>.`;
|
||||
});
|
||||
}
|
||||
|
||||
function uploadFolder() {
|
||||
const files = document.querySelector('#folderForm input[type="file"]').files;
|
||||
if (files.length === 0) {
|
||||
alert('Please select a folder.');
|
||||
return;
|
||||
function toggleFileGroup(header) {
|
||||
const content = header.nextElementSibling;
|
||||
const arrow = header.querySelector('span:first-child');
|
||||
const filename = header.querySelector('span:nth-child(2)');
|
||||
content.classList.toggle('show');
|
||||
if (content.classList.contains('show')) {
|
||||
arrow.textContent = '▼';
|
||||
filename.style.display = 'inline';
|
||||
} else {
|
||||
arrow.textContent = '▶';
|
||||
filename.style.display = 'inline';
|
||||
}
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
for (const file of files) {
|
||||
formData.append('file', file);
|
||||
}
|
||||
|
||||
fetch('/upload/folder', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('folderResult').innerHTML = `Folder uploaded. View its contents <a href="/${data.vanity}">here</a>.`;
|
||||
});
|
||||
function toggleOtherLinks(event, button) {
|
||||
event.stopPropagation();
|
||||
const otherLinks = button.closest('.file-group-header').nextElementSibling.querySelector('.other-links');
|
||||
otherLinks.classList.toggle('show');
|
||||
}
|
||||
|
||||
function shortenUrl() {
|
||||
const url = document.querySelector('#urlForm input[name="url"]').value;
|
||||
const url = document.getElementById('urlInput').value;
|
||||
const isPrivate = document.getElementById('urlIsPrivate').checked;
|
||||
const password = isPrivate ? document.getElementById('urlPassword').value : null;
|
||||
|
||||
const data = {
|
||||
url: url,
|
||||
password: password
|
||||
};
|
||||
|
||||
fetch('/shorten', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: new URLSearchParams({ url }),
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const shortUrl = `${window.location.origin}/${data.vanity}`;
|
||||
let resultHtml = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
|
||||
if (isPrivate) {
|
||||
resultHtml += `<br>Password-protected link: <a href="${shortUrl}/${password}" target="_blank">${shortUrl}/${password}</a>`;
|
||||
}
|
||||
document.getElementById('urlResult').innerHTML = resultHtml;
|
||||
} else {
|
||||
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
document.getElementById('urlResult').innerHTML = `An error occurred: ${error.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
// Global drag and drop
|
||||
document.addEventListener('dragover', function(e) {
|
||||
e.preventDefault();
|
||||
if (!isFileModalOpen) {
|
||||
document.querySelector('.global-drop-area').style.display = 'flex';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('dragleave', function(e) {
|
||||
if (e.clientX === 0 || e.clientY === 0) {
|
||||
document.querySelector('.global-drop-area').style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('drop', function(e) {
|
||||
e.preventDefault();
|
||||
document.querySelector('.global-drop-area').style.display = 'none';
|
||||
if (!isFileModalOpen && e.dataTransfer.files.length > 0) {
|
||||
instantUploadFile(e.dataTransfer.files[0]);
|
||||
}
|
||||
});
|
||||
|
||||
function instantUploadFile(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
fetch('/upload/file', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="/${data.vanity}">here</a>.`;
|
||||
if (data.success) {
|
||||
document.getElementById('directDownloadUrl').href = data.download_url;
|
||||
document.getElementById('directDownloadUrl').textContent = data.download_url;
|
||||
document.getElementById('normalUrl').href = data.url;
|
||||
document.getElementById('normalUrl').textContent = data.url;
|
||||
document.querySelector('.instant-upload-overlay').style.display = 'flex';
|
||||
} else {
|
||||
alert('Error uploading file: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while uploading the file');
|
||||
});
|
||||
}
|
||||
|
||||
// Add this new function to close the modal
|
||||
function closeInstantUploadModal() {
|
||||
document.querySelector('.instant-upload-overlay').style.display = 'none';
|
||||
}
|
||||
|
||||
// Add event listeners for closing the modal
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const closeModalButton = document.querySelector('.close-modal');
|
||||
const instantUploadOverlay = document.querySelector('.instant-upload-overlay');
|
||||
|
||||
closeModalButton.addEventListener('click', closeInstantUploadModal);
|
||||
|
||||
instantUploadOverlay.addEventListener('click', function(event) {
|
||||
if (event.target === instantUploadOverlay) {
|
||||
closeInstantUploadModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Paste functionality for file upload
|
||||
document.addEventListener('paste', function(e) {
|
||||
if (document.getElementById('fileModal').style.display === 'block') {
|
||||
const items = e.clipboardData.items;
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].kind === 'file') {
|
||||
const file = items[i].getAsFile();
|
||||
filesToUpload.push(file);
|
||||
updateSelectedFilesList();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
@ -1,6 +1,150 @@
|
||||
<h2>Login</h2>
|
||||
<form method="post">
|
||||
<input type="text" name="username" placeholder="Username" required>
|
||||
<input type="password" name="password" placeholder="Password" required>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - sxbin</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #1a1a1a;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.container {
|
||||
background-color: #2a2a2a;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
width: 300px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #4CAF50;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #4CAF50;
|
||||
background-color: #333;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.remember-me {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.remember-me input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
p {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
a {
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
font-size: 24px;
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-button:hover {
|
||||
color: #45a049;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
background-color: #2a2a2a;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.error-message {
|
||||
background-color: #ff6b6b;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/" class="back-button">←</a>
|
||||
<div class="content">
|
||||
<div class="container">
|
||||
<h2>Login to sxbin</h2>
|
||||
<div id="error-message" class="error-message"></div>
|
||||
<form id="login-form" method="POST">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
<label>
|
||||
<input type="checkbox" name="remember"> Remember me
|
||||
</label>
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
||||
<p>Don't have an account? <a href="{{ url_for('register') }}">Register</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('login-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
fetch('/login', {
|
||||
method: 'POST',
|
||||
body: new FormData(this),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.href = data.redirect;
|
||||
} else {
|
||||
document.getElementById('error-message').textContent = data.error;
|
||||
document.getElementById('error-message').style.display = 'block';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
22
templates/og_file.html
Normal file
22
templates/og_file.html
Normal file
@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ filename }} - sxbin</title>
|
||||
<meta property="og:title" content="{{ filename }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ request.url }}">
|
||||
<meta property="og:description" content="File size: {{ file_size|filesizeformat }} | Uploaded by: {{ username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
|
||||
{% if is_embeddable %}
|
||||
<meta property="og:image" content="{{ file_url }}/raw">
|
||||
{% endif %}
|
||||
<meta property="og:site_name" content="sxbin">
|
||||
<meta property="theme-color" content="#4CAF50">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
window.location.href = "{{ file_url }}";
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
20
templates/og_pastebin.html
Normal file
20
templates/og_pastebin.html
Normal file
@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pastebin {{ vanity }} - sxbin</title>
|
||||
<meta property="og:title" content="Pastebin {{ vanity }}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ request.url }}">
|
||||
<meta property="og:description" content="{{ first_lines|truncate(200) }}">
|
||||
<meta property="og:site_name" content="sxbin">
|
||||
<meta property="theme-color" content="#4CAF50">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Pastebin {{ vanity }}</h1>
|
||||
<pre>{{ first_lines }}</pre>
|
||||
<p>Created by: {{ username }}</p>
|
||||
<p>Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
|
||||
</body>
|
||||
</html>
|
24
templates/og_shorturl.html
Normal file
24
templates/og_shorturl.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Shortened URL - sxbin</title>
|
||||
<meta property="og:title" content="Shortened URL - sxbin">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ request.url }}">
|
||||
<meta property="og:description" content="Shortened URL {% if is_private %}(Password Protected){% endif %} | Created by: {{ username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
|
||||
<meta property="og:site_name" content="sxbin">
|
||||
<meta property="theme-color" content="#4CAF50">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Shortened URL</h1>
|
||||
<p>Original URL: {{ long_url }}</p>
|
||||
<p>Created by: {{ username }}</p>
|
||||
<p>Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}</p>
|
||||
{% if is_private %}
|
||||
<p>This URL is password protected.</p>
|
||||
{% endif %}
|
||||
<a href="{{ url_for('redirect_vanity', vanity=vanity) }}">Access the URL</a>
|
||||
</body>
|
||||
</html>
|
28
templates/password_prompt.html
Normal file
28
templates/password_prompt.html
Normal file
@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Password Protected Content - sxbin</title>
|
||||
<meta property="og:title" content="Password Protected Content - sxbin">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ request.url }}">
|
||||
<meta property="og:description" content="This {{ content_type }} is password protected. Enter the password to view.">
|
||||
<meta property="og:site_name" content="sxbin">
|
||||
<meta property="theme-color" content="#4CAF50">
|
||||
<style>
|
||||
/* Add your styles here */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Password Protected Content</h1>
|
||||
<p>This {{ content_type }} is password protected. Please enter the password to view.</p>
|
||||
{% if error %}
|
||||
<p style="color: red;">{{ error }}</p>
|
||||
{% endif %}
|
||||
<form method="POST">
|
||||
<input type="password" name="password" required>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
@ -3,52 +3,230 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pastebin Content</title>
|
||||
<title>Pastebin {{ vanity }} - sxbin</title>
|
||||
<meta property="og:title" content="Pastebin {{ vanity }} - sxbin">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="{{ request.url }}">
|
||||
<meta property="og:description" content="Pastebin{% if is_private %} (Password Protected){% endif %} | Uploaded by: {{ content.username }} | Date: {{ created_at.strftime('%Y-%m-%d %H:%M:%S') }}">
|
||||
<meta property="og:site_name" content="sxbin">
|
||||
<meta property="theme-color" content="#4CAF50">
|
||||
<style>
|
||||
{{ css|safe }}
|
||||
/* Add any additional styles here */
|
||||
:root {
|
||||
--bg-color: #1e1e1e;
|
||||
--text-color: #e0e0e0;
|
||||
--highlight-bg: #2d2d2d;
|
||||
--highlight-border: #444;
|
||||
--button-bg: #3a3a3a;
|
||||
--button-text: #e0e0e0;
|
||||
}
|
||||
|
||||
.light-mode {
|
||||
--bg-color: #ffffff;
|
||||
--text-color: #333333;
|
||||
--highlight-bg: #f8f8f8;
|
||||
--highlight-border: #ccc;
|
||||
--button-bg: #f0f0f0;
|
||||
--button-text: #333333;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background-color: var(--bg-color);
|
||||
transition: background-color 0.3s, color 0.3s;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.button-container button {
|
||||
|
||||
button {
|
||||
background-color: var(--button-bg);
|
||||
color: var(--button-text);
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ccc;
|
||||
background-color: var(--highlight-bg);
|
||||
border: 1px solid var(--highlight-border);
|
||||
border-radius: 4px;
|
||||
padding: 1em;
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highlight pre {
|
||||
color: var(--text-color);
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.highlight .linenos {
|
||||
color: #999;
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
border-right: 1px solid var(--highlight-border);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.highlight .code {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
#theme-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: var(--button-bg);
|
||||
color: var(--button-text);
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s, opacity 0.3s;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
form {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
form .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.home-button {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
font-size: 24px;
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.home-button:hover {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
{{ css|safe }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Pastebin Content</h1>
|
||||
<p>Created at: {{ created_at }}</p>
|
||||
<p>Detected Language: {{ language }}</p>
|
||||
<div class="button-container">
|
||||
<button id="copy-button" onclick="copyToClipboard()">Copy Raw</button>
|
||||
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" target="_blank"><button>View Raw</button></a>
|
||||
</div>
|
||||
<div class="highlight">
|
||||
{{ content|safe }}
|
||||
<a href="/" class="home-button">⌂</a>
|
||||
<div class="container">
|
||||
<h2>Content</h2>
|
||||
<p>Uploaded by: {{ 'Anonymous' if content.username == 'None' else content.username }}</p>
|
||||
<p>Created at: {{ created_at }}</p>
|
||||
|
||||
<div class="highlight">
|
||||
{{ highlighted_content|safe }}
|
||||
</div>
|
||||
<div class="btn-container">
|
||||
<button onclick="copyToClipboard()" class="btn">Copy</button>
|
||||
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" class="btn">View Raw</a>
|
||||
{% if current_user.is_authenticated and current_user.id == content.user_id %}
|
||||
<a href="{{ url_for('edit_content', vanity=vanity) }}" class="btn">Edit</a>
|
||||
{% if is_private %}
|
||||
<button onclick="openEditPasswordModal()" class="btn">Edit Password</button>
|
||||
{% else %}
|
||||
<button onclick="openAddPasswordModal()" class="btn">Add Password</button>
|
||||
{% endif %}
|
||||
<form action="{{ url_for('delete_content', vanity=vanity) }}" method="post">
|
||||
<button type="submit" class="btn">Delete</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button id="theme-toggle">Toggle Theme</button>
|
||||
|
||||
<footer style="text-align: center; margin-top: 20px; padding: 10px; background-color: #2a2a2a; color: #f0f0f0;">
|
||||
<p>
|
||||
Source code: <a href="https://git.spitkov.hu/cgcristi/aCloud" target="_blank">Spitkov's Git</a> |
|
||||
<a href="https://office.bence.lol/form/#/2/form/view/z5Cf3CL6tZtPjzKsbcEPync6JE3iyMl22h6thUQg1a4/" target="_blank">Suggestions & Bugs</a> |
|
||||
<a href="https://office.bence.lol/kanban/#/2/kanban/view/hx6RTcpN0pR7hc1HHkMzG4awMoMdHjR2zbHjG7Xh+wU/embed/" target="_blank">Todo List</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
const rawContent = {{ raw_content|tojson }};
|
||||
|
||||
function copyToClipboard() {
|
||||
navigator.clipboard.writeText(rawContent).then(() => {
|
||||
const copyButton = document.getElementById("copy-button");
|
||||
const originalText = copyButton.textContent;
|
||||
copyButton.textContent = "Copied!";
|
||||
setTimeout(() => {
|
||||
copyButton.textContent = originalText;
|
||||
}, 2000);
|
||||
alert('Copied to clipboard!');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
}
|
||||
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const html = document.documentElement;
|
||||
|
||||
function toggleTheme() {
|
||||
html.classList.toggle('light-mode');
|
||||
localStorage.setItem('lightMode', html.classList.contains('light-mode'));
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('click', toggleTheme);
|
||||
|
||||
// Check for saved theme preference
|
||||
if (localStorage.getItem('lightMode') === 'true') {
|
||||
html.classList.add('light-mode');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,6 +1,171 @@
|
||||
<h2>Register</h2>
|
||||
<form method="post">
|
||||
<input type="text" name="username" placeholder="Username" required>
|
||||
<input type="password" name="password" placeholder="Password" required>
|
||||
<input type="submit" value="Register">
|
||||
</form>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Register - sxbin</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
background-color: #1a1a1a;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.container {
|
||||
background-color: #2a2a2a;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||
width: 300px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
color: #4CAF50;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #4CAF50;
|
||||
background-color: #333;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
p {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
a {
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.back-button {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
font-size: 24px;
|
||||
color: #4CAF50;
|
||||
text-decoration: none;
|
||||
}
|
||||
.back-button:hover {
|
||||
color: #45a049;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
background-color: #2a2a2a;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.error-message {
|
||||
background-color: #ff6b6b;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 10px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.password-requirement {
|
||||
font-size: 0.9em;
|
||||
margin-top: 5px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.requirement-not-met {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.requirement-met {
|
||||
color: #4CAF50;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/" class="back-button">←</a>
|
||||
<div class="content">
|
||||
<div class="container">
|
||||
<h2>Register for sxbin</h2>
|
||||
<div id="error-message" class="error-message"></div>
|
||||
<form id="register-form" method="POST">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
<div id="password-length" class="password-requirement requirement-not-met">At least 5 characters</div>
|
||||
<div id="password-uppercase" class="password-requirement requirement-not-met">At least one uppercase letter</div>
|
||||
<input type="submit" value="Register" id="submit-button" disabled>
|
||||
</form>
|
||||
<p>Already have an account? <a href="{{ url_for('login') }}">Login</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const passwordInput = document.getElementById('password');
|
||||
const lengthRequirement = document.getElementById('password-length');
|
||||
const uppercaseRequirement = document.getElementById('password-uppercase');
|
||||
const submitButton = document.getElementById('submit-button');
|
||||
|
||||
passwordInput.addEventListener('input', function() {
|
||||
const password = this.value;
|
||||
const meetsLengthRequirement = password.length >= 5;
|
||||
const meetsUppercaseRequirement = /[A-Z]/.test(password);
|
||||
|
||||
lengthRequirement.className = meetsLengthRequirement ? 'password-requirement requirement-met' : 'password-requirement requirement-not-met';
|
||||
uppercaseRequirement.className = meetsUppercaseRequirement ? 'password-requirement requirement-met' : 'password-requirement requirement-not-met';
|
||||
|
||||
submitButton.disabled = !(meetsLengthRequirement && meetsUppercaseRequirement);
|
||||
});
|
||||
|
||||
document.getElementById('register-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
fetch('/register', {
|
||||
method: 'POST',
|
||||
body: new FormData(this),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
window.location.href = data.redirect;
|
||||
} else {
|
||||
document.getElementById('error-message').textContent = data.error;
|
||||
document.getElementById('error-message').style.display = 'block';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
@ -16,12 +16,19 @@
|
||||
h2 {
|
||||
color: #4CAF50;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
.file-item {
|
||||
background-color: #2a2a2a;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
a {
|
||||
color: #4CAF50;
|
||||
@ -30,13 +37,34 @@
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.folder {
|
||||
font-weight: bold;
|
||||
}
|
||||
.file-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>{{ username }}'s Files</h2>
|
||||
<ul>
|
||||
<p>Current folder: {{ current_folder or 'Root' }}</p>
|
||||
|
||||
{% if parent_url is not none %}
|
||||
<p><a href="{{ parent_url }}">Parent Directory</a></p>
|
||||
{% endif %}
|
||||
|
||||
<ul class="file-list">
|
||||
{% for folder in folders %}
|
||||
<li class="file-item folder">
|
||||
<span class="file-icon">📁</span>
|
||||
<a href="{{ url_for('serve_user_page', username=username, filename=folder.path) }}">{{ folder.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% for file in files %}
|
||||
<li><a href="{{ url_for('download_folder_file', vanity=username, file_name=file) }}">{{ file }}</a></li>
|
||||
<li class="file-item">
|
||||
<span class="file-icon">📄</span>
|
||||
<a href="{{ url_for('serve_user_page', username=username, filename=file.path) }}">{{ file.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
|
@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Python IDE</title><script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/brython/3.9.0/brython_stdlib.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/mode/python/python.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/addon/hint/show-hint.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/addon/hint/python-hint.js"></script><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/codemirror.min.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/theme/monokai.min.css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.0/addon/hint/show-hint.css"><style>body{background-color:#1e1e1e;color:#d4d4d4;font-family:'Consolas','Courier New',monospace;margin:0;padding:20px}h1{color:#569cd6}#code{width:100%;height:300px}#output{width:100%;height:200px;background-color:#1e1e1e;border:1px solid #3c3c3c;padding:10px;margin-top:10px;overflow-y:auto;font-family:'Consolas','Courier New',monospace}#run-button,#save-button{background-color:#0e639c;color:white;border:none;padding:10px 20px;margin-top:10px;cursor:pointer}#run-button:hover,#save-button:hover{background-color:#1177bb}.toolbar{margin-bottom:10px}.toolbar button{background-color:#3c3c3c;color:#d4d4d4;border:none;padding:5px 10px;margin-right:5px;cursor:pointer}.toolbar button:hover{background-color:#4e4e4e}</style></head><body onload="brython()"><h1>Python IDE</h1><div class="toolbar"><button id="undo-button">Undo</button><button id="redo-button">Redo</button><button id="save-button">Save</button></div><textarea id="code" placeholder="Enter your Python code here"></textarea><br><button id="run-button">Run</button><div id="output"></div><script type="text/python">from browser import document,window
|
||||
import sys
|
||||
class _0x1234:
|
||||
def __init__(_0x5678):_0x5678._0x9abc=document["output"]
|
||||
def write(_0x5678,_0xdef0):_0x5678._0x9abc.innerHTML+=_0xdef0
|
||||
sys.stdout=_0x1234()
|
||||
sys.stderr=_0x1234()
|
||||
def _0x2345(_0x6789):
|
||||
document["output"].innerHTML=""
|
||||
_0xabcd=window.editor.getValue()
|
||||
try:exec(_0xabcd)
|
||||
except Exception as _0xef01:print(f"Error: {str(_0xef01)}")
|
||||
document["run-button"].bind("click",_0x2345)
|
||||
def _0x3456(_0x789a):window.editor.undo()
|
||||
def _0x4567(_0x89ab):window.editor.redo()
|
||||
document["undo-button"].bind("click",_0x3456)
|
||||
document["redo-button"].bind("click",_0x4567)
|
||||
def _0x5678(_0x9abc):
|
||||
_0xbcde=window.editor.getValue()
|
||||
_0xcdef=window.Blob.new([_0xbcde],{'type':'text/plain'})
|
||||
_0xdef0=window.URL.createObjectURL(_0xcdef)
|
||||
_0xef01=document.createElement('a')
|
||||
_0xef01.href=_0xdef0
|
||||
_0xef01.download='python_code.py'
|
||||
_0xef01.click()
|
||||
window.URL.revokeObjectURL(_0xdef0)
|
||||
document["save-button"].bind("click",_0x5678)</script><script>var _0x1234=CodeMirror.fromTextArea(document.getElementById("code"),{mode:"python",theme:"monokai",lineNumbers:!0,autoCloseBrackets:!0,matchBrackets:!0,indentUnit:4,tabSize:4,indentWithTabs:!1,extraKeys:{"Ctrl-Space":"autocomplete","Tab":function(_0x5678){_0x5678.somethingSelected()?_0x5678.indentSelection("add"):_0x5678.replaceSelection(_0x5678.getOption("indentWithTabs")?"\t":Array(_0x5678.getOption("indentUnit")+1).join(" "),"end","+input")}},hintOptions:{completeSingle:!1}});_0x1234.on("inputRead",function(_0x2345,_0x3456){if("+input"===_0x3456.origin){var _0x4567=_0x2345.getCursor(),_0x5678=_0x2345.getTokenAt(_0x4567);("variable"===_0x5678.type||"."===_0x5678.string)&&_0x2345.showHint({completeSingle:!1})}})</script></body></html>
|
Loading…
Reference in New Issue
Block a user