Leo před 3 roky
rodič
revize
47d29e5085

+ 3 - 0
.yarnrc

@@ -0,0 +1,3 @@
+{
+  registry: "https://registry.npm.taobao.org"
+}

+ 20 - 23
components/NovelCover/index.tsx

@@ -1,40 +1,37 @@
 import clsx from "clsx";
+import Link from "next/link";
 import type { ElementType } from "react";
 
-interface NovelItemProps {
+interface NovelCoverProps {
   component?: ElementType;
   className?: string;
+  href?: string;
+  title?: string;
+  alt?: string;
+  src?: string;
 }
 
-export default function NovelItem(props: NovelItemProps) {
-  const { component: Component, className, ...other } = props;
+export default function NovelCover(props: NovelCoverProps) {
+  let {
+    component: Component = Link,
+    className,
+    alt = "",
+    src = "",
+    ...other
+  } = props;
 
-  if (Component) {
-    return (
-      <Component className={clsx("novel-cover", className)} {...other}>
-        <img
-          src="https://www.wuxiaworld.com/cdn-cgi/image/fit=contain,quality=75,format=auto/https://cdn.wuxiaworld.com/images/covers/og.jpg?ver=362f12103bcf1ea3fc048174300b89a65643336b"
-          alt="Minecraft"
-          draggable="false"
-          loading="lazy"
-        />
-      </Component>
-    );
+  if (!other.href) {
+    Component = "div";
   }
 
   return (
-    <a
-      href="/minecraft"
-      title="Minecraft"
-      className={clsx("novel-cover", className)}
-      {...other}
-    >
+    <Component className={clsx("novel-cover", className)} {...other}>
       <img
-        src="https://www.wuxiaworld.com/cdn-cgi/image/fit=contain,quality=75,format=auto/https://cdn.wuxiaworld.com/images/covers/og.jpg?ver=362f12103bcf1ea3fc048174300b89a65643336b"
-        alt="Minecraft"
+        src={src}
+        alt={alt || other.title || ""}
         draggable="false"
         loading="lazy"
       />
-    </a>
+    </Component>
   );
 }

+ 19 - 14
components/NovelItem/index.tsx

@@ -1,22 +1,27 @@
-export default function NovelItem() {
+
+import Link from "next/link";
+import NovelCover from "../NovelCover";
+
+interface NovelItemProps {
+  slug?: string;
+  img?: string;
+  name?: string;
+}
+
+export default function NovelItem(props: NovelItemProps) {
+  const { slug, img, name } = props;
+  const href = `/novel/${slug}`;
   return (
     <li className="novel-item">
-      <a href="/minecraft" title="Minecraft" className="novel-cover">
-        <img
-          src="https://www.wuxiaworld.com/cdn-cgi/image/fit=contain,quality=75,format=auto/https://cdn.wuxiaworld.com/images/covers/og.jpg?ver=362f12103bcf1ea3fc048174300b89a65643336b"
-          alt="Minecraft"
-          draggable="false"
-          loading="lazy"
-        />
-      </a>
+      <NovelCover href={href} alt={name} src={img} />
       <h3 className="novel-item-title">
-        <a href="/minecraft" title="Minecraft">
-          Minecraft
-        </a>
+        <Link href={href} title={name}>
+          {name}
+        </Link>
       </h3>
-      <p className="novel-item-desc">
+      {/* <p className="novel-item-desc">
         <a href="">Fantasy Romance</a>
-      </p>
+      </p> */}
     </li>
   );
 }

+ 3 - 5
components/common/Footer/index.tsx

@@ -3,11 +3,9 @@ export default function Footer() {
   return (
     <footer className="footer">
       <div className="container">
-        <Link href="/">
-          <a className="logo" title="NovelDit">
-            <img src="/logo.svg" alt="NovelDit" />
-            <span>NovelDit</span>
-          </a>
+        <Link href="/" className="logo" title="NovelDit">
+          <img src="/logo.svg" alt="NovelDit" />
+          <span>NovelDit</span>
         </Link>
         <p className="copyright">© 2022 NovelDit Inc. All rights reserved.</p>
         <div className="links">

+ 20 - 204
components/common/Header/index.tsx

@@ -1,9 +1,12 @@
-import { useCallback, useState } from "react";
+import { useCallback, useContext, useState } from "react";
 import clsx from "clsx";
 import Link from "next/link";
 import ClickAwayListener from "@mui/base/ClickAwayListener";
+import { Context } from "../../../libs/context";
+import toggleTheme from "../../../libs/toggleTheme";
 
 export default function Header() {
+  const store = useContext(Context);
   const [open, setOpen] = useState(false);
 
   const toggleMenu = useCallback(() => {
@@ -14,221 +17,34 @@ export default function Header() {
     setOpen(false);
   }, [setOpen]);
 
-  const toggleTheme = function () {
-    const _html = document.documentElement;
-    if (_html.classList.contains("dark")) {
-      _html.classList.remove("dark");
-      localStorage.setItem("theme", "lite");
-    } else {
-      _html.classList.add("dark");
-      localStorage.setItem("theme", "dark");
-    }
-  };
-
   return (
     <header className={clsx("header", { open })}>
       <ClickAwayListener onClickAway={closeMenu}>
         <div className="container">
-          <Link href="/">
-            <a className="logo mr-5" title="NovelDit">
-              {/* eslint-disable-next-line @next/next/no-img-element */}
-              <img src="/logo.svg" alt="NovelDit" />
-              <span>NovelDit</span>
-            </a>
+          <Link href="/" className="logo mr-5" title="NovelDit">
+            {/* eslint-disable-next-line @next/next/no-img-element */}
+            <img src="/logo.svg" alt="NovelDit" />
+            <span>NovelDit</span>
           </Link>
           <div className="menu">
             <nav>
               <ul>
                 <li>
-                  <a className="menu-item" href="" title="Browse">
+                  <Link className="menu-item" href="/novels" title="Genre">
                     <svg width="24" height="24">
                       <use href="/icons.svg#browse"></use>
                     </svg>
-                    <strong>Browse</strong>
-                  </a>
-                  <div className="sub-menu">
-                    <ul className="sub-menu-sized">
-                      <li>
-                        <a href="/stories/novel" title="Novels">
-                          Novels
-                        </a>
-                        <div className="sub-menu-list">
-                          <p>
-                            <strong>MALE LEAD</strong>
-                            <a href="/stories/novel-urban-male" title="Urban">
-                              Urban
-                            </a>
-                            <a
-                              href="/stories/novel-eastern-male"
-                              title="Eastern"
-                            >
-                              Eastern
-                            </a>
-                            <a href="/stories/novel-games-male" title="Games">
-                              Games
-                            </a>
-                            <a
-                              href="/stories/novel-fantasy-male"
-                              title="Fantasy"
-                            >
-                              Fantasy
-                            </a>
-                            <a href="/stories/novel-scifi-male" title="Sci-fi">
-                              Sci-fi
-                            </a>
-                            <a href="/stories/novel-acg-male" title="ACG">
-                              ACG
-                            </a>
-                            <a href="/stories/novel-horror-male" title="Horror">
-                              Horror
-                            </a>
-                            <a href="/stories/novel-sports-male" title="Sports">
-                              Sports
-                            </a>
-                            <a href="/stories/novel-action-male" title="Action">
-                              Action
-                            </a>
-                            <a href="/stories/novel-war-male" title="War">
-                              War
-                            </a>
-                            <a
-                              href="/stories/novel-realistic-male"
-                              title="Realistic"
-                            >
-                              Realistic
-                            </a>
-                            <a
-                              href="/stories/novel-history-male"
-                              title="History"
-                            >
-                              History
-                            </a>
-                          </p>
-                          <p>
-                            <strong>FEMALE LEAD</strong>
-                            <a href="/stories/novel-urban-female" title="Urban">
-                              Urban
-                            </a>
-                            <a
-                              href="/stories/novel-fantasy-female"
-                              title="Fantasy"
-                            >
-                              Fantasy
-                            </a>
-                            <a
-                              href="/stories/novel-history-female"
-                              title="History"
-                            >
-                              History
-                            </a>
-                            <a href="/stories/novel-teen-female" title="Teen">
-                              Teen
-                            </a>
-                            <a href="/stories/novel-lgbt-female" title="LGBT+">
-                              LGBT+
-                            </a>
-                            <a
-                              href="/stories/novel-scifi-female"
-                              title="Sci-fi"
-                            >
-                              Sci-fi
-                            </a>
-                            <a
-                              href="/stories/novel-general-female"
-                              title="General"
-                            >
-                              General
-                            </a>
-                          </p>
-                        </div>
-                      </li>
-                      <li>
-                        <a href="/stories/fanfic" title="Fan-fic">
-                          Fan-fic
-                        </a>
-                        <div className="sub-menu-list">
-                          <p>
-                            <a
-                              href="/stories/fanfic-anime-comics"
-                              title="Anime &amp; Comics"
-                            >
-                              Anime &amp; Comics
-                            </a>
-                            <a
-                              href="/stories/fanfic-video-games"
-                              title="Video Games"
-                            >
-                              Video Games
-                            </a>
-                            <a
-                              href="/stories/fanfic-celebrities"
-                              title="Celebrities"
-                            >
-                              Celebrities
-                            </a>
-                            <a
-                              href="/stories/fanfic-music-bands"
-                              title="Music &amp; Bands"
-                            >
-                              Music &amp; Bands
-                            </a>
-                            <a href="/stories/fanfic-movies" title="Movies">
-                              Movies
-                            </a>
-                            <a
-                              href="/stories/fanfic-book-literature"
-                              title="Book&amp;Literature"
-                            >
-                              Book&amp;Literature
-                            </a>
-                            <a href="/stories/fanfic-tv" title="TV">
-                              TV
-                            </a>
-                            <a href="/stories/fanfic-theater" title="Theater">
-                              Theater
-                            </a>
-                            <a href="/stories/fanfic-others" title="Others">
-                              Others
-                            </a>
-                          </p>
-                        </div>
-                      </li>
-                    </ul>
-                  </div>
-                </li>
-                <li>
-                  <a className="menu-item" href="" title="Rankings">
-                    <svg viewBox="0 0 1024 1024" width="24" height="24">
-                      <use href="/icons.svg#ranking"></use>
-                    </svg>
-                    <strong>Rankings</strong>
-                  </a>
+                    <strong>Genre</strong>
+                  </Link>
                   <div className="sub-menu">
-                    <ul>
-                      <li>
-                        <a
-                          href="/ranking/novel/bi_annual/power_rank?timeType=3&amp;sourceType=2&amp;signStatus=1"
-                          title="Novels ranking"
-                        >
-                          Novels ranking
-                        </a>
-                      </li>
-                      <li>
-                        <a
-                          href="/ranking/comic/all_time/comic_power_rank"
-                          title="Comics ranking"
-                        >
-                          Comics ranking
-                        </a>
-                      </li>
-                      <li>
-                        <a
-                          href="/ranking/fanfic/bi_annual/power_rank"
-                          title="Fan-fic ranking"
-                        >
-                          Fan-fic ranking
-                        </a>
-                      </li>
+                    <ul className="sub-menu-items">
+                      {store.genre.map((item) => (
+                        <li key={item.uri}>
+                          <Link href={`/novels/${item.uri}`} title={item.name}>
+                            {item.name}
+                          </Link>
+                        </li>
+                      ))}
                     </ul>
                   </div>
                 </li>
@@ -242,7 +58,7 @@ export default function Header() {
             </form>
           </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>

+ 120 - 0
components/novel/Settings/index.tsx

@@ -0,0 +1,120 @@
+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";
+
+type FontSize = 1 | 2 | 3 | 4 | 5;
+const fontSizes: FontSize[] = [1, 2, 3, 4, 5];
+
+interface SettingsProps {
+  isSerif: boolean;
+  fontSize: number;
+  changeIsSerif: (isSerif: boolean) => void;
+  changeFontSize: (fontSize: FontSize) => void;
+}
+
+const Settings = (props: SettingsProps) => {
+  const { isSerif, fontSize, changeIsSerif, changeFontSize } = props;
+
+  return (
+    <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 btn-dark-outline"
+          onClick={() => toggleTheme("light")}
+        >
+          <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 btn-light-outline"
+          onClick={() => toggleTheme("dark")}
+        >
+          <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 w-full">
+        <button
+          className={clsx("btn", "btn-primary", {
+            "btn-outline": isSerif,
+          })}
+          onClick={() => changeIsSerif(false)}
+        >
+          Nunito Sans
+        </button>
+        <button
+          className={clsx("btn", "btn-primary", {
+            "btn-outline": !isSerif,
+          })}
+          onClick={() => changeIsSerif(true)}
+        >
+          Merriweather
+        </button>
+      </div>
+      <div className={styles["toolbar-display-label"]}>Sizes</div>
+      <div className="flex">
+        <button
+          className="mr-4"
+          onClick={() =>
+            changeFontSize(
+              Math.max(Math.min(...fontSizes), fontSize - 1) as FontSize
+            )
+          }
+        >
+          <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"
+          value={fontSize}
+          min={1}
+          max={5}
+          step={1}
+          onChange={(e) => changeFontSize(Number(e.target.value) as FontSize)}
+        />
+        <datalist id="tickmarks">
+          {fontSizes.map((fs) => (
+            <option key={fs} value={fs}>
+              {fs}
+            </option>
+          ))}
+        </datalist>
+        <button
+          className="ml-4"
+          onClick={() =>
+            changeFontSize(
+              Math.min(Math.max(...fontSizes), fontSize + 1) as FontSize
+            )
+          }
+        >
+          <svg className="w-5 h-5" viewBox="0 0 16 16">
+            <use href="/icons.svg#font-up"></use>
+          </svg>
+        </button>
+      </div>
+    </div>
+  );
+};
+
+export default Settings;

+ 50 - 0
components/novel/Toc/index.tsx

@@ -0,0 +1,50 @@
+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";
+
+type FontSize = 1 | 2 | 3 | 4 | 5;
+const fontSizes: FontSize[] = [1, 2, 3, 4, 5];
+
+interface TocProps {
+  novel: string;
+  chapter: string;
+}
+
+const Toc = (props: TocProps) => {
+  const { novel, chapter } = props;
+
+  const { data: { data: chapters } = { data: null } } = useGet<ChapterListData>(
+    `/api/novel/${novel}/chapters`
+  );
+
+  return (
+    <div className={styles["toolbar-inner"]}>
+      <div className={styles["toolbar-display-title"]}>Chapters</div>
+      {chapters ? (
+        <ol>
+          {chapters.chapters.map((item) => (
+            <li key={item.id}>
+              <Link href={`/novel/${item.uri}`} title={item.name}>
+                <i>1</i>
+                <strong>{item.name}</strong>
+              </Link>
+            </li>
+          ))}
+        </ol>
+      ) : null}
+    </div>
+  );
+};
+
+export default Toc;

+ 52 - 0
components/novel/Toolbar/index.tsx

@@ -0,0 +1,52 @@
+import clsx from "clsx";
+import styles from "../../../styles/chapter.module.scss";
+
+interface ToolbarProps {
+  open: boolean;
+  onChangeSettings: (type?: string) => void;
+}
+
+const Toolbar = (props: ToolbarProps) => {
+  const { open, onChangeSettings } = props;
+
+  return (
+    <div
+      className={clsx(styles["toolbar"], {
+        hidden: !open,
+      })}
+    >
+      <div className={styles["toolbar-items"]}>
+        <button
+          className={styles["toolbar-item"]}
+          title="Table Of Contents"
+          onClick={() => onChangeSettings("toc")}
+        >
+          <svg>
+            <use xlinkHref="/icons.svg#items"></use>
+          </svg>
+        </button>
+        <button
+          className={styles["toolbar-item"]}
+          title="Display Options"
+          onClick={() => onChangeSettings("settings")}
+        >
+          <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>
+  );
+};
+
+export default Toolbar;

+ 2 - 0
libs/config.ts

@@ -0,0 +1,2 @@
+export const isServer = typeof window === undefined;
+export const apiHost = "https://novels.yergoo.com";

+ 7 - 0
libs/context.ts

@@ -0,0 +1,7 @@
+import React from "react";
+import { GenreItem } from "../types/http";
+
+export interface ContextProps {
+  genre: GenreItem[];
+}
+export const Context = React.createContext<ContextProps>({ genre: [] });

+ 12 - 0
libs/toggleTheme.ts

@@ -0,0 +1,12 @@
+type Theme = "light" | "dark";
+export default function toggleTheme(theme?: Theme) {
+  const _html = document.documentElement;
+
+  if (theme === "light" || (!theme && _html.classList.contains("dark"))) {
+    _html.classList.remove("dark");
+    localStorage.setItem("theme", "lite");
+  } else {
+    _html.classList.add("dark");
+    localStorage.setItem("theme", "dark");
+  }
+}

+ 12 - 2
next.config.js

@@ -2,6 +2,16 @@
 const nextConfig = {
   reactStrictMode: true,
   swcMinify: true,
-}
+  async rewrites() {
+    return {
+      fallback: [
+        {
+          source: "/api/:path*",
+          destination: `https://novels.yergoo.com/api/:path*`,
+        },
+      ],
+    };
+  },
+};
 
-module.exports = nextConfig
+module.exports = nextConfig;

+ 7 - 3
package.json

@@ -10,10 +10,13 @@
   },
   "dependencies": {
     "@mui/base": "^5.0.0-alpha.103",
+    "@next/font": "^13.0.1",
     "clsx": "^1.2.1",
-    "next": "12.3.1",
+    "next": "13.0.0",
+    "qs": "^6.11.0",
     "react": "18.2.0",
-    "react-dom": "18.2.0"
+    "react-dom": "18.2.0",
+    "swr": "^1.3.0"
   },
   "devDependencies": {
     "@tailwindcss/aspect-ratio": "^0.4.2",
@@ -21,12 +24,13 @@
     "@tailwindcss/line-clamp": "^0.4.2",
     "@tailwindcss/typography": "^0.5.7",
     "@types/node": "18.11.3",
+    "@types/qs": "^6.9.7",
     "@types/react": "18.0.21",
     "@types/react-dom": "18.0.6",
     "autoprefixer": "^10.4.12",
     "daisyui": "^2.33.0",
     "eslint": "8.25.0",
-    "eslint-config-next": "12.3.1",
+    "eslint-config-next": "13.0.0",
     "mini-svg-data-uri": "^1.4.4",
     "postcss": "^8.4.18",
     "sass": "^1.55.0",

+ 41 - 7
pages/_app.tsx

@@ -1,8 +1,14 @@
 import type { ReactElement, ReactNode } from "react";
+import { SWRConfig } from "swr";
 import type { NextPage } from "next";
+import App, { AppContext } from "next/app";
 import type { AppProps } from "next/app";
 import Layout from "../components/common/Layout";
 import "../styles/globals.scss";
+import useGet from "../utils/hooks/useGet";
+import { GenreItem } from "../types/http";
+import { get } from "../utils/http";
+import { Context } from "../libs/context";
 
 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
   getLayout?: (page: ReactElement) => ReactNode;
@@ -10,14 +16,42 @@ export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
 
 type AppPropsWithLayout = AppProps & {
   Component: NextPageWithLayout;
+  pageProps: {
+    fallback?: {
+      [key: string]: any;
+    };
+    [key: string]: any;
+  };
 };
 
-function defaultGetLayout(page: ReactElement): ReactNode {
-  return <Layout>{page}</Layout>;
-}
+const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
+  const { fallback, genre, ...otherProps } = pageProps;
 
-export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
-  const getLayout = Component.getLayout ?? defaultGetLayout;
+  return (
+    <Context.Provider value={{ genre }}>
+      <SWRConfig value={{ fallback, revalidateIfStale: false }}>
+        {Component.getLayout ? (
+          Component.getLayout(<Component {...otherProps} />)
+        ) : (
+          <Layout>
+            <Component {...otherProps} />
+          </Layout>
+        )}
+      </SWRConfig>
+    </Context.Provider>
+  );
+};
+
+MyApp.getInitialProps = async function (context: AppContext) {
+  App.getInitialProps(context);
+
+  const { data } = await fetch("https://novels.yergoo.com/api/genre/list").then(
+    (res) => res.json()
+  );
+
+  return {
+    pageProps: { genre: data },
+  };
+};
 
-  return getLayout(<Component {...pageProps} />);
-}
+export default MyApp;

+ 0 - 20
pages/_document.js

@@ -1,20 +0,0 @@
-import { Html, Head, Main, NextScript } from "next/document";
-
-export default function Document() {
-  return (
-    <Html>
-      <Head>
-      <script dangerouslySetInnerHTML={{ __html: 'try{if(localStorage.theme==="dark"||(!("theme" in localStorage)&&window.matchMedia("(prefers-color-scheme: dark)").matches)){document.documentElement.classList.add("dark");}else{document.documentElement.classList.remove("dark");}}catch(_){}'}}></script>
-        <link
-          rel="stylesheet"
-          href="https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,wght@0,300;0,400;0,600;0,700;1,400&amp;display=swap"
-          data-ignore="true"
-        />
-      </Head>
-      <body>
-        <Main />
-        <NextScript />
-      </body>
-    </Html>
-  );
-}

+ 37 - 0
pages/_document.tsx

@@ -0,0 +1,37 @@
+import { Html, Head, Main, NextScript } from "next/document";
+
+export default function Document() {
+  return (
+    <Html>
+      <Head>
+        <script
+          dangerouslySetInnerHTML={{
+            __html:
+              'try{if(localStorage.theme==="dark"||(!("theme" in localStorage)&&window.matchMedia("(prefers-color-scheme: dark)").matches)){document.documentElement.classList.add("dark");}else{document.documentElement.classList.remove("dark");}}catch(_){}',
+          }}
+        ></script>
+        <link rel="preconnect" href="https://fonts.googleapis.com" />
+        <link
+          rel="preconnect"
+          href="https://fonts.gstatic.com"
+          crossOrigin="true"
+        />
+        <link
+          rel="preload"
+          as="style"
+          href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;1,400&amp;family=Nunito+Sans:ital,wght@0,300;0,400;0,600;0,700;1,400&amp;display=swap"
+          data-ignore="true"
+        />
+        <link
+          rel="stylesheet"
+          href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,400;0,700;1,400&amp;family=Nunito+Sans:ital,wght@0,300;0,400;0,600;0,700;1,400&amp;display=swap"
+          data-ignore="true"
+        />
+      </Head>
+      <body>
+        <Main />
+        <NextScript />
+      </body>
+    </Html>
+  );
+}

+ 27 - 7
pages/index.tsx

@@ -1,19 +1,39 @@
-import type { NextPage } from "next";
 import NovelItem from "../components/NovelItem";
+import type { ListItem } from "../types/http";
+import useGet from "../utils/hooks/useGet";
+
+const Home = () => {
+  const { data } = useGet<ListItem[]>("/api/list");
 
-const Home: NextPage = () => {
   return (
     <main className="container">
       <h2 className="novel-title">Popular This Week</h2>
       <ul className="novel-list">
-        {Array(16)
-          .fill(1)
-          .map((i, idx) => (
-            <NovelItem key={idx} />
-          ))}
+        {(data?.data || []).map((item) => (
+          <NovelItem
+            key={item.uri}
+            slug={item.uri}
+            img={item.img}
+            name={item.name}
+          />
+        ))}
       </ul>
     </main>
   );
 };
 
+export async function getServerSideProps() {
+  const data = await fetch(`https://novels.yergoo.com/api/list`).then((res) =>
+    res.json()
+  );
+
+  return {
+    props: {
+      fallback: {
+        "/api/list": data,
+      },
+    },
+  };
+}
+
 export default Home;

+ 148 - 257
pages/novel/[slug]/[chapter].tsx

@@ -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&lt;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;

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 55 - 3752
pages/novel/[slug]/index.tsx


+ 78 - 0
pages/novels/[genre].tsx

@@ -0,0 +1,78 @@
+import { useContext } from "react";
+import NovelItem from "../../components/NovelItem";
+import type { ListItem } from "../../types/http";
+import useGet from "../../utils/hooks/useGet";
+import { Context } from "../../libs/context";
+import Link from "next/link";
+import styles from "../../styles/genre.module.scss";
+import { useRouter } from "next/router";
+import clsx from "clsx";
+import { GetServerSideProps } from "next";
+
+const Genre = () => {
+  const { query } = useRouter();
+  const { data } = useGet<ListItem[]>(
+    query.genre ? `/api/genre/${query.genre}` : "/api/list"
+  );
+  const store = useContext(Context);
+
+  return (
+    <main className="container">
+      <h2 className="novel-title">Genres</h2>
+      <div className={styles.genres}>
+        <Link
+          href="/novels"
+          title="All novels"
+          className={clsx(styles.genre, {
+            [styles.current]: !query.genre,
+          })}
+        >
+          All
+        </Link>
+        {store.genre.map((item) => (
+          <Link
+            href={`/novels/${item.uri}`}
+            key={item.uri}
+            title={item.name}
+            className={clsx(styles.genre, {
+              [styles.current]: query.genre === item.uri,
+            })}
+          >
+            {item.name}
+          </Link>
+        ))}
+      </div>
+      <h2 className="novel-title">List</h2>
+      <ul className="novel-list">
+        {(data?.data || []).map((item) => (
+          <NovelItem
+            key={item.uri}
+            slug={item.uri}
+            img={item.img}
+            name={item.name}
+          />
+        ))}
+      </ul>
+    </main>
+  );
+};
+
+export const getServerSideProps: GetServerSideProps<
+  { fallback: { [key: string]: any } },
+  { genre: string }
+> = async ({ params }) => {
+  const key = params?.genre ? `/api/genre/${params.genre}` : `/api/list`;
+  const data = await fetch(`https://novels.yergoo.com${key}`).then((res) =>
+    res.json()
+  );
+
+  return {
+    props: {
+      fallback: {
+        [key]: data,
+      },
+    },
+  };
+};
+
+export default Genre;

+ 2 - 0
pages/novels/index.tsx

@@ -0,0 +1,2 @@
+export { getServerSideProps } from "./[genre]";
+export { default } from "./[genre]";

+ 46 - 5
styles/chapter.module.scss

@@ -1,15 +1,22 @@
 .chapter-page {
-  @apply w-full lg:w-0 lg:max-w-3xl flex-1 py-5 px-10 border-x border-x-gray-200 bg-white;
+  @apply font-sans w-full flex-1 py-5 px-5 border-x border-x-gray-200 bg-white;
+  @apply lg:w-0 lg:max-w-3xl lg:px-10;
   :global(.dark) & {
     @apply bg-gray-800 border-x-gray-700;
   }
+  &.serif {
+    @apply font-serif;
+  }
 }
 .chapter-header {
   @apply flex px-5 items-center relative;
   @apply lg:sticky;
+  &.open {
+    @apply sticky;
+  }
 }
 .chapter-main {
-  @apply flex justify-center bg-gray-100;
+  @apply flex justify-center bg-gray-100 lg:pr-12 lg:min-h-[calc(100vh-65px)];
   :global(.dark) & {
     @apply bg-gray-900;
   }
@@ -20,6 +27,9 @@
     @apply mb-5;
   }
   h1 {
+    @apply text-3xl font-bold;
+  }
+  h2 {
     @apply text-2xl font-bold;
   }
   p {
@@ -27,7 +37,9 @@
   }
 }
 .toolbar {
-  @apply fixed bottom-0 left-0 w-full bg-gray-800 z-30 text-gray-400; // hidden;
+  color-scheme: dark;
+
+  @apply fixed bottom-0 left-0 w-full bg-gray-800 z-20 text-gray-400; // hidden;
   @apply lg:right-0 lg:w-auto lg:left-auto lg:h-full lg:flex lg:items-center lg:justify-center;
   .toolbar-items {
     @apply flex lg:flex-col lg:w-full;
@@ -51,8 +63,10 @@
   }
 }
 .toolbar-display {
-  @apply fixed bottom-0 left-0 w-full bg-gray-900 z-40 pt-4 pb-5 px-5 hidden;
-  @apply lg:relative lg:w-96;
+  color-scheme: dark;
+
+  @apply fixed bottom-0 left-0 w-full bg-gray-900 text-gray-100 z-30 pt-4 pb-16 px-5;
+  @apply lg:w-96 lg:max-h-[calc(100vh-65px)] lg:sticky lg:top-[65ox] lg:pb-4 lg:px-0;
   .toolbar-display-title {
     @apply text-base mb-2;
   }
@@ -62,4 +76,31 @@
   // :global(.btn-group){
   //   @apply w-full;
   // }
+  .toolbar-inner {
+    @apply max-h-[70vh] flex flex-col overflow-hidden flex-1 flex-shrink-0;
+    @apply lg:max-h-full px-5;
+    ol {
+      @apply flex-1 h-0 overflow-y-auto;
+      @apply lg:-mr-5 lg:pr-5;
+      li {
+        a {
+          @apply flex w-full h-full py-1;
+          &:hover {
+            strong {
+              text-decoration: underline;
+            }
+          }
+        }
+        i {
+          @apply w-9 mr-1 flex-shrink-0 not-italic text-slate-400;
+        }
+        strong {
+          @apply block font-normal flex-1;
+        }
+        small {
+          @apply text-slate-400 ml-2;
+        }
+      }
+    }
+  }
 }

+ 13 - 0
styles/genre.module.scss

@@ -0,0 +1,13 @@
+.genres {
+  @apply flex flex-wrap whitespace-nowrap gap-x-5;
+}
+.genre {
+  @apply py-2 px-3 rounded-sm border border-transparent;
+  @apply inline-flex items-center cursor-pointer text-sm  rounded px-2.5 py-2 ring-inset ring-1 ring-transparent hover:underline;
+  &.current {
+    @apply ring-1 ring-blue-400  text-blue-600 bg-gray-100; // dark:text-white dark:ring-blue-500 bg-gray-100 dark:bg-opacity-10;
+    :global(.dark) & {
+      @apply bg-opacity-10 text-white;
+    }
+  }
+}

+ 17 - 1
styles/globals.scss

@@ -92,7 +92,17 @@
   .btn {
     @apply inline-flex px-4 py-3 font-medium rounded items-center justify-center text-sm whitespace-nowrap; // space-x-3;
     &.btn-primary {
-      @apply bg-sky-500;
+      @apply bg-blue-500 border border-blue-500;
+      &.btn-outline,
+      &.btn-light-outline {
+        @apply bg-blue-500/30;
+      }
+      &.btn-dark-outline {
+        @apply dark:bg-blue-500/30;
+      }
+      &.btn-light-outline {
+        @apply dark:bg-blue-500;
+      }
     }
     svg {
       @apply w-4 h-4;
@@ -169,6 +179,12 @@
         &.sub-menu-sized {
           @apply h-[312px] w-[640px];
         }
+        &.sub-menu-items {
+          @apply w-[640px] flex flex-wrap;
+          li {
+            @apply w-1/5;
+          }
+        }
       }
       .sub-menu-list {
         @apply absolute left-[132px] hidden top-0 h-[312px] w-[508px] rounded-r-lg py-2 px-4 bg-white dark:bg-slate-900 dark:highlight-white/10;

+ 8 - 1
tailwind.config.js

@@ -273,7 +273,14 @@ module.exports = {
         },
       }),
       fontFamily: {
-        sans: ["Nunito Sans,SF Pro Text,SF Pro Icons, Inter var", ...defaultTheme.fontFamily.sans],
+        sans: [
+          "Nunito Sans",
+          "SF Pro Text",
+          "SF Pro Icons",
+          "Inter var",
+          ...defaultTheme.fontFamily.sans,
+        ],
+        serif: ["Merriweather", ...defaultTheme.fontFamily.serif],
         mono: ["Fira Code VF", ...defaultTheme.fontFamily.mono],
         source: ["Source Sans Pro", ...defaultTheme.fontFamily.sans],
         "ubuntu-mono": ["Ubuntu Mono", ...defaultTheme.fontFamily.mono],

+ 63 - 0
types/http.d.ts

@@ -0,0 +1,63 @@
+export interface ResData<T> {
+  data: T;
+  errmsg: string;
+  errno: number;
+}
+
+export interface Detail {
+  id: number;
+  img: string;
+  name: string;
+  other_name: string;
+  uri: string;
+  author: string;
+  desc: string;
+  genre: string;
+  host: string;
+  status: 0;
+  source: string;
+  create_time: string;
+  update_time: string;
+  Chapters: number;
+}
+
+export interface ListItem {
+  author: string;
+  genre: string;
+  img: string;
+  name: string;
+  stauts: number;
+  uri: string;
+}
+
+export interface ChapterItem {
+  id: number;
+  novel_id: number;
+  name: string;
+  source_url: string;
+  uri: string;
+  sort: number;
+  create_time: string;
+  update_time: string;
+}
+
+export interface ChapterListData {
+  chapters: ChapterItem[];
+  novel: {
+    name: string;
+  };
+}
+
+export interface ChapterData {
+  chapter: string;
+  content: string;
+  next: string;
+  pre: string;
+  title: string;
+}
+
+export interface GenreItem {
+  id: number;
+  name: string;
+  uri: string;
+}

+ 12 - 0
utils/hooks/useGet.ts

@@ -0,0 +1,12 @@
+import useSWR from "swr";
+import qs from "qs";
+import { get } from "../http";
+import { ResData } from "../../types/http";
+
+function useGet<T = any, E = any>(api: string, query?: unknown) {
+  let url = api;
+  if (query) url += "?" + qs.stringify(query);
+  return useSWR<ResData<T>, E>(url, get);
+}
+
+export default useGet;

+ 9 - 0
utils/hooks/usePost.ts

@@ -0,0 +1,9 @@
+import useSWR from "swr";
+import { post } from "../http";
+import { ResData } from "../../types/http";
+
+function usePost<T = any, E = any>(api: string, data?: unknown) {
+  return useSWR<ResData<T>, E>([api, data], post);
+}
+
+export default usePost;

+ 20 - 0
utils/http/index.ts

@@ -0,0 +1,20 @@
+import { apiHost, isServer } from "../../libs/config";
+import type { ResData } from "../../types/http";
+
+export function http<T = any>(uri: string, config?: RequestInit) {
+  return fetch(isServer ? `${apiHost}${uri}` : uri, config).then((res) =>
+    res.json()
+  ) as Promise<ResData<T>>;
+}
+
+export function get<T = any>(uri: string) {
+  return http<T>(uri);
+}
+
+export function post<T = any>(uri: string, body?: unknown) {
+  return http<T>(uri, {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify(body),
+  });
+}

+ 131 - 102
yarn.lock

@@ -93,82 +93,87 @@
     prop-types "^15.8.1"
     react-is "^18.2.0"
 
-"@next/env@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.1.tgz#18266bd92de3b4aa4037b1927aa59e6f11879260"
-  integrity sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==
+"@next/env@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/env/-/env-13.0.0.tgz#38527956680693c90b4522ab4ab9a2fbe3a17f67"
+  integrity sha512-65v9BVuah2Mplohm4+efsKEnoEuhmlGm8B2w6vD1geeEP2wXtlSJCvR/cCRJ3fD8wzCQBV41VcMBQeYET6MRkg==
 
-"@next/eslint-plugin-next@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.3.1.tgz#b821f27b0f175954d8d18e5d323fce040ecc79a6"
-  integrity sha512-sw+lTf6r6P0j+g/n9y4qdWWI2syPqZx+uc0+B/fRENqfR3KpSid6MIKqc9gNwGhJASazEQ5b3w8h4cAET213jw==
+"@next/eslint-plugin-next@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.0.0.tgz#cf3d799b21671554c1f5889c01d2513afb9973cd"
+  integrity sha512-z+gnX4Zizatqatc6f4CQrcC9oN8Us3Vrq/OLyc98h7K/eWctrnV91zFZodmJHUjx0cITY8uYM7LXD7IdYkg3kg==
   dependencies:
     glob "7.1.7"
 
-"@next/swc-android-arm-eabi@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.1.tgz#b15ce8ad376102a3b8c0f3c017dde050a22bb1a3"
-  integrity sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==
-
-"@next/swc-android-arm64@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.3.1.tgz#85d205f568a790a137cb3c3f720d961a2436ac9c"
-  integrity sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==
-
-"@next/swc-darwin-arm64@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.1.tgz#b105457d6760a7916b27e46c97cb1a40547114ae"
-  integrity sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==
-
-"@next/swc-darwin-x64@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz#6947b39082271378896b095b6696a7791c6e32b1"
-  integrity sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==
-
-"@next/swc-freebsd-x64@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.1.tgz#2b6c36a4d84aae8b0ea0e0da9bafc696ae27085a"
-  integrity sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==
-
-"@next/swc-linux-arm-gnueabihf@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.1.tgz#6e421c44285cfedac1f4631d5de330dd60b86298"
-  integrity sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==
-
-"@next/swc-linux-arm64-gnu@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.1.tgz#8863f08a81f422f910af126159d2cbb9552ef717"
-  integrity sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==
-
-"@next/swc-linux-arm64-musl@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.1.tgz#0038f07cf0b259d70ae0c80890d826dfc775d9f3"
-  integrity sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==
-
-"@next/swc-linux-x64-gnu@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.1.tgz#c66468f5e8181ffb096c537f0dbfb589baa6a9c1"
-  integrity sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==
-
-"@next/swc-linux-x64-musl@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.1.tgz#c6269f3e96ac0395bc722ad97ce410ea5101d305"
-  integrity sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==
-
-"@next/swc-win32-arm64-msvc@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.1.tgz#83c639ee969cee36ce247c3abd1d9df97b5ecade"
-  integrity sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==
-
-"@next/swc-win32-ia32-msvc@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.1.tgz#52995748b92aa8ad053440301bc2c0d9fbcf27c2"
-  integrity sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==
-
-"@next/swc-win32-x64-msvc@12.3.1":
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz#27d71a95247a9eaee03d47adee7e3bd594514136"
-  integrity sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==
+"@next/font@^13.0.1":
+  version "13.0.1"
+  resolved "https://registry.npmmirror.com/@next/font/-/font-13.0.1.tgz#def5401c7f031e8b87c31ad08907154b669df187"
+  integrity sha512-ktst4FFbEPtD8SJVQr2HuAqOoFpJx3xKkLOHzCV7D5PUk6KL/sFQa+t6UEucN70X037tqBV39BsFhMJ56hsPOw==
+
+"@next/swc-android-arm-eabi@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.0.tgz#15cd89d19d3c00d123fdfe367bab38c362f6c515"
+  integrity sha512-+DUQkYF93gxFjWY+CYWE1QDX6gTgnUiWf+W4UqZjM1Jcef8U97fS6xYh+i+8rH4MM0AXHm7OSakvfOMzmjU6VA==
+
+"@next/swc-android-arm64@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-android-arm64/-/swc-android-arm64-13.0.0.tgz#9410365bb07097268d4773a46b02cfe6b3fe3ab7"
+  integrity sha512-RW9Uy3bMSc0zVGCa11klFuwfP/jdcdkhdruqnrJ7v+7XHm6OFKkSRzX6ee7yGR1rdDZvTnP4GZSRSpzjLv/N0g==
+
+"@next/swc-darwin-arm64@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.0.0.tgz#caf262fb5cb8bb335f6f344fd67a44dc8bf6a084"
+  integrity sha512-APA26nps1j4qyhOIzkclW/OmgotVHj1jBxebSpMCPw2rXfiNvKNY9FA0TcuwPmUCNqaTnm703h6oW4dvp73A4Q==
+
+"@next/swc-darwin-x64@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.0.0.tgz#6b214753410e1d8512a1491045acea1e188df7d6"
+  integrity sha512-qsUhUdoFuRJiaJ7LnvTQ6GZv1QnMDcRXCIjxaN0FNVXwrjkq++U7KjBUaxXkRzLV4C7u0NHLNOp0iZwNNE7ypw==
+
+"@next/swc-freebsd-x64@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-13.0.0.tgz#eeb176bdb585f48882bdac1d04271b918ca87590"
+  integrity sha512-sCdyCbboS7CwdnevKH9J6hkJI76LUw1jVWt4eV7kISuLiPba3JmehZSWm80oa4ADChRVAwzhLAo2zJaYRrInbg==
+
+"@next/swc-linux-arm-gnueabihf@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-13.0.0.tgz#2c2a9622c93f87a8baca94e068f674da4cae6018"
+  integrity sha512-/X/VxfFA41C9jrEv+sUsPLQ5vbDPVIgG0CJrzKvrcc+b+4zIgPgtfsaWq9ockjHFQi3ycvlZK4TALOXO8ovQ6Q==
+
+"@next/swc-linux-arm64-gnu@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.0.0.tgz#69505827e2928fb18034150fd4d754d54c4a1c4b"
+  integrity sha512-x6Oxr1GIi0ZtNiT6jbw+JVcbEi3UQgF7mMmkrgfL4mfchOwXtWSHKTSSPnwoJWJfXYa0Vy1n8NElWNTGAqoWFw==
+
+"@next/swc-linux-arm64-musl@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.0.0.tgz#487a88f2583a046e882328fe0665b37eca4fd0f6"
+  integrity sha512-SnMH9ngI+ipGh3kqQ8+mDtWunirwmhQnQeZkEq9e/9Xsgjf04OetqrqRHKM1HmJtG2qMUJbyXFJ0F81TPuT+3g==
+
+"@next/swc-linux-x64-gnu@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.0.0.tgz#29e89c7e4fd2e2b16ad059076f6261998aee53df"
+  integrity sha512-VSQwTX9EmdbotArtA1J67X8964oQfe0xHb32x4tu+JqTR+wOHyG6wGzPMdXH2oKAp6rdd7BzqxUXXf0J+ypHlw==
+
+"@next/swc-linux-x64-musl@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.0.0.tgz#2f63aae922d2b2829aec21bf8f9adda8b6c16365"
+  integrity sha512-xBCP0nnpO0q4tsytXkvIwWFINtbFRyVY5gxa1zB0vlFtqYR9lNhrOwH3CBrks3kkeaePOXd611+8sjdUtrLnXA==
+
+"@next/swc-win32-arm64-msvc@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.0.0.tgz#4117bad96c2a6775f70294fba45c63951a8a21ac"
+  integrity sha512-NutwDafqhGxqPj/eiUixJq9ImS/0sgx6gqlD7jRndCvQ2Q8AvDdu1+xKcGWGNnhcDsNM/n1avf1e62OG1GaqJg==
+
+"@next/swc-win32-ia32-msvc@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.0.0.tgz#5914eb86f9ea92a00d76cb094dd9734b3bf2012c"
+  integrity sha512-zNaxaO+Kl/xNz02E9QlcVz0pT4MjkXGDLb25qxtAzyJL15aU0+VjjbIZAYWctG59dvggNIUNDWgoBeVTKB9xLg==
+
+"@next/swc-win32-x64-msvc@13.0.0":
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.0.0.tgz#c54a5a739dee04b20338d305226a2acdf701f67f"
+  integrity sha512-FFOGGWwTCRMu9W7MF496Urefxtuo2lttxF1vwS+1rIRsKvuLrWhVaVTj3T8sf2EBL6gtJbmh4TYlizS+obnGKA==
 
 "@nodelib/fs.scandir@2.1.5":
   version "2.1.5"
@@ -250,6 +255,11 @@
   resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
   integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
 
+"@types/qs@^6.9.7":
+  version "6.9.7"
+  resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
+  integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
+
 "@types/react-dom@18.0.6":
   version "18.0.6"
   resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.6.tgz#36652900024842b74607a17786b6662dd1e103a1"
@@ -543,6 +553,11 @@ chalk@^4.0.0:
   optionalDependencies:
     fsevents "~2.3.2"
 
+client-only@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.npmmirror.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
+  integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
+
 clsx@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
@@ -773,12 +788,12 @@ escape-string-regexp@^4.0.0:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
   integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
 
-eslint-config-next@12.3.1:
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.3.1.tgz#5d4eb0b7903cea81fd0d5106601d3afb0a453ff4"
-  integrity sha512-EN/xwKPU6jz1G0Qi6Bd/BqMnHLyRAL0VsaQaWA7F3KkjAgZHi4f1uL1JKGWNxdQpHTW/sdGONBd0bzxUka/DJg==
+eslint-config-next@13.0.0:
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/eslint-config-next/-/eslint-config-next-13.0.0.tgz#d533ee1dbd6576fd3759ba4db4d5a6c4e039c242"
+  integrity sha512-y2nqWS2tycWySdVhb+rhp6CuDmDazGySqkzzQZf3UTyfHyC7og1m5m/AtMFwCo5mtvDqvw1BENin52kV9733lg==
   dependencies:
-    "@next/eslint-plugin-next" "12.3.1"
+    "@next/eslint-plugin-next" "13.0.0"
     "@rushstack/eslint-patch" "^1.1.3"
     "@typescript-eslint/parser" "^5.21.0"
     eslint-import-resolver-node "^0.3.6"
@@ -1521,31 +1536,31 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
 
-next@12.3.1:
-  version "12.3.1"
-  resolved "https://registry.yarnpkg.com/next/-/next-12.3.1.tgz#127b825ad2207faf869b33393ec8c75fe61e50f1"
-  integrity sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==
+next@13.0.0:
+  version "13.0.0"
+  resolved "https://registry.npmmirror.com/next/-/next-13.0.0.tgz#6f07064a4f374562cf58677bef4dd06326ca648b"
+  integrity sha512-puH1WGM6rGeFOoFdXXYfUxN9Sgi4LMytCV5HkQJvVUOhHfC1DoVqOfvzaEteyp6P04IW+gbtK2Q9pInVSrltPA==
   dependencies:
-    "@next/env" "12.3.1"
+    "@next/env" "13.0.0"
     "@swc/helpers" "0.4.11"
     caniuse-lite "^1.0.30001406"
     postcss "8.4.14"
-    styled-jsx "5.0.7"
+    styled-jsx "5.1.0"
     use-sync-external-store "1.2.0"
   optionalDependencies:
-    "@next/swc-android-arm-eabi" "12.3.1"
-    "@next/swc-android-arm64" "12.3.1"
-    "@next/swc-darwin-arm64" "12.3.1"
-    "@next/swc-darwin-x64" "12.3.1"
-    "@next/swc-freebsd-x64" "12.3.1"
-    "@next/swc-linux-arm-gnueabihf" "12.3.1"
-    "@next/swc-linux-arm64-gnu" "12.3.1"
-    "@next/swc-linux-arm64-musl" "12.3.1"
-    "@next/swc-linux-x64-gnu" "12.3.1"
-    "@next/swc-linux-x64-musl" "12.3.1"
-    "@next/swc-win32-arm64-msvc" "12.3.1"
-    "@next/swc-win32-ia32-msvc" "12.3.1"
-    "@next/swc-win32-x64-msvc" "12.3.1"
+    "@next/swc-android-arm-eabi" "13.0.0"
+    "@next/swc-android-arm64" "13.0.0"
+    "@next/swc-darwin-arm64" "13.0.0"
+    "@next/swc-darwin-x64" "13.0.0"
+    "@next/swc-freebsd-x64" "13.0.0"
+    "@next/swc-linux-arm-gnueabihf" "13.0.0"
+    "@next/swc-linux-arm64-gnu" "13.0.0"
+    "@next/swc-linux-arm64-musl" "13.0.0"
+    "@next/swc-linux-x64-gnu" "13.0.0"
+    "@next/swc-linux-x64-musl" "13.0.0"
+    "@next/swc-win32-arm64-msvc" "13.0.0"
+    "@next/swc-win32-ia32-msvc" "13.0.0"
+    "@next/swc-win32-x64-msvc" "13.0.0"
 
 node-releases@^2.0.6:
   version "2.0.6"
@@ -1788,6 +1803,13 @@ punycode@^2.1.0:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
+qs@^6.11.0:
+  version "6.11.0"
+  resolved "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
+  integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
+  dependencies:
+    side-channel "^1.0.4"
+
 queue-microtask@^1.2.2:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -1800,7 +1822,7 @@ quick-lru@^5.1.1:
 
 react-dom@18.2.0:
   version "18.2.0"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
+  resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
   integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
   dependencies:
     loose-envify "^1.1.0"
@@ -1818,7 +1840,7 @@ react-is@^18.2.0:
 
 react@18.2.0:
   version "18.2.0"
-  resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
+  resolved "https://registry.npmmirror.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
   integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
   dependencies:
     loose-envify "^1.1.0"
@@ -2022,10 +2044,12 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 
-styled-jsx@5.0.7:
-  version "5.0.7"
-  resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.7.tgz#be44afc53771b983769ac654d355ca8d019dff48"
-  integrity sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==
+styled-jsx@5.1.0:
+  version "5.1.0"
+  resolved "https://registry.npmmirror.com/styled-jsx/-/styled-jsx-5.1.0.tgz#4a5622ab9714bd3fcfaeec292aa555871f057563"
+  integrity sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==
+  dependencies:
+    client-only "0.0.1"
 
 supports-color@^7.1.0:
   version "7.2.0"
@@ -2039,6 +2063,11 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
+swr@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.npmmirror.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8"
+  integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==
+
 tailwindcss@^3:
   version "3.2.1"
   resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.2.1.tgz#1bd828fff3172489962357f8d531c184080a6786"

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů