clearance

This commit is contained in:
teraflops 2025-06-02 23:26:33 +02:00
parent 17bb747542
commit e46dec38ca
Signed by: teraflops
GPG Key ID: 2B77D97AF6F8968C
5 changed files with 0 additions and 1004 deletions

View File

@ -1,110 +0,0 @@
{% extends "base.html" %}
{% block title %}Binary file viewer{% endblock %}
{% block content %}
<div class="container mt-4">
<div class="card shadow-sm">
<div class="card-body">
<h1 class="mb-3">Viewing File: {{ filename }}</h1>
<p class="text-muted">Tipo MIME: {{ mime_type }}</p>
<div class="row mb-4">
<div class="col-md-6">
<h4>File Info</h4>
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>File Name:</strong> {{ filename }}</li>
<li class="list-group-item"><strong>Size:</strong> {{ size }} bytes</li>
<li class="list-group-item"><strong>Uploaded by:</strong> {{ owner.username }}</li>
<li class="list-group-item"><strong>Upload Date:</strong> {{ paste.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</li>
</ul>
</div>
<div class="col-md-6">
<h4>Acciones</h4>
<div class="d-flex flex-column">
<a href="{{ url_for('download_paste', id=paste.id) }}" class="btn btn-success mb-2">
<i class="fas fa-download me-2"></i> Descargar Archivo
</a>
{% if 'admin' in session %}
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal" aria-label="Delete Paste">
<i class="fas fa-trash-alt me-2"></i> Delete Paste
</button>
{% endif %}
</div>
</div>
</div>
<div class="card">
<div class="card-body text-center">
<h5 class="card-title">Download from smartphone</h5>
<p class="card-text">Scan this code from your mobile device:</p>
<div id="qrcode" class="my-3 mx-auto" style="width:200px; height:200px;"></div>
<button id="download-qr" class="btn btn-outline-primary mt-2">
<i class="fas fa-download me-2"></i> Download QR
</button>
</div>
</div>
</div>
</div>
</div>
{% if 'admin' in session %}
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form action="{{ url_for('admin_delete_paste', paste_id=paste.id) }}" method="POST">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Confirmar Eliminación</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
¿Are you sure? This action cannot be undone.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger">Delete</button>
</div>
</div>
</form>
</div>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', () => {
const pasteId = "{{ paste.id }}";
const downloadUrl = "{{ url_for('download_paste', id=paste.id, _external=True) }}";
const qrCodeContainer = document.getElementById('qrcode');
new QRCode(qrCodeContainer, {
text: downloadUrl,
width: 200,
height: 200,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
const downloadQrButton = document.getElementById('download-qr');
downloadQrButton.addEventListener('click', () => {
const canvas = qrCodeContainer.querySelector('canvas');
if (canvas) {
const pngUrl = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
const downloadLink = document.createElement('a');
downloadLink.href = pngUrl;
downloadLink.download = `qr_code_paste_${pasteId}.png`;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
alert('QR descargado exitosamente!');
} else {
alert('Error al generar el código QR.');
}
});
});
</script>
{% endblock %}

View File

@ -1,73 +0,0 @@
{% extends "base.html" %}
{% block title %}Media Viewer{% endblock %}
{% block content %}
<div class="container mt-4">
<h1 class="text-center">Viewing Media: {{ filename }}</h1>
<p class="text-muted text-center">MIME Type: {{ mime_type }}</p>
<div class="media-container text-center">
{% if mime_type.startswith("image/") %}
<img src="{{ url_for('get_file', filename=filename) }}" alt="Image" class="img-fluid" />
{% elif mime_type.startswith("video/") %}
<video controls class="w-100">
<source src="{{ url_for('get_file', filename=filename) }}" type="{{ mime_type }}">
Your browser does not support the video tag.
</video>
{% elif mime_type.startswith("audio/") %}
<audio controls class="w-100">
<source src="{{ url_for('get_file', filename=filename) }}" type="{{ mime_type }}">
Your browser does not support the audio element.
</audio>
{% elif mime_type == "application/pdf" %}
<embed src="{{ url_for('get_file', filename=filename) }}" type="{{ mime_type }}" width="100%" height="600px">
{% endif %}
</div>
<div class="d-flex justify-content-center mt-4">
<button class="btn btn-primary me-2" onclick="toggleMetadata()">Toggle Metadata</button>
<a href="{{ url_for('download_paste', id=paste_id) }}" class="btn btn-success">📥 Download</a>
</div>
<div class="metadata mt-4">
<div id="metadata-content" class="metadata-content" style="display: none;">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% if metadata %}
{% for track in metadata %}
{% for key, value in track.items() %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
{% endfor %}
{% else %}
<tr>
<td colspan="2">No metadata available for this file.</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<script>
function toggleMetadata() {
const content = document.getElementById("metadata-content");
if (content.style.display === "none") {
content.style.display = "block";
} else {
content.style.display = "none";
}
}
</script>
{% endblock %}

View File

@ -1,139 +0,0 @@
{% extends "base.html" %}
{% block title %}Media Viewer{% endblock %}
{% block content %}
<div class="container mt-4">
<h1 class="text-center">Viewing Media: {{ filename }}</h1>
<p class="text-muted text-center">MIME Type: {{ mime_type }}</p>
<div class="media-container text-center">
{% if mime_type.startswith("image/") %}
<img src="{{ url_for('get_file', filename=filename) }}" alt="Image" class="img-fluid" />
{% elif mime_type.startswith("video/") %}
<video controls class="w-100">
<source src="{{ url_for('get_file', filename=filename) }}" type="{{ mime_type }}">
Your browser does not support the video tag.
</video>
{% elif mime_type.startswith("audio/") %}
<audio controls class="w-100">
<source src="{{ url_for('get_file', filename=filename) }}" type="{{ mime_type }}">
Your browser does not support the audio element.
</audio>
{% elif mime_type == "application/pdf" %}
<embed src="{{ url_for('get_file', filename=filename) }}" type="{{ mime_type }}" width="100%" height="600px">
{% endif %}
</div>
<div class="d-flex justify-content-center mt-4">
<button class="btn btn-primary me-2" onclick="toggleMetadata()">Toggle Metadata</button>
<a href="{{ url_for('download_paste', id=paste_id) }}" class="btn btn-primary me-2">📥 Download</a>
<a href="{{ url_for('get_file', filename=filename) }}" class="btn btn-primary me-2" target="_blank">🌐 View Raw</a>
{% if current_user.is_authenticated %}
<!-- Botón de favoritos, con clase "btn btn-sm btn-primary" para mantener la consistencia -->
<button id="favorite-button" class="btn btn-sm btn-primary">
❤️ Add to Favorites
</button>
{% endif %}
</div>
<div class="metadata mt-4">
<div id="metadata-content" class="metadata-content" style="display: none;">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Property</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% if metadata %}
{% for track in metadata %}
{% for key, value in track.items() %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
{% endfor %}
{% else %}
<tr>
<td colspan="2">No metadata available for this file.</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Contenedor para el Toast (puede ir en base.html o aquí) -->
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 1100">
<div id="liveToast" class="toast align-items-center text-white bg-primary border-0" role="alert" aria-live="assertive" aria-atomic="true" style="display: none;">
<div class="d-flex">
<div class="toast-body">
<!-- Mensaje dinámico -->
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
<script>
function toggleMetadata() {
const content = document.getElementById("metadata-content");
if (content.style.display === "none") {
content.style.display = "block";
} else {
content.style.display = "none";
}
}
// Solo configuramos el listener si el usuario está autenticado
{% if current_user.is_authenticated %}
document.addEventListener('DOMContentLoaded', () => {
const favoriteButton = document.getElementById('favorite-button');
if (favoriteButton) {
favoriteButton.addEventListener('click', function() {
fetch(`/paste/{{ paste_id }}/favorite`, {
method: 'POST',
headers: {
'Authorization': 'Bearer {{ token }}'
}
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to toggle favorite');
}
return response.json();
})
.then(data => {
const message = data.message || 'Action completed!';
showToast(message, 'bg-success');
})
.catch(error => {
console.error('Error:', error);
showToast('Error adding to favorites.', 'bg-danger');
});
});
}
});
// Función para mostrar el toast
function showToast(message, bgColor = 'bg-primary') {
const toastElement = document.getElementById('liveToast');
const toastBody = toastElement.querySelector('.toast-body');
toastBody.textContent = message;
toastElement.className = `toast align-items-center text-white ${bgColor} border-0`;
toastElement.style.display = 'block'; // Mostrar el contenedor
const toast = new bootstrap.Toast(toastElement);
toast.show();
}
{% endif %}
</script>
{% endblock %}
{% block footer %}
{{ super() }} <!-- Esto conserva el contenido original del footer definido en base.html -->
{% endblock %}

View File

@ -1,198 +0,0 @@
{% extends "base.html" %}
{% block title %}Paste {{ paste.id }}{% endblock %}
{% block content %}
<div class="container mt-4">
{% if paste.owner_id == current_user.id %}
<div class="modal fade" id="shareModal" tabindex="-1" aria-labelledby="shareModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Share Paste</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<input type="text" id="share-username" placeholder="Username" class="form-control">
<div id="user-suggestions" class="list-group position-absolute d-none" style="z-index: 1050;"></div>
</div>
<div class="form-check">
<input type="checkbox" id="share-can-edit" class="form-check-input" checked>
<label class="form-check-label" for="share-can-edit">Allow Edit</label>
</div>
</div>
<div class="modal-footer">
<button type="button" id="share-submit" class="btn btn-success">Share Paste</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
<h1 class="mb-0">Paste {{ paste.id }}</h1>
<div class="d-flex flex-wrap gap-2">
{% if paste.owner_id == current_user.id %}
<button id="share-button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#shareModal" title="Share Paste">
<i class="fas fa-share-alt"></i>
</button>
{% endif %}
{% if current_user.is_authenticated %}
<button id="favorite-button" class="btn btn-sm btn-secondary" title="Toggle Favorite">
<i class="fas fa-heart"></i>
</button>
{% endif %}
{% if can_edit %}
<a id="edit-button" href="{{ url_for('edit_paste_web', id=paste.id) }}" class="btn btn-sm btn-warning" title="Edit Paste">
<i class="fas fa-edit"></i>
</a>
{% endif %}
<button id="copy-url-button" class="btn btn-sm btn-secondary" title="Copy URL">
<i class="fas fa-link"></i>
</button>
</div>
</div>
<!-- 🔥 Botón de IA fuera del grupo de botones -->
<button id="ai-generate-code" class="btn btn-sm btn-outline-primary magic-btn" title="AI Suggestions">
</button>
<!-- Código Mejorado por IA -->
<div id="generated-code-container" class="mt-3 d-none">
<h5 class="text-primary">💻 AI-Enhanced Code:</h5>
<div id="generated-code-box">
<pre id="generated-code"></pre>
</div>
</div>
<style>
/* Botón IA como icono */
.magic-btn {
font-size: 18px;
padding: 6px 10px;
border-radius: 50%;
transition: background 0.2s ease-in-out;
}
.magic-btn:hover {
background-color: #007bff;
color: white;
}
/* Contenedor de código */
#generated-code-box {
background-color: #1e1e1e;
color: #c9d1d9;
font-family: "Fira Code", monospace;
font-size: 14px;
border: 1px solid #444;
padding: 10px;
max-height: 300px;
overflow-y: auto;
border-radius: 8px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2);
white-space: pre-wrap;
word-wrap: break-word;
}
</style>
<p class="text-muted small">
{% if paste.filename and '.' in paste.filename %}
<strong>Extension:</strong> {{ paste.filename.split('.')[-1] }} |
{% endif %}
<strong>MIME Type:</strong> {{ paste.content_type }} |
<strong>Language:</strong> {{ paste.language }} |
<strong>Size:</strong>
{% if paste.size < 1024 %}
{{ paste.size }} bytes
{% elif paste.size < 1024 * 1024 %}
{{ '%.2f' | format(paste.size / 1024) }} KB
{% else %}
{{ '%.2f' | format(paste.size / (1024 * 1024)) }} MB
{% endif %}
</p>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2 flex-wrap">
<div class="mb-2">
{% if not is_markdown %}
<button id="copy-button" class="btn btn-sm btn-secondary me-2" title="Copy Content">
<i class="fas fa-clipboard"></i>
</button>
{% endif %}
<a class="btn btn-sm btn-primary" href="{{ url_for('download_paste', id=paste.id) }}" download title="Download Paste">
<i class="fas fa-download"></i>
</a>
</div>
<div>
<label for="pygments-style" class="form-label me-2 mb-0">Style:</label>
<select id="pygments-style" class="form-select form-select-sm style-selector d-inline-block w-auto">
{% for style in pygments_styles %}
<option value="{{ style }}" {% if style == default_pygments_style %}selected{% endif %}>{{ style.replace('-', ' ').capitalize() }}</option>
{% endfor %}
</select>
</div>
</div>
{% if is_markdown %}
<div class="markdown-body">{{ md_html_code|safe }}</div>
{% else %}
<div class="highlight"><code class="language-{{ paste.language|lower }}">{{ html_code|safe }}</code></div>
{% endif %}
<p class="mt-3">View the raw version <a href="{{ url_for('get_paste_raw', id=paste.id) }}">here</a>.</p>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script src="{{ url_for('static', filename='js/share-paste.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/share-autocomplete.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/copy-paste.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/pygments-style.js') }}" defer></script>
<script src="{{ url_for('static', filename='js/unshare.js') }}" defer></script>
<script>
document.getElementById("ai-generate-code").addEventListener("click", function () {
document.getElementById("generated-code").textContent = ""; // Limpiar salida previa
document.getElementById("generated-code-container").classList.remove("d-none");
fetch("{{ url_for('generate_code_from_paste', id=paste.id) }}")
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let text = "";
const outputElement = document.getElementById("generated-code");
function readChunk() {
reader.read().then(({ done, value }) => {
if (done) {
return;
}
let chunkText = decoder.decode(value, { stream: true });
// Limpiar texto si contiene estructura JSON
chunkText = chunkText.replace(/data: \{"id":.*?"choices":\[\{"delta":\{"content":"(.*?)"\}\}\]\}/g, "$1");
text += chunkText;
outputElement.textContent = text;
outputElement.scrollTop = outputElement.scrollHeight; // Hacer scroll automático
readChunk(); // Continuar leyendo los datos
});
}
readChunk();
})
.catch(error => alert("Error generando código: " + error));
});
</script>
{% endblock %}

