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>
|
1070
templates/index.html
1070
templates/index.html
File diff suppressed because it is too large
Load Diff
@ -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