|
|
@@ -1,47 +1,110 @@
|
|
|
import clsx from "clsx";
|
|
|
+import { GetServerSideProps } from "next";
|
|
|
import Link from "next/link";
|
|
|
+import { useRouter } from "next/router";
|
|
|
import { ReactElement, useState } from "react";
|
|
|
import Footer from "../../../components/common/Footer";
|
|
|
import Header from "../../../components/common/Header";
|
|
|
import NovelCover from "../../../components/NovelCover";
|
|
|
import styles from "../../../styles/chapter.module.scss";
|
|
|
+import { ChapterData, ChapterListData } from "../../../types/http";
|
|
|
+import useGet from "../../../utils/hooks/useGet";
|
|
|
+import { get } from "../../../utils/http";
|
|
|
import type { NextPageWithLayout } from "../../_app";
|
|
|
+import toggleTheme from "../../../libs/toggleTheme";
|
|
|
+import Settings from "../../../components/novel/Settings";
|
|
|
+import Toolbar from "../../../components/novel/Toolbar";
|
|
|
+import Toc from "../../../components/novel/Toc";
|
|
|
|
|
|
const Chapter: NextPageWithLayout = () => {
|
|
|
+ const { query } = useRouter();
|
|
|
+
|
|
|
+ const { data: chapterData = null } = useGet<ChapterData>(
|
|
|
+ `/api/novel/chapter/${query.slug}/${query.chapter}`
|
|
|
+ );
|
|
|
+ // const { data: { data: chapterData } = { data: null } } = useGet<ChapterData>(
|
|
|
+ // `/api/novel/chapter/${query.slug}/${query.chapter}`
|
|
|
+ // );
|
|
|
+
|
|
|
const [open, setOpen] = useState(false);
|
|
|
+ const [menu, setMenu] = useState("");
|
|
|
+ const [isSerif, setIsSerif] = useState(
|
|
|
+ JSON.parse(
|
|
|
+ typeof localStorage !== "undefined" &&
|
|
|
+ localStorage.getItem("novelIsSerif")
|
|
|
+ ? localStorage.getItem("novelIsSerif") || "false"
|
|
|
+ : "false"
|
|
|
+ ) as boolean
|
|
|
+ );
|
|
|
|
|
|
- const toggleTheme = function () {
|
|
|
- const _html = document.documentElement;
|
|
|
- if (_html.classList.contains("dark")) {
|
|
|
- _html.classList.remove("dark");
|
|
|
- localStorage.setItem("theme", "lite");
|
|
|
+ const [fontSize, setFontSize] = useState(
|
|
|
+ Number(
|
|
|
+ typeof localStorage !== "undefined" &&
|
|
|
+ localStorage.getItem("novelFontSize")
|
|
|
+ ? localStorage.getItem("novelFontSize")
|
|
|
+ : "3"
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ const handleOpenToolbar = () => {
|
|
|
+ if (open) {
|
|
|
+ setOpen(false);
|
|
|
+ setMenu("");
|
|
|
} else {
|
|
|
- _html.classList.add("dark");
|
|
|
- localStorage.setItem("theme", "dark");
|
|
|
+ setOpen(true);
|
|
|
}
|
|
|
};
|
|
|
+
|
|
|
+ const handleChangeSettings = (type: string = "") => {
|
|
|
+ if (menu === type) {
|
|
|
+ setMenu("");
|
|
|
+ } else {
|
|
|
+ setOpen(true);
|
|
|
+ setMenu(type);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleStopPropagation = (e: React.MouseEvent) => {
|
|
|
+ e.stopPropagation();
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSetIsSerif = (isSerif: boolean) => {
|
|
|
+ localStorage.setItem("novelIsSerif", JSON.stringify(isSerif));
|
|
|
+ setIsSerif(isSerif);
|
|
|
+ };
|
|
|
+ const handleSetFontSize = (size: number) => {
|
|
|
+ localStorage.setItem("novelFontSize", `${size}`);
|
|
|
+ setFontSize(size);
|
|
|
+ };
|
|
|
+
|
|
|
+ if (!chapterData) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
return (
|
|
|
<>
|
|
|
- <header className={clsx("header", styles["chapter-header"])}>
|
|
|
- <Link href="/">
|
|
|
- <a className="logo mr-2" title="NovelDit">
|
|
|
- {/* eslint-disable-next-line @next/next/no-img-element */}
|
|
|
- <img src="/logo.svg" alt="NovelDit" />
|
|
|
- </a>
|
|
|
+ <header
|
|
|
+ className={clsx("header", styles["chapter-header"], {
|
|
|
+ [styles["open"]]: open,
|
|
|
+ })}
|
|
|
+ >
|
|
|
+ <Link href="/" className="logo mr-2" title="NovelDit">
|
|
|
+ {/* eslint-disable-next-line @next/next/no-img-element */}
|
|
|
+ <img src="/logo.svg" alt="NovelDit" />
|
|
|
</Link>
|
|
|
<div className="flex-1">
|
|
|
<div className="text-base breadcrumbs">
|
|
|
<ul>
|
|
|
<li>
|
|
|
- <a>Supreme Harem God System</a>
|
|
|
+ <Link href={`/novel/${query.slug}`}>{chapterData.title}</Link>
|
|
|
</li>
|
|
|
- <li>Characters And Their Images. (Might Contain Spoilers)</li>
|
|
|
+ <li>{chapterData.chapter}</li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div className="buttons">
|
|
|
- <button className="btn" onClick={toggleTheme}>
|
|
|
+ <button className="btn" onClick={() => toggleTheme()}>
|
|
|
<svg className="icon-sun" viewBox="0 0 16 16">
|
|
|
<use href="/icons.svg#sun"></use>
|
|
|
</svg>
|
|
|
@@ -51,258 +114,86 @@ const Chapter: NextPageWithLayout = () => {
|
|
|
</button>
|
|
|
</div>
|
|
|
</header>
|
|
|
- <main className={styles["chapter-main"]}>
|
|
|
- <div className={styles["chapter-page"]}>
|
|
|
+ <main className={styles["chapter-main"]} onClick={handleOpenToolbar}>
|
|
|
+ <div
|
|
|
+ className={clsx(styles["chapter-page"], {
|
|
|
+ [styles["serif"]]: isSerif,
|
|
|
+ "text-sm": fontSize === 1,
|
|
|
+ "text-base": fontSize === 2,
|
|
|
+ "text-lg": fontSize === 3,
|
|
|
+ "text-xl": fontSize === 4,
|
|
|
+ "text-2xl": fontSize === 5,
|
|
|
+ })}
|
|
|
+ >
|
|
|
<article className={styles["novel"]}>
|
|
|
<header>
|
|
|
- <h1 className="">
|
|
|
- Characters And Their Images. (Might Contain Spoilers)
|
|
|
- </h1>
|
|
|
+ <h1 className="">{chapterData.title}</h1>
|
|
|
+ <h2 className="">{chapterData.chapter}</h2>
|
|
|
</header>
|
|
|
- <div className="content">
|
|
|
- <p>
|
|
|
- Look, I won't have perfect images since I have just searched
|
|
|
- them on the internet after all... you can use your imagination a
|
|
|
- little, I will just give you a reference image.
|
|
|
- </p>
|
|
|
- <p>Thank you<3 </p>
|
|
|
- <p>... </p>
|
|
|
- <p>Nux Leander </p>
|
|
|
- <p>
|
|
|
- Nux's Look: His long thick raven hair, combined with his smooth
|
|
|
- white skin with a perfectly carved face. His golden eyes had a
|
|
|
- unique shine within them. His sword-like eyebrows, thin nose and
|
|
|
- sharp jawline made his face immortal-like even though it was a
|
|
|
- little bruised.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- Here is the image (in paragraph comments), just change the eye
|
|
|
- colour to golden, and of course, normal-looking hands. He is
|
|
|
- frail now, but that won't be for long.
|
|
|
- </p>
|
|
|
- <p>... </p>
|
|
|
- <p>Felberta Alveye: </p>
|
|
|
- <p>
|
|
|
- She was a classic example of a MILF! Her perfect hourglass
|
|
|
- figure was as enchanting as it could ever get, Nux couldn't help
|
|
|
- but glance at those milky breasts that were enough for a normal
|
|
|
- weeb to die from a nosebleed. Her raven hair matched Nux's,
|
|
|
- while her black eyeballs were as deep as an abyss. Her thin
|
|
|
- eyebrows, small nose and cherry-like red and luscious lips made
|
|
|
- her look like a succubus.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- Here is the Image, just imagine the eyes and hair in black
|
|
|
- colour.
|
|
|
- </p>
|
|
|
- <p>Skyla Hale </p>
|
|
|
- <p>
|
|
|
- Skyla was a beautiful fair-skinned woman; she was 1.68 meters
|
|
|
- tall, with short auburn hair, big green coloured eyes, a cute
|
|
|
- small nose, and pouty red lips. Her breasts weren't really big
|
|
|
- but they weren't small either. They were what they call, 'they
|
|
|
- would perfectly fit in my hands' type. She had a perky bottom
|
|
|
- and although she might appear serious, if one gets to know her,
|
|
|
- she was a clumsy type.
|
|
|
- </p>
|
|
|
- <p>Here's her image. </p>
|
|
|
- <p>Lane Wynee </p>
|
|
|
- <p>
|
|
|
- Sister Lane, or Lane Wyne, was a beautiful and serious woman.
|
|
|
- She had dark brown colour hair and eyes, she had a well-carved
|
|
|
- face, with sharp brows, a thin nose and soft pink lips. Nux's
|
|
|
- favourite part about her was her big but firm breasts which were
|
|
|
- even larger than Fel's. Her body curves were wonderful, coupled
|
|
|
- with her maid uniform; she was an ideal dream maid girl in any
|
|
|
- teen's fantasy.
|
|
|
- </p>
|
|
|
- <p>The Image, change her eyes to the colour mentioned. </p>
|
|
|
- <p>Edda Osburn </p>
|
|
|
- <p>
|
|
|
- Edda was a 1.65 meters tall woman, she had white coloured hair
|
|
|
- with shades of pink on the tips, this combined well with
|
|
|
- pink-violet eyes, and her tanned skin gave her a different charm
|
|
|
- than others.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- Her small nose, and rosy red lips, combined with the perfect
|
|
|
- hourglass physique that rivals Felberta made her an extremely
|
|
|
- beautiful and alluring woman.
|
|
|
- </p>
|
|
|
- <p>Here is her picture~ </p>
|
|
|
- <p>Thyra Cruse </p>
|
|
|
- <p>
|
|
|
- Thyra was a beautiful fair-skinned woman with short raven hair
|
|
|
- and Icy blue eyes. She had a small nose and cherry-like lips.
|
|
|
- She has a great physique; her breasts were of the same size as
|
|
|
- Skyla. Her black coloured clothes contrasted well with her fair
|
|
|
- skin and icy blue eyes. All in all, she was a really beautiful
|
|
|
- woman.
|
|
|
- </p>
|
|
|
- <p>Picture. </p>
|
|
|
- <p>Allura Skyfall </p>
|
|
|
- <p>
|
|
|
- Concubine Allura, or Allura Skyfall, is a 1.7-meter tall woman,
|
|
|
- just like her name, she has an alluring, sinful body and a
|
|
|
- perfect hourglass figure. White hairs, Crystal blue eyes, and
|
|
|
- juicy red lips. A fatal Beauty indeed.
|
|
|
- </p>
|
|
|
- <p>Picture. </p>
|
|
|
- <p>Amaya Skyfall. </p>
|
|
|
- <p>
|
|
|
- The woman had long dark black hair, juicy red lips, a thin nose
|
|
|
- and a peerless face.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- A perfect face, a perfect body, it was as if this woman was born
|
|
|
- to define the word perfect.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- Her breasts weren't large, but they weren't small either, her
|
|
|
- body wasn't curvy, it was more lean however, combining it with
|
|
|
- her divine face, she was…
|
|
|
- </p>
|
|
|
- <p>Outright gorgeous. </p>
|
|
|
- <p>Picture. </p>
|
|
|
- <p>Evane Skyfall. </p>
|
|
|
- <p>
|
|
|
- The woman had long blonde hair, beautiful green eyes, a small
|
|
|
- nose and light pink lips, her features were sharp and combined
|
|
|
- with her perfect hourglass-like mature body, she looked
|
|
|
- breathtaking.
|
|
|
- </p>
|
|
|
- <p>Picture. </p>
|
|
|
- <p>Arvina Skyfall. </p>
|
|
|
- <p>
|
|
|
- Brown-red, fiery eyes, sword-like eyebrows, light-blonde hair,
|
|
|
- luscious red lips and a lean physique, the woman was really
|
|
|
- enchanting; however, together with being beautiful, she also
|
|
|
- released a ferocious, dangerous aura.
|
|
|
- </p>
|
|
|
- <p>An Aura of a Warrior. </p>
|
|
|
- <p>Picture. </p>
|
|
|
- <p>Ember Windstar. </p>
|
|
|
- <p>
|
|
|
- Nux's eyes fell on a woman wearing a black coat sitting on a
|
|
|
- chair with a leisurely look on her face.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- No, actually, she did not look leisurely at all, she looked like
|
|
|
- a wild lioness who was looking at her prey, she was a dangerous
|
|
|
- woman.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- And on top of that, she was a beautiful woman, with black-red
|
|
|
- hair, sword-like eyebrows, red eyes that shined ferociously, a
|
|
|
- straight nose, and a wide smile on her face,
|
|
|
- </p>
|
|
|
- <p>Picture </p>
|
|
|
- <p>... </p>
|
|
|
- <p>
|
|
|
- More characters will be updated after they are introduced, so
|
|
|
- look forward to it. Also, I won't be adding random side
|
|
|
- characters like Florence and Willa, if they appear more in
|
|
|
- future, I might add them here, but right now, they haven't
|
|
|
- really appeared in the story and I certainly don't have any
|
|
|
- plans to add either.
|
|
|
- </p>
|
|
|
- <p>
|
|
|
- If you have any questions or requests, just tell me and I'll
|
|
|
- answer you.
|
|
|
- </p>
|
|
|
- </div>
|
|
|
+ <div
|
|
|
+ className="content"
|
|
|
+ dangerouslySetInnerHTML={{ __html: chapterData.content }}
|
|
|
+ />
|
|
|
</article>
|
|
|
</div>
|
|
|
- <div className={styles["toolbar-display"]}>
|
|
|
- <div className={styles["toolbar-inner"]}>
|
|
|
- <div className={styles["toolbar-display-title"]}>
|
|
|
- Display Options
|
|
|
- </div>
|
|
|
- <div className={styles["toolbar-display-label"]}>Background</div>
|
|
|
- <div className="btn-group w-full">
|
|
|
- <button className="btn btn-primary">
|
|
|
- <svg className="icon-sun mr-2" viewBox="0 0 16 16">
|
|
|
- <use href="/icons.svg#sun"></use>
|
|
|
- </svg>
|
|
|
- Light
|
|
|
- </button>
|
|
|
- <button className="btn btn-primary">Yellow</button>
|
|
|
- <button className="btn btn-primary">
|
|
|
- <svg className="icon-moon mr-2" viewBox="0 0 16 16">
|
|
|
- <use href="/icons.svg#moon"></use>
|
|
|
- </svg>
|
|
|
- Dark
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <div className={styles["toolbar-display-label"]}>Font</div>
|
|
|
- <div className="btn-group">
|
|
|
- <button className="btn btn-primary">Serif</button>
|
|
|
- <button className="btn btn-primary">sans-serif</button>
|
|
|
- </div>
|
|
|
- <div className={styles["toolbar-display-label"]}>Sizes</div>
|
|
|
- <div className="flex">
|
|
|
- <button className="mr-4">
|
|
|
- <svg className="w-5 h-5" viewBox="0 0 16 16">
|
|
|
- <use href="/icons.svg#font-dn"></use>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <input
|
|
|
- className="w-0 flex-1"
|
|
|
- type="range"
|
|
|
- list="tickmarks"
|
|
|
- min={14}
|
|
|
- max={24}
|
|
|
- step={2}
|
|
|
- />
|
|
|
- <datalist id="tickmarks">
|
|
|
- <option value="14">14</option>
|
|
|
- <option value="16">16</option>
|
|
|
- <option value="18">18</option>
|
|
|
- <option value="20">20</option>
|
|
|
- <option value="22">22</option>
|
|
|
- <option value="24">24</option>
|
|
|
- </datalist>
|
|
|
- <button className="ml-4">
|
|
|
- <svg className="w-5 h-5" viewBox="0 0 16 16">
|
|
|
- <use href="/icons.svg#font-up"></use>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div
|
|
|
+ className={clsx(styles["toolbar-display"], {
|
|
|
+ hidden: !menu,
|
|
|
+ })}
|
|
|
+ onClick={handleStopPropagation}
|
|
|
+ >
|
|
|
+ {menu === "settings" ? (
|
|
|
+ <Settings
|
|
|
+ isSerif={isSerif}
|
|
|
+ fontSize={fontSize}
|
|
|
+ changeIsSerif={handleSetIsSerif}
|
|
|
+ changeFontSize={handleSetFontSize}
|
|
|
+ />
|
|
|
+ ) : null}
|
|
|
+ {menu === "toc" ? (
|
|
|
+ <Toc
|
|
|
+ novel={query.slug as string}
|
|
|
+ chapter={query.chapter as string}
|
|
|
+ />
|
|
|
+ ) : null}
|
|
|
</div>
|
|
|
</main>
|
|
|
- <div className={styles["toolbar"]}>
|
|
|
- <div className={styles["toolbar-items"]}>
|
|
|
- <button className={styles["toolbar-item"]} title="Table Of Contents">
|
|
|
- <svg>
|
|
|
- <use xlinkHref="/icons.svg#items"></use>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <button className={styles["toolbar-item"]} title="Display Options">
|
|
|
- <svg>
|
|
|
- <use xlinkHref="/icons.svg#set"></use>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <button className={styles["toolbar-item"]} title="">
|
|
|
- <svg>
|
|
|
- <use xlinkHref="/icons.svg#reviews"></use>
|
|
|
- </svg>
|
|
|
- </button>
|
|
|
- <a
|
|
|
- className={styles["toolbar-item"]}
|
|
|
- href="/help"
|
|
|
- title="Help center"
|
|
|
- >
|
|
|
- <svg>
|
|
|
- <use xlinkHref="/icons.svg#help_bold"></use>
|
|
|
- </svg>
|
|
|
- </a>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <Toolbar open={open} onChangeSettings={handleChangeSettings} />
|
|
|
</>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
Chapter.getLayout = (page: ReactElement) => page;
|
|
|
|
|
|
+export const getServerSideProps: GetServerSideProps<
|
|
|
+ { fallback: { [key: string]: any } },
|
|
|
+ { slug: string; chapter: string }
|
|
|
+> = async (context) => {
|
|
|
+ if (!context.params) {
|
|
|
+ return {
|
|
|
+ props: {
|
|
|
+ fallback: {},
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+ const { slug, chapter } = context.params;
|
|
|
+ const [chapterData, chapters] = await Promise.all([
|
|
|
+ get<ChapterData>(
|
|
|
+ `https://novels.yergoo.com/api/novel/chapter/${slug}/${chapter}`
|
|
|
+ ),
|
|
|
+ get<ChapterListData>(
|
|
|
+ `https://novels.yergoo.com/api/novel/${slug}/chapters`
|
|
|
+ ),
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return {
|
|
|
+ props: {
|
|
|
+ fallback: {
|
|
|
+ [`/api/novel/chapter/${slug}/${chapter}`]: chapterData,
|
|
|
+ [`/api/novel/${slug}/chapters`]: chapters,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
export default Chapter;
|