View File

@ -1,484 +0,0 @@
{% extends "base.html" %}
{% block title %}Paste {{ paste.id }}{% endblock %}
{% block content %}
<div class="container mt-4">
<!-- Encabezado del Paste -->
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
<h1 class="mb-0">Paste {{ paste.id }}</h1>
<!-- Barra de botones -->
<div class="d-flex flex-wrap gap-2">
{% if paste.owner_id == current_user.id %}
<!-- Botón para compartir -->
<button id="share-button" class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#shareModal">
Share
</button>
{% endif %}
{% if current_user.is_authenticated %}
<!-- Botón de favoritos -->
<button id="favorite-button" class="btn btn-sm btn-secondary">
❤️ {% if paste in current_user.favorite_pastes %}Remove from Favorites{% else %}Add to Favorites{% endif %}
</button>
{% endif %}
<!-- Botón para editar (solo si se puede editar) -->
{% if can_edit %}
<a id="edit-button" href="{{ url_for('edit_paste_web', id=paste.id) }}" class="btn btn-sm btn-warning">
Edit Paste
</a>
{% endif %}
</div>
</div>
<!-- Modal de Compartir Paste (solo si eres el propietario) -->
<!-- Modal de Compartir Paste (solo si eres el propietario) -->
{% if paste.owner_id == current_user.id %}
<div class="modal fade" id="shareModal" tabindex="-1" aria-labelledby="shareModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Share Paste</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3 position-relative">
<input type="text"
id="share-username"
placeholder="Username"
class="form-control mb-3"
autocomplete="off">
<div id="user-suggestions" class="list-group position-absolute d-none" style="z-index: 1050;"></div>
</div>
<div class="form-check">
<input type="checkbox" id="share-can-edit" class="form-check-input" checked>
<label class="form-check-label" for="share-can-edit">Allow Edit</label>
</div>
</div>
<div class="modal-footer">
<button type="button" id="share-submit" class="btn btn-success">Share Paste</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Sección principal: contenido del Paste -->
<div class="mb-3">
<!-- Botones de copiar y descargar -->
<div class="d-flex justify-content-between align-items-center mb-2 flex-wrap">
<div class="mb-2">
{% if not is_markdown %}
<button id="copy-button" class="btn btn-sm btn-secondary me-2">📋 Copy</button>
{% endif %}
<a class="btn btn-sm btn-primary" href="{{ url_for('download_paste', id=paste.id) }}" download>
📥 Download
</a>
</div>
<!-- Desplegable de Estilos de Pygments -->
<div>
<label for="pygments-style" class="form-label me-2 mb-0">Style:</label>
<select id="pygments-style" class="form-select form-select-sm style-selector d-inline-block w-auto">
{% for style in pygments_styles %}
<option value="{{ style }}"
{% if style == default_pygments_style %}selected{% endif %}>
{{ style.replace('-', ' ').capitalize() }}
</option>
{% endfor %}
</select>
</div>
</div>
<!-- Mostrar contenido según Markdown o texto plano -->
{% if is_markdown %}
<div class="markdown-body">
{{ md_html_code|safe }}
</div>
{% else %}
<div class="highlight">
<code class="language-{{ paste.language|lower }}">{{ html_code|safe }}</code>
</div>
{% endif %}
<p class="mt-3">
View the raw version <a href="{{ url_for('get_paste_raw', id=paste.id) }}">here</a>.
</p>
</div>
<!-- Botón para ver la lista de usuarios con acceso (oculto por defecto en un collapse) -->
{% if current_user.is_authenticated %}
<button
class="btn btn-sm btn-info mb-2"
type="button"
data-bs-toggle="collapse"
data-bs-target="#sharedWithCollapse"
aria-expanded="false"
aria-controls="sharedWithCollapse">
See users with access
</button>
<div class="collapse" id="sharedWithCollapse">
<div class="card card-body">
<h5>Shared With:</h5>
<ul class="list-group">
<ul class="list-group">
{% for user, can_edit in shared_with %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>{{ user }}</span>
<span>
{% if can_edit %}
<span class="badge bg-success">Can Edit</span>
{% else %}
<span class="badge bg-secondary">Read Only</span>
{% endif %}
<!-- Botón “Unshare” que abrirá el modal -->
<button class="btn btn-sm btn-danger ms-2"
onclick="showUnshareModal('{{ paste.id }}', '{{ user }}')">
Unshare
</button>
</span>
</li>
{% endfor %}
</ul>
<!-- Modal de confirmación (oculto por defecto) -->
<div class="modal fade" id="unshareModal" tabindex="-1" aria-labelledby="unshareModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="unshareModalLabel">Confirm Unshare</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Texto dinámico que pondremos por JS -->
<p id="unshareModalMessage"></p>
</div>
<div class="modal-footer">
<!-- Botón de confirmación -->
<button type="button" id="confirmUnshareBtn" class="btn btn-danger">Yes, Unshare</button>
<!-- Botón para cancelar/cerrar -->
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</div>
</div>
<script>
// Variables globales para guardar pasteId y username a descompartir
let unsharePasteId = null;
let unshareUsername = null;
function showUnshareModal(pasteId, username) {
unsharePasteId = pasteId;
unshareUsername = username;
// Cambiar el texto que aparece en el modal
const messageElem = document.getElementById('unshareModalMessage');
messageElem.textContent = `Are you sure you want to remove access for "${username}"?`;
// Mostrar el modal
const modalElement = document.getElementById('unshareModal');
const modalInstance = new bootstrap.Modal(modalElement);
modalInstance.show();
}
// Botón “Yes, Unshare” dentro del modal
document.getElementById('confirmUnshareBtn').addEventListener('click', function () {
fetch(`/paste/${unsharePasteId}/unshare`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "username": unshareUsername })
})
.then(response => response.json())
.then(data => {
// Cerrar el modal
const modalElement = document.getElementById('unshareModal');
const modalInstance = bootstrap.Modal.getInstance(modalElement);
modalInstance.hide();
// Mostrar resultado en un toast
if (data.message) {
showToast(data.message, 'bg-success');
} else if (data.error) {
showToast(data.error, 'bg-danger');
}
// Recargar la página o eliminar el <li> para ese usuario
location.reload();
})
.catch(err => {
console.error(err);
showToast('Error while unsharing paste.', 'bg-danger');
});
});
</script>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function () {
// Funcionalidad de Compartir
const shareUsernameInput = document.getElementById('share-username');
const suggestionsBox = document.getElementById('user-suggestions');
const shareSubmitButton = document.getElementById('share-submit');
if (shareUsernameInput && suggestionsBox && shareSubmitButton) {
// Función para buscar usuarios
async function fetchUsers(query) {
try {
const response = await fetch(`/api/users/search?q=${encodeURIComponent(query)}`);
const users = await response.json();
return users;
} catch (error) {
console.error('Error fetching users:', error);
return [];
}
}
// Mostrar sugerencias
function showSuggestions(users) {
suggestionsBox.innerHTML = '';
if (users.length === 0) {
suggestionsBox.classList.add('d-none');
return;
}
users.forEach(user => {
const item = document.createElement('button');
item.type = 'button';
item.classList.add('list-group-item', 'list-group-item-action');
item.textContent = user.username;
item.addEventListener('click', () => {
shareUsernameInput.value = user.username;
suggestionsBox.classList.add('d-none');
});
suggestionsBox.appendChild(item);
});
suggestionsBox.classList.remove('d-none');
}
// Evento de entrada
shareUsernameInput.addEventListener('input', async function () {
const query = this.value.trim();
if (query.length >= 2) { // Solo buscar si hay al menos 2 caracteres
const users = await fetchUsers(query);
showSuggestions(users);
} else {
suggestionsBox.classList.add('d-none');
}
});
// Ocultar sugerencias si se hace clic fuera del input
document.addEventListener('click', function (event) {
if (!shareUsernameInput.contains(event.target) && !suggestionsBox.contains(event.target)) {
suggestionsBox.classList.add('d-none');
}
});
// Event listener para el botón de compartir
shareSubmitButton.addEventListener('click', () => {
const username = shareUsernameInput.value.trim();
const canEdit = document.getElementById('share-can-edit').checked;
if (!username) {
showToast('Por favor, ingresa un nombre de usuario.', 'bg-warning');
return;
}
fetch(`/paste/{{ paste.id }}/share`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ username, can_edit: canEdit })
})
.then(response => response.json())
.then(data => {
if (data.message) {
showToast(data.message, 'bg-success');
const shareModal = bootstrap.Modal.getInstance(document.getElementById('shareModal'));
shareModal?.hide();
// Limpia campos
shareUsernameInput.value = '';
document.getElementById('share-can-edit').checked = true;
} else if (data.error) {
showToast(data.error, 'bg-danger');
}
})
.catch(error => {
console.error('Error sharing paste:', error);
showToast('Error sharing paste.', 'bg-danger');
});
});
}
// Funcionalidad de Favoritos
const favoriteButton = document.getElementById('favorite-button');
if (favoriteButton) {
favoriteButton.addEventListener('click', function () {
const action = "{{ 'unfavorite' if paste in current_user.favorite_pastes else 'favorite' }}";
const url = `/paste/{{ paste.id }}/${action}`;
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(response => response.json())
.then(data => {
if (data.message.includes('added')) {
favoriteButton.innerHTML = '❤️ Remove from Favorites';
} else {
favoriteButton.innerHTML = '❤️ Add to Favorites';
}
if (data.success) {
showToast(data.message, 'bg-success');
} else {
showToast(data.message || 'Error updating favorites.', 'bg-danger');
}
})
.catch(error => {
console.error('Error updating favorites:', error);
showToast('Error updating favorites.', 'bg-danger');
});
});
}
// Funcionalidad de Toggle Editable
{% if can_edit and paste.editable %}
const toggleEditableButton = document.getElementById('toggle-editable-button');
if (toggleEditableButton) {
toggleEditableButton.addEventListener('click', function() {
const url = `/paste/{{ paste.id }}/toggle_editable`;
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (data.editable) {
toggleEditableButton.innerHTML = 'Disable Editable';
toggleEditableButton.classList.remove('btn-success');
toggleEditableButton.classList.add('btn-warning');
const editButton = document.getElementById('edit-button');
if (editButton) editButton.style.display = 'inline-block';
} else {
toggleEditableButton.innerHTML = 'Enable Editable';
toggleEditableButton.classList.remove('btn-warning');
toggleEditableButton.classList.add('btn-success');
const editButton = document.getElementById('edit-button');
if (editButton) editButton.style.display = 'none';
}
showToast(data.message, 'bg-success');
} else {
showToast(data.error || 'Action failed', 'bg-danger');
}
})
.catch(error => {
console.error('Error updating editable:', error);
showToast('Error updating editable.', 'bg-danger');
});
});
}
{% endif %}
// Funcionalidad de Copiar al Portapapeles (si NO es Markdown)
{% if not is_markdown %}
const copyButton = document.getElementById('copy-button');
if (copyButton) {
copyButton.addEventListener('click', function () {
const codeElement = document.querySelector('.highlight code');
if (!codeElement) {
showToast('Error: code snippet not found.', 'bg-danger');
return;
}
let code = codeElement.innerText.replace(/^\s*\d+\s/gm, '').trim();
navigator.clipboard.writeText(code)
.then(() => {
showToast('¡Copied to clipboard!', 'bg-success');
})
.catch(err => {
console.error('Error copying to clipboard:', err);
showToast('Error copying code.', 'bg-danger');
});
});
}
{% endif %}
// Funcionalidad del Selector de Estilos de Pygments
const pygmentsStyleSelector = document.getElementById('pygments-style');
if (pygmentsStyleSelector) {
pygmentsStyleSelector.addEventListener('change', function () {
const selectedStyle = this.value;
document.querySelectorAll('.highlight code').forEach(function (codeElement) {
// Remover clases relacionadas con estilos anteriores
[...codeElement.classList].forEach(cls => {
if (cls.startsWith('bg-') || cls.startsWith('hljs-') || cls.startsWith('theme-')) {
codeElement.classList.remove(cls);
}
});
// Añadir la clase del estilo seleccionado
codeElement.classList.add(selectedStyle);
});
showToast('Pygments style changed locally.', 'bg-success');
});
}
// Funcionalidad de Unshare Modal
const confirmUnshareBtn = document.getElementById('confirmUnshareBtn');
if (confirmUnshareBtn) {
let unsharePasteId = null;
let unshareUsername = null;
confirmUnshareBtn.addEventListener('click', function () {
fetch(`/paste/${unsharePasteId}/unshare`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ "username": unshareUsername })
})
.then(response => response.json())
.then(data => {
// Cerrar el modal
const modalElement = document.getElementById('unshareModal');
const modalInstance = bootstrap.Modal.getInstance(modalElement);
modalInstance.hide();
// Mostrar resultado en un toast
if (data.message) {
showToast(data.message, 'bg-success');
} else if (data.error) {
showToast(data.error, 'bg-danger');
}
// Recargar la página o eliminar el <li> para ese usuario
location.reload();
})
.catch(err => {
console.error(err);
showToast('Error sharing paste.', 'bg-danger');
});
});
}
});
</script>
{% endblock %}