mirror of
https://github.com/danbulant/Mangades
synced 2026-06-20 23:01:28 +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='icon' type='image/png' href='/favicon.png'>
|
||||
<link rel="modulepreload" href="/build/main.js" />
|
||||
<link rel='stylesheet' href='/swiper.min.css'>
|
||||
|
||||
<script type="module" src="/build/main.js"></script>
|
||||
<script src="https://unpkg.com/web-streams-polyfill/dist/polyfill.min.js"></script>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ html, body {
|
|||
body {
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
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": {
|
||||
"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 Hmr from 'rollup-plugin-hot'
|
||||
import Hmr from 'rollup-plugin-hot';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import livereload from 'rollup-plugin-livereload';
|
||||
import { terser } from 'rollup-plugin-terser';
|
||||
import { copySync, removeSync } from 'fs-extra'
|
||||
import { spassr } from 'spassr'
|
||||
import getConfig from '@roxi/routify/lib/utils/config'
|
||||
import autoPreprocess from 'svelte-preprocess'
|
||||
import postcssImport from 'postcss-import'
|
||||
import { injectManifest } from 'rollup-plugin-workbox'
|
||||
|
||||
import { copySync, removeSync } from 'fs-extra';
|
||||
import { spassr } from 'spassr';
|
||||
import getConfig from '@roxi/routify/lib/utils/config';
|
||||
import autoPreprocess from 'svelte-preprocess';
|
||||
import postcssImport from 'postcss-import';
|
||||
import { injectManifest } from 'rollup-plugin-workbox';
|
||||
|
||||
const { distDir } = getConfig() // use Routify's distDir for SSOT
|
||||
const assetsDir = 'assets'
|
||||
|
|
@ -22,7 +21,6 @@ const production = !process.env.ROLLUP_WATCH;
|
|||
removeSync(distDir)
|
||||
removeSync(buildDir)
|
||||
|
||||
|
||||
const serve = () => ({
|
||||
writeBundle: async () => {
|
||||
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.chapter ? "Chapter " + chapter.attributes.chapter : ""}</td>
|
||||
<td>
|
||||
<div class="title">{chapter.attributes.title}</div>
|
||||
<div class="scanlation">{scanlationGroup}</div>
|
||||
<div class="title">{chapter.attributes.title || " "}</div>
|
||||
<div class="scanlation">{scanlationGroup || "Unknown group"}</div>
|
||||
</td>
|
||||
<td class="action no-wrap"><a href={$url("./" + chapter.id)} on:click|stopPropagation={() => !disabledDownload && dispatch("view")}>View</a></td>
|
||||
</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 { BaseGenerator } from "../../util/baseGenerator";
|
||||
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;
|
||||
|
||||
|
|
@ -19,10 +24,10 @@
|
|||
$: title = manga.title.en || manga.title.jp || Object.values(manga.title)[0];
|
||||
|
||||
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);
|
||||
data.data = data.data
|
||||
.filter(datum => !datum.attributes?.externalUrl)
|
||||
.filter(item => !item.attributes?.externalUrl)
|
||||
.sort((a, b) => a.attributes.chapter - b.attributes.chapter);
|
||||
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>
|
||||
|
||||
<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." />
|
||||
</svelte:head>
|
||||
|
||||
{#await anilistData then data}
|
||||
{#if data.bannerImage}
|
||||
<img class="banner" src={data.bannerImage} alt="">
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
<main>
|
||||
|
||||
<h1>{title}</h1>
|
||||
|
||||
<h3>
|
||||
|
|
@ -232,7 +292,7 @@
|
|||
{#if manga.year}
|
||||
{manga.year} ·
|
||||
{/if}
|
||||
{manga.status}
|
||||
{#await anilistData then data} {data.status} {data.isAdult ? "· 18+" : ""} {/await}
|
||||
</h3>
|
||||
|
||||
<div class="flex">
|
||||
|
|
@ -263,68 +323,131 @@
|
|||
</div>
|
||||
|
||||
{#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.
|
||||
<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>
|
||||
{/if}
|
||||
|
||||
<br>
|
||||
|
||||
<div class="state {state}">
|
||||
<div class="progress" style="width: {progress * 100}%;"></div>
|
||||
<Tabs list={tabs} bind:selected />
|
||||
|
||||
<p>
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
<Swiper
|
||||
on:init={swiperInit}
|
||||
spaceBetween={50}
|
||||
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}
|
||||
<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>
|
||||
|
||||
{#await chapters}
|
||||
Loading chapters...
|
||||
{:then chapters}
|
||||
{#if chapters.data.length === 0}
|
||||
<p>No chapters found.</p>
|
||||
{/if}
|
||||
<table>
|
||||
<tbody>
|
||||
{#each chapters.data as chapter, i}
|
||||
<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)} />
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
|
||||
<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>
|
||||
<br>
|
||||
{#await chapters}
|
||||
Loading chapters...
|
||||
{:then chapters}
|
||||
{#if chapters.data.length === 0}
|
||||
<p>No chapters found.</p>
|
||||
{/if}
|
||||
<table>
|
||||
<tbody>
|
||||
{#each chapters.data as chapter, i}
|
||||
<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)} />
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
{/await}
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
<SwiperSlide>
|
||||
<div style="min-height: 30rem;">
|
||||
<ArtList {mangaId} />
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
<SwiperSlide>
|
||||
<div style="min-height: 30rem;">
|
||||
{#await anilistData then data}
|
||||
<a href={data.siteUrl}>Anilist entry</a> <br> <br>
|
||||
|
||||
Genres:
|
||||
{#each data.genres as genre}
|
||||
<span class="genre">{genre}</span>
|
||||
{/each}
|
||||
<br>
|
||||
|
||||
AL popularity: {data.popularity} <br>
|
||||
favorite on AL: {data.isFavourite ? "yes" : "no"} <br>
|
||||
AL score: {data.averageScore} <br>
|
||||
Also known as: {data.synonyms.join(", ")} {Object.values(manga.title).filter(t => t !== title).join(", ")}
|
||||
{/await}
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
</main>
|
||||
|
||||
<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 {
|
||||
border-radius: 10px;
|
||||
height: 350px;
|
||||
|
|
@ -379,6 +502,7 @@
|
|||
}
|
||||
main {
|
||||
font-size: 1.1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
.no-wrap {
|
||||
white-space: nowrap;
|
||||
|
|
|
|||
Loading…
Reference in a new issue