1
0
forked from spitkov/sxbin

i dont even remember what i did i just gonn apsuh because its not up to date and i dproably made some curical improvements that i will realsie ver time nd i cant speel because im alread yhigh

This commit is contained in:
spitkov 2024-09-14 20:36:31 +02:00
parent 3edd810c36
commit c76ff11c26
4 changed files with 473 additions and 198 deletions

165
app.py
View File

@ -230,47 +230,45 @@ def serve_user_page(username, filename=None):
current_folder=current_folder) current_folder=current_folder)
@app.route('/<vanity>', methods=['GET', 'POST']) @app.route('/<vanity>', methods=['GET', 'POST'])
def redirect_vanity(vanity): @app.route('/<vanity>/<password>', methods=['GET', 'POST'])
@app.route('/<vanity>/download', methods=['GET', 'POST'])
@app.route('/<vanity>/download/<password>', methods=['GET', 'POST'])
def redirect_vanity(vanity, password=None):
db = get_db() db = get_db()
cursor = db.cursor() cursor = db.cursor()
cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.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() content = cursor.fetchone()
print(f"Fetched content for vanity {vanity}: {content}")
if content: if content:
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] content_type, content_data, created_at, user_id, is_private, stored_password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7]
print(f"Debug - Vanity: {vanity}, Type: {content_type}, Is Private: {is_private}, Password: {password}") if is_private and stored_password:
print(f"User ID: {user_id}, Username: {username}") if password:
if password != stored_password:
return "Incorrect password", 403
elif request.method == 'POST':
entered_password = request.form.get('password')
if entered_password != stored_password:
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password")
else:
return render_template('password_prompt.html', vanity=vanity, error=None)
# Remove '/download' from the content_data if present
content_data = content_data.replace('/download', '')
if content_type == 'url': if content_type == 'url':
return redirect(content_data) return redirect(content_data)
elif content_type == 'file': elif content_type == 'file':
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data) file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data)
if os.path.exists(file_path): if os.path.exists(file_path):
return send_file(file_path) if 'download' in request.path:
return send_file(file_path, as_attachment=True)
else:
return send_file(file_path)
else: else:
return "File not found", 404 return "File not found", 404
elif content_type == 'pastebin': elif content_type == 'pastebin':
if is_private: return render_pastebin(content_data, created_at, user_id, username, vanity, 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 return "Not found", 404
@ -295,17 +293,33 @@ def render_pastebin(content_data, created_at, user_id, username, vanity, is_priv
vanity=vanity, vanity=vanity,
is_private=is_private) is_private=is_private)
@app.route('/<vanity>/raw') @app.route('/<vanity>/raw', methods=['GET', 'POST'])
def raw_vanity(vanity): @app.route('/<vanity>/raw/<password>', methods=['GET'])
def raw_vanity(vanity, password=None):
db = get_db() db = get_db()
cursor = db.cursor() cursor = db.cursor()
cursor.execute("SELECT * FROM content WHERE vanity = ? AND type = 'pastebin'", (vanity,)) cursor.execute("SELECT * FROM content WHERE vanity = ? AND type = 'pastebin'", (vanity,))
target = cursor.fetchone() content = cursor.fetchone()
if target: if content:
return target[2], 200, {'Content-Type': 'text/plain; charset=utf-8'} content_type, content_data, created_at, user_id, is_private, stored_password = content[1], content[2], content[3], content[4], content[5], content[6]
if is_private and stored_password:
if password:
if password != stored_password:
return "Incorrect password", 403
elif request.method == 'POST':
entered_password = request.form.get('password')
if entered_password != stored_password:
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password", raw=True)
else:
return render_template('password_prompt.html', vanity=vanity, error=None, raw=True)
# Remove '/download' from the content_data if present
content_data = content_data.replace('/download', '')
return content_data, 200, {'Content-Type': 'text/plain; charset=utf-8'}
return 'Not Found', 404 return 'Not Found', 404
# Replace the LoginForm and RegistrationForm classes with simple classes # Replace the LoginForm and RegistrationForm classes with simple classes
class LoginForm: class LoginForm:
def __init__(self, username, password, remember): def __init__(self, username, password, remember):
@ -445,19 +459,34 @@ def toggle_index(username):
@login_required @login_required
def upload_user_file(username): def upload_user_file(username):
if current_user.username != username: if current_user.username != username:
return "Unauthorized", 401 return jsonify({"success": False, "error": "Unauthorized"}), 401
subpath = request.form.get('subpath', '').rstrip('/') subpath = request.form.get('subpath', '').rstrip('/')
if 'file' not in request.files: if 'file' not in request.files:
return 'No file part', 400 return jsonify({"success": False, "error": 'No file part'}), 400
file = request.files['file'] file = request.files['file']
if file.filename == '': if file.filename == '':
return 'No selected file', 400 return jsonify({"success": False, "error": 'No selected file'}), 400
if file: if file:
filename = secure_filename(file.filename) filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], username, subpath, filename) file_path = os.path.join(app.config['UPLOAD_FOLDER'], username, subpath, filename)
os.makedirs(os.path.dirname(file_path), exist_ok=True) os.makedirs(os.path.dirname(file_path), exist_ok=True)
file.save(file_path)
return redirect(url_for('user_files', username=username, subpath=subpath)) # Use chunked upload
chunk_size = 4096 # 4KB chunks
try:
with open(file_path, 'wb') as f:
while True:
chunk = file.stream.read(chunk_size)
if not chunk:
break
f.write(chunk)
return jsonify({"success": True, "filename": filename}), 200
except Exception as e:
app.logger.error(f"Error uploading file: {str(e)}")
return jsonify({"success": False, "error": str(e)}), 500
return jsonify({"success": False, "error": "File upload failed"}), 500
@app.route('/dash/<username>/delete/<path:filename>', methods=['POST']) @app.route('/dash/<username>/delete/<path:filename>', methods=['POST'])
@login_required @login_required
@ -639,8 +668,8 @@ def shorten_url():
(vanity, 'url', long_url, datetime.now(), user_id)) (vanity, 'url', long_url, datetime.now(), user_id))
db.commit() db.commit()
short_url = f"{request.host_url}{vanity}" # Return only the vanity code, not the full URL
return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200 return jsonify({'success': True, 'vanity': vanity}), 200
except Exception as e: except Exception as e:
print("Exception occurred:", str(e)) print("Exception occurred:", str(e))
return jsonify({'success': False, 'error': str(e)}), 400 return jsonify({'success': False, 'error': str(e)}), 400
@ -676,7 +705,7 @@ def edit_content(vanity):
return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400 return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400
@app.route('/edit_password/<vanity>', methods=['GET', 'POST']) @app.route('/edit_password/<vanity>', methods=['POST'])
@login_required @login_required
def edit_password(vanity): def edit_password(vanity):
db = get_db() db = get_db()
@ -687,21 +716,20 @@ def edit_password(vanity):
if not content or content[4] != current_user.id: if not content or content[4] != current_user.id:
return jsonify({'success': False, 'error': 'Unauthorized'}), 401 return jsonify({'success': False, 'error': 'Unauthorized'}), 401
if request.method == 'POST': data = request.get_json()
data = request.get_json() action = data.get('action')
action = data.get('action') if action == 'update':
if action == 'update': new_password = data.get('new_password')
new_password = data.get('new_password') if not is_valid_password(new_password):
cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity)) return jsonify({'success': False, 'error': 'Invalid password'}), 400
db.commit() cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity))
return jsonify({'success': True}) elif action == 'remove':
elif action == 'remove': cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,))
cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,)) else:
db.commit() return jsonify({'success': False, 'error': 'Invalid action'}), 400
return jsonify({'success': True})
return jsonify({'success': False, 'error': 'Invalid action'})
return render_template('edit_password.html', vanity=vanity) db.commit()
return jsonify({'success': True})
@app.route('/delete/content/<vanity>', methods=['POST']) @app.route('/delete/content/<vanity>', methods=['POST'])
@login_required @login_required
@ -725,7 +753,7 @@ def delete_content(vanity):
return jsonify({'success': True}), 200 return jsonify({'success': True}), 200
@app.route('/<vanity>/info') @app.route('/<vanity>/info', methods=['GET', 'POST'])
def content_info(vanity): def content_info(vanity):
db = get_db() db = get_db()
cursor = db.cursor() cursor = db.cursor()
@ -733,7 +761,15 @@ def content_info(vanity):
content = cursor.fetchone() content = cursor.fetchone()
if 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 = content[1], content[2], content[3], content[4], content[5], content[6]
if is_private and password:
if request.method == 'POST':
entered_password = request.form.get('password')
if entered_password != password:
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password")
else:
return render_template('password_prompt.html', vanity=vanity, error=None)
username = get_username(user_id) username = get_username(user_id)
@ -753,7 +789,8 @@ def content_info(vanity):
'created_at': created_at, 'created_at': created_at,
'username': username, 'username': username,
'file_size': file_size, 'file_size': file_size,
'is_media': is_media 'is_media': is_media,
'is_private': is_private
} }
return render_template('content_info.html', info=info) return render_template('content_info.html', info=info)
@ -1012,19 +1049,26 @@ def upload_file():
user_id = current_user.id if current_user.is_authenticated else None user_id = current_user.id if current_user.is_authenticated else None
password = request.form.get('password')
is_private = 1 if password else 0
db = get_db() db = get_db()
cursor = db.cursor() cursor = db.cursor()
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)", cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private, password) VALUES (?, ?, ?, ?, ?, ?, ?)",
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id)) (vanity_with_extension, 'file', new_filename, datetime.now(), user_id, is_private, password))
db.commit() db.commit()
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True) short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
# Remove '/download' suffix if it exists
short_url = short_url.replace('/download', '')
download_url = short_url + '/download'
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True) deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
return jsonify({ return jsonify({
'success': True, 'success': True,
'vanity': vanity_with_extension, 'vanity': vanity_with_extension,
'url': short_url, 'url': short_url,
'download_url': download_url,
'deletion_url': deletion_url, 'deletion_url': deletion_url,
'filename': new_filename 'filename': new_filename
}) })
@ -1033,6 +1077,11 @@ def upload_file():
return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500 return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500
# Add this function to validate passwords
def is_valid_password(password):
banned_passwords = ['info', 'download']
return password not in banned_passwords
if __name__ == '__main__': if __name__ == '__main__':
# Start the cleanup thread # Start the cleanup thread
cleanup_thread = threading.Thread(target=delete_old_files) cleanup_thread = threading.Thread(target=delete_old_files)

