full enhance of anime page and player.
This commit is contained in:
@@ -1,327 +0,0 @@
|
||||
let animeData = null;
|
||||
let extensionName = null;
|
||||
let animeId = null;
|
||||
let isLocal = false;
|
||||
|
||||
const episodePagination = Object.create(PaginationManager);
|
||||
episodePagination.init(12, renderEpisodes);
|
||||
|
||||
YouTubePlayerUtils.init('player');
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadAnime();
|
||||
setupDescriptionModal();
|
||||
setupEpisodeSearch();
|
||||
});
|
||||
|
||||
function markAsLocal() {
|
||||
isLocal = true;
|
||||
const pill = document.getElementById('local-pill');
|
||||
if (!pill) return;
|
||||
|
||||
pill.textContent = 'Local';
|
||||
pill.style.display = 'inline-flex';
|
||||
pill.style.background = 'rgba(34,197,94,.2)';
|
||||
pill.style.color = '#22c55e';
|
||||
pill.style.borderColor = 'rgba(34,197,94,.3)';
|
||||
}
|
||||
|
||||
async function checkLocalLibraryEntry() {
|
||||
try {
|
||||
const res = await fetch(`/api/library/anime/${animeId}`);
|
||||
if (!res.ok) return;
|
||||
|
||||
markAsLocal();
|
||||
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function loadAnime() {
|
||||
try {
|
||||
|
||||
const urlData = URLUtils.parseEntityPath('anime');
|
||||
if (!urlData) {
|
||||
showError("Invalid URL");
|
||||
return;
|
||||
}
|
||||
|
||||
extensionName = urlData.extensionName;
|
||||
animeId = urlData.entityId;
|
||||
await checkLocalLibraryEntry();
|
||||
|
||||
const fetchUrl = extensionName
|
||||
? `/api/anime/${animeId}?source=${extensionName}`
|
||||
: `/api/anime/${animeId}?source=anilist`;
|
||||
|
||||
const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
showError("Anime Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
animeData = data;
|
||||
animeData.entry_type = 'ANIME';
|
||||
const metadata = MediaMetadataUtils.formatAnimeData(data, !!extensionName);
|
||||
|
||||
updatePageTitle(metadata.title);
|
||||
updateMetadata(metadata);
|
||||
updateDescription(data.description || data.summary);
|
||||
updateCharacters(metadata.characters);
|
||||
updateExtensionPill();
|
||||
|
||||
setupWatchButton();
|
||||
|
||||
const hasTrailer = YouTubePlayerUtils.playTrailer(
|
||||
metadata.trailer,
|
||||
'player',
|
||||
metadata.banner
|
||||
);
|
||||
|
||||
setupEpisodes(metadata.episodes);
|
||||
|
||||
await setupAddToListButton();
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error loading anime:', err);
|
||||
showError("Error loading anime");
|
||||
}
|
||||
}
|
||||
|
||||
function updatePageTitle(title) {
|
||||
document.title = `${title} | WaifuBoard`;
|
||||
document.getElementById('title').innerText = title;
|
||||
}
|
||||
|
||||
function updateMetadata(metadata) {
|
||||
|
||||
if (metadata.poster) {
|
||||
document.getElementById('poster').src = metadata.poster;
|
||||
}
|
||||
|
||||
document.getElementById('score').innerText = `${metadata.score}% Score`;
|
||||
|
||||
document.getElementById('year').innerText = metadata.year;
|
||||
|
||||
document.getElementById('genres').innerText = metadata.genres;
|
||||
|
||||
document.getElementById('format').innerText = metadata.format;
|
||||
|
||||
document.getElementById('status').innerText = metadata.status;
|
||||
|
||||
document.getElementById('season').innerText = metadata.season;
|
||||
|
||||
document.getElementById('studio').innerText = metadata.studio;
|
||||
|
||||
document.getElementById('episodes').innerText = metadata.episodes;
|
||||
}
|
||||
|
||||
function updateDescription(rawDescription) {
|
||||
const desc = MediaMetadataUtils.truncateDescription(rawDescription, 4);
|
||||
|
||||
document.getElementById('description-preview').innerHTML = desc.short;
|
||||
document.getElementById('full-description').innerHTML = desc.full;
|
||||
|
||||
const readMoreBtn = document.getElementById('read-more-btn');
|
||||
if (desc.isTruncated) {
|
||||
readMoreBtn.style.display = 'inline-flex';
|
||||
} else {
|
||||
readMoreBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function updateCharacters(characters) {
|
||||
const container = document.getElementById('char-list');
|
||||
container.innerHTML = '';
|
||||
|
||||
if (characters.length > 0) {
|
||||
characters.forEach(char => {
|
||||
container.innerHTML += `
|
||||
<div class="character-item">
|
||||
<div class="char-dot"></div> ${char.name}
|
||||
</div>`;
|
||||
});
|
||||
} else {
|
||||
container.innerHTML = `
|
||||
<div class="character-item" style="color: #666;">
|
||||
No character data available
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateExtensionPill() {
|
||||
const pill = document.getElementById('extension-pill');
|
||||
if (!pill) return;
|
||||
|
||||
if (extensionName) {
|
||||
pill.textContent = extensionName.charAt(0).toUpperCase() + extensionName.slice(1).toLowerCase();
|
||||
pill.style.display = 'inline-flex';
|
||||
} else {
|
||||
pill.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function setupWatchButton() {
|
||||
const watchBtn = document.getElementById('watch-btn');
|
||||
if (watchBtn) {
|
||||
watchBtn.onclick = () => {
|
||||
const source = isLocal ? 'local' : (extensionName || 'anilist');
|
||||
window.location.href = URLUtils.buildWatchUrl(animeId, num, source);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function setupAddToListButton() {
|
||||
const btn = document.getElementById('add-to-list-btn');
|
||||
if (!btn || !animeData) return;
|
||||
|
||||
ListModalManager.currentData = animeData;
|
||||
const entryType = ListModalManager.getEntryType(animeData);
|
||||
|
||||
await ListModalManager.checkIfInList(animeId, extensionName || 'anilist', entryType);
|
||||
|
||||
const tempBtn = document.querySelector('.hero-buttons .btn-blur');
|
||||
if (tempBtn) {
|
||||
ListModalManager.updateButton('.hero-buttons .btn-blur');
|
||||
} else {
|
||||
|
||||
updateCustomAddButton();
|
||||
}
|
||||
|
||||
btn.onclick = () => ListModalManager.open(animeData, extensionName || 'anilist');
|
||||
}
|
||||
|
||||
function updateCustomAddButton() {
|
||||
const btn = document.getElementById('add-to-list-btn');
|
||||
if (!btn) return;
|
||||
|
||||
if (ListModalManager.isInList) {
|
||||
btn.innerHTML = `
|
||||
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
|
||||
</svg>
|
||||
In Your List
|
||||
`;
|
||||
btn.style.background = 'rgba(34, 197, 94, 0.2)';
|
||||
btn.style.color = '#22c55e';
|
||||
btn.style.borderColor = 'rgba(34, 197, 94, 0.3)';
|
||||
} else {
|
||||
btn.innerHTML = '+ Add to List';
|
||||
btn.style.background = null;
|
||||
btn.style.color = null;
|
||||
btn.style.borderColor = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setupEpisodes(totalEpisodes) {
|
||||
|
||||
const limitedTotal = Math.min(Math.max(totalEpisodes, 1), 5000);
|
||||
|
||||
episodePagination.setTotalItems(limitedTotal);
|
||||
renderEpisodes();
|
||||
}
|
||||
|
||||
function renderEpisodes() {
|
||||
const grid = document.getElementById('episodes-grid');
|
||||
if (!grid) return;
|
||||
|
||||
grid.innerHTML = '';
|
||||
|
||||
const range = episodePagination.getPageRange();
|
||||
const start = range.start + 1;
|
||||
|
||||
const end = range.end;
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
createEpisodeButton(i, grid);
|
||||
}
|
||||
|
||||
episodePagination.renderControls(
|
||||
'pagination-controls',
|
||||
'page-info',
|
||||
'prev-page',
|
||||
'next-page'
|
||||
);
|
||||
}
|
||||
|
||||
function createEpisodeButton(num, container) {
|
||||
const btn = document.createElement('div');
|
||||
btn.className = 'episode-btn';
|
||||
btn.innerText = `Ep ${num}`;
|
||||
btn.onclick = () => {
|
||||
const source = isLocal ? 'local' : (extensionName || 'anilist');
|
||||
window.location.href = URLUtils.buildWatchUrl(animeId, num, source);
|
||||
};
|
||||
container.appendChild(btn);
|
||||
}
|
||||
|
||||
function setupDescriptionModal() {
|
||||
const modal = document.getElementById('desc-modal');
|
||||
if (!modal) return;
|
||||
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'desc-modal') {
|
||||
closeDescriptionModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openDescriptionModal() {
|
||||
document.getElementById('desc-modal').classList.add('active');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeDescriptionModal() {
|
||||
document.getElementById('desc-modal').classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function setupEpisodeSearch() {
|
||||
const searchInput = document.getElementById('ep-search');
|
||||
if (!searchInput) return;
|
||||
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
const grid = document.getElementById('episodes-grid');
|
||||
const totalEpisodes = episodePagination.totalItems;
|
||||
|
||||
if (val > 0 && val <= totalEpisodes) {
|
||||
grid.innerHTML = '';
|
||||
createEpisodeButton(val, grid);
|
||||
document.getElementById('pagination-controls').style.display = 'none';
|
||||
} else if (!e.target.value) {
|
||||
renderEpisodes();
|
||||
} else {
|
||||
grid.innerHTML = '<div style="color:#666; width:100%; text-align:center;">Episode not found</div>';
|
||||
document.getElementById('pagination-controls').style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
document.getElementById('title').innerText = message;
|
||||
}
|
||||
|
||||
function saveToList() {
|
||||
if (!animeId) return;
|
||||
ListModalManager.save(animeId, extensionName || 'anilist');
|
||||
}
|
||||
|
||||
function deleteFromList() {
|
||||
if (!animeId) return;
|
||||
ListModalManager.delete(animeId, extensionName || 'anilist');
|
||||
}
|
||||
|
||||
function closeAddToListModal() {
|
||||
ListModalManager.close();
|
||||
}
|
||||
|
||||
window.openDescriptionModal = openDescriptionModal;
|
||||
window.closeDescriptionModal = closeDescriptionModal;
|
||||
window.changePage = (delta) => {
|
||||
if (delta > 0) episodePagination.nextPage();
|
||||
else episodePagination.prevPage();
|
||||
};
|
||||
408
desktop/src/scripts/anime/entry.js
Normal file
408
desktop/src/scripts/anime/entry.js
Normal file
@@ -0,0 +1,408 @@
|
||||
let animeData = null;
|
||||
let extensionName = null;
|
||||
|
||||
let animeId = null;
|
||||
let isLocal = false;
|
||||
|
||||
const episodePagination = Object.create(PaginationManager);
|
||||
episodePagination.init(50, renderEpisodes);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadAnimeData();
|
||||
setupDescriptionModal();
|
||||
setupEpisodeSearch();
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const initialEp = urlParams.get('episode');
|
||||
|
||||
if (initialEp) {
|
||||
setTimeout(() => {
|
||||
if (animeData) {
|
||||
|
||||
const source = isLocal ? 'local' : (extensionName || 'anilist');
|
||||
|
||||
if (typeof AnimePlayer !== 'undefined') {
|
||||
AnimePlayer.init(animeId, source, isLocal, animeData);
|
||||
AnimePlayer.playEpisode(parseInt(initialEp));
|
||||
}
|
||||
}
|
||||
}, 800);
|
||||
}
|
||||
});
|
||||
|
||||
async function loadAnimeData() {
|
||||
try {
|
||||
|
||||
const pathParts = window.location.pathname.split('/');
|
||||
|
||||
const cleanParts = pathParts.filter(p => p.length > 0);
|
||||
|
||||
if (cleanParts.length >= 3 && cleanParts[0] === 'anime') {
|
||||
|
||||
extensionName = cleanParts[1];
|
||||
animeId = cleanParts[2];
|
||||
} else if (cleanParts.length === 2 && cleanParts[0] === 'anime') {
|
||||
|
||||
extensionName = null;
|
||||
|
||||
animeId = cleanParts[1];
|
||||
} else {
|
||||
showError("Invalid URL Format");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const localRes = await fetch(`/api/library/anime/${animeId}`);
|
||||
if (localRes.ok) isLocal = true;
|
||||
} catch {}
|
||||
|
||||
const fetchUrl = extensionName
|
||||
? `/api/anime/${animeId}?source=${extensionName}`
|
||||
: `/api/anime/${animeId}?source=anilist`;
|
||||
|
||||
const res = await fetch(fetchUrl, { headers: AuthUtils.getSimpleAuthHeaders() });
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
showError("Anime Not Found");
|
||||
return;
|
||||
}
|
||||
|
||||
animeData = data;
|
||||
animeData.entry_type = 'ANIME';
|
||||
|
||||
const metadata = MediaMetadataUtils.formatAnimeData(data, !!extensionName);
|
||||
|
||||
document.title = `${metadata.title} | WaifuBoard`;
|
||||
document.getElementById('title').innerText = metadata.title;
|
||||
if (metadata.poster) document.getElementById('poster').src = metadata.poster;
|
||||
|
||||
document.getElementById('score').innerText = `${metadata.score}% Score`;
|
||||
document.getElementById('year').innerText = metadata.year;
|
||||
document.getElementById('genres').innerText = metadata.genres.split(',').join(' • ');
|
||||
document.getElementById('format').innerText = metadata.format;
|
||||
document.getElementById('status').innerText = metadata.status;
|
||||
document.getElementById('season').innerText = metadata.season;
|
||||
document.getElementById('studio').innerText = metadata.studio;
|
||||
document.getElementById('episodes').innerText = `${metadata.episodes} Ep`;
|
||||
|
||||
updateLocalPill();
|
||||
updateDescription(data.description || data.summary);
|
||||
updateRelations(data.relations?.edges);
|
||||
updateCharacters(metadata.characters);
|
||||
updateRecommendations(data.recommendations?.nodes);
|
||||
|
||||
const source = isLocal ? 'local' : (extensionName || 'anilist');
|
||||
|
||||
if (typeof AnimePlayer !== 'undefined') {
|
||||
AnimePlayer.init(animeId, source, isLocal, animeData);
|
||||
}
|
||||
|
||||
YouTubePlayerUtils.init('trailer-player');
|
||||
YouTubePlayerUtils.playTrailer(metadata.trailer, 'trailer-player', metadata.banner);
|
||||
|
||||
const watchBtn = document.getElementById('watch-btn');
|
||||
if (watchBtn) {
|
||||
watchBtn.onclick = () => {
|
||||
if (typeof AnimePlayer !== 'undefined') AnimePlayer.playEpisode(1);
|
||||
};
|
||||
}
|
||||
|
||||
const total = metadata.episodes || 12;
|
||||
setupEpisodes(total);
|
||||
|
||||
setupAddToListButton();
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error loading anime details:', err);
|
||||
showError("Error loading anime");
|
||||
}
|
||||
}
|
||||
|
||||
function updateLocalPill() {
|
||||
if (isLocal) {
|
||||
const pill = document.getElementById('local-pill');
|
||||
if(pill) pill.style.display = 'inline-flex';
|
||||
}
|
||||
if (extensionName) {
|
||||
const pill = document.getElementById('extension-pill');
|
||||
if(pill) {
|
||||
pill.innerText = extensionName;
|
||||
pill.style.display = 'inline-flex';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateDescription(rawDescription) {
|
||||
const desc = MediaMetadataUtils.truncateDescription(rawDescription, 3);
|
||||
const previewEl = document.getElementById('description-preview');
|
||||
const fullEl = document.getElementById('full-description');
|
||||
|
||||
if (previewEl) {
|
||||
previewEl.innerHTML = desc.short +
|
||||
(desc.isTruncated ? ' <span onclick="openDescriptionModal()" style="color:white; cursor:pointer; font-weight:700; margin-left:5px;">Read more</span>' : '');
|
||||
}
|
||||
if (fullEl) fullEl.innerHTML = desc.full;
|
||||
}
|
||||
|
||||
function setupDescriptionModal() {
|
||||
const modal = document.getElementById('desc-modal');
|
||||
if (!modal) return;
|
||||
modal.addEventListener('click', (e) => {
|
||||
if (e.target.id === 'desc-modal') {
|
||||
closeDescriptionModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openDescriptionModal() {
|
||||
const modal = document.getElementById('desc-modal');
|
||||
if(modal) modal.classList.add('active');
|
||||
}
|
||||
|
||||
function closeDescriptionModal() {
|
||||
const modal = document.getElementById('desc-modal');
|
||||
if(modal) modal.classList.remove('active');
|
||||
}
|
||||
|
||||
function setupEpisodes(totalEpisodes) {
|
||||
const limitedTotal = Math.min(Math.max(totalEpisodes, 1), 5000);
|
||||
episodePagination.setTotalItems(limitedTotal);
|
||||
renderEpisodes();
|
||||
}
|
||||
|
||||
function renderEpisodes() {
|
||||
const grid = document.getElementById('episodes-grid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '';
|
||||
|
||||
const range = episodePagination.getPageRange();
|
||||
const currentEp = (typeof AnimePlayer !== 'undefined') ? AnimePlayer.getCurrentEpisode() : 0;
|
||||
|
||||
for (let i = range.start + 1; i <= range.end; i++) {
|
||||
const btn = document.createElement('div');
|
||||
btn.className = 'episode-btn';
|
||||
if (currentEp === i) btn.classList.add('active-playing');
|
||||
|
||||
btn.innerText = i;
|
||||
|
||||
btn.onclick = () => {
|
||||
if (typeof AnimePlayer !== 'undefined') {
|
||||
AnimePlayer.playEpisode(i);
|
||||
renderEpisodes();
|
||||
}
|
||||
};
|
||||
grid.appendChild(btn);
|
||||
}
|
||||
|
||||
const totalItems = episodePagination.totalItems || 0;
|
||||
const itemsPerPage = episodePagination.itemsPerPage || 50;
|
||||
const safeTotalPages = Math.ceil(totalItems / itemsPerPage) || 1;
|
||||
|
||||
const info = document.getElementById('page-info');
|
||||
if(info) info.innerText = `Page ${episodePagination.currentPage} of ${safeTotalPages}`;
|
||||
|
||||
const prevBtn = document.getElementById('prev-page');
|
||||
const nextBtn = document.getElementById('next-page');
|
||||
|
||||
if(prevBtn) {
|
||||
prevBtn.disabled = episodePagination.currentPage === 1;
|
||||
prevBtn.onclick = () => { episodePagination.prevPage(); };
|
||||
}
|
||||
|
||||
if(nextBtn) {
|
||||
|
||||
nextBtn.disabled = episodePagination.currentPage >= safeTotalPages;
|
||||
nextBtn.onclick = () => { episodePagination.nextPage(); };
|
||||
}
|
||||
}
|
||||
|
||||
function setupEpisodeSearch() {
|
||||
const searchInput = document.getElementById('ep-search');
|
||||
if (!searchInput) return;
|
||||
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
const grid = document.getElementById('episodes-grid');
|
||||
const controls = document.getElementById('pagination-controls');
|
||||
|
||||
if (val > 0) {
|
||||
grid.innerHTML = '';
|
||||
const btn = document.createElement('div');
|
||||
btn.className = 'episode-btn';
|
||||
btn.innerText = `${val}`;
|
||||
btn.onclick = () => { if (typeof AnimePlayer !== 'undefined') AnimePlayer.playEpisode(val); };
|
||||
grid.appendChild(btn);
|
||||
if(controls) controls.style.display = 'none';
|
||||
} else if (!e.target.value) {
|
||||
if(controls) controls.style.display = 'flex';
|
||||
renderEpisodes();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateCharacters(characters) {
|
||||
const container = document.getElementById('char-list');
|
||||
if (!container) return;
|
||||
container.innerHTML = '';
|
||||
if (!characters || characters.length === 0) return;
|
||||
|
||||
characters.forEach((char, index) => {
|
||||
const img = char.node?.image?.large || char.image;
|
||||
const name = char.node?.name?.full || char.name;
|
||||
const role = char.role || 'Supporting';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = `character-item ${index >= 12 ? 'hidden-char' : ''}`;
|
||||
card.style.display = index >= 12 ? 'none' : 'flex';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="char-avatar"><img src="${img}" loading="lazy"></div>
|
||||
<div class="char-info"><div class="char-name">${name}</div><div class="char-role">${role}</div></div>
|
||||
`;
|
||||
container.appendChild(card);
|
||||
});
|
||||
|
||||
const showMoreBtn = document.getElementById('show-more-chars');
|
||||
if (showMoreBtn) {
|
||||
if (characters.length > 12) {
|
||||
showMoreBtn.style.display = 'block';
|
||||
showMoreBtn.onclick = () => {
|
||||
document.querySelectorAll('.hidden-char').forEach(el => el.style.display = 'flex');
|
||||
showMoreBtn.style.display = 'none';
|
||||
};
|
||||
} else {
|
||||
showMoreBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateRelations(relations) {
|
||||
const container = document.getElementById('relations-grid');
|
||||
const section = document.getElementById('relations-section');
|
||||
|
||||
if (!container || !relations || relations.length === 0) {
|
||||
if (section) section.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
section.style.display = 'block';
|
||||
container.innerHTML = '';
|
||||
|
||||
const priorityMap = {
|
||||
'ADAPTATION': 1,
|
||||
'PREQUEL': 2,
|
||||
'SEQUEL': 3,
|
||||
'PARENT': 4,
|
||||
'SIDE_STORY': 5,
|
||||
'SPIN_OFF': 6,
|
||||
'ALTERNATIVE': 7,
|
||||
'CHARACTER': 8,
|
||||
'OTHER': 99
|
||||
};
|
||||
|
||||
relations.sort((a, b) => {
|
||||
const pA = priorityMap[a.relationType] || 50;
|
||||
const pB = priorityMap[b.relationType] || 50;
|
||||
return pA - pB;
|
||||
});
|
||||
|
||||
relations.forEach(rel => {
|
||||
const media = rel.node;
|
||||
if (!media) return;
|
||||
|
||||
const title = media.title?.romaji || media.title?.english || 'Unknown';
|
||||
const cover = media.coverImage?.large || media.coverImage?.medium || '';
|
||||
|
||||
const typeDisplay = rel.relationType ? rel.relationType.replace(/_/g, ' ') : 'RELATION';
|
||||
const rawType = rel.relationType;
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = 'relation-card-horizontal';
|
||||
|
||||
let targetUrl = null;
|
||||
|
||||
if (rawType === 'ADAPTATION') {
|
||||
targetUrl = `/book/${media.id}`;
|
||||
|
||||
} else if (media.type === 'ANIME' && rawType !== 'OTHER') {
|
||||
targetUrl = `/anime/${media.id}`;
|
||||
|
||||
}
|
||||
|
||||
if (targetUrl) {
|
||||
el.onclick = () => { window.location.href = targetUrl; };
|
||||
} else {
|
||||
el.classList.add('no-link');
|
||||
|
||||
el.onclick = null;
|
||||
}
|
||||
|
||||
el.innerHTML = `
|
||||
<img src="${cover}" class="rel-img" alt="${title}" onerror="this.src='/public/assets/placeholder.svg'">
|
||||
<div class="rel-info">
|
||||
<span class="rel-type">${typeDisplay}</span>
|
||||
<span class="rel-title">${title}</span>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
function updateRecommendations(recommendations) {
|
||||
const container = document.getElementById('recommendations-grid');
|
||||
if (!container || !recommendations) return;
|
||||
container.innerHTML = '';
|
||||
|
||||
recommendations.forEach(rec => {
|
||||
const media = rec.mediaRecommendation;
|
||||
if (!media) return;
|
||||
|
||||
const title = media.title?.romaji || 'Unknown';
|
||||
const cover = media.coverImage?.large || media.coverImage?.medium || '';
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = 'card';
|
||||
|
||||
el.onclick = () => { window.location.href = `/anime/${media.id}`; };
|
||||
|
||||
el.innerHTML = `
|
||||
<div class="card-img-wrap"><img src="${cover}" loading="lazy" onerror="this.src='/public/assets/no-cover.jpg'"></div>
|
||||
<div class="card-content"><h3>${title}</h3></div>
|
||||
`;
|
||||
container.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
async function setupAddToListButton() {
|
||||
const btn = document.getElementById('add-to-list-btn');
|
||||
if (!btn || !animeData) return;
|
||||
|
||||
ListModalManager.currentData = animeData;
|
||||
const entryType = 'ANIME';
|
||||
await ListModalManager.checkIfInList(animeId, extensionName || 'anilist', entryType);
|
||||
updateCustomAddButton();
|
||||
btn.onclick = () => ListModalManager.open(animeData, extensionName || 'anilist');
|
||||
}
|
||||
|
||||
function updateCustomAddButton() {
|
||||
const btn = document.getElementById('add-to-list-btn');
|
||||
if (btn && ListModalManager.isInList) {
|
||||
btn.innerHTML = `✓ In Your List`;
|
||||
btn.style.background = 'rgba(34, 197, 94, 0.2)';
|
||||
btn.style.borderColor = '#22c55e';
|
||||
btn.style.color = '#22c55e';
|
||||
}
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
const el = document.getElementById('title');
|
||||
if(el) el.innerText = msg;
|
||||
}
|
||||
|
||||
window.openDescriptionModal = openDescriptionModal;
|
||||
window.closeDescriptionModal = closeDescriptionModal;
|
||||
window.scrollRecommendations = (dir) => {
|
||||
const el = document.getElementById('recommendations-grid');
|
||||
if(el) el.scrollBy({ left: 240 * 3 * dir, behavior: 'smooth' });
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -52,10 +52,9 @@ const ContinueWatchingManager = {
|
||||
|
||||
if (entryType === 'ANIME') {
|
||||
url = item.source === 'anilist'
|
||||
? `/watch/${item.entry_id}/${nextProgress}`
|
||||
: `/watch/${item.entry_id}/${nextProgress}?${item.source}`;
|
||||
? `/anime/${item.entry_id}?episode=${nextProgress}`
|
||||
: `/anime/${item.entry_id}/${item.source}/?episode=${nextProgress}`;
|
||||
} else {
|
||||
|
||||
url = item.source === 'anilist'
|
||||
? `/book/${item.entry_id}?chapter=${nextProgress}`
|
||||
: `/read/${item.source}/${nextProgress}/${item.entry_id}?source=${item.source}`;
|
||||
|
||||
Reference in New Issue
Block a user