diff --git a/.gitignore b/.gitignore
index d09ab9d..a3d465c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,11 @@
-node_modules
-electron
-dist
-.env
-build
+desktop/node_modules
+desktop/electron
+desktop/dist
+desktop/.env
+desktop/build
+
+docker/node_modules
+docker/electron
+docker/dist
+docker/.env
+docker/build
diff --git a/desktop/.dockerignore b/desktop/.dockerignore
new file mode 100644
index 0000000..f675cdb
--- /dev/null
+++ b/desktop/.dockerignore
@@ -0,0 +1,8 @@
+node_modules
+electron
+dist
+.env
+build
+.gitignore
+Dockerfile
+.dockerignore
diff --git a/desktop/.gitignore b/desktop/.gitignore
new file mode 100644
index 0000000..d09ab9d
--- /dev/null
+++ b/desktop/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+electron
+dist
+.env
+build
diff --git a/desktop/Dockerfile b/desktop/Dockerfile
new file mode 100644
index 0000000..08a5094
--- /dev/null
+++ b/desktop/Dockerfile
@@ -0,0 +1,15 @@
+FROM node:20-alpine
+
+WORKDIR /app
+
+COPY package*.json ./
+
+RUN npm ci
+
+RUN npm uninstall @ryuziii/discord-rpc
+
+COPY . .
+
+EXPOSE 54322
+
+CMD ["npm", "run", "start"]
diff --git a/main.js b/desktop/main.js
similarity index 100%
rename from main.js
rename to desktop/main.js
diff --git a/package-lock.json b/desktop/package-lock.json
similarity index 100%
rename from package-lock.json
rename to desktop/package-lock.json
diff --git a/package.json b/desktop/package.json
similarity index 100%
rename from package.json
rename to desktop/package.json
diff --git a/preload.js b/desktop/preload.js
similarity index 100%
rename from preload.js
rename to desktop/preload.js
diff --git a/public/assets/avatar.png b/desktop/public/assets/avatar.png
similarity index 100%
rename from public/assets/avatar.png
rename to desktop/public/assets/avatar.png
diff --git a/public/assets/placeholder.svg b/desktop/public/assets/placeholder.svg
similarity index 100%
rename from public/assets/placeholder.svg
rename to desktop/public/assets/placeholder.svg
diff --git a/public/assets/waifuboards.ico b/desktop/public/assets/waifuboards.ico
similarity index 100%
rename from public/assets/waifuboards.ico
rename to desktop/public/assets/waifuboards.ico
diff --git a/server.js b/desktop/server.js
similarity index 100%
rename from server.js
rename to desktop/server.js
diff --git a/src/api/anilist/anilist.service.ts b/desktop/src/api/anilist/anilist.service.ts
similarity index 100%
rename from src/api/anilist/anilist.service.ts
rename to desktop/src/api/anilist/anilist.service.ts
diff --git a/src/api/anilist/anilist.ts b/desktop/src/api/anilist/anilist.ts
similarity index 100%
rename from src/api/anilist/anilist.ts
rename to desktop/src/api/anilist/anilist.ts
diff --git a/src/api/anime/anime.controller.ts b/desktop/src/api/anime/anime.controller.ts
similarity index 100%
rename from src/api/anime/anime.controller.ts
rename to desktop/src/api/anime/anime.controller.ts
diff --git a/src/api/anime/anime.routes.ts b/desktop/src/api/anime/anime.routes.ts
similarity index 100%
rename from src/api/anime/anime.routes.ts
rename to desktop/src/api/anime/anime.routes.ts
diff --git a/src/api/anime/anime.service.ts b/desktop/src/api/anime/anime.service.ts
similarity index 100%
rename from src/api/anime/anime.service.ts
rename to desktop/src/api/anime/anime.service.ts
diff --git a/src/api/books/books.controller.ts b/desktop/src/api/books/books.controller.ts
similarity index 100%
rename from src/api/books/books.controller.ts
rename to desktop/src/api/books/books.controller.ts
diff --git a/src/api/books/books.routes.ts b/desktop/src/api/books/books.routes.ts
similarity index 100%
rename from src/api/books/books.routes.ts
rename to desktop/src/api/books/books.routes.ts
diff --git a/src/api/books/books.service.ts b/desktop/src/api/books/books.service.ts
similarity index 100%
rename from src/api/books/books.service.ts
rename to desktop/src/api/books/books.service.ts
diff --git a/src/api/extensions/extensions.controller.ts b/desktop/src/api/extensions/extensions.controller.ts
similarity index 100%
rename from src/api/extensions/extensions.controller.ts
rename to desktop/src/api/extensions/extensions.controller.ts
diff --git a/src/api/extensions/extensions.routes.ts b/desktop/src/api/extensions/extensions.routes.ts
similarity index 100%
rename from src/api/extensions/extensions.routes.ts
rename to desktop/src/api/extensions/extensions.routes.ts
diff --git a/src/api/gallery/gallery.controller.ts b/desktop/src/api/gallery/gallery.controller.ts
similarity index 100%
rename from src/api/gallery/gallery.controller.ts
rename to desktop/src/api/gallery/gallery.controller.ts
diff --git a/src/api/gallery/gallery.routes.ts b/desktop/src/api/gallery/gallery.routes.ts
similarity index 100%
rename from src/api/gallery/gallery.routes.ts
rename to desktop/src/api/gallery/gallery.routes.ts
diff --git a/src/api/gallery/gallery.service.ts b/desktop/src/api/gallery/gallery.service.ts
similarity index 100%
rename from src/api/gallery/gallery.service.ts
rename to desktop/src/api/gallery/gallery.service.ts
diff --git a/src/api/list/list.controller.ts b/desktop/src/api/list/list.controller.ts
similarity index 100%
rename from src/api/list/list.controller.ts
rename to desktop/src/api/list/list.controller.ts
diff --git a/src/api/list/list.routes.ts b/desktop/src/api/list/list.routes.ts
similarity index 100%
rename from src/api/list/list.routes.ts
rename to desktop/src/api/list/list.routes.ts
diff --git a/src/api/list/list.service.ts b/desktop/src/api/list/list.service.ts
similarity index 100%
rename from src/api/list/list.service.ts
rename to desktop/src/api/list/list.service.ts
diff --git a/src/api/proxy/proxy.controller.ts b/desktop/src/api/proxy/proxy.controller.ts
similarity index 100%
rename from src/api/proxy/proxy.controller.ts
rename to desktop/src/api/proxy/proxy.controller.ts
diff --git a/src/api/proxy/proxy.routes.ts b/desktop/src/api/proxy/proxy.routes.ts
similarity index 100%
rename from src/api/proxy/proxy.routes.ts
rename to desktop/src/api/proxy/proxy.routes.ts
diff --git a/src/api/proxy/proxy.service.ts b/desktop/src/api/proxy/proxy.service.ts
similarity index 100%
rename from src/api/proxy/proxy.service.ts
rename to desktop/src/api/proxy/proxy.service.ts
diff --git a/src/api/rpc/rp.service.ts b/desktop/src/api/rpc/rp.service.ts
similarity index 100%
rename from src/api/rpc/rp.service.ts
rename to desktop/src/api/rpc/rp.service.ts
diff --git a/src/api/rpc/rpc.controller.ts b/desktop/src/api/rpc/rpc.controller.ts
similarity index 100%
rename from src/api/rpc/rpc.controller.ts
rename to desktop/src/api/rpc/rpc.controller.ts
diff --git a/src/api/rpc/rpc.routes.ts b/desktop/src/api/rpc/rpc.routes.ts
similarity index 100%
rename from src/api/rpc/rpc.routes.ts
rename to desktop/src/api/rpc/rpc.routes.ts
diff --git a/src/api/types.ts b/desktop/src/api/types.ts
similarity index 100%
rename from src/api/types.ts
rename to desktop/src/api/types.ts
diff --git a/src/api/user/user.controller.ts b/desktop/src/api/user/user.controller.ts
similarity index 100%
rename from src/api/user/user.controller.ts
rename to desktop/src/api/user/user.controller.ts
diff --git a/src/api/user/user.routes.ts b/desktop/src/api/user/user.routes.ts
similarity index 100%
rename from src/api/user/user.routes.ts
rename to desktop/src/api/user/user.routes.ts
diff --git a/src/api/user/user.service.ts b/desktop/src/api/user/user.service.ts
similarity index 100%
rename from src/api/user/user.service.ts
rename to desktop/src/api/user/user.service.ts
diff --git a/src/scripts/anime/anime.js b/desktop/src/scripts/anime/anime.js
similarity index 100%
rename from src/scripts/anime/anime.js
rename to desktop/src/scripts/anime/anime.js
diff --git a/src/scripts/anime/animes.js b/desktop/src/scripts/anime/animes.js
similarity index 100%
rename from src/scripts/anime/animes.js
rename to desktop/src/scripts/anime/animes.js
diff --git a/src/scripts/anime/player.js b/desktop/src/scripts/anime/player.js
similarity index 100%
rename from src/scripts/anime/player.js
rename to desktop/src/scripts/anime/player.js
diff --git a/src/scripts/auth-guard.js b/desktop/src/scripts/auth-guard.js
similarity index 100%
rename from src/scripts/auth-guard.js
rename to desktop/src/scripts/auth-guard.js
diff --git a/src/scripts/books/book.js b/desktop/src/scripts/books/book.js
similarity index 100%
rename from src/scripts/books/book.js
rename to desktop/src/scripts/books/book.js
diff --git a/src/scripts/books/books.js b/desktop/src/scripts/books/books.js
similarity index 100%
rename from src/scripts/books/books.js
rename to desktop/src/scripts/books/books.js
diff --git a/src/scripts/books/reader.js b/desktop/src/scripts/books/reader.js
similarity index 100%
rename from src/scripts/books/reader.js
rename to desktop/src/scripts/books/reader.js
diff --git a/src/scripts/gallery/gallery.js b/desktop/src/scripts/gallery/gallery.js
similarity index 100%
rename from src/scripts/gallery/gallery.js
rename to desktop/src/scripts/gallery/gallery.js
diff --git a/src/scripts/gallery/image.js b/desktop/src/scripts/gallery/image.js
similarity index 100%
rename from src/scripts/gallery/image.js
rename to desktop/src/scripts/gallery/image.js
diff --git a/src/scripts/list.js b/desktop/src/scripts/list.js
similarity index 100%
rename from src/scripts/list.js
rename to desktop/src/scripts/list.js
diff --git a/src/scripts/marketplace.js b/desktop/src/scripts/marketplace.js
similarity index 100%
rename from src/scripts/marketplace.js
rename to desktop/src/scripts/marketplace.js
diff --git a/src/scripts/rpc-inapp.js b/desktop/src/scripts/rpc-inapp.js
similarity index 100%
rename from src/scripts/rpc-inapp.js
rename to desktop/src/scripts/rpc-inapp.js
diff --git a/src/scripts/schedule/schedule.js b/desktop/src/scripts/schedule/schedule.js
similarity index 100%
rename from src/scripts/schedule/schedule.js
rename to desktop/src/scripts/schedule/schedule.js
diff --git a/src/scripts/titlebar.js b/desktop/src/scripts/titlebar.js
similarity index 100%
rename from src/scripts/titlebar.js
rename to desktop/src/scripts/titlebar.js
diff --git a/src/scripts/updateNotifier.js b/desktop/src/scripts/updateNotifier.js
similarity index 100%
rename from src/scripts/updateNotifier.js
rename to desktop/src/scripts/updateNotifier.js
diff --git a/src/scripts/users.js b/desktop/src/scripts/users.js
similarity index 100%
rename from src/scripts/users.js
rename to desktop/src/scripts/users.js
diff --git a/src/scripts/utils/auth-utils.js b/desktop/src/scripts/utils/auth-utils.js
similarity index 100%
rename from src/scripts/utils/auth-utils.js
rename to desktop/src/scripts/utils/auth-utils.js
diff --git a/src/scripts/utils/continue-watching-manager.js b/desktop/src/scripts/utils/continue-watching-manager.js
similarity index 100%
rename from src/scripts/utils/continue-watching-manager.js
rename to desktop/src/scripts/utils/continue-watching-manager.js
diff --git a/src/scripts/utils/list-modal-manager.js b/desktop/src/scripts/utils/list-modal-manager.js
similarity index 100%
rename from src/scripts/utils/list-modal-manager.js
rename to desktop/src/scripts/utils/list-modal-manager.js
diff --git a/src/scripts/utils/media-metadata-utils.js b/desktop/src/scripts/utils/media-metadata-utils.js
similarity index 100%
rename from src/scripts/utils/media-metadata-utils.js
rename to desktop/src/scripts/utils/media-metadata-utils.js
diff --git a/src/scripts/utils/notification-utils.js b/desktop/src/scripts/utils/notification-utils.js
similarity index 100%
rename from src/scripts/utils/notification-utils.js
rename to desktop/src/scripts/utils/notification-utils.js
diff --git a/src/scripts/utils/pagination-manager.js b/desktop/src/scripts/utils/pagination-manager.js
similarity index 100%
rename from src/scripts/utils/pagination-manager.js
rename to desktop/src/scripts/utils/pagination-manager.js
diff --git a/src/scripts/utils/search-manager.js b/desktop/src/scripts/utils/search-manager.js
similarity index 100%
rename from src/scripts/utils/search-manager.js
rename to desktop/src/scripts/utils/search-manager.js
diff --git a/src/scripts/utils/url-utils.js b/desktop/src/scripts/utils/url-utils.js
similarity index 100%
rename from src/scripts/utils/url-utils.js
rename to desktop/src/scripts/utils/url-utils.js
diff --git a/src/scripts/utils/youtube-player-utils.js b/desktop/src/scripts/utils/youtube-player-utils.js
similarity index 100%
rename from src/scripts/utils/youtube-player-utils.js
rename to desktop/src/scripts/utils/youtube-player-utils.js
diff --git a/src/shared/database.js b/desktop/src/shared/database.js
similarity index 100%
rename from src/shared/database.js
rename to desktop/src/shared/database.js
diff --git a/src/shared/extensions.js b/desktop/src/shared/extensions.js
similarity index 100%
rename from src/shared/extensions.js
rename to desktop/src/shared/extensions.js
diff --git a/src/shared/headless.js b/desktop/src/shared/headless.js
similarity index 100%
rename from src/shared/headless.js
rename to desktop/src/shared/headless.js
diff --git a/src/shared/queries.js b/desktop/src/shared/queries.js
similarity index 100%
rename from src/shared/queries.js
rename to desktop/src/shared/queries.js
diff --git a/src/shared/schemas.js b/desktop/src/shared/schemas.js
similarity index 100%
rename from src/shared/schemas.js
rename to desktop/src/shared/schemas.js
diff --git a/src/views/views.routes.ts b/desktop/src/views/views.routes.ts
similarity index 100%
rename from src/views/views.routes.ts
rename to desktop/src/views/views.routes.ts
diff --git a/tsconfig.json b/desktop/tsconfig.json
similarity index 100%
rename from tsconfig.json
rename to desktop/tsconfig.json
diff --git a/views/anime/anime.html b/desktop/views/anime/anime.html
similarity index 100%
rename from views/anime/anime.html
rename to desktop/views/anime/anime.html
diff --git a/views/anime/animes.html b/desktop/views/anime/animes.html
similarity index 100%
rename from views/anime/animes.html
rename to desktop/views/anime/animes.html
diff --git a/views/anime/watch.html b/desktop/views/anime/watch.html
similarity index 100%
rename from views/anime/watch.html
rename to desktop/views/anime/watch.html
diff --git a/views/books/book.html b/desktop/views/books/book.html
similarity index 100%
rename from views/books/book.html
rename to desktop/views/books/book.html
diff --git a/views/books/books.html b/desktop/views/books/books.html
similarity index 100%
rename from views/books/books.html
rename to desktop/views/books/books.html
diff --git a/views/books/read.html b/desktop/views/books/read.html
similarity index 100%
rename from views/books/read.html
rename to desktop/views/books/read.html
diff --git a/views/css/anime/anime.css b/desktop/views/css/anime/anime.css
similarity index 100%
rename from views/css/anime/anime.css
rename to desktop/views/css/anime/anime.css
diff --git a/views/css/anime/watch.css b/desktop/views/css/anime/watch.css
similarity index 100%
rename from views/css/anime/watch.css
rename to desktop/views/css/anime/watch.css
diff --git a/views/css/books/book.css b/desktop/views/css/books/book.css
similarity index 100%
rename from views/css/books/book.css
rename to desktop/views/css/books/book.css
diff --git a/views/css/books/reader.css b/desktop/views/css/books/reader.css
similarity index 100%
rename from views/css/books/reader.css
rename to desktop/views/css/books/reader.css
diff --git a/views/css/components/anilist-modal.css b/desktop/views/css/components/anilist-modal.css
similarity index 100%
rename from views/css/components/anilist-modal.css
rename to desktop/views/css/components/anilist-modal.css
diff --git a/views/css/components/hero.css b/desktop/views/css/components/hero.css
similarity index 100%
rename from views/css/components/hero.css
rename to desktop/views/css/components/hero.css
diff --git a/views/css/components/navbar.css b/desktop/views/css/components/navbar.css
similarity index 100%
rename from views/css/components/navbar.css
rename to desktop/views/css/components/navbar.css
diff --git a/views/css/components/titlebar.css b/desktop/views/css/components/titlebar.css
similarity index 100%
rename from views/css/components/titlebar.css
rename to desktop/views/css/components/titlebar.css
diff --git a/views/css/components/updateNotifier.css b/desktop/views/css/components/updateNotifier.css
similarity index 100%
rename from views/css/components/updateNotifier.css
rename to desktop/views/css/components/updateNotifier.css
diff --git a/views/css/gallery/gallery.css b/desktop/views/css/gallery/gallery.css
similarity index 100%
rename from views/css/gallery/gallery.css
rename to desktop/views/css/gallery/gallery.css
diff --git a/views/css/gallery/image.css b/desktop/views/css/gallery/image.css
similarity index 100%
rename from views/css/gallery/image.css
rename to desktop/views/css/gallery/image.css
diff --git a/views/css/globals.css b/desktop/views/css/globals.css
similarity index 100%
rename from views/css/globals.css
rename to desktop/views/css/globals.css
diff --git a/views/css/list.css b/desktop/views/css/list.css
similarity index 100%
rename from views/css/list.css
rename to desktop/views/css/list.css
diff --git a/views/css/marketplace.css b/desktop/views/css/marketplace.css
similarity index 100%
rename from views/css/marketplace.css
rename to desktop/views/css/marketplace.css
diff --git a/views/css/schedule/schedule.css b/desktop/views/css/schedule/schedule.css
similarity index 100%
rename from views/css/schedule/schedule.css
rename to desktop/views/css/schedule/schedule.css
diff --git a/views/css/users.css b/desktop/views/css/users.css
similarity index 100%
rename from views/css/users.css
rename to desktop/views/css/users.css
diff --git a/views/gallery/gallery.html b/desktop/views/gallery/gallery.html
similarity index 100%
rename from views/gallery/gallery.html
rename to desktop/views/gallery/gallery.html
diff --git a/views/gallery/image.html b/desktop/views/gallery/image.html
similarity index 100%
rename from views/gallery/image.html
rename to desktop/views/gallery/image.html
diff --git a/views/list.html b/desktop/views/list.html
similarity index 100%
rename from views/list.html
rename to desktop/views/list.html
diff --git a/views/marketplace.html b/desktop/views/marketplace.html
similarity index 100%
rename from views/marketplace.html
rename to desktop/views/marketplace.html
diff --git a/views/schedule.html b/desktop/views/schedule.html
similarity index 100%
rename from views/schedule.html
rename to desktop/views/schedule.html
diff --git a/views/users.html b/desktop/views/users.html
similarity index 100%
rename from views/users.html
rename to desktop/views/users.html
diff --git a/docker/.dockerignore b/docker/.dockerignore
new file mode 100644
index 0000000..f675cdb
--- /dev/null
+++ b/docker/.dockerignore
@@ -0,0 +1,8 @@
+node_modules
+electron
+dist
+.env
+build
+.gitignore
+Dockerfile
+.dockerignore
diff --git a/docker/.gitignore b/docker/.gitignore
new file mode 100644
index 0000000..d09ab9d
--- /dev/null
+++ b/docker/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+electron
+dist
+.env
+build
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 0000000..f3206c7
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,15 @@
+FROM mcr.microsoft.com/playwright:v1.50.0-jammy
+
+WORKDIR /app
+
+COPY package*.json ./
+
+RUN npm ci
+
+RUN npx playwright install --with-deps chromium
+
+COPY . .
+
+EXPOSE 54322
+
+CMD ["npm", "run", "start"]
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000..0b03e29
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,53 @@
+# π WaifuBoard
+
+**Lightweight all-in-one app for boorus, manga and light novels β no sources included, total freedom via extensions.**
+
+
+
${score}% β’ ${ep}
+${score}% β’ ${type}
+${msg}
`; + } + + if (msnry) msnry.layout(); + + if (!favoritesMode && data.hasNextPage) { + sentinel.style.display = 'block'; + observer.observe(sentinel); + } else { + sentinel.style.display = 'none'; + observer.unobserve(sentinel); + } + + if (isLoadMore) currentPage++; + else currentPage = 1; + + } catch (err) { + console.error('Error:', err); + if (!isLoadMore) { + resultsContainer.innerHTML = 'Error loading gallery
'; + } + } finally { + isLoading = false; + } +} + +async function loadExtensions() { + try { + const res = await fetch('/api/extensions/gallery'); + const data = await res.json(); + + providerSelector.innerHTML = ''; + + const favoritesOption = document.createElement('option'); + favoritesOption.value = 'favorites'; + favoritesOption.textContent = 'Favorites'; + providerSelector.appendChild(favoritesOption); + + (data.extensions || []).forEach(ext => { + const opt = document.createElement('option'); + opt.value = ext; + opt.textContent = ext.charAt(0).toUpperCase() + ext.slice(1); + providerSelector.appendChild(opt); + }); + + } catch (err) { + console.error('Error loading extensions:', err); + } +} + +providerSelector.addEventListener('change', () => { + if (providerSelector.value === 'favorites') { + searchInput.placeholder = "Search in favorites..."; + } else { + searchInput.placeholder = "Search in gallery..."; + } + searchGallery(false); +}); + +let searchTimeout; + +searchInput.addEventListener('input', () => { + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + searchGallery(false); + }, 500); +}); +searchInput.addEventListener('keydown', e => { + if (e.key === 'Enter') { + clearTimeout(searchTimeout); + searchGallery(false); + } +}); + +const observer = new IntersectionObserver(entries => { + if (entries[0].isIntersecting && !isLoading && !favoritesMode) { + observer.unobserve(sentinel); + searchGallery(true); + } +}, { rootMargin: '1000px' }); + +loadFavorites().then(() => { + loadExtensions().then(() => { + searchGallery(false); + }); +}); + +window.addEventListener('scroll', () => { + document.getElementById('navbar')?.classList.toggle('scrolled', window.scrollY > 50); +}); \ No newline at end of file diff --git a/docker/src/scripts/gallery/image.js b/docker/src/scripts/gallery/image.js new file mode 100644 index 0000000..3a60d8c --- /dev/null +++ b/docker/src/scripts/gallery/image.js @@ -0,0 +1,320 @@ +const itemMainContentContainer = document.getElementById('item-main-content'); +let currentItem = null; + +function getAuthHeaders(extra = {}) { + const token = localStorage.getItem("token"); + return token + ? { ...extra, Authorization: `Bearer ${token}` } + : extra; +} + +function getProxiedItemUrl(url, headers = null) { + if (!url || !headers) { + return url; + } + + let proxyUrl = `/api/proxy?url=${encodeURIComponent(url)}`; + + const lowerCaseHeaders = {}; + for (const key in headers) { + if (Object.prototype.hasOwnProperty.call(headers, key)) { + lowerCaseHeaders[key.toLowerCase()] = headers[key]; + } + } + + const referer = lowerCaseHeaders.referer; + if (referer) { + proxyUrl += `&referer=${encodeURIComponent(referer)}`; + } + + const origin = lowerCaseHeaders.origin; + if (origin) { + proxyUrl += `&origin=${encodeURIComponent(origin)}`; + } + + const userAgent = lowerCaseHeaders['user-agent']; + if (userAgent) { + proxyUrl += `&userAgent=${encodeURIComponent(userAgent)}`; + } + + return proxyUrl; +} + +function getUrlParams() { + const path = window.location.pathname.split('/').filter(s => s); + if (path.length < 3 || path[0] !== 'gallery') return null; + + if (path[1] === 'favorites' && path[2]) { + return { fromFavorites: true, id: path[2] }; + } else { + return { provider: path[1], id: path.slice(2).join('/') }; + } +} + +async function toggleFavorite() { + if (!currentItem?.id) return; + + const btn = document.getElementById('fav-btn'); + const wasFavorited = btn.classList.contains('favorited'); + + try { + if (wasFavorited) { + await fetch(`/api/gallery/favorites/${encodeURIComponent(currentItem.id)}`, { + method: 'DELETE', + headers: getAuthHeaders() + }); + } else { + const serializedHeaders = currentItem.headers ? JSON.stringify(currentItem.headers) : ""; + const tagsString = Array.isArray(currentItem.tags) ? currentItem.tags.join(',') : (currentItem.tags || ''); + + await fetch('/api/gallery/favorites', { + method: 'POST', + headers: getAuthHeaders({ + 'Content-Type': 'application/json' + }), + body: JSON.stringify({ + id: currentItem.id, + title: currentItem.title || 'Waifu', + image_url: currentItem.originalImage, + thumbnail_url: currentItem.originalImage, + tags: tagsString, + provider: currentItem.provider || "", + headers: serializedHeaders + }) + }); + + } + + btn.classList.toggle('favorited', !wasFavorited); + btn.innerHTML = !wasFavorited + ? ` Saved!` + : ` Save Image`; + + } catch (err) { + console.error('Error toggling favorite:', err); + alert('Error updating favorites'); + } +} + +function copyLink() { + navigator.clipboard.writeText(window.location.href); + const btn = document.getElementById('copy-link-btn'); + const old = btn.innerHTML; + btn.innerHTML = ` Copied!`; + setTimeout(() => btn.innerHTML = old, 2000); +} + +async function loadSimilarImages(item) { + if (!item.tags || item.tags.length === 0) { + document.getElementById('similar-section').innerHTML = 'No tags available to search for similar images.
'; + return; + } + + const firstTag = item.tags[0]; + const container = document.getElementById('similar-section'); + + try { + const res = await fetch(`/api/gallery/search?q=${encodeURIComponent(firstTag)}&perPage=20`); + if (!res.ok) throw new Error(); + + const data = await res.json(); + const results = (data.results || []) + .filter(r => r.id !== item.id) + .slice(0, 15); + + if (results.length === 0) { + container.innerHTML = 'No similar images found.
'; + return; + } + + container.innerHTML = ` +Could not load similar images.
'; + } +} + +function renderItem(item) { + const proxiedFullImage = getProxiedItemUrl(item.fullImage, item.headers); + + let sourceText; + if (item.fromFavorites) { + sourceText = item.headers && item.provider && item.provider !== 'Favorites' + ? `Source: ${item.provider}` + : 'Favorites'; + } else { + sourceText = `Source: ${item.provider}`; + } + + const originalProviderText = (item.fromFavorites && item.provider && item.provider !== 'Favorites') + ? ` (Original: ${item.provider})` + : ''; + + itemMainContentContainer.innerHTML = ` +Error: HTML container 'item-main-content' not found. Please update gallery-image.html.
`; + document.getElementById('similar-section').style.display = 'none'; +} + +window.addEventListener('scroll', () => { + document.getElementById('navbar')?.classList.toggle('scrolled', window.scrollY > 50); +}); \ No newline at end of file diff --git a/docker/src/scripts/list.js b/docker/src/scripts/list.js new file mode 100644 index 0000000..fd6dd40 --- /dev/null +++ b/docker/src/scripts/list.js @@ -0,0 +1,368 @@ +const API_BASE = '/api'; +let currentList = []; +let filteredList = []; + +document.addEventListener('DOMContentLoaded', async () => { + await loadList(); + setupEventListeners(); +}); + +function getEntryLink(item) { + const isAnime = item.entry_type?.toUpperCase() === 'ANIME'; + const baseRoute = isAnime ? '/anime' : '/book'; + const source = item.source || 'anilist'; + + if (source === 'anilist') { + return `${baseRoute}/${item.entry_id}`; + } else { + return `${baseRoute}/${source}/${item.entry_id}`; + } +} + +async function populateSourceFilter() { + const select = document.getElementById('source-filter'); + if (!select) return; + + select.innerHTML = ` + + + `; + + try { + const response = await fetch(`${API_BASE}/extensions`); + if (response.ok) { + const data = await response.json(); + const extensions = data.extensions || []; + + extensions.forEach(extName => { + if (extName.toLowerCase() !== 'anilist' && extName.toLowerCase() !== 'local') { + const option = document.createElement('option'); + option.value = extName; + option.textContent = extName.charAt(0).toUpperCase() + extName.slice(1); + select.appendChild(option); + } + }); + } + } catch (error) { + console.error('Error loading extensions:', error); + } +} + +function updateLocalList(entryData, action) { + const entryId = entryData.entry_id; + const source = entryData.source; + + const findIndex = (list) => list.findIndex(e => + e.entry_id === entryId && e.source === source + ); + + const currentIndex = findIndex(currentList); + if (currentIndex !== -1) { + if (action === 'update') { + + currentList[currentIndex] = { ...currentList[currentIndex], ...entryData }; + } else if (action === 'delete') { + currentList.splice(currentIndex, 1); + } + } else if (action === 'update') { + + currentList.push(entryData); + } + + filteredList = [...currentList]; + + updateStats(); + applyFilters(); + window.ListModalManager.close(); +} + +function setupEventListeners() { + + document.querySelectorAll('.view-btn').forEach(btn => { + btn.addEventListener('click', () => { + document.querySelectorAll('.view-btn').forEach(b => b.classList.remove('active')); + btn.classList.add('active'); + const view = btn.dataset.view; + const container = document.getElementById('list-container'); + if (view === 'list') { + container.classList.add('list-view'); + } else { + container.classList.remove('list-view'); + } + }); + }); + + document.getElementById('status-filter').addEventListener('change', applyFilters); + document.getElementById('source-filter').addEventListener('change', applyFilters); + document.getElementById('type-filter').addEventListener('change', applyFilters); + document.getElementById('sort-filter').addEventListener('change', applyFilters); + + document.querySelector('.search-input').addEventListener('input', (e) => { + const query = e.target.value.toLowerCase(); + if (query) { + filteredList = currentList.filter(item => + item.title?.toLowerCase().includes(query) + ); + } else { + filteredList = [...currentList]; + } + applyFilters(); + }); + + document.getElementById('modal-save-btn')?.addEventListener('click', async () => { + + const entryToSave = window.ListModalManager.currentEntry || window.ListModalManager.currentData; + + if (!entryToSave) return; + + const success = await window.ListModalManager.save(entryToSave.entry_id, entryToSave.source); + + if (success) { + + const updatedEntry = window.ListModalManager.currentEntry; + updatedEntry.updated_at = new Date().toISOString(); + + updateLocalList(updatedEntry, 'update'); + } + + }); + + document.getElementById('modal-delete-btn')?.addEventListener('click', async () => { + const entryToDelete = window.ListModalManager.currentEntry || window.ListModalManager.currentData; + + if (!entryToDelete) return; + + const success = await window.ListModalManager.delete(entryToDelete.entry_id, entryToDelete.source); + + if (success) { + updateLocalList(entryToDelete, 'delete'); + } + + }); + + document.getElementById('add-list-modal')?.addEventListener('click', (e) => { + if (e.target.id === 'add-list-modal') { + window.ListModalManager.close(); + } + }); +} + +async function loadList() { + const loadingState = document.getElementById('loading-state'); + const emptyState = document.getElementById('empty-state'); + const container = document.getElementById('list-container'); + + await populateSourceFilter(); + + try { + loadingState.style.display = 'flex'; + emptyState.style.display = 'none'; + container.innerHTML = ''; + + const response = await fetch(`${API_BASE}/list`, { + headers: window.AuthUtils.getSimpleAuthHeaders() + }); + + if (!response.ok) { + throw new Error('Failed to load list'); + } + + const data = await response.json(); + currentList = data.results || []; + filteredList = [...currentList]; + + loadingState.style.display = 'none'; + + if (currentList.length === 0) { + emptyState.style.display = 'flex'; + } else { + updateStats(); + applyFilters(); + } + } catch (error) { + console.error('Error loading list:', error); + loadingState.style.display = 'none'; + if (window.NotificationUtils) { + window.NotificationUtils.error('Failed to load your list. Please try again.'); + } else { + alert('Failed to load your list. Please try again.'); + } + } +} + +function updateStats() { + + const total = currentList.length; + const watching = currentList.filter(item => item.status === 'WATCHING').length; + const completed = currentList.filter(item => item.status === 'COMPLETED').length; + const planning = currentList.filter(item => item.status === 'PLANNING').length; + + document.getElementById('total-count').textContent = total; + document.getElementById('watching-count').textContent = watching; + document.getElementById('completed-count').textContent = completed; + document.getElementById('planned-count').textContent = planning; +} + +function applyFilters() { + const statusFilter = document.getElementById('status-filter').value; + const sourceFilter = document.getElementById('source-filter').value; + const typeFilter = document.getElementById('type-filter').value; + const sortFilter = document.getElementById('sort-filter').value; + + let filtered = [...filteredList]; + + if (statusFilter !== 'all') { + filtered = filtered.filter(item => item.status === statusFilter); + } + + if (sourceFilter !== 'all') { + filtered = filtered.filter(item => item.source === sourceFilter); + } + + if (typeFilter !== 'all') { + filtered = filtered.filter(item => item.entry_type === typeFilter); + } + + switch (sortFilter) { + case 'title': + filtered.sort((a, b) => (a.title || '').localeCompare(b.title || '')); + break; + case 'score': + filtered.sort((a, b) => (b.score || 0) - (a.score || 0)); + break; + case 'progress': + filtered.sort((a, b) => (b.progress || 0) - (a.progress || 0)); + break; + case 'updated': + default: + + filtered.sort((a, b) => new Date(b.updated_at) - new Date(a.updated_at)); + break; + } + + renderList(filtered); +} + +function renderList(items) { + const container = document.getElementById('list-container'); + container.innerHTML = ''; + + if (items.length === 0) { + + if (currentList.length === 0) { + document.getElementById('empty-state').style.display = 'flex'; + } else { + + container.innerHTML = 'No entries match your filters
No extensions found for the selected filter (${filterType}).
`; + } +} + +async function loadMarketplace() { + extensionsGrid.innerHTML = ''; + + for (let i = 0; i < 6; i++) { + extensionsGrid.innerHTML += ` +Could not connect to the extension repository or local endpoint. Detail: ${error.message}
+Create your first profile to get started
+User ID: ${integration.anilistUserId}
+Expires: ${new Date(integration.expiresAt).toLocaleDateString()}
++ Sync your anime list by logging in with AniList. +
++ You will be redirected and then returned here. +
+${unitLabel} ${progressText} - ${item.source}
+Update available: v1.x
+ + Click To Download + +Update available: v1.x
+ + + Click To Download + +Loading description...
+Episode --
+Update available: v1.x
+ + + Click To Download + +| # | +Title | + +Provider | +Action | +
|---|
Update available: v1.x
+ + + Click To Download + +Update available: v1.x
+ + + Click To Download + +Update available: v1.x
+ + + Click To Download + +Update available: v1.x
+ + + Click To Download + +Loading similar images...
+Update available: v1.x
+ + + Click To Download + +Loading your list...
+Update available: v1.x
+ + Click To Download + +Explore, install, and manage all available data source extensions for WaifuBoard.
+Update available: v1.x
+ + + Click To Download + +Update available: v1.x
+ + + Click To Download + +Select your profile to continue
+Update available: v1.x
+ + Click To Download + +