View File

@ -205,6 +205,59 @@
button:hover { button:hover {
background-color: #45a049; background-color: #45a049;
} }
.file-group {
border: 1px solid #4CAF50;
border-radius: 5px;
margin-bottom: 10px;
overflow: hidden;
}
.file-group-header {
background-color: #4CAF50;
color: white;
padding: 10px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.file-group-header span:first-child {
margin-right: 10px;
}
.file-group-header span:nth-child(2) {
flex-grow: 1;
text-align: left;
}
.file-group-content {
padding: 10px;
display: none;
}
.file-group-content.show {
display: block;
}
.other-links-btn {
background-color: #45a049;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}
.other-links {
margin-top: 10px;
display: none;
}
.other-links.show {
display: block;
}
</style> </style>
</head> </head>
<body> <body>
@ -255,6 +308,15 @@
<p>Drag and drop files here or click to select files</p> <p>Drag and drop files here or click to select files</p>
<input type="file" id="fileInput" multiple> <input type="file" id="fileInput" multiple>
</div> </div>
<div id="selectedFiles"></div>
<div>
<input type="checkbox" id="fileIsPrivate" name="fileIsPrivate">
<label for="fileIsPrivate">Add password protection</label>
</div>
<div id="filePasswordField" style="display: none;">
<input type="password" id="filePassword" placeholder="Enter password">
</div>
<button onclick="uploadSelectedFiles()">Upload</button>
<div class="progress-bar"> <div class="progress-bar">
<div id="progressBar" class="progress"></div> <div id="progressBar" class="progress"></div>
</div> </div>
@ -367,6 +429,10 @@
document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none'; document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none';
}); });
document.getElementById('fileIsPrivate').addEventListener('change', function() {
document.getElementById('filePasswordField').style.display = this.checked ? 'block' : 'none';
});
function uploadText() { function uploadText() {
const content = document.getElementById('textContent').value; const content = document.getElementById('textContent').value;
const isPrivate = document.getElementById('isPrivate').checked; const isPrivate = document.getElementById('isPrivate').checked;
@ -407,6 +473,8 @@
const uploadArea = document.getElementById('uploadArea'); const uploadArea = document.getElementById('uploadArea');
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
const progressBar = document.getElementById('progressBar'); const progressBar = document.getElementById('progressBar');
const selectedFiles = document.getElementById('selectedFiles');
let filesToUpload = [];
uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('click', () => fileInput.click());
uploadArea.addEventListener('dragover', (e) => { uploadArea.addEventListener('dragover', (e) => {
@ -424,15 +492,34 @@
fileInput.addEventListener('change', (e) => handleFiles(e.target.files)); fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
function handleFiles(files) { function handleFiles(files) {
for (let file of files) { filesToUpload = Array.from(files);
uploadFile(file); updateSelectedFilesList();
}
} }
function uploadFile(file) { function updateSelectedFilesList() {
selectedFiles.innerHTML = filesToUpload.map(file => `<div>${file.name}</div>`).join('');
}
function uploadSelectedFiles() {
const isPrivate = document.getElementById('fileIsPrivate').checked;
const password = isPrivate ? document.getElementById('filePassword').value : null;
filesToUpload.forEach(file => uploadFile(file, isPrivate, password));
}
function uploadFile(file, isPrivate, password) {
if (isPrivate && (password === 'info' || password === 'download')) {
document.getElementById('fileResult').innerHTML += `Error: Password cannot be 'info' or 'download'<br>`;
return;
}
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
if (isPrivate && password) {
formData.append('password', password);
}
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload/file', true); xhr.open('POST', '/upload/file', true);
@ -448,7 +535,37 @@
if (xhr.status === 200) { if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText); const response = JSON.parse(xhr.responseText);
if (response.success) { if (response.success) {
document.getElementById('fileResult').innerHTML += `File uploaded: <a href="${response.url}" target="_blank">${response.url}</a><br>`; let resultHtml = `
<div class="file-group">
<div class="file-group-header" onclick="toggleFileGroup(this)">
<span></span>
<span>${file.name}</span>
<button class="other-links-btn" onclick="toggleOtherLinks(event, this)">Other Links</button>
</div>
<div class="file-group-content show">
<p>File uploaded: <a href="${response.url}" target="_blank">${response.url}</a></p>
<div class="other-links">
`;
if (isPrivate) {
resultHtml += `
<p>Password-protected link: <a href="${response.url}/${password}" target="_blank">${response.url}/${password}</a></p>
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
<p>Password-protected direct download link: <a href="${response.download_url}/${password}" target="_blank">${response.download_url}/${password}</a></p>
`;
} else {
resultHtml += `
<p>Direct download link: <a href="${response.download_url}" target="_blank">${response.download_url}</a></p>
`;
}
resultHtml += `
</div>
</div>
</div>
`;
document.getElementById('fileResult').innerHTML += resultHtml;
} else { } else {
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`; document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
} }
@ -469,6 +586,26 @@
xhr.send(formData); xhr.send(formData);
} }
function toggleFileGroup(header) {
const content = header.nextElementSibling;
const arrow = header.querySelector('span:first-child');
const filename = header.querySelector('span:nth-child(2)');
content.classList.toggle('show');
if (content.classList.contains('show')) {
arrow.textContent = '▼';
filename.style.display = 'inline';
} else {
arrow.textContent = '▶';
filename.style.display = 'inline';
}
}
function toggleOtherLinks(event, button) {
event.stopPropagation();
const otherLinks = button.closest('.file-group-header').nextElementSibling.querySelector('.other-links');
otherLinks.classList.toggle('show');
}
function shortenUrl() { function shortenUrl() {
const url = document.getElementById('urlInput').value; const url = document.getElementById('urlInput').value;
@ -487,7 +624,7 @@
}) })
.then(data => { .then(data => {
if (data.success) { if (data.success) {
const shortUrl = `${window.location.origin}/${data.short_url}`; const shortUrl = `${window.location.origin}/${data.vanity}`;
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`; document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
} else { } else {
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`; document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;

View File

@ -78,12 +78,9 @@
<div id="errorAlert" class="alert"> <div id="errorAlert" class="alert">
Incorrect password. Please try again. Incorrect password. Please try again.
</div> </div>
<form method="POST" onsubmit="return validatePassword()"> <form method="POST" action="{{ url_for('redirect_vanity', vanity=vanity) }}">
<input type="password" id="password" name="password" placeholder="Enter password" required> <input type="password" name="password" placeholder="Enter password">
<div class="button-container"> <button type="submit">Submit</button>
<button type="button" onclick="window.history.back()">Cancel</button>
<button type="submit">OK</button>
</div>
</form> </form>
</div> </div>

View File

@ -66,6 +66,8 @@
.file-list { .file-list {
list-style-type: none; list-style-type: none;
padding: 0; padding: 0;
position: relative;
z-index: 1;
} }
.file-item { .file-item {
background-color: var(--highlight-bg); background-color: var(--highlight-bg);
@ -75,6 +77,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
position: relative;
z-index: 2;
} }
.file-icon { .file-icon {
margin-right: 10px; margin-right: 10px;
@ -250,7 +254,7 @@
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
border-radius: 4px; border-radius: 4px;
} }
@ -310,11 +314,10 @@
.dropdown-content { .dropdown-content {
display: none; display: none;
position: absolute; position: absolute;
right: 0;
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
} }
@ -398,7 +401,7 @@
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
right: 0; right: 0;
border-radius: 4px; border-radius: 4px;
} }
@ -624,11 +627,10 @@
.dropdown-content { .dropdown-content {
display: none; display: none;
position: absolute; position: absolute;
right: 0;
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
border-radius: 4px; border-radius: 4px;
} }
@ -647,63 +649,6 @@
display: block; display: block;
} }
.file-item, .upload-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
background-color: var(--highlight-bg);
margin-bottom: 5px;
border-radius: 4px;
}
.file-name, .upload-info {
flex-grow: 1;
margin-right: 10px;
word-break: break-all;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropbtn {
background-color: transparent;
color: var(--text-color);
padding: 5px;
font-size: 16px;
border: none;
cursor: pointer;
}
.dropdown-content {
display: none;
position: absolute;
right: 0;
background-color: var(--bg-color);
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
border-radius: 4px;
}
.dropdown-content a {
color: var(--text-color);
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown-content a:hover {
background-color: var(--highlight-bg);
}
.dropdown:hover .dropdown-content {
display: block;
}
/* Ensure the dropdown is always on top */
.file-item, .upload-item { .file-item, .upload-item {
position: relative; position: relative;
} }
@ -725,15 +670,15 @@
.file-item:last-child .dropdown-content, .file-item:last-child .dropdown-content,
.upload-item:last-child .dropdown-content { .upload-item:last-child .dropdown-content {
bottom: 100%; bottom: auto;
top: auto; top: 100%;
} }
/* Media query for smaller screens */ /* Media query for smaller screens */
@media screen and (max-height: 600px) { @media screen and (max-height: 600px) {
.dropdown-content { .dropdown-content {
bottom: 100%; bottom: auto;
top: auto; top: 100%;
} }
} }
@ -773,9 +718,10 @@
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 9999;
border-radius: 4px; border-radius: 4px;
right: 0; right: 0;
margin-top: 2px; /* Add a small gap between button and dropdown */
} }
.dropdown-content a { .dropdown-content a {
@ -793,24 +739,131 @@
display: block; display: block;
} }
/* New styles for adaptive positioning */ /* Ensure the dropdown is always on top */
.file-list, .upload-list { .file-item:hover, .upload-item:hover {
z-index: 1001;
}
.dropdown:hover {
z-index: 1002;
}
/* Styles for the collapsible file groups */
.file-group {
margin-bottom: 10px;
border: 1px solid var(--highlight-border);
border-radius: 4px;
overflow: hidden;
}
.file-group-header {
display: flex;
align-items: center;
background-color: var(--highlight-bg);
padding: 10px;
cursor: pointer;
}
.file-group-arrow {
margin-right: 10px;
font-size: 16px;
transition: transform 0.3s;
}
.file-group-arrow.collapsed {
transform: rotate(-90deg);
}
.file-group-content.expanded {
display: block;
}
.file-group-links {
margin-top: 10px;
}
.file-group-link {
display: block;
margin-bottom: 5px;
}
.file-group-link a {
color: var(--text-color);
text-decoration: none;
}
.file-group-link a:hover {
text-decoration: underline;
}
.file-list {
position: relative; position: relative;
z-index: 1;
}
.file-item {
position: relative;
z-index: 2;
}
.dropdown {
position: relative;
z-index: 1000; /* Increased z-index */
}
.dropbtn {
background-color: transparent;
color: var(--text-color);
padding: 5px;
font-size: 16px;
border: none;
cursor: pointer;
} }
.dropdown-content { .dropdown-content {
bottom: auto; display: none;
position: absolute;
right: 0;
top: 100%; top: 100%;
background-color: var(--bg-color);
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1001; /* Increased z-index */
border-radius: 4px;
overflow: visible; /* Changed from hidden to visible */
}
.dropdown-content a {
color: var(--text-color);
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown-content a:hover {
background-color: var(--highlight-bg);
} }
.dropdown:hover .dropdown-content { .dropdown:hover .dropdown-content {
display: block; display: block;
} }
/* JavaScript will add this class when needed */ /* Ensure the dropdown is always on top */
.dropdown-content.above { .file-item:hover,
bottom: 100%; .upload-item:hover {
top: auto; z-index: 1002; /* Increased z-index */
}
.dropdown:hover {
z-index: 1003; /* Increased z-index */
}
/* Remove any max-height or overflow properties on parent containers */
.tab-content,
.file-list,
.upload-list {
overflow: visible;
max-height: none;
} }
</style> </style>
</head> </head>
@ -920,12 +973,10 @@
<a href="{{ url_for('edit_content', vanity=upload.vanity) }}">Edit</a> <a href="{{ url_for('edit_content', vanity=upload.vanity) }}">Edit</a>
{% endif %} {% endif %}
<a href="#" onclick="deleteUpload('{{ upload.vanity }}')">Delete</a> <a href="#" onclick="deleteUpload('{{ upload.vanity }}')">Delete</a>
{% if upload.type == 'pastebin' %} {% if upload.is_private %}
{% if upload.is_private %} <a href="#" onclick="openEditPasswordModal('{{ upload.vanity }}')">Edit Password</a>
<a href="#" onclick="openEditPasswordModal('{{ upload.vanity }}')">Edit Password</a> {% else %}
{% else %} <a href="#" onclick="openAddPasswordModal('{{ upload.vanity }}')">Add Password</a>
<a href="#" onclick="openAddPasswordModal('{{ upload.vanity }}')">Add Password</a>
{% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -971,7 +1022,7 @@
</div> </div>
</div> </div>
<div id="editPasswordModal" class="modal"> <div id="passwordModal" class="modal">
<div class="modal-content"> <div class="modal-content">
<h3 id="passwordModalTitle">Edit Password</h3> <h3 id="passwordModalTitle">Edit Password</h3>
<input type="password" id="newPassword1" placeholder="Enter new password"> <input type="password" id="newPassword1" placeholder="Enter new password">
@ -1096,8 +1147,7 @@
margin: 0 5px; margin: 0 5px;
border: none; border: none;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 14px; /* Increased width */
min-width: 120px; /* Increased width */
text-align: center; text-align: center;
} }
@ -1123,13 +1173,14 @@
.dropdown-content { .dropdown-content {
display: none; display: none;
position: absolute; position: absolute;
right: 0;
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
right: 0;
top: 100%;
} }
.dropdown-content a { .dropdown-content a {
@ -1212,7 +1263,7 @@
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
right: 0; right: 0;
border-radius: 4px; border-radius: 4px;
} }
@ -1402,7 +1453,7 @@
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
border-radius: 4px; border-radius: 4px;
} }
@ -1458,7 +1509,7 @@
background-color: var(--bg-color); background-color: var(--bg-color);
min-width: 160px; min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1; z-index: 1000;
border-radius: 4px; border-radius: 4px;
} }
@ -1478,37 +1529,65 @@
} }
/* Ensure the dropdown is always on top */ /* Ensure the dropdown is always on top */
.file-item, .upload-item { .file-item:hover, .upload-item:hover {
position: relative; z-index: 1001;
} }
.dropdown { .dropdown:hover {
position: static; z-index: 1002;
} }
.dropdown-content { /* Styles for the collapsible file groups */
position: absolute; .file-group {
top: 100%; margin-bottom: 10px;
right: 0; border: 1px solid var(--highlight-border);
border-radius: 4px;
overflow: hidden;
} }
/* New styles for adaptive positioning */ .file-group-header {
.file-list, .upload-list { display: flex;
position: relative; align-items: center;
background-color: var(--highlight-bg);
padding: 10px;
cursor: pointer;
} }
.file-item:last-child .dropdown-content, .file-group-arrow {
.upload-item:last-child .dropdown-content { margin-right: 10px;
bottom: 100%; font-size: 16px;
top: auto; transition: transform 0.3s;
} }
/* Media query for smaller screens */ .file-group-arrow.collapsed {
@media screen and (max-height: 600px) { transform: rotate(-90deg);
.dropdown-content { }
bottom: 100%;
top: auto; .file-group-content {
} display: none;
padding: 10px;
}
.file-group-content.expanded {
display: block;
}
.file-group-links {
margin-top: 10px;
}
.file-group-link {
display: block;
margin-bottom: 5px;
}
.file-group-link a {
color: var(--text-color);
text-decoration: none;
}
.file-group-link a:hover {
text-decoration: underline;
} }
</style> </style>
@ -1896,18 +1975,18 @@
currentVanity = vanity; currentVanity = vanity;
document.getElementById('passwordModalTitle').textContent = 'Edit Password'; document.getElementById('passwordModalTitle').textContent = 'Edit Password';
document.getElementById('removePasswordBtn').style.display = 'inline-block'; document.getElementById('removePasswordBtn').style.display = 'inline-block';
document.getElementById('editPasswordModal').style.display = 'block'; document.getElementById('passwordModal').style.display = 'block';
} }
function openAddPasswordModal(vanity) { function openAddPasswordModal(vanity) {
currentVanity = vanity; currentVanity = vanity;
document.getElementById('passwordModalTitle').textContent = 'Add Password'; document.getElementById('passwordModalTitle').textContent = 'Add Password';
document.getElementById('removePasswordBtn').style.display = 'none'; document.getElementById('removePasswordBtn').style.display = 'none';
document.getElementById('editPasswordModal').style.display = 'block'; document.getElementById('passwordModal').style.display = 'block';
} }
function closePasswordModal() { function closePasswordModal() {
document.getElementById('editPasswordModal').style.display = 'none'; document.getElementById('passwordModal').style.display = 'none';
document.getElementById('newPassword1').value = ''; document.getElementById('newPassword1').value = '';
document.getElementById('newPassword2').value = ''; document.getElementById('newPassword2').value = '';
} }
@ -2026,19 +2105,31 @@
xhr.onload = function() { xhr.onload = function() {
if (xhr.status === 200) { if (xhr.status === 200) {
const response = JSON.parse(xhr.responseText); try {
if (response.success) { const response = JSON.parse(xhr.responseText);
if (response.success) {
document.getElementById('fileResult').innerHTML += `File uploaded: ${file.name}<br>`;
addFileToList(file.name);
} else {
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
}
} catch (error) {
console.error('Error parsing JSON:', error);
document.getElementById('fileResult').innerHTML += `File uploaded: ${file.name}<br>`; document.getElementById('fileResult').innerHTML += `File uploaded: ${file.name}<br>`;
// Add the new file to the file list
addFileToList(file.name); addFileToList(file.name);
progressBar.style.width = '0%';
progressBar.textContent = '';
} else {
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
} }
} else { } else {
document.getElementById('fileResult').innerHTML += `Error: ${xhr.statusText}<br>`; document.getElementById('fileResult').innerHTML += `Error: ${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); xhr.send(formData);
@ -2082,16 +2173,17 @@
const dropdowns = document.querySelectorAll('.dropdown'); const dropdowns = document.querySelectorAll('.dropdown');
dropdowns.forEach(dropdown => { dropdowns.forEach(dropdown => {
const dropbtn = dropdown.querySelector('.dropbtn');
const dropdownContent = dropdown.querySelector('.dropdown-content'); const dropdownContent = dropdown.querySelector('.dropdown-content');
dropdown.addEventListener('mouseenter', () => { dropbtn.addEventListener('click', (e) => {
const rect = dropdownContent.getBoundingClientRect(); e.stopPropagation();
const spaceBelow = window.innerHeight - rect.bottom; dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block';
});
if (spaceBelow < 10) { // Not enough space below document.addEventListener('click', (e) => {
dropdownContent.classList.add('above'); if (!dropdown.contains(e.target)) {
} else { dropdownContent.style.display = 'none';
dropdownContent.classList.remove('above');
} }
}); });
}); });