diff --git a/components/homepage/HomeCover.tsx b/components/homepage/HomeCover.tsx index ecee468..694d735 100644 --- a/components/homepage/HomeCover.tsx +++ b/components/homepage/HomeCover.tsx @@ -6,7 +6,7 @@ export const HomeCover = () => { return ( <>
{ }; export const ContentContainer = ({ children }: { children: React.ReactNode }) => { - return
{children}
; + return
{children}
; }; diff --git a/components/readerpage/DrawerTOC.tsx b/components/readerpage/DrawerTOC.tsx index 5e3f117..a8b0a37 100644 --- a/components/readerpage/DrawerTOC.tsx +++ b/components/readerpage/DrawerTOC.tsx @@ -1,19 +1,23 @@ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; +import { useActiveHeading } from "@/hooks/useActiveHeading"; import useDrawerTOCState from "@/stores/useDrawerTOCState"; import { TTOCItem } from "@/types/toc.type"; import Link from "next/link"; +import { MdMenuBook } from "react-icons/md"; +import { twMerge } from "tailwind-merge"; export const DrawerTOC = (props: { data: TTOCItem[] }) => { const isTOCOpen = useDrawerTOCState((state) => state.isOpen); const setIsTOCOpen = useDrawerTOCState((state) => state.changeDrawerTOCOpen); + const activeId = useActiveHeading(props.data.map((item) => `#${item.anchorId}`)); return ( -
setIsTOCOpen(!isTOCOpen)} className="p-2 font-bold"> - {"TOC"} +
setIsTOCOpen(!isTOCOpen)} className="p-1 font-bold"> +
@@ -23,17 +27,17 @@ export const DrawerTOC = (props: { data: TTOCItem[] }) => {
    {props.data?.map((item) => ( { setIsTOCOpen(false); }} key={`drawer-toc-${item.anchorId}`} href={`#${item.anchorId}`} > -
  • {`${item.title}`}
  • +
  • {`${item.title}`}
  • ))}
diff --git a/components/readerpage/PostComments.tsx b/components/readerpage/PostComments.tsx index 3e819bb..779f4a2 100644 --- a/components/readerpage/PostComments.tsx +++ b/components/readerpage/PostComments.tsx @@ -6,7 +6,7 @@ export const PostComments = (props: { postId: string }) => { const { theme } = useTheme(); return ( Config.Giscus && ( -
+
{ + const activeId = useActiveHeading(props.data.map((item) => `#${item.anchorId}`)); + return ( {"TABLE OF CONTENTS"} - +
    {props.data?.map((item) => ( - +
  • {`${item.title}`}
  • diff --git a/components/utils/Footer.tsx b/components/utils/Footer.tsx index 283de3d..bce342e 100644 --- a/components/utils/Footer.tsx +++ b/components/utils/Footer.tsx @@ -19,11 +19,6 @@ export const Footer = () => { }} >
    -
    - - Powered by vercel - -
    {"Source Code"} diff --git a/components/utils/NavBar.tsx b/components/utils/NavBar.tsx index be42cdd..59f90a4 100644 --- a/components/utils/NavBar.tsx +++ b/components/utils/NavBar.tsx @@ -33,7 +33,7 @@ export const NavBar = () => { return ( setIsSideNavOpen(open)}> -
    +

    {Config.SiteTitle}

    @@ -62,7 +62,7 @@ export const NavBar = () => { {theme === "light" ? : }
    -
    +
    {
    - + {MenuItems.map((menuItem) => ( {
    )}
-
{normalizeDate(postItem.frontMatter.time)}
- {postItem.frontMatter.summary && ( -
-

{postItem.frontMatter.summary}

-
- )} +
{normalizeDate(postItem.frontMatter.time)}
{postItem.frontMatter.tags && (
{postItem.frontMatter.tags.map((tagName) => ( - + {tagName} ))}
)} + {postItem.frontMatter.summary && ( +
+

{postItem.frontMatter.summary}

+
+ )}
))} diff --git a/hooks/useActiveHeading.tsx b/hooks/useActiveHeading.tsx new file mode 100644 index 0000000..cb2ae2b --- /dev/null +++ b/hooks/useActiveHeading.tsx @@ -0,0 +1,48 @@ +import * as React from "react"; + +function removeFirstHash(str: string): string { + return str.replace(/^#/, ""); +} + +/** + * React hook to highlight a heading a user is currently reading. + * @param headingList List of links to the headings (ex. "#title") + * @param options Options for the Intersectionobserver (ex. rootMargin) + * @returns The Id/Link to the heading, that is currently active. + */ +export function useActiveHeading(headingList: string[], options?: IntersectionObserverInit) { + const [activeId, setActiveId] = React.useState(""); + + React.useEffect(() => { + const callback: IntersectionObserverCallback = (headingEntries) => { + // Get all headings that are currently visible on the page + const visibleHeadings = headingEntries.filter((e) => e.isIntersecting); + + if (visibleHeadings.length === 0) { + //Necessary if a user scrolls down and then reloads. + //In that case the IntersectionObserver didn't see the Heading onscreen + const element = headingEntries.reverse().find((e) => e.boundingClientRect.bottom < 150); + if (element) { + setActiveId(`#${element.target.id}`); + } + } else { + // If there is more than one visible heading, + // choose the one that is closest to the top of the page + // the entries are always sorted top to bottom. + setActiveId(`#${visibleHeadings[0].target.id}`); + } + }; + + const observer = new IntersectionObserver(callback, options); + + //Observe all (non-null) elements + headingList + .map((heading) => document?.querySelector(`[id='${removeFirstHash(heading)}']`)) + //Remove null elments + .flatMap((f) => (!!f ? [f] : [])) + .forEach((element) => observer.observe(element)); + + return () => observer.disconnect(); + }, [headingList, options]); + return activeId; +} diff --git a/lib/rss.tsx b/lib/rss.tsx index 1de4175..6b3c41b 100644 --- a/lib/rss.tsx +++ b/lib/rss.tsx @@ -2,6 +2,7 @@ import { CopyrightAnnouncement, LatestPostCountInHomePage, WebsiteURL } from "@/ import { Config } from "@/data/config"; import { Feed } from "feed"; import fs from "fs"; +import { JSDOM } from "jsdom"; import { MDXRemote } from "next-mdx-remote"; import { serialize } from "next-mdx-remote/serialize"; import { renderToString } from "react-dom/server"; @@ -17,6 +18,25 @@ import { getPostFileContent, sortedPosts } from "./post-process"; const NoticeForRSSReaders = `\n---\n**NOTE:** Different RSS reader may have deficient even no support for svg formulations rendering. If it happens, please read the origin page to have better experience.`; +function minifyHTMLCode(htmlString: string): string { + const dom = new JSDOM(htmlString); + const document = dom.window.document; + const elements = document.querySelectorAll("*"); + const unusedElements = document.querySelectorAll("script, style"); + + // Remove all class attributes. + elements.forEach((element) => { + element.removeAttribute("class"); + }); + + // Remove all script and style tags. + unusedElements.forEach((element) => { + element.parentElement?.removeChild(element); + }); + + return dom.serialize(); +} + /** * Generate the RSS Feed File in `./public` so it could be visited by https://domain/rss.xml */ @@ -49,7 +69,7 @@ export const generateRSSFeed = async () => { format: "md", }, }); - const htmlContent = renderToString(); + const htmlContent = minifyHTMLCode(renderToString()); feed.addItem({ title: post.frontMatter.title, diff --git a/lib/search.ts b/lib/search.ts index da6df46..f2aba48 100644 --- a/lib/search.ts +++ b/lib/search.ts @@ -1,5 +1,7 @@ import { cutForSearch } from "@node-rs/jieba"; +import Colors from "colors"; import minisearch from "minisearch"; +import sizeof from "object-sizeof"; import { getPostFileContent, sortedPosts } from "./post-process"; // Due to the flaws of the word tokenizer, @@ -30,6 +32,8 @@ function makeSearchIndex() { content: content, }); } + const sizeofIndex = (sizeof(miniSearch) / 1024 ** 2).toFixed(3); + console.log(Colors.cyan(`Search index is ready. And the size of index is ${sizeofIndex} mb`)); return miniSearch; } diff --git a/package-lock.json b/package-lock.json index c7acffd..33dc6d2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "next-seo": "^6.4.0", "next-share": "^0.27.0", "next-themes": "^0.2.1", + "object-sizeof": "^2.6.3", "postcss": "8.4.31", "prism": "^1.0.0", "prismjs": "^1.29.0", @@ -2672,7 +2673,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -5917,7 +5917,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -12524,6 +12523,37 @@ "node": ">= 0.4" } }, + "node_modules/object-sizeof": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/object-sizeof/-/object-sizeof-2.6.3.tgz", + "integrity": "sha512-GNkVRrLh11Qr5BGr73dwwPE200/78QG2rbx30cnXPnMvt7UuttH4Dup5t+LtcQhARkg8Hbr0c8Kiz52+CFxYmw==", + "dependencies": { + "buffer": "^6.0.3" + } + }, + "node_modules/object-sizeof/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/object.assign": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", diff --git a/package.json b/package.json index e426185..9532130 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "next-seo": "^6.4.0", "next-share": "^0.27.0", "next-themes": "^0.2.1", + "object-sizeof": "^2.6.3", "postcss": "8.4.31", "prism": "^1.0.0", "prismjs": "^1.29.0", diff --git a/pages/404.tsx b/pages/404.tsx index 28c7fe0..c0c64b4 100644 --- a/pages/404.tsx +++ b/pages/404.tsx @@ -1,8 +1,9 @@ import { ContentContainer, Page } from "@/components/layouts"; import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import { Footer } from "@/components/utils/Footer"; import { NavBar } from "@/components/utils/NavBar"; -import { fontSourceSerifScreenCN } from "@/styles/font"; +import { fontFangZhengXiaoBiaoSongCN, fontSourceSerifScreenCN } from "@/styles/font"; import { TfiFaceSad } from "react-icons/tfi"; export default function NotFoundPage() { @@ -14,9 +15,12 @@ export default function NotFoundPage() { -
+

+ {"404 NOT FOUND"} +

+ +
-

{"404 NOT FOUND"}

{"This page does not exist for it might be removed or closed."}

diff --git a/pages/about.tsx b/pages/about.tsx index 225f518..c333fce 100644 --- a/pages/about.tsx +++ b/pages/about.tsx @@ -38,11 +38,11 @@ export default function AboutPage() { Additionally, I am also interested in XXXX.
-
+ -
+ -
    +
      {Config.SocialLinks.github && (
    • {"📕 Check out my github profile at "} diff --git a/pages/api/search/[keyword].ts b/pages/api/search/[keyword].ts index 30f98ef..14d8de4 100644 --- a/pages/api/search/[keyword].ts +++ b/pages/api/search/[keyword].ts @@ -11,7 +11,7 @@ export default function handler(req: NextApiRequest, res: NextApiResponse { // Only the TOC length reaches 3 can be displayed. // In order to avoid large blank spaces that ruin the visual perception - const isTOCLongEnough = props.tocList.length > 2; + const isTOCLongEnough = props.tocList.length > 4; const handleRightSwipe = useSwipeable({ onSwipedRight: () => { isTOCLongEnough && setIsTOCOpen(true); @@ -68,12 +69,12 @@ const ReaderPage = (props: ReaderPageProps) => { -
      -
      +
      +
      {props.frontMatter.coverURL && }
      {props.frontMatter?.title}
      @@ -95,7 +96,7 @@ const ReaderPage = (props: ReaderPageProps) => {
      {"TAGS: "}
      {props.frontMatter.tags.map((tagName) => ( @@ -122,7 +123,7 @@ const ReaderPage = (props: ReaderPageProps) => { )}
      -
      + { postId={props.postId} allowShare={props.frontMatter.allowShare} /> -
      -
        + +
          {props.prevPostListItem && (
        • {
      {isTOCLongEnough && ( -
      +
      )} diff --git a/pages/friends.tsx b/pages/friends.tsx index 9cbbc67..8913700 100644 --- a/pages/friends.tsx +++ b/pages/friends.tsx @@ -15,19 +15,19 @@ export default function FriendsPage() { -

      +

      {"FRIENDS"}

      -
      +
      {FriendsList.map((item) => ( - + {item.title} ))}
      -
      +
      {"Welcome to exchange our friend links and every high-quality blog websites are welcomed. "} diff --git a/pages/posts/[[...slug]].tsx b/pages/posts/[[...slug]].tsx index 79ac606..b47d74a 100644 --- a/pages/posts/[[...slug]].tsx +++ b/pages/posts/[[...slug]].tsx @@ -62,7 +62,7 @@ export default function PostsPage(props: PostsPageProps) { {!isEmptyArray(props.tagList) && props.pageNumber === 1 && ( <> -
      +
      {props.tagList.map((item) => ( ([]); const { toast } = useToast(); - const fetchAPI = async (param: string) => { + const fetchSearchAPI = async (param: string) => { const response = (await axios.get(`/api/search/${param}`)).data; return response; }; - const querySearch = useQuery("searchData", () => fetchAPI(searchText), { + const querySearch = useQuery("searchData", () => fetchSearchAPI(searchText), { enabled: false, onSuccess: (data) => { setSearchResult(data); @@ -52,8 +52,8 @@ export default function SearchPage() { toast({ title: "Enter a Keyword", description: "Please enter one keyword at least." }); return; } - if (searchText.length < 10) { - toast({ title: "Keywords too short", description: "Keyword length must be at least 10." }); + if (searchText.length < 4) { + toast({ title: "Keywords too short", description: "Keyword length must be at least 5." }); return; } querySearch.refetch(); diff --git a/pages/sponsor.tsx b/pages/sponsor.tsx index 47cc3f3..16fe3a6 100644 --- a/pages/sponsor.tsx +++ b/pages/sponsor.tsx @@ -1,5 +1,6 @@ import { ContentContainer, Page } from "@/components/layouts"; import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; import { Footer } from "@/components/utils/Footer"; import { NavBar } from "@/components/utils/NavBar"; import { SEO } from "@/components/utils/SEO"; @@ -54,11 +55,11 @@ export default function AboutPage() {
      - +
      )} -
      + {!isEmptyString(Config.Sponsor?.AlipayLink) && (
      @@ -76,7 +77,7 @@ export default function AboutPage() {
      )} -
      + {!isEmptyString(Config.Sponsor?.PaypalId) && (
      @@ -94,7 +95,7 @@ export default function AboutPage() {
      )} -
      + {!isEmptyString(Config.Sponsor?.PatreonId) && (
      @@ -112,7 +113,7 @@ export default function AboutPage() {
      )} -
      +
      diff --git a/pages/tags/[...slug].tsx b/pages/tags/[...slug].tsx index 48ff694..f61b4a6 100644 --- a/pages/tags/[...slug].tsx +++ b/pages/tags/[...slug].tsx @@ -46,12 +46,12 @@ export default function TagsContentPage(props: TagsContentPageProps) { return ( - +