mirror of
https://github.com/danbulant/Mangades
synced 2026-07-08 12:30:36 +00:00
swiper + more information on manga page
This commit is contained in:
parent
fb8b2b3548
commit
8e253443d1
9 changed files with 268 additions and 65 deletions
|
|
@ -12,6 +12,7 @@
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
<link rel='icon' type='image/png' href='/favicon.png'>
|
||||||
<link rel="modulepreload" href="/build/main.js" />
|
<link rel="modulepreload" href="/build/main.js" />
|
||||||
|
<link rel='stylesheet' href='/swiper.min.css'>
|
||||||
|
|
||||||
<script type="module" src="/build/main.js"></script>
|
<script type="module" src="/build/main.js"></script>
|
||||||
<script src="https://unpkg.com/web-streams-polyfill/dist/polyfill.min.js"></script>
|
<script src="https://unpkg.com/web-streams-polyfill/dist/polyfill.min.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ html, body {
|
||||||
body {
|
body {
|
||||||
color: #333;
|
color: #333;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 8px;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
assets/swiper.min.css
vendored
Normal file
13
assets/swiper.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -58,6 +58,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fflate": "^0.6.10",
|
"fflate": "^0.6.10",
|
||||||
"streamsaver": "^2.0.5"
|
"streamsaver": "^2.0.5",
|
||||||
|
"swiper": "^8.1.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
import svelte from 'rollup-plugin-svelte-hot';
|
import svelte from 'rollup-plugin-svelte-hot';
|
||||||
import Hmr from 'rollup-plugin-hot'
|
import Hmr from 'rollup-plugin-hot';
|
||||||
import resolve from '@rollup/plugin-node-resolve';
|
import resolve from '@rollup/plugin-node-resolve';
|
||||||
import commonjs from '@rollup/plugin-commonjs';
|
import commonjs from '@rollup/plugin-commonjs';
|
||||||
import livereload from 'rollup-plugin-livereload';
|
import livereload from 'rollup-plugin-livereload';
|
||||||
import { terser } from 'rollup-plugin-terser';
|
import { terser } from 'rollup-plugin-terser';
|
||||||
import { copySync, removeSync } from 'fs-extra'
|
import { copySync, removeSync } from 'fs-extra';
|
||||||
import { spassr } from 'spassr'
|
import { spassr } from 'spassr';
|
||||||
import getConfig from '@roxi/routify/lib/utils/config'
|
import getConfig from '@roxi/routify/lib/utils/config';
|
||||||
import autoPreprocess from 'svelte-preprocess'
|
import autoPreprocess from 'svelte-preprocess';
|
||||||
import postcssImport from 'postcss-import'
|
import postcssImport from 'postcss-import';
|
||||||
import { injectManifest } from 'rollup-plugin-workbox'
|
import { injectManifest } from 'rollup-plugin-workbox';
|
||||||
|
|
||||||
|
|
||||||
const { distDir } = getConfig() // use Routify's distDir for SSOT
|
const { distDir } = getConfig() // use Routify's distDir for SSOT
|
||||||
const assetsDir = 'assets'
|
const assetsDir = 'assets'
|
||||||
|
|
@ -22,7 +21,6 @@ const production = !process.env.ROLLUP_WATCH;
|
||||||
removeSync(distDir)
|
removeSync(distDir)
|
||||||
removeSync(buildDir)
|
removeSync(buildDir)
|
||||||
|
|
||||||
|
|
||||||
const serve = () => ({
|
const serve = () => ({
|
||||||
writeBundle: async () => {
|
writeBundle: async () => {
|
||||||
const options = {
|
const options = {
|
||||||
|
|
|
||||||
35
src/components/artList.svelte
Normal file
35
src/components/artList.svelte
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script>
|
||||||
|
import request from "../util/request";
|
||||||
|
|
||||||
|
export var mangaId;
|
||||||
|
|
||||||
|
let list;
|
||||||
|
$: list = request("cover?manga[]=" + mangaId + "&locales[]=en&locales[]=uk&locales[]=ja");
|
||||||
|
|
||||||
|
$: list.then(data => console.log(data));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#await list}
|
||||||
|
Loading art
|
||||||
|
{:then list}
|
||||||
|
{#each list.data.sort((a, b) => a.attributes.volume - b.attributes.volume) as item}
|
||||||
|
<img width=512 height=805 src="https://cors-anywhere.danbulant.workers.dev/?https://uploads.mangadex.org/covers/{mangaId}/{item.attributes.fileName}.512.jpg" alt="">
|
||||||
|
{/each}
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 10rem;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -30,8 +30,8 @@
|
||||||
<td class="no-wrap">{chapter.attributes.volume ? "Vol " + chapter.attributes.volume : ""}</td>
|
<td class="no-wrap">{chapter.attributes.volume ? "Vol " + chapter.attributes.volume : ""}</td>
|
||||||
<td class="no-wrap">{chapter.attributes.chapter ? "Chapter " + chapter.attributes.chapter : ""}</td>
|
<td class="no-wrap">{chapter.attributes.chapter ? "Chapter " + chapter.attributes.chapter : ""}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="title">{chapter.attributes.title}</div>
|
<div class="title">{chapter.attributes.title || " "}</div>
|
||||||
<div class="scanlation">{scanlationGroup}</div>
|
<div class="scanlation">{scanlationGroup || "Unknown group"}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="action no-wrap"><a href={$url("./" + chapter.id)} on:click|stopPropagation={() => !disabledDownload && dispatch("view")}>View</a></td>
|
<td class="action no-wrap"><a href={$url("./" + chapter.id)} on:click|stopPropagation={() => !disabledDownload && dispatch("view")}>View</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
||||||
31
src/components/tabs/tabs.svelte
Normal file
31
src/components/tabs/tabs.svelte
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
<script>
|
||||||
|
export var list;
|
||||||
|
export var selected = list[0];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#each list as item}
|
||||||
|
<button on:click={() => selected = item} class:active={selected == item}>{item}</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
button:first-child {
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
}
|
||||||
|
button:last-child {
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
}
|
||||||
|
button.active {
|
||||||
|
background: rgb(202, 202, 202);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -6,6 +6,11 @@
|
||||||
import request from "../../util/request";
|
import request from "../../util/request";
|
||||||
import { BaseGenerator } from "../../util/baseGenerator";
|
import { BaseGenerator } from "../../util/baseGenerator";
|
||||||
import { arraysEqual } from "../../util/arrays";
|
import { arraysEqual } from "../../util/arrays";
|
||||||
|
import { makeRequest } from "../../util/anilist";
|
||||||
|
import Tabs from "../../components/tabs/tabs.svelte";
|
||||||
|
import { slide } from "svelte/transition";
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/svelte';
|
||||||
|
import ArtList from "../../components/artList.svelte";
|
||||||
|
|
||||||
export var scoped;
|
export var scoped;
|
||||||
|
|
||||||
|
|
@ -19,10 +24,10 @@
|
||||||
$: title = manga.title.en || manga.title.jp || Object.values(manga.title)[0];
|
$: title = manga.title.en || manga.title.jp || Object.values(manga.title)[0];
|
||||||
|
|
||||||
async function getMangaChapters(id) {
|
async function getMangaChapters(id) {
|
||||||
const data = await request("manga/" + id + "/feed?limit=500&translatedLanguage[]=en&includes[]=scanlation_group");
|
const data = await request("manga/" + id + "/feed?limit=500&translatedLanguage[]=en&translatedLanguage[]=uk&includes[]=scanlation_group");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
data.data = data.data
|
data.data = data.data
|
||||||
.filter(datum => !datum.attributes?.externalUrl)
|
.filter(item => !item.attributes?.externalUrl)
|
||||||
.sort((a, b) => a.attributes.chapter - b.attributes.chapter);
|
.sort((a, b) => a.attributes.chapter - b.attributes.chapter);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
@ -213,6 +218,54 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function anilistInfo(title) {
|
||||||
|
return makeRequest(`
|
||||||
|
query ($search: String) {
|
||||||
|
Media(search: $search, format: MANGA) {
|
||||||
|
id
|
||||||
|
type
|
||||||
|
format
|
||||||
|
status
|
||||||
|
chapters
|
||||||
|
volumes
|
||||||
|
countryOfOrigin
|
||||||
|
bannerImage
|
||||||
|
genres
|
||||||
|
synonyms
|
||||||
|
averageScore
|
||||||
|
popularity
|
||||||
|
isFavourite
|
||||||
|
isFavouriteBlocked
|
||||||
|
isAdult
|
||||||
|
siteUrl
|
||||||
|
coverImage {
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, { search: title }).then(t => t.data.Media);
|
||||||
|
}
|
||||||
|
|
||||||
|
let anilistData;
|
||||||
|
$: anilistData = anilistInfo(title);
|
||||||
|
|
||||||
|
var selected = "Chapters";
|
||||||
|
const tabs = ["Chapters", "Art", "More information"];
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if(swiper && tabs.indexOf(selected) !== swiper.realIndex) swiper.slideTo(tabs.indexOf(selected));
|
||||||
|
}
|
||||||
|
|
||||||
|
let swiper;
|
||||||
|
function swiperInit(e) {
|
||||||
|
swiper = e.detail[0];
|
||||||
|
}
|
||||||
|
function swiperUpdate() {
|
||||||
|
if(selected !== tabs[swiper.realIndex])
|
||||||
|
selected = tabs[swiper.realIndex];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:beforeUnload={beforeUnload} />
|
<svelte:window on:beforeUnload={beforeUnload} />
|
||||||
|
|
@ -222,7 +275,14 @@
|
||||||
<meta name="description" value="Read {title} online, or download it as EPUB or CBZ file. Free of charge and ads." />
|
<meta name="description" value="Read {title} online, or download it as EPUB or CBZ file. Free of charge and ads." />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
{#await anilistData then data}
|
||||||
|
{#if data.bannerImage}
|
||||||
|
<img class="banner" src={data.bannerImage} alt="">
|
||||||
|
{/if}
|
||||||
|
{/await}
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
||||||
<h1>{title}</h1>
|
<h1>{title}</h1>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
|
|
@ -232,7 +292,7 @@
|
||||||
{#if manga.year}
|
{#if manga.year}
|
||||||
{manga.year} ·
|
{manga.year} ·
|
||||||
{/if}
|
{/if}
|
||||||
{manga.status}
|
{#await anilistData then data} {data.status} {data.isAdult ? "· 18+" : ""} {/await}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
|
@ -263,68 +323,131 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if copyrightOpen}
|
{#if copyrightOpen}
|
||||||
<p class="copyright">
|
<p class="copyright" transition:slide={{ duration: 500 }}>
|
||||||
Open <a href="https://mangadex.org/title/{mangaId}">Mangadex.org page of this manga</a>, select MORE and click REPORT. I cannot delete the content, even if you report it to this website's hosting, as this is just one of many clients to mangadex.
|
Open <a href="https://mangadex.org/title/{mangaId}">Mangadex.org page of this manga</a>, select MORE and click REPORT. I cannot delete the content, even if you report it to this website's hosting, as this is just one of many clients to mangadex.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
In case of reports, I can just block the content from being loaded in this page, but that doesn't mean it's deleted nor that any other client can't access it. And this website is smaller than mangadex, so it doesn't make sense to bother yourself with reporting it to this site when you can report it to one and have it removed from more sites, including this one.
|
In case of reports, I can just block the content from being loaded in this page, but that doesn't mean it's deleted nor that any other client can't access it. To properly request deletion, contact Mangadex.org. After it's deleted from Mangadex.org, this website will no longer allow access to it (since it physically cannot, as it doesn't store any content).
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="state {state}">
|
<Tabs list={tabs} bind:selected />
|
||||||
<div class="progress" style="width: {progress * 100}%;"></div>
|
|
||||||
|
|
||||||
<p>
|
<Swiper
|
||||||
{text}
|
on:init={swiperInit}
|
||||||
</p>
|
spaceBetween={50}
|
||||||
</div>
|
autoHeight={true}
|
||||||
|
slidesPerView={1}
|
||||||
|
on:slideChange={swiperUpdate}
|
||||||
|
on:swiper={(e) => console.log(e.detail[0])}
|
||||||
|
>
|
||||||
|
<SwiperSlide>
|
||||||
|
<div style="min-height: 30rem;">
|
||||||
|
<div class="state {state}">
|
||||||
|
<div class="progress" style="width: {progress * 100}%;"></div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if queue.length > 0}
|
||||||
|
<p><i>{queue.length} downloads queued.</i></p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="download">
|
||||||
|
<select name="format" bind:value={format} id="select-format">
|
||||||
|
<option value="cbz"><b>.cbz</b> Comic Book Zip</option>
|
||||||
|
<option value="epub"><b>.epub</b> Electronic publication</option>
|
||||||
|
</select>
|
||||||
|
<button disabled={!selected.length} on:click={downloadMulti}>Download</button>
|
||||||
|
<button disabled={!selected.length} on:click={downloadSeparate}>Download Separate</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<p>
|
||||||
|
<b>
|
||||||
|
Do not close the tab when a download is in progress.
|
||||||
|
</b>
|
||||||
|
</p>
|
||||||
|
<button on:click={selectAll}>
|
||||||
|
Select all
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if queue.length > 0}
|
{#await chapters}
|
||||||
<p><i>{queue.length} downloads queued.</i></p>
|
Loading chapters...
|
||||||
{/if}
|
{:then chapters}
|
||||||
|
{#if chapters.data.length === 0}
|
||||||
<div class="download">
|
<p>No chapters found.</p>
|
||||||
<select name="format" bind:value={format} id="select-format">
|
{/if}
|
||||||
<option value="cbz"><b>.cbz</b> Comic Book Zip</option>
|
<table>
|
||||||
<option value="epub"><b>.epub</b> Electronic publication</option>
|
<tbody>
|
||||||
</select>
|
{#each chapters.data as chapter, i}
|
||||||
<button disabled={!selected.length} on:click={downloadMulti}>Download</button>
|
<Chapter progress={(progressMap.get(chapter.id) || 0) / chapter.attributes.pages} {chapter} disabledDownload={!!progress} selected={selected.includes(chapter)} on:select={() => select(chapter)} on:download={() => downloadSingle(chapter)} />
|
||||||
<button disabled={!selected.length} on:click={downloadSeparate}>Download Separate</button>
|
{/each}
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
<div class="flex">
|
{/await}
|
||||||
<p>
|
</div>
|
||||||
<b>
|
</SwiperSlide>
|
||||||
Do not close the tab when a download is in progress.
|
<SwiperSlide>
|
||||||
</b>
|
<div style="min-height: 30rem;">
|
||||||
</p>
|
<ArtList {mangaId} />
|
||||||
<button on:click={selectAll}>
|
</div>
|
||||||
Select all
|
</SwiperSlide>
|
||||||
</button>
|
<SwiperSlide>
|
||||||
</div>
|
<div style="min-height: 30rem;">
|
||||||
|
{#await anilistData then data}
|
||||||
{#await chapters}
|
<a href={data.siteUrl}>Anilist entry</a> <br> <br>
|
||||||
Loading chapters...
|
|
||||||
{:then chapters}
|
Genres:
|
||||||
{#if chapters.data.length === 0}
|
{#each data.genres as genre}
|
||||||
<p>No chapters found.</p>
|
<span class="genre">{genre}</span>
|
||||||
{/if}
|
{/each}
|
||||||
<table>
|
<br>
|
||||||
<tbody>
|
|
||||||
{#each chapters.data as chapter, i}
|
AL popularity: {data.popularity} <br>
|
||||||
<Chapter progress={(progressMap.get(chapter.id) || 0) / chapter.attributes.pages} {chapter} disabledDownload={!!progress} selected={selected.includes(chapter)} on:select={() => select(chapter)} on:download={() => downloadSingle(chapter)} />
|
favorite on AL: {data.isFavourite ? "yes" : "no"} <br>
|
||||||
{/each}
|
AL score: {data.averageScore} <br>
|
||||||
</tbody>
|
Also known as: {data.synonyms.join(", ")} {Object.values(manga.title).filter(t => t !== title).join(", ")}
|
||||||
</table>
|
{/await}
|
||||||
{/await}
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
<p>DISCLAIMER: This site isn't distributing any content and is using mangadex.org API. Website is open source, and I don't claim any copyright on the publications.</p>
|
</Swiper>
|
||||||
<br>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
.banner {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 30vh;
|
||||||
|
object-fit: cover;
|
||||||
|
animation: reveal 2s cubic-bezier(0, 0, 0.08, 0.99);
|
||||||
|
}
|
||||||
|
.genre {
|
||||||
|
border-radius: 5px;
|
||||||
|
background: rgb(204, 204, 204);
|
||||||
|
padding: 0.3rem;
|
||||||
|
margin: 0.3rem;
|
||||||
|
}
|
||||||
|
.tabbed {
|
||||||
|
min-height: 20rem;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion) {
|
||||||
|
.banner {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes reveal {
|
||||||
|
from {
|
||||||
|
max-height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
max-height: 30vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
.cover {
|
.cover {
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
height: 350px;
|
height: 350px;
|
||||||
|
|
@ -379,6 +502,7 @@
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.no-wrap {
|
.no-wrap {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue