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
165
app.py
165
app.py
@ -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)
|
||||
|
@ -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,14 +492,33 @@
|
||||
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}`;
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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;
|
||||
|
||||
if (spaceBelow < 10) { // Not enough space below
|
||||
dropdownContent.classList.add('above');
|
||||
} else {
|
||||
dropdownContent.classList.remove('above');
|
||||
dropbtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
dropdownContent.style.display = dropdownContent.style.display === 'block' ? 'none' : 'block';
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdown.contains(e.target)) {
|
||||
dropdownContent.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user