Leo 3 лет назад
Родитель
Сommit
ea28de9b3d
11 измененных файлов с 100 добавлено и 30 удалено
  1. 5 4
      components/novel/Toc/index.tsx
  2. 2 1
      libs/config.ts
  3. 24 0
      libs/gtag.ts
  4. 1 0
      package.json
  5. 35 12
      pages/_app.tsx
  6. 5 0
      pages/_document.tsx
  7. 12 9
      styles/chapter.module.scss
  8. 6 1
      utils/hooks/useGet.ts
  9. 3 1
      utils/hooks/usePost.ts
  10. 2 2
      utils/http/index.ts
  11. 5 0
      yarn.lock

+ 5 - 4
components/novel/Toc/index.tsx

@@ -5,16 +5,16 @@ import type { ChapterListData } from "../../../types/http";
 import styles from "../../../styles/chapter.module.scss";
 import styles from "../../../styles/chapter.module.scss";
 import { useCallback } from "react";
 import { useCallback } from "react";
 import VirtualScroll from "../../VirtualScroll";
 import VirtualScroll from "../../VirtualScroll";
-
-type FontSize = 1 | 2 | 3 | 4 | 5;
+import clsx from "clsx";
 
 
 interface TocProps {
 interface TocProps {
   novel: string;
   novel: string;
   chapter: string;
   chapter: string;
+  className?: string;
 }
 }
 
 
 const Toc = (props: TocProps) => {
 const Toc = (props: TocProps) => {
-  const { novel } = props;
+  const { novel, className } = props;
 
 
   const { data: { data: chapters } = { data: null } } = useGet<ChapterListData>(
   const { data: { data: chapters } = { data: null } } = useGet<ChapterListData>(
     `/api/novel/${novel}/chapters`
     `/api/novel/${novel}/chapters`
@@ -43,7 +43,7 @@ const Toc = (props: TocProps) => {
   );
   );
 
 
   return (
   return (
-    <div className={styles["toolbar-inner"]}>
+    <div className={clsx(styles["toolbar-inner"], className)}>
       <div className={styles["toolbar-display-title"]}>Chapters</div>
       <div className={styles["toolbar-display-title"]}>Chapters</div>
       {chapters ? (
       {chapters ? (
         <VirtualScroll
         <VirtualScroll
@@ -51,6 +51,7 @@ const Toc = (props: TocProps) => {
           itemHeight={32}
           itemHeight={32}
           total={chapters.chapters.length}
           total={chapters.chapters.length}
           getItems={getTocItem}
           getItems={getTocItem}
+          className={styles["toolbar-scrollbar"]}
         />
         />
       ) : null}
       ) : null}
       {/* {chapters ? (
       {/* {chapters ? (

+ 2 - 1
libs/config.ts

@@ -1,2 +1,3 @@
 export const isServer = typeof window === undefined;
 export const isServer = typeof window === undefined;
-export const apiHost = "https://novels.yergoo.com";
+export const API_HOST = "https://novels.yergoo.com";
+export const GA_TRACKING_ID = "G-T6QZWX1BQ2";

+ 24 - 0
libs/gtag.ts

@@ -0,0 +1,24 @@
+export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID;
+
+// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
+export const pageview = (url: string) => {
+  window.gtag("config", GA_TRACKING_ID, {
+    page_path: url,
+  });
+};
+
+type GTagEvent = {
+  action: string;
+  category: string;
+  label: string;
+  value: number;
+};
+
+// https://developers.google.com/analytics/devguides/collection/gtagjs/events
+export const event = ({ action, category, label, value }: GTagEvent) => {
+  window.gtag("event", action, {
+    event_category: category,
+    event_label: label,
+    value: value,
+  });
+};

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "@tailwindcss/forms": "^0.5.3",
     "@tailwindcss/forms": "^0.5.3",
     "@tailwindcss/line-clamp": "^0.4.2",
     "@tailwindcss/line-clamp": "^0.4.2",
     "@tailwindcss/typography": "^0.5.7",
     "@tailwindcss/typography": "^0.5.7",
+    "@types/gtag.js": "^0.0.12",
     "@types/node": "18.11.3",
     "@types/node": "18.11.3",
     "@types/qs": "^6.9.7",
     "@types/qs": "^6.9.7",
     "@types/react": "18.0.21",
     "@types/react": "18.0.21",

+ 35 - 12
pages/_app.tsx

@@ -1,13 +1,17 @@
 import { SWRConfig } from "swr";
 import { SWRConfig } from "swr";
+import Script from "next/script";
 import type { NextPage } from "next";
 import type { NextPage } from "next";
+import { useRouter } from "next/router";
 import type { AppProps } from "next/app";
 import type { AppProps } from "next/app";
 import App, { AppContext } from "next/app";
 import App, { AppContext } from "next/app";
-import type { ReactElement, ReactNode } from "react";
+import { ReactElement, ReactNode, useEffect } from "react";
 
 
 import { Context } from "../libs/context";
 import { Context } from "../libs/context";
 import Layout from "../components/common/Layout";
 import Layout from "../components/common/Layout";
 
 
 import "../styles/globals.scss";
 import "../styles/globals.scss";
+import { pageview } from "../libs/gtag";
+import { GA_TRACKING_ID } from "../libs/config";
 
 
 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
 export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
   getLayout?: (page: ReactElement) => ReactNode;
   getLayout?: (page: ReactElement) => ReactNode;
@@ -25,19 +29,38 @@ type AppPropsWithLayout = AppProps & {
 
 
 const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
 const MyApp = ({ Component, pageProps }: AppPropsWithLayout) => {
   const { fallback, genre, ...otherProps } = pageProps;
   const { fallback, genre, ...otherProps } = pageProps;
+  const router = useRouter();
+
+  useEffect(() => {
+    const handleRouteChange = (url: string) => {
+      pageview(url);
+    };
+    router.events.on("routeChangeComplete", handleRouteChange);
+    router.events.on("hashChangeComplete", handleRouteChange);
+    return () => {
+      router.events.off("routeChangeComplete", handleRouteChange);
+      router.events.off("hashChangeComplete", handleRouteChange);
+    };
+  }, [router.events]);
 
 
   return (
   return (
-    <Context.Provider value={{ genre }}>
-      <SWRConfig value={{ fallback, revalidateIfStale: false }}>
-        {Component.getLayout ? (
-          Component.getLayout(<Component {...otherProps} />)
-        ) : (
-          <Layout>
-            <Component {...otherProps} />
-          </Layout>
-        )}
-      </SWRConfig>
-    </Context.Provider>
+    <>
+      <Script
+        strategy="afterInteractive"
+        src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
+      />
+      <Context.Provider value={{ genre }}>
+        <SWRConfig value={{ fallback, revalidateIfStale: false }}>
+          {Component.getLayout ? (
+            Component.getLayout(<Component {...otherProps} />)
+          ) : (
+            <Layout>
+              <Component {...otherProps} />
+            </Layout>
+          )}
+        </SWRConfig>
+      </Context.Provider>
+    </>
   );
   );
 };
 };
 
 

+ 5 - 0
pages/_document.tsx

@@ -27,6 +27,11 @@ export default function Document() {
           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"
           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"
           data-ignore="true"
         />
         />
+        <script
+          dangerouslySetInnerHTML={{
+            __html: `window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());`,
+          }}
+        />
       </Head>
       </Head>
       <body>
       <body>
         <Main />
         <Main />

+ 12 - 9
styles/chapter.module.scss

@@ -1,6 +1,6 @@
 .chapter-page {
 .chapter-page {
-  @apply 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;
+  @apply w-full flex-1 py-5 px-5 bg-white;
+  @apply lg:w-0 lg:max-w-3xl lg:px-10 lg:border-x lg:border-x-gray-200;
   :global(.dark) & {
   :global(.dark) & {
     @apply bg-gray-800 border-x-gray-700;
     @apply bg-gray-800 border-x-gray-700;
   }
   }
@@ -36,8 +36,8 @@
 .toolbar {
 .toolbar {
   color-scheme: dark;
   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;
+  @apply fixed bottom-0 left-0 w-full bg-gray-900 z-30 text-gray-400; // hidden;
+  @apply lg:bg-gray-800 lg:right-0 lg:w-auto lg:left-auto lg:h-full lg:flex lg:items-center lg:justify-center;
   .toolbar-items {
   .toolbar-items {
     @apply flex lg:flex-col lg:w-full;
     @apply flex lg:flex-col lg:w-full;
   }
   }
@@ -58,15 +58,15 @@
       @apply w-5 h-5 lg:w-4 lg:h-4;
       @apply w-5 h-5 lg:w-4 lg:h-4;
     }
     }
     &.toolbar-item-current {
     &.toolbar-item-current {
-      @apply lg:bg-blue-700 lg:text-white;
+      @apply lg:bg-blue-700 text-white;
     }
     }
   }
   }
 }
 }
 .toolbar-display {
 .toolbar-display {
   color-scheme: dark;
   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;
+  @apply fixed bottom-0 left-0 w-full bg-gray-900 text-gray-100 z-30 pt-4 pb-16 px-5 shadow-xl shadow-white/10;
+  @apply lg:w-96 lg:max-h-[calc(100vh-65px)] lg:sticky lg:top-[65px] lg:pb-4 lg:px-0;
   .toolbar-display-title {
   .toolbar-display-title {
     @apply text-base mb-2;
     @apply text-base mb-2;
   }
   }
@@ -77,11 +77,14 @@
   //   @apply w-full;
   //   @apply w-full;
   // }
   // }
   .toolbar-inner {
   .toolbar-inner {
-    @apply max-h-[70vh] flex flex-col overflow-hidden flex-1 flex-shrink-0;
+    @apply max-h-[70vh] flex flex-col flex-1 flex-shrink-0;
     @apply lg:max-h-full lg:px-5;
     @apply lg:max-h-full lg:px-5;
+    .toolbar-scrollbar {
+      @apply -mr-5 pr-5;
+    }
     ol {
     ol {
       // @apply flex-1 h-0 overflow-y-auto;
       // @apply flex-1 h-0 overflow-y-auto;
-      // @apply lg:-mr-5 lg:pr-5;
+      // @apply -mr-5 pr-5;
       li {
       li {
         a {
         a {
           @apply flex w-full h-full py-1;
           @apply flex w-full h-full py-1;

+ 6 - 1
utils/hooks/useGet.ts

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

+ 3 - 1
utils/hooks/usePost.ts

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

+ 2 - 2
utils/http/index.ts

@@ -1,8 +1,8 @@
 import type { ResData } from "../../types/http";
 import type { ResData } from "../../types/http";
-import { apiHost, isServer } from "../../libs/config";
+import { API_HOST, isServer } from "../../libs/config";
 
 
 export function http<T = any>(uri: string, config?: RequestInit) {
 export function http<T = any>(uri: string, config?: RequestInit) {
-  return fetch(isServer ? `${apiHost}${uri}` : uri, config).then((res) =>
+  return fetch(isServer ? `${API_HOST}${uri}` : uri, config).then((res) =>
     res.json()
     res.json()
   ) as Promise<ResData<T>>;
   ) as Promise<ResData<T>>;
 }
 }

+ 5 - 0
yarn.lock

@@ -240,6 +240,11 @@
     lodash.merge "^4.6.2"
     lodash.merge "^4.6.2"
     postcss-selector-parser "6.0.10"
     postcss-selector-parser "6.0.10"
 
 
+"@types/gtag.js@^0.0.12":
+  version "0.0.12"
+  resolved "https://registry.npmmirror.com/@types/gtag.js/-/gtag.js-0.0.12.tgz#095122edca896689bdfcdd73b057e23064d23572"
+  integrity sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==
+
 "@types/json5@^0.0.29":
 "@types/json5@^0.0.29":
   version "0.0.29"
   version "0.0.29"
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
   resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"