forked from spitkov/sxbin
this took me over 10 hours and my head hurts and im feeling so bad that i cant even explain what i did but like passwords redesigning and etc
This commit is contained in:
parent
d404490442
commit
3edd810c36
350
app.py
350
app.py
@ -1,4 +1,4 @@
|
||||
from flask import Flask, request, jsonify, send_from_directory, render_template, url_for, redirect, send_file, session, make_response, flash, g
|
||||
from flask import Flask, request, jsonify, send_from_directory, render_template, url_for, redirect, send_file, session, make_response, flash, g, Response, current_app
|
||||
from werkzeug.utils import secure_filename
|
||||
import shortuuid
|
||||
import os
|
||||
@ -37,30 +37,8 @@ def get_db():
|
||||
def init_db():
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
# Create users table
|
||||
cursor.execute('''
|
||||
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 content table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS content (
|
||||
vanity TEXT PRIMARY KEY,
|
||||
type TEXT NOT NULL,
|
||||
data TEXT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
user_id INTEGER,
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)
|
||||
''')
|
||||
|
||||
with app.open_resource('schema.sql', mode='r') as f:
|
||||
db.cursor().executescript(f.read())
|
||||
db.commit()
|
||||
print("Database initialized with users and content tables.")
|
||||
|
||||
@ -69,17 +47,23 @@ with app.app_context():
|
||||
init_db()
|
||||
|
||||
def migrate_db():
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
# Check if api_key column exists
|
||||
cursor.execute("PRAGMA table_info(users)")
|
||||
columns = [column[1] for column in cursor.fetchall()]
|
||||
|
||||
if 'api_key' not in columns:
|
||||
print("Adding api_key column to users table")
|
||||
cursor.execute("ALTER TABLE users ADD COLUMN api_key TEXT")
|
||||
db.commit()
|
||||
with app.app_context():
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
# Check if is_private column exists
|
||||
cursor.execute("PRAGMA table_info(content)")
|
||||
columns = [column[1] for column in cursor.fetchall()]
|
||||
|
||||
if 'is_private' not in columns:
|
||||
print("Adding is_private column to content table")
|
||||
cursor.execute("ALTER TABLE content ADD COLUMN is_private INTEGER DEFAULT 0")
|
||||
db.commit()
|
||||
|
||||
# Call migrate_db() after init_db()
|
||||
with app.app_context():
|
||||
init_db()
|
||||
migrate_db()
|
||||
|
||||
@app.teardown_appcontext
|
||||
def close_connection(exception):
|
||||
@ -142,16 +126,11 @@ class User(UserMixin):
|
||||
|
||||
@staticmethod
|
||||
def hash_password(password):
|
||||
salt = os.urandom(32)
|
||||
key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
|
||||
return salt + key
|
||||
return password # Store passwords in plaintext for simplicity
|
||||
|
||||
@staticmethod
|
||||
def verify_password(stored_password, provided_password):
|
||||
salt = stored_password[:32]
|
||||
stored_key = stored_password[32:]
|
||||
new_key = hashlib.pbkdf2_hmac('sha256', provided_password.encode('utf-8'), salt, 100000)
|
||||
return stored_key == new_key
|
||||
return stored_password == provided_password
|
||||
|
||||
@staticmethod
|
||||
def generate_api_key():
|
||||
@ -250,62 +229,71 @@ def serve_user_page(username, filename=None):
|
||||
parent_url=parent_url,
|
||||
current_folder=current_folder)
|
||||
|
||||
@app.route('/<vanity>')
|
||||
@app.route('/<vanity>/download')
|
||||
@app.route('/<vanity>', methods=['GET', 'POST'])
|
||||
def redirect_vanity(vanity):
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
|
||||
cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.vanity = ?", (vanity,))
|
||||
content = cursor.fetchone()
|
||||
|
||||
print(f"Fetched content for vanity {vanity}: {content}")
|
||||
|
||||
if content:
|
||||
content_type, content_data, created_at, user_id = content[1], content[2], content[3], content[4]
|
||||
content_type, content_data, created_at, user_id, is_private, password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7]
|
||||
|
||||
if content_type == 'file':
|
||||
print(f"Debug - Vanity: {vanity}, Type: {content_type}, Is Private: {is_private}, Password: {password}")
|
||||
print(f"User ID: {user_id}, Username: {username}")
|
||||
|
||||
if content_type == 'url':
|
||||
return redirect(content_data)
|
||||
elif content_type == 'file':
|
||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data)
|
||||
if os.path.exists(file_path):
|
||||
if '/download' in request.path:
|
||||
return send_file(file_path, as_attachment=True)
|
||||
else:
|
||||
# Embed the file if it's an image, video, or audio
|
||||
file_extension = os.path.splitext(content_data)[1].lower()
|
||||
if file_extension in ['.jpg', '.jpeg', '.png', '.gif', '.svg']:
|
||||
return send_file(file_path, mimetype=f'image/{file_extension[1:]}')
|
||||
elif file_extension in ['.mp3', '.wav']:
|
||||
return send_file(file_path, mimetype=f'audio/{file_extension[1:]}')
|
||||
elif file_extension in ['.mp4', '.webm']:
|
||||
return send_file(file_path, mimetype=f'video/{file_extension[1:]}')
|
||||
else:
|
||||
# For other file types, send as attachment
|
||||
return send_file(file_path, as_attachment=True)
|
||||
return send_file(file_path)
|
||||
else:
|
||||
return "File not found", 404
|
||||
elif content_type == 'url':
|
||||
return redirect(content_data)
|
||||
elif content_type == 'pastebin':
|
||||
try:
|
||||
lexer = guess_lexer(content_data)
|
||||
language = lexer.aliases[0]
|
||||
except ClassNotFound:
|
||||
language = 'text'
|
||||
lexer = get_lexer_by_name(language)
|
||||
|
||||
formatter = HtmlFormatter(style='monokai', linenos=True, cssclass="source")
|
||||
highlighted_code = highlight(content_data, lexer, formatter)
|
||||
css = formatter.get_style_defs('.source')
|
||||
return render_template('content.html',
|
||||
content={
|
||||
'user_id': user_id,
|
||||
'username': get_username(user_id),
|
||||
'created_at': created_at,
|
||||
'vanity': vanity
|
||||
},
|
||||
highlighted_content=highlighted_code,
|
||||
css=css,
|
||||
raw_content=content_data,
|
||||
language=language,
|
||||
url=None) # Set url to None for pastebins
|
||||
return render_template('404.html'), 404
|
||||
if is_private:
|
||||
if request.method == 'POST':
|
||||
entered_password = request.form.get('password')
|
||||
print(f"Entered password: {entered_password}")
|
||||
print(f"Stored password: {password}")
|
||||
if password and entered_password:
|
||||
if entered_password == password:
|
||||
print("Password match!")
|
||||
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
|
||||
else:
|
||||
print("Password mismatch!")
|
||||
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password")
|
||||
else:
|
||||
print(f"Missing password. Entered: {entered_password}, Stored: {password}")
|
||||
return render_template('password_prompt.html', vanity=vanity, error="An error occurred. Please try again.")
|
||||
return render_template('password_prompt.html', vanity=vanity)
|
||||
else:
|
||||
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
|
||||
|
||||
return "Not found", 404
|
||||
|
||||
def render_pastebin(content_data, created_at, user_id, username, vanity, is_private):
|
||||
try:
|
||||
lexer = guess_lexer(content_data)
|
||||
language = lexer.aliases[0]
|
||||
except ClassNotFound:
|
||||
language = 'text'
|
||||
lexer = get_lexer_by_name(language)
|
||||
|
||||
formatter = HtmlFormatter(style='monokai', linenos=True, cssclass="source")
|
||||
highlighted_code = highlight(content_data, lexer, formatter)
|
||||
css = formatter.get_style_defs('.source')
|
||||
return render_template('pastebin.html',
|
||||
content={'data': content_data, 'user_id': user_id, 'username': username or 'Anonymous'},
|
||||
highlighted_content=highlighted_code,
|
||||
css=css,
|
||||
raw_content=content_data,
|
||||
language=language,
|
||||
created_at=created_at,
|
||||
vanity=vanity,
|
||||
is_private=is_private)
|
||||
|
||||
@app.route('/<vanity>/raw')
|
||||
def raw_vanity(vanity):
|
||||
@ -422,7 +410,8 @@ def user_files(username, subpath=''):
|
||||
'type': upload[1],
|
||||
'vanity': upload[0],
|
||||
'data': upload[2],
|
||||
'created_at': upload[3]
|
||||
'created_at': upload[3],
|
||||
'is_private': upload[5]
|
||||
})
|
||||
|
||||
parent_folder = os.path.dirname(subpath.rstrip('/')) if subpath else None
|
||||
@ -488,19 +477,6 @@ def delete_user_file(username, filename):
|
||||
except Exception as e:
|
||||
return f"An error occurred: {str(e)}", 500
|
||||
|
||||
@app.route('/dash/<username>/rename', methods=['POST'])
|
||||
@login_required
|
||||
def rename_user_file(username):
|
||||
if current_user.username != username:
|
||||
return "Unauthorized", 401
|
||||
old_filename = request.form['old_filename']
|
||||
new_filename = secure_filename(request.form['new_filename'])
|
||||
old_path = os.path.join(app.config['UPLOAD_FOLDER'], username, old_filename)
|
||||
new_path = os.path.join(app.config['UPLOAD_FOLDER'], username, new_filename)
|
||||
if os.path.exists(old_path):
|
||||
os.rename(old_path, new_path)
|
||||
return redirect(url_for('user_files', username=username))
|
||||
|
||||
|
||||
@app.route('/dash/<username>/delete_folder/<folder_name>', methods=['POST'])
|
||||
@login_required
|
||||
@ -570,7 +546,9 @@ def edit_file(username, filename):
|
||||
content = request.form['content']
|
||||
with open(file_path, 'w') as f:
|
||||
f.write(content)
|
||||
return redirect(url_for('user_files', username=username))
|
||||
# Get the directory path to redirect back to
|
||||
dir_path = os.path.dirname(filename)
|
||||
return redirect(url_for('user_files', username=username, subpath=dir_path))
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
content = f.read()
|
||||
@ -588,55 +566,60 @@ def debug_users():
|
||||
@app.route('/upload/pastebin', methods=['POST'])
|
||||
def upload_pastebin():
|
||||
try:
|
||||
print("Received request to upload pastebin")
|
||||
data = request.get_json()
|
||||
print(f"Received JSON data: {data}")
|
||||
|
||||
if not data or 'content' not in data:
|
||||
print("Error: Content is missing from the request")
|
||||
return jsonify({'success': False, 'error': 'Content is required'}), 400
|
||||
|
||||
content = data['content']
|
||||
password = data.get('password')
|
||||
print(f"Content: {content[:50]}...") # Print first 50 characters of content
|
||||
print(f"Password received from client: {password}")
|
||||
|
||||
is_private = 1 if password else 0
|
||||
print(f"Is private: {is_private}")
|
||||
|
||||
vanity = shortuuid.uuid()[:8]
|
||||
print(f"Generated vanity: {vanity}")
|
||||
|
||||
user_id = current_user.id if current_user.is_authenticated else None
|
||||
print(f"User ID: {user_id}")
|
||||
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
||||
(vanity, 'pastebin', content, datetime.now(), user_id))
|
||||
|
||||
if is_private:
|
||||
print(f"Inserting private pastebin into database with password: {password}")
|
||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private, password) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
(vanity, 'pastebin', content, datetime.now(), user_id, is_private, password))
|
||||
print(f"Executed SQL with values: {vanity}, pastebin, {content[:50]}..., {datetime.now()}, {user_id}, {is_private}, {password}")
|
||||
else:
|
||||
print("Inserting public pastebin into database")
|
||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private) VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(vanity, 'pastebin', content, datetime.now(), user_id, is_private))
|
||||
print(f"Executed SQL with values: {vanity}, pastebin, {content[:50]}..., {datetime.now()}, {user_id}, {is_private}")
|
||||
|
||||
db.commit()
|
||||
print("Database commit successful")
|
||||
|
||||
# Verify the inserted data
|
||||
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
|
||||
inserted_data = cursor.fetchone()
|
||||
print(f"Inserted data: {inserted_data}")
|
||||
|
||||
short_url = url_for('redirect_vanity', vanity=vanity, _external=True)
|
||||
deletion_url = url_for('delete_content', vanity=vanity, _external=True)
|
||||
print(f"Generated short URL: {short_url}")
|
||||
print(f"Generated deletion URL: {deletion_url}")
|
||||
|
||||
return jsonify({'success': True, 'vanity': vanity, 'url': short_url, 'deletion_url': deletion_url}), 200
|
||||
except Exception as e:
|
||||
print("Exception occurred:", str(e))
|
||||
return jsonify({'success': False, 'error': str(e)}), 400
|
||||
print(f"Exception occurred in upload_pastebin: {str(e)}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
@app.route('/upload/file', methods=['POST'])
|
||||
def upload_file():
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'success': False, 'error': 'No file part'}), 400
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({'success': False, 'error': 'No selected file'}), 400
|
||||
if file:
|
||||
filename = secure_filename(file.filename)
|
||||
extension = os.path.splitext(filename)[1].lower()
|
||||
vanity = shortuuid.uuid()[:8]
|
||||
vanity_with_extension = f"{vanity}{extension}"
|
||||
new_filename = vanity_with_extension
|
||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
|
||||
file.save(file_path)
|
||||
|
||||
user_id = current_user.id if current_user.is_authenticated else None
|
||||
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
||||
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id))
|
||||
db.commit()
|
||||
|
||||
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
|
||||
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
|
||||
return jsonify({'success': True, 'vanity': vanity_with_extension, 'url': short_url, 'deletion_url': deletion_url, 'filename': new_filename}), 200
|
||||
|
||||
@app.route('/shorten', methods=['POST'])
|
||||
def shorten_url():
|
||||
@ -656,7 +639,7 @@ def shorten_url():
|
||||
(vanity, 'url', long_url, datetime.now(), user_id))
|
||||
db.commit()
|
||||
|
||||
short_url = url_for('redirect_vanity', vanity=vanity, _external=True)
|
||||
short_url = f"{request.host_url}{vanity}"
|
||||
return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200
|
||||
except Exception as e:
|
||||
print("Exception occurred:", str(e))
|
||||
@ -693,6 +676,33 @@ def edit_content(vanity):
|
||||
|
||||
return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400
|
||||
|
||||
@app.route('/edit_password/<vanity>', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def edit_password(vanity):
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
|
||||
content = cursor.fetchone()
|
||||
|
||||
if not content or content[4] != current_user.id:
|
||||
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
|
||||
|
||||
if request.method == 'POST':
|
||||
data = request.get_json()
|
||||
action = data.get('action')
|
||||
if action == 'update':
|
||||
new_password = data.get('new_password')
|
||||
cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity))
|
||||
db.commit()
|
||||
return jsonify({'success': True})
|
||||
elif action == 'remove':
|
||||
cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,))
|
||||
db.commit()
|
||||
return jsonify({'success': True})
|
||||
return jsonify({'success': False, 'error': 'Invalid action'})
|
||||
|
||||
return render_template('edit_password.html', vanity=vanity)
|
||||
|
||||
@app.route('/delete/content/<vanity>', methods=['POST'])
|
||||
@login_required
|
||||
def delete_content(vanity):
|
||||
@ -817,7 +827,7 @@ def api_upload():
|
||||
file.save(file_path)
|
||||
|
||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
||||
(new_filename, 'file', new_filename, datetime.now(), user[0]))
|
||||
(vanity, 'file', new_filename, datetime.now(), user[0]))
|
||||
db.commit()
|
||||
|
||||
url = url_for('redirect_vanity', vanity=new_filename, _external=True, _scheme='https')
|
||||
@ -953,6 +963,76 @@ def create_folder(username):
|
||||
flash(f"Error creating folder: {str(e)}", 'error')
|
||||
return redirect(url_for('user_files', username=username, subpath=subpath))
|
||||
|
||||
@app.route('/dash/<username>/rename', methods=['POST'])
|
||||
@login_required
|
||||
def rename_user_file(username):
|
||||
if current_user.username != username:
|
||||
return "Unauthorized", 401
|
||||
|
||||
old_filename = request.form['old_filename']
|
||||
new_filename = secure_filename(request.form['new_filename'])
|
||||
item_type = request.form['item_type']
|
||||
current_path = request.form.get('current_path', '').rstrip('/')
|
||||
|
||||
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
|
||||
full_current_path = os.path.join(user_folder, current_path)
|
||||
|
||||
old_path = os.path.join(full_current_path, old_filename)
|
||||
new_path = os.path.join(full_current_path, new_filename)
|
||||
|
||||
if not os.path.exists(old_path):
|
||||
flash(f"The {item_type} '{old_filename}' does not exist.", 'error')
|
||||
return redirect(url_for('user_files', username=username, subpath=current_path))
|
||||
|
||||
try:
|
||||
os.rename(old_path, new_path)
|
||||
flash(f"Successfully renamed {item_type} from '{old_filename}' to '{new_filename}'.", 'success')
|
||||
except OSError as e:
|
||||
flash(f"Error renaming {item_type}: {str(e)}", 'error')
|
||||
|
||||
return redirect(url_for('user_files', username=username, subpath=current_path))
|
||||
|
||||
@app.route('/upload/file', methods=['POST'])
|
||||
def upload_file():
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'success': False, 'error': 'No file part'}), 400
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
return jsonify({'success': False, 'error': 'No selected file'}), 400
|
||||
if file:
|
||||
try:
|
||||
filename = secure_filename(file.filename)
|
||||
extension = os.path.splitext(filename)[1].lower()
|
||||
vanity = shortuuid.uuid()[:8]
|
||||
vanity_with_extension = f"{vanity}{extension}"
|
||||
new_filename = vanity_with_extension
|
||||
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], new_filename)
|
||||
|
||||
file.save(file_path)
|
||||
|
||||
user_id = current_user.id if current_user.is_authenticated else None
|
||||
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
||||
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id))
|
||||
db.commit()
|
||||
|
||||
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
|
||||
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'vanity': vanity_with_extension,
|
||||
'url': short_url,
|
||||
'deletion_url': deletion_url,
|
||||
'filename': new_filename
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Start the cleanup thread
|
||||
cleanup_thread = threading.Thread(target=delete_old_files)
|
||||
|
@ -11,5 +11,7 @@ CREATE TABLE IF NOT EXISTS content (
|
||||
data 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)
|
||||
);
|
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>
|
@ -1,3 +1,4 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@ -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,7 @@
|
||||
overflow: hidden;
|
||||
font-size: 1.2em;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
@ -107,6 +123,88 @@
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -126,35 +224,51 @@
|
||||
</div>
|
||||
|
||||
<div class="upload-options">
|
||||
<button onclick="showForm('text')">Upload Text</button>
|
||||
<button onclick="showForm('file')">Upload File</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="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>
|
||||
<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 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...">
|
||||
<button onclick="shortenUrl()">Shorten URL</button>
|
||||
<div id="urlResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -168,7 +282,7 @@
|
||||
<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 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;
|
||||
@ -184,7 +298,6 @@
|
||||
typewriterTextElement.innerHTML += message[index];
|
||||
}
|
||||
index++;
|
||||
updateCursorPosition();
|
||||
setTimeout(typeCharacter, typingSpeed);
|
||||
} else if (callback) {
|
||||
setTimeout(callback, typingSpeed);
|
||||
@ -194,99 +307,171 @@
|
||||
typeCharacter();
|
||||
}
|
||||
|
||||
function updateCursorPosition() {
|
||||
|
||||
const textRect = typewriterTextElement.getBoundingClientRect();
|
||||
cursorElement.style.left = (textRect.width + textRect.left - cursorElement.offsetWidth) + 'px';
|
||||
}
|
||||
|
||||
typeMessage(message1, function() {
|
||||
typeMessage(message2);
|
||||
});
|
||||
});;
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('uploadForm').onsubmit = function(e) {
|
||||
e.preventDefault();
|
||||
var formData = new FormData(this);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/upload/file', true);
|
||||
|
||||
xhr.upload.onprogress = function(e) {
|
||||
if (e.lengthComputable) {
|
||||
var percentComplete = (e.loaded / e.total) * 100;
|
||||
document.getElementById('progressBar').style.display = 'block';
|
||||
document.getElementById('progressBar').value = percentComplete;
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
alert('Upload complete!');
|
||||
document.getElementById('progressBar').style.display = 'none';
|
||||
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 {
|
||||
alert('Upload failed.');
|
||||
clearInterval(fadeIn);
|
||||
}
|
||||
};
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
function showForm(type) {
|
||||
document.querySelectorAll('.upload-form').forEach(form => {
|
||||
form.classList.remove('active');
|
||||
});
|
||||
document.getElementById(type + 'Form').classList.add('active');
|
||||
}, 30);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target.className === "modal") {
|
||||
event.target.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('isPrivate').addEventListener('change', function() {
|
||||
document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none';
|
||||
});
|
||||
|
||||
function uploadText() {
|
||||
const content = document.querySelector('#textForm textarea').value;
|
||||
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({ content: content }),
|
||||
body: JSON.stringify(data),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(response => 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}">${simpleUrl}</a>`;
|
||||
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}`;
|
||||
document.getElementById('textResult').innerHTML = `An error occurred: ${error.message}`;
|
||||
});
|
||||
}
|
||||
|
||||
function uploadFile() {
|
||||
const uploadArea = document.getElementById('uploadArea');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
|
||||
uploadArea.addEventListener('click', () => fileInput.click());
|
||||
uploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
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) {
|
||||
for (let file of files) {
|
||||
uploadFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
function uploadFile(file) {
|
||||
const formData = new FormData();
|
||||
const fileInput = document.querySelector('#fileForm input[type="file"]');
|
||||
formData.append('file', fileInput.files[0]);
|
||||
fetch('/upload/file', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const simpleUrl = `${window.location.origin}/${data.vanity}`;
|
||||
document.getElementById('fileResult').innerHTML = `File uploaded. Access it <a href="${simpleUrl}">${simpleUrl}</a>`;
|
||||
} else {
|
||||
document.getElementById('fileResult').innerHTML = `Error: ${data.error}`;
|
||||
formData.append('file', file);
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/upload/file', true);
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable) {
|
||||
const percentComplete = (e.loaded / e.total) * 100;
|
||||
progressBar.style.width = percentComplete + '%';
|
||||
progressBar.textContent = percentComplete.toFixed(2) + '%';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
document.getElementById('fileResult').innerHTML = `An error occurred: ${error}`;
|
||||
});
|
||||
};
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.success) {
|
||||
document.getElementById('fileResult').innerHTML += `File uploaded: <a href="${response.url}" target="_blank">${response.url}</a><br>`;
|
||||
} else {
|
||||
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
|
||||
}
|
||||
} else {
|
||||
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 shortenUrl() {
|
||||
const url = document.querySelector('#urlForm input[name="url"]').value;
|
||||
const url = document.getElementById('urlInput').value;
|
||||
|
||||
fetch('/shorten', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -294,20 +479,25 @@
|
||||
},
|
||||
body: JSON.stringify({ url: url }),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${data.short_url}">${data.short_url}</a>`;
|
||||
const shortUrl = `${window.location.origin}/${data.short_url}`;
|
||||
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
|
||||
} else {
|
||||
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
document.getElementById('urlResult').innerHTML = `An error occurred: ${error}`;
|
||||
document.getElementById('urlResult').innerHTML = `An error occurred: ${error.message}`;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
109
templates/password_prompt.html
Normal file
109
templates/password_prompt.html
Normal file
@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Enter Password</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #1a1a1a;
|
||||
color: #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
input[type="password"] {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid #4CAF50;
|
||||
border-radius: 4px;
|
||||
background-color: #333;
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
button[type="submit"] {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
button[type="button"] {
|
||||
background-color: #888;
|
||||
color: white;
|
||||
}
|
||||
.error {
|
||||
color: #ff6b6b;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.alert {
|
||||
background-color: #ff6b6b;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>This content is password protected</h2>
|
||||
<div id="errorAlert" class="alert">
|
||||
Incorrect password. Please try again.
|
||||
</div>
|
||||
<form method="POST" onsubmit="return validatePassword()">
|
||||
<input type="password" id="password" name="password" placeholder="Enter password" required>
|
||||
<div class="button-container">
|
||||
<button type="button" onclick="window.history.back()">Cancel</button>
|
||||
<button type="submit">OK</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function validatePassword() {
|
||||
var password = document.getElementById('password').value;
|
||||
if (password.trim() === '') {
|
||||
document.getElementById('errorAlert').style.display = 'block';
|
||||
document.getElementById('errorAlert').textContent = 'Please enter a password.';
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
{% if error %}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('errorAlert').style.display = 'block';
|
||||
document.getElementById('errorAlert').textContent = '{{ error }}';
|
||||
});
|
||||
{% endif %}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -1,37 +1,232 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="en" class="dark-mode">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Pastebin Content</title>
|
||||
<title>Content</title>
|
||||
<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 {
|
||||
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;
|
||||
}
|
||||
|
||||
#theme-toggle {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--text-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.highlight pre {
|
||||
color: var(--text-color);
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--bg-color);
|
||||
margin: 15% auto;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--highlight-border);
|
||||
width: 300px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.modal-content h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.modal-content input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid var(--highlight-border);
|
||||
background-color: var(--highlight-bg);
|
||||
color: var(--text-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.modal-buttons button {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-ok {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #888;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-remove {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
{{ 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 class="container">
|
||||
<h2>Content</h2>
|
||||
<p>Uploaded by: {{ 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>
|
||||
<div class="highlight">
|
||||
{{ content|safe }}
|
||||
|
||||
<button id="theme-toggle">Toggle Theme</button>
|
||||
|
||||
<div id="editPasswordModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h3 id="passwordModalTitle">Edit Password</h3>
|
||||
<input type="password" id="newPassword1" placeholder="Enter new password">
|
||||
<input type="password" id="newPassword2" placeholder="Confirm new password">
|
||||
<div class="modal-buttons">
|
||||
<button onclick="closePasswordModal()" class="btn-cancel">Cancel</button>
|
||||
<button onclick="updatePassword()" class="btn-ok">OK</button>
|
||||
<button onclick="removePassword()" class="btn-remove" id="removePasswordBtn">Remove Password</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@ -39,16 +234,107 @@
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
function openEditPasswordModal() {
|
||||
document.getElementById('passwordModalTitle').textContent = 'Edit Password';
|
||||
document.getElementById('removePasswordBtn').style.display = 'inline-block';
|
||||
document.getElementById('editPasswordModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function openAddPasswordModal() {
|
||||
document.getElementById('passwordModalTitle').textContent = 'Add Password';
|
||||
document.getElementById('removePasswordBtn').style.display = 'none';
|
||||
document.getElementById('editPasswordModal').style.display = 'block';
|
||||
}
|
||||
|
||||
function closePasswordModal() {
|
||||
document.getElementById('editPasswordModal').style.display = 'none';
|
||||
document.getElementById('newPassword1').value = '';
|
||||
document.getElementById('newPassword2').value = '';
|
||||
}
|
||||
|
||||
function updatePassword() {
|
||||
const password1 = document.getElementById('newPassword1').value;
|
||||
const password2 = document.getElementById('newPassword2').value;
|
||||
|
||||
if (password1 !== password2) {
|
||||
alert('Passwords do not match');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('{{ url_for("edit_password", vanity=vanity) }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'update',
|
||||
new_password: password1
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Password updated successfully');
|
||||
closePasswordModal();
|
||||
location.reload(); // Reload the page to reflect the changes
|
||||
} else {
|
||||
alert('Error updating password: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while updating the password');
|
||||
});
|
||||
}
|
||||
|
||||
function removePassword() {
|
||||
if (confirm('Are you sure you want to remove the password?')) {
|
||||
fetch('{{ url_for("edit_password", vanity=vanity) }}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'remove'
|
||||
}),
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
alert('Password removed successfully');
|
||||
closePasswordModal();
|
||||
location.reload(); // Reload the page to reflect the changes
|
||||
} else {
|
||||
alert('Error removing password: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('An error occurred while removing the password');
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user