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:
parent
3edd810c36
commit
c76ff11c26
145
app.py
145
app.py
@ -230,46 +230,44 @@ 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):
|
||||||
|
if 'download' in request.path:
|
||||||
|
return send_file(file_path, as_attachment=True)
|
||||||
|
else:
|
||||||
return send_file(file_path)
|
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:
|
|
||||||
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
|
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):
|
||||||
|
return jsonify({'success': False, 'error': 'Invalid password'}), 400
|
||||||
cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity))
|
cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity))
|
||||||
db.commit()
|
|
||||||
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:
|
||||||
|
return jsonify({'success': False, 'error': 'Invalid action'}), 400
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return jsonify({'success': True})
|
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'])
|
@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)
|
||||||
|
@ -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 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFile(file) {
|
|
||||||
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}`;
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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,13 +973,11 @@
|
|||||||
<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>
|
||||||
</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) {
|
||||||
|
try {
|
||||||
const response = JSON.parse(xhr.responseText);
|
const response = JSON.parse(xhr.responseText);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
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 {
|
} else {
|
||||||
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
|
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>`;
|
||||||
|
addFileToList(file.name);
|
||||||
|
}
|
||||||
} 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');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user