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)
@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()
cursor = db.cursor()
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, 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}")
print(f"User ID: {user_id}, Username: {username}")
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")
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':
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):
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:
return "File not found", 404
elif content_type == 'pastebin':
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 render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
return "Not found", 404
@ -295,17 +293,33 @@ def render_pastebin(content_data, created_at, user_id, username, vanity, is_priv
vanity=vanity,
is_private=is_private)
@app.route('/<vanity>/raw')
def raw_vanity(vanity):
@app.route('/<vanity>/raw', methods=['GET', 'POST'])
@app.route('/<vanity>/raw/<password>', methods=['GET'])
def raw_vanity(vanity, password=None):
db = get_db()
cursor = db.cursor()
cursor.execute("SELECT * FROM content WHERE vanity = ? AND type = 'pastebin'", (vanity,))
target = cursor.fetchone()
content = cursor.fetchone()
if target:
return target[2], 200, {'Content-Type': 'text/plain; charset=utf-8'}
if content:
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
# Replace the LoginForm and RegistrationForm classes with simple classes
class LoginForm:
def __init__(self, username, password, remember):
@ -445,19 +459,34 @@ def toggle_index(username):
@login_required
def upload_user_file(username):
if current_user.username != username:
return "Unauthorized", 401
return jsonify({"success": False, "error": "Unauthorized"}), 401
subpath = request.form.get('subpath', '').rstrip('/')
if 'file' not in request.files:
return 'No file part', 400
return jsonify({"success": False, "error": 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return 'No selected file', 400
return jsonify({"success": False, "error": 'No selected file'}), 400
if file:
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], username, subpath, filename)
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'])
@login_required
@ -639,8 +668,8 @@ def shorten_url():
(vanity, 'url', long_url, datetime.now(), user_id))
db.commit()
short_url = f"{request.host_url}{vanity}"
return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200
# Return only the vanity code, not the full URL
return jsonify({'success': True, 'vanity': vanity}), 200
except Exception as e:
print("Exception occurred:", str(e))
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
@app.route('/edit_password/<vanity>', methods=['GET', 'POST'])
@app.route('/edit_password/<vanity>', methods=['POST'])
@login_required
def edit_password(vanity):
db = get_db()
@ -687,21 +716,20 @@ def edit_password(vanity):
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'})
data = request.get_json()
action = data.get('action')
if action == 'update':
new_password = data.get('new_password')
if not is_valid_password(new_password):
return jsonify({'success': False, 'error': 'Invalid password'}), 400
cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity))
elif action == 'remove':
cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,))
else:
return jsonify({'success': False, 'error': 'Invalid action'}), 400
return render_template('edit_password.html', vanity=vanity)
db.commit()
return jsonify({'success': True})
@app.route('/delete/content/<vanity>', methods=['POST'])
@login_required
@ -725,7 +753,7 @@ def delete_content(vanity):
return jsonify({'success': True}), 200
@app.route('/<vanity>/info')
@app.route('/<vanity>/info', methods=['GET', 'POST'])
def content_info(vanity):
db = get_db()
cursor = db.cursor()
@ -733,7 +761,15 @@ def content_info(vanity):
content = cursor.fetchone()
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)
@ -753,7 +789,8 @@ def content_info(vanity):
'created_at': created_at,
'username': username,
'file_size': file_size,
'is_media': is_media
'is_media': is_media,
'is_private': is_private
}
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
password = request.form.get('password')
is_private = 1 if password else 0
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))
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, is_private, password))
db.commit()
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)
return jsonify({
'success': True,
'vanity': vanity_with_extension,
'url': short_url,
'download_url': download_url,
'deletion_url': deletion_url,
'filename': new_filename
})
@ -1033,6 +1077,11 @@ def upload_file():
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__':
# Start the cleanup thread
cleanup_thread = threading.Thread(target=delete_old_files)

View File

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

View File

@ -78,12 +78,9 @@
<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 method="POST" action="{{ url_for('redirect_vanity', vanity=vanity) }}">
<input type="password" name="password" placeholder="Enter password">
<button type="submit">Submit</button>
</form>
</div>

View File

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