mypastebin/templates/text_paste.html_bak
2025-05-29 22:40:58 +02:00

485 lines
19 KiB
Plaintext

{% 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 %}