better ui for anilist entries & fixes

This commit is contained in:
2025-12-07 02:24:30 +01:00
parent 6ae823ac0b
commit 1973069949
15 changed files with 1723 additions and 668 deletions

View File

@@ -34,8 +34,8 @@ function getSimpleAuthHeaders() {
};
}
// Lógica de chequeo de lista corregida para usar /list/entry/{id} y esperar 'found'
async function checkIfInList() {
const entryId = window.location.pathname.split('/').pop();
const source = extensionName || 'anilist';
const entryType = 'ANIME';
@@ -51,8 +51,7 @@ async function checkIfInList() {
if (response.ok) {
const data = await response.json();
if (data.found && data.entry) {
if (data.found && data.entry) { // Esperamos 'found'
isInList = true;
currentListEntry = data.entry;
} else {
@@ -69,6 +68,8 @@ async function checkIfInList() {
function updateAddToListButton() {
const btn = document.getElementById('add-to-list-btn');
if (!btn) return;
if (isInList) {
btn.innerHTML = `
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
@@ -87,36 +88,73 @@ function updateAddToListButton() {
}
}
// Función openAddToListModal actualizada con todos los campos extendidos
function openAddToListModal() {
if (isInList) {
// Referencias
const modalTitle = document.getElementById('modal-title');
const deleteBtn = document.getElementById('modal-delete-btn');
document.getElementById('modal-status').value = currentListEntry.status || 'PLANNING';
document.getElementById('modal-progress').value = currentListEntry.progress || 0;
document.getElementById('modal-score').value = currentListEntry.score || '';
// Mapeo de prefijos (usamos 'entry-' para el HTML del modal extendido)
const statusEl = document.getElementById('entry-status');
const progressEl = document.getElementById('entry-progress');
const scoreEl = document.getElementById('entry-score');
const startDateEl = document.getElementById('entry-start-date');
const endDateEl = document.getElementById('entry-end-date');
const repeatCountEl = document.getElementById('entry-repeat-count');
const notesEl = document.getElementById('entry-notes');
const privateEl = document.getElementById('entry-is-private');
document.getElementById('modal-title').textContent = 'Edit List Entry';
document.getElementById('modal-delete-btn').style.display = 'block';
if (isInList && currentListEntry) {
statusEl.value = currentListEntry.status || 'PLANNING';
progressEl.value = currentListEntry.progress || 0;
scoreEl.value = currentListEntry.score || '';
// Campos extendidos
startDateEl.value = currentListEntry.start_date ? currentListEntry.start_date.split('T')[0] : '';
endDateEl.value = currentListEntry.end_date ? currentListEntry.end_date.split('T')[0] : '';
repeatCountEl.value = currentListEntry.repeat_count || 0;
notesEl.value = currentListEntry.notes || '';
privateEl.checked = currentListEntry.is_private === true || currentListEntry.is_private === 1;
modalTitle.textContent = 'Edit List Entry';
deleteBtn.style.display = 'block';
} else {
// Valores por defecto
statusEl.value = 'PLANNING';
progressEl.value = 0;
scoreEl.value = '';
startDateEl.value = '';
endDateEl.value = '';
repeatCountEl.value = 0;
notesEl.value = '';
privateEl.checked = false;
document.getElementById('modal-status').value = 'PLANNING';
document.getElementById('modal-progress').value = 0;
document.getElementById('modal-score').value = '';
document.getElementById('modal-title').textContent = 'Add to List';
document.getElementById('modal-delete-btn').style.display = 'none';
modalTitle.textContent = 'Add to List';
deleteBtn.style.display = 'none';
}
document.getElementById('modal-progress').max = totalEpisodes || 999;
document.getElementById('add-list-modal').classList.add('active');}
progressEl.max = totalEpisodes || 999;
document.getElementById('add-list-modal').classList.add('active');
}
function closeAddToListModal() {
document.getElementById('add-list-modal').classList.remove('active');
}
// Función saveToList actualizada con todos los campos extendidos
async function saveToList() {
const status = document.getElementById('modal-status').value;
const progress = parseInt(document.getElementById('modal-progress').value) || 0;
const score = parseFloat(document.getElementById('modal-score').value) || null;
const status = document.getElementById('entry-status').value;
const progress = parseInt(document.getElementById('entry-progress').value) || 0;
const scoreValue = document.getElementById('entry-score').value;
const score = scoreValue ? parseFloat(scoreValue) : null;
// Nuevos campos
const start_date = document.getElementById('entry-start-date').value || null;
const end_date = document.getElementById('entry-end-date').value || null;
const repeat_count = parseInt(document.getElementById('entry-repeat-count').value) || 0;
const notes = document.getElementById('entry-notes').value || null;
const is_private = document.getElementById('entry-is-private').checked;
try {
const response = await fetch(`${API_BASE}/list/entry`, {
@@ -128,7 +166,13 @@ async function saveToList() {
entry_type: 'ANIME',
status: status,
progress: progress,
score: score
score: score,
// Campos extendidos
start_date: start_date,
end_date: end_date,
repeat_count: repeat_count,
notes: notes,
is_private: is_private
})
});
@@ -136,16 +180,10 @@ async function saveToList() {
throw new Error('Failed to save entry');
}
isInList = true;
currentListEntry = {
entry_id: parseInt(animeId),
source: extensionName || 'anilist',
entry_type: 'ANIME',
const data = await response.json();
status,
progress,
score
};
isInList = true;
currentListEntry = data.entry;
updateAddToListButton();
closeAddToListModal();
showNotification(isInList ? 'Updated successfully!' : 'Added to your list!', 'success');
@@ -158,8 +196,11 @@ async function saveToList() {
async function deleteFromList() {
if (!confirm('Remove this anime from your list?')) return;
const source = extensionName || 'anilist';
const entryType = 'ANIME';
try {
const response = await fetch(`${API_BASE}/list/entry/${animeId}`, {
const response = await fetch(`${API_BASE}/list/entry/${animeId}?source=${source}&entry_type=${entryType}`, {
method: 'DELETE',
headers: getSimpleAuthHeaders()

View File

@@ -46,6 +46,7 @@ function getBookEntryType(bookData) {
return (format === 'MANGA' || format === 'ONE_SHOT' || format === 'MANHWA') ? 'MANGA' : 'NOVEL';
}
// CORRECCIÓN: Usar el endpoint /list/entry/{id} y esperar 'found'
async function checkIfInList() {
if (!currentBookData) return;
@@ -54,6 +55,7 @@ async function checkIfInList() {
const entryType = getBookEntryType(currentBookData);
// URL CORRECTA: /list/entry/{id}?source={source}&entry_type={entryType}
const fetchUrl = `${API_BASE}/list/entry/${entryId}?source=${source}&entry_type=${entryType}`;
try {
@@ -64,6 +66,7 @@ async function checkIfInList() {
if (response.ok) {
const data = await response.json();
// LÓGICA CORRECTA: Comprobar data.found
if (data.found && data.entry) {
isInList = true;
@@ -73,6 +76,11 @@ async function checkIfInList() {
currentListEntry = null;
}
updateAddToListButton();
} else if (response.status === 404) {
// Manejar 404 como 'no encontrado' si la API lo devuelve así
isInList = false;
currentListEntry = null;
updateAddToListButton();
}
} catch (error) {
console.error('Error checking single list entry:', error);
@@ -103,40 +111,71 @@ function updateAddToListButton() {
}
}
/**
* REFACTORIZADO para usar la estructura del modal completo.
* Asume que el HTML usa IDs como 'entry-status', 'entry-progress', 'entry-score', etc.
*/
function openAddToListModal() {
if (!currentBookData) return;
const totalUnits = currentBookData.chapters || currentBookData.volumes || 999;
const entryType = getBookEntryType(currentBookData);
if (isInList) {
// Referencias a los elementos del nuevo modal (usando 'entry-' prefix)
const modalTitle = document.getElementById('modal-title');
const deleteBtn = document.getElementById('modal-delete-btn');
const progressLabel = document.getElementById('progress-label');
document.getElementById('modal-status').value = currentListEntry.status || 'PLANNING';
document.getElementById('modal-progress').value = currentListEntry.progress || 0;
document.getElementById('modal-score').value = currentListEntry.score || '';
document.getElementById('modal-title').textContent = 'Edit Library Entry';
document.getElementById('modal-delete-btn').style.display = 'block';
} else {
document.getElementById('modal-status').value = 'PLANNING';
document.getElementById('modal-progress').value = 0;
document.getElementById('modal-score').value = '';
document.getElementById('modal-title').textContent = 'Add to Library';
document.getElementById('modal-delete-btn').style.display = 'none';
// **VERIFICACIÓN CRÍTICA**
if (!modalTitle || !deleteBtn || !progressLabel) {
console.error("Error: Uno o más elementos críticos del modal (título, botón eliminar, o etiqueta de progreso) no se encontraron. Verifique los IDs en el HTML.");
return;
}
const progressLabel = document.getElementById('modal-progress-label');
// --- Población de Datos ---
if (isInList && currentListEntry) {
// Datos comunes
document.getElementById('entry-status').value = currentListEntry.status || 'PLANNING';
document.getElementById('entry-progress').value = currentListEntry.progress || 0;
document.getElementById('entry-score').value = currentListEntry.score || '';
// Nuevos datos
// Usar formato ISO si viene como ISO, o limpiar si es necesario. Tu ejemplo JSON no tenía fechas.
document.getElementById('entry-start-date').value = currentListEntry.start_date ? currentListEntry.start_date.split('T')[0] : '';
document.getElementById('entry-end-date').value = currentListEntry.end_date ? currentListEntry.end_date.split('T')[0] : '';
document.getElementById('entry-repeat-count').value = currentListEntry.repeat_count || 0;
document.getElementById('entry-notes').value = currentListEntry.notes || '';
document.getElementById('entry-is-private').checked = currentListEntry.is_private === true || currentListEntry.is_private === 1;
modalTitle.textContent = 'Edit Library Entry';
deleteBtn.style.display = 'block';
} else {
// Valores por defecto
document.getElementById('entry-status').value = 'PLANNING';
document.getElementById('entry-progress').value = 0;
document.getElementById('entry-score').value = '';
document.getElementById('entry-start-date').value = '';
document.getElementById('entry-end-date').value = '';
document.getElementById('entry-repeat-count').value = 0;
document.getElementById('entry-notes').value = '';
document.getElementById('entry-is-private').checked = false;
modalTitle.textContent = 'Add to Library';
deleteBtn.style.display = 'none';
}
// --- Configuración de Etiquetas y Máximo ---
if (progressLabel) {
const format = currentBookData.format?.toUpperCase() || 'MANGA';
if (format === 'MANGA' || format === 'ONE_SHOT' || format === 'MANHWA') {
if (entryType === 'MANGA') {
progressLabel.textContent = 'Chapters Read';
} else {
progressLabel.textContent = 'Volumes/Parts Read';
}
}
document.getElementById('modal-progress').max = totalUnits;
document.getElementById('entry-progress').max = totalUnits;
document.getElementById('add-list-modal').classList.add('active');
}
@@ -144,10 +183,23 @@ function closeAddToListModal() {
document.getElementById('add-list-modal').classList.remove('active');
}
/**
* REFACTORIZADO para guardar TODOS los campos del modal.
*/
async function saveToList() {
const status = document.getElementById('modal-status').value;
const progress = parseInt(document.getElementById('modal-progress').value) || 0;
const score = parseFloat(document.getElementById('modal-score').value) || null;
// Datos comunes
const status = document.getElementById('entry-status').value;
const progress = parseInt(document.getElementById('entry-progress').value) || 0;
const scoreValue = document.getElementById('entry-score').value;
const score = scoreValue ? parseFloat(scoreValue) : null;
// Nuevos datos
const start_date = document.getElementById('entry-start-date').value || null;
const end_date = document.getElementById('entry-end-date').value || null;
const repeat_count = parseInt(document.getElementById('entry-repeat-count').value) || 0;
const notes = document.getElementById('entry-notes').value || null;
const is_private = document.getElementById('entry-is-private').checked;
if (!currentBookData) {
showNotification('Cannot save: Book data not loaded.', 'error');
@@ -167,7 +219,13 @@ async function saveToList() {
entry_type: entryType,
status: status,
progress: progress,
score: score
score: score,
// Nuevos campos
start_date: start_date,
end_date: end_date,
repeat_count: repeat_count,
notes: notes,
is_private: is_private
})
});
@@ -175,8 +233,10 @@ async function saveToList() {
throw new Error('Failed to save entry');
}
const data = await response.json();
isInList = true;
currentListEntry = { entry_id: idToSave, source: extensionName || 'anilist', entry_type: entryType, status, progress, score };
currentListEntry = data.entry; // Usar la respuesta del servidor si está disponible
updateAddToListButton();
closeAddToListModal();
showNotification(isInList ? 'Updated successfully!' : 'Added to your library!', 'success');
@@ -186,16 +246,19 @@ async function saveToList() {
}
}
// CORRECCIÓN: Usar el endpoint /list/entry/{id} con los parámetros correctos.
async function deleteFromList() {
if (!confirm('Remove this book from your library?')) return;
const idToDelete = extensionName ? bookSlug : bookId;
const source = extensionName || 'anilist';
const entryType = getBookEntryType(currentBookData); // Obtener el tipo de entrada
try {
const response = await fetch(`${API_BASE}/list/entry/${idToDelete}`, {
// URL CORRECTA para DELETE: /list/entry/{id}?source={source}&entry_type={entryType}
const response = await fetch(`${API_BASE}/list/entry/${idToDelete}?source=${source}&entry_type=${entryType}`, {
method: 'DELETE',
headers: getSimpleAuthHeaders()
});
if (!response.ok) {
@@ -519,6 +582,7 @@ function openReader(bookId, chapterId, provider) {
}
document.addEventListener('DOMContentLoaded', () => {
// El ID del modal sigue siendo 'add-list-modal' para mantener la compatibilidad con el código original.
const modal = document.getElementById('add-list-modal');
if (modal) {
modal.addEventListener('click', (e) => {

View File

@@ -224,6 +224,7 @@ function createListItem(item) {
const progressPercent = totalUnits > 0 ? (progress / totalUnits) * 100 : 0;
const score = item.score ? item.score.toFixed(1) : null;
const repeatCount = item.repeat_count || 0;
const entryType = (item.entry_type || 'ANIME').toUpperCase();
let unitLabel = 'units';
@@ -243,6 +244,15 @@ function createListItem(item) {
'DROPPED': 'Dropped'
};
const extraInfo = [];
if (repeatCount > 0) {
extraInfo.push(`<span class="meta-pill repeat-pill">🔁 ${repeatCount}</span>`);
}
if (item.is_private) {
extraInfo.push('<span class="meta-pill private-pill">🔒 Private</span>');
}
div.innerHTML = `
<a href="${itemLink}" class="item-poster-link">
<img src="${posterUrl}" alt="${item.title || 'Entry'}" class="item-poster" onerror="this.src='/public/assets/placeholder.png'">
@@ -256,6 +266,7 @@ function createListItem(item) {
<span class="meta-pill status-pill">${statusLabels[item.status] || item.status}</span>
<span class="meta-pill type-pill">${entryType}</span>
<span class="meta-pill source-pill">${item.source.toUpperCase()}</span>
${extraInfo.join('')}
</div>
</div>
@@ -282,9 +293,21 @@ function createListItem(item) {
function openEditModal(item) {
currentEditingEntry = item;
// Campos existentes
document.getElementById('edit-status').value = item.status;
document.getElementById('edit-progress').value = item.progress || 0;
document.getElementById('edit-score').value = item.score || '';
// Asegura que el score se muestre si existe.
document.getElementById('edit-score').value = item.score !== null && item.score !== undefined ? item.score : '';
// Nuevos campos
// Usamos split('T')[0] para asegurar que solo se muestra la parte de la fecha (YYYY-MM-DD) si viene con formato DATETIME.
document.getElementById('edit-start-date').value = item.start_date?.split('T')[0] || '';
document.getElementById('edit-end-date').value = item.end_date?.split('T')[0] || '';
document.getElementById('edit-repeat-count').value = item.repeat_count || 0;
document.getElementById('edit-notes').value = item.notes || '';
// Maneja el booleano o el entero (1/0)
document.getElementById('edit-is-private').checked = item.is_private === 1 || item.is_private === true;
const entryType = (item.entry_type || 'ANIME').toUpperCase();
const progressLabel = document.querySelector('label[for="edit-progress"]');
@@ -315,9 +338,20 @@ function closeEditModal() {
async function saveEntry() {
if (!currentEditingEntry) return;
// Campos existentes
const status = document.getElementById('edit-status').value;
const progress = parseInt(document.getElementById('edit-progress').value) || 0;
const score = parseFloat(document.getElementById('edit-score').value) || null;
// Usar null si el score está vacío
const scoreValue = document.getElementById('edit-score').value;
const score = scoreValue ? parseFloat(scoreValue) : null;
// Nuevos campos
const start_date = document.getElementById('edit-start-date').value || null;
const end_date = document.getElementById('edit-end-date').value || null;
const repeat_count = parseInt(document.getElementById('edit-repeat-count').value) || 0;
const notesValue = document.getElementById('edit-notes').value;
const notes = notesValue ? notesValue : null;
const is_private = document.getElementById('edit-is-private').checked;
try {
const response = await fetch(`${API_BASE}/list/entry`, {
@@ -329,7 +363,13 @@ async function saveEntry() {
entry_type: currentEditingEntry.entry_type || 'ANIME',
status: status,
progress: progress,
score: score
score: score,
// Nuevos datos a enviar al backend
start_date: start_date,
end_date: end_date,
repeat_count: repeat_count,
notes: notes,
is_private: is_private
})
});
@@ -354,10 +394,13 @@ async function deleteEntry() {
}
try {
const response = await fetch(`${API_BASE}/list/entry/${currentEditingEntry.entry_id}`, {
method: 'DELETE',
headers: getSimpleAuthHeaders()
});
const response = await fetch(
`${API_BASE}/list/entry/${currentEditingEntry.entry_id}?source=${currentEditingEntry.source}`,
{
method: 'DELETE',
headers: getSimpleAuthHeaders()
}
);
if (!response.ok) {
throw new Error('Failed to delete entry');
@@ -372,6 +415,7 @@ async function deleteEntry() {
}
}
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.style.cssText = `