diff --git a/gitbook/README.md b/gitbook/README.md deleted file mode 100644 index 7b9b562..0000000 --- a/gitbook/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# GitBook 主題架構說明 - -GitBook 主題提供類似線上文件的閱讀體驗:左側為樹狀導覽、右側顯示文章資訊與公告,內頁則專注呈現 Notion 內容。本文件整理主題的檔案結構、各佈局元件的責任分工,以及常見的使用方式,方便日後整合或擴充。 - -## 專案結構 - -``` -. -├── components/ # 主題專用的視覺元件與工具(導覽清單、目錄抽屜、底部工具列…) -│ └── ui/dashboard/ # 儀表板頁面使用的額外組件 -├── config.js # 主題設定(首頁導向、導覽開關、Widget 等) -├── index.js # 統一對外匯出的入口 -├── layouts/ # 依頁面職責拆分的佈局元件 -│ ├── ArchiveLayout.jsx # 歸檔頁 -│ ├── AuthLayouts.jsx # 登入/註冊頁 -│ ├── BaseLayout.jsx # 主架構:左右欄、行動裝置導覽、Loading 與廣告 -│ ├── ListLayouts.jsx # 首頁/文章列表/搜尋結果與儀表板 -│ ├── NotFoundLayout.jsx # 404 頁面 -│ ├── SlugLayout.jsx # 文章詳情頁 -│ └── TaxonomyLayouts.jsx # 分類與標籤索引 -├── style.js # 只對 GitBook 主題生效的全域樣式 -└── README.md # 本說明文件 -``` - -## 主要佈局與職責 - -- `LayoutBase`:包住所有內容的骨架,負責導覽抽屜、公告、目錄、回頂按鈕與 Loading 遮罩,並透過 `useGitBookGlobal` 共享狀態(搜尋、TOC、導覽列表)。 -- `LayoutIndex`:讀取 `config.js` 的 `GITBOOK_INDEX_PAGE`,在前端重新導向至指定文章,若找不到對應 slug 會注入錯誤提示。 -- `LayoutPostList` / `LayoutSearch`:此主題採左側導覽管理文章,頁面本身僅回傳空節點,確保路由存在。 -- `LayoutArchive`:使用 `BlogArchiveItem` 依年份渲染歸檔清單。 -- `LayoutSlug`:顯示單篇內容,處理加密文章、Meta 標題、分類/標籤、前後篇與留言,並在內容缺漏時自動導回 404。 -- `LayoutCategoryIndex` / `LayoutTagIndex`:輸出分類與標籤的索引列表,套用統一樣式與 `locale` 文案。 -- `LayoutSignIn` / `LayoutSignUp`:Clerk 驗證元件容器,會先顯示官方預設表單,再渲染 Notion 內容。 -- `LayoutDashboard`:搭配 `components/ui/dashboard/*` 顯示自訂儀表板。 -- `Layout404`:延遲檢查內容載入狀態,若仍無法抓到文章容器便導回首頁。 - -## 使用說明 - -1. **在 Next.js 匯入主題佈局** - ```jsx - import { - LayoutBase, - LayoutSlug, - LayoutIndex, - LayoutCategoryIndex - } from '@/themes/gitbook' - - const Post = props => ( - - - - ) - - export default Post - ``` - - `LayoutBase` 處理共同框架;內層依頁面需求替換為 `LayoutIndex`、`LayoutArchive`、`LayoutCategoryIndex`…等。 - - 需要使用 TOC 或導覽狀態時,可透過 `useGitBookGlobal()` 取得 `searchModal`、`tocVisible` 等共享資料。 - -2. **調整主題設定** - - 於 `config.js` 修改選項,或透過環境變數覆寫,例如 `NEXT_PUBLIC_GITBOOK_INDEX_PAGE`、`NEXT_PUBLIC_GITBOOK_AUTO_SORT`。 - - 常用開關: - - `GITBOOK_AUTO_SORT`:自動依分類整理導覽。 - - `GITBOOK_EXCLUSIVE_COLLAPSE`:導覽是否一次只展開一組。 - - `GITBOOK_FOLDER_HOVER_EXPAND`:滑鼠懸停是否自動展開導覽資料夾。 - - `GITBOOK_WIDGET_REVOLVER_MAPS` / `GITBOOK_WIDGET_TO_TOP`:控制右側 Widget。 - -3. **客製樣式與元件** - - 全域樣式集中在 `style.js`,若需調整字體或底色可於此擴充。 - - 導覽列表、公告、底部工具列等都在 `components/`,可直接替換或新增對應元件。 - - 儀表板相關 UI 放在 `components/ui/dashboard/`,可拆分或擴充模組化功能。 - -4. **新增頁面佈局** - - 建議在 `layouts/` 下新增檔案並於 `index.js` 匯出,維持與現有架構一致。 - - 若需要共用狀態,可在新佈局內透過 `useGitBookGlobal` 共享資料,避免重複定義內容。 - -## 開發提醒 - -- 本主題內的註解與靜態文字皆已改為臺灣常用的繁體中文,若新增內容請維持相同用語風格。 -- 導覽列表仰賴 `allNavPages`,若調整資料格式請同步更新 `NavPostList` 相關邏輯。 -- 重新導向或動態載入元件時請注意瀏覽器環境判斷(`isBrowser`),避免在 SSR 階段觸發錯誤。 - -如需額外備註,可持續更新本 README 讓後續維護者快速上手。 diff --git a/gitbook/components/Announcement.js b/gitbook/components/Announcement.js deleted file mode 100755 index 62df1ec..0000000 --- a/gitbook/components/Announcement.js +++ /dev/null @@ -1,21 +0,0 @@ -// import { useGlobal } from '@/lib/global' -import dynamic from 'next/dynamic' - -const NotionPage = dynamic(() => import('@/components/NotionPage')) - -const Announcement = ({ notice, className }) => { -// const { locale } = useGlobal() - if (notice?.blockMap) { - return
-
- {/*
{locale.COMMON.ANNOUNCEMENT}
*/} - {notice && (
- -
)} -
-
- } else { - return <> - } -} -export default Announcement diff --git a/gitbook/components/ArticleAround.js b/gitbook/components/ArticleAround.js deleted file mode 100644 index 02846ac..0000000 --- a/gitbook/components/ArticleAround.js +++ /dev/null @@ -1,41 +0,0 @@ -import { useGlobal } from '@/lib/global' -import SmartLink from '@/components/SmartLink' - -/** - * 上一篇、下一篇文章 - * @param {prev,next} param0 - * @returns - */ -export default function ArticleAround({ prev, next }) { - const { locale } = useGlobal() - - if (!prev || !next) { - return <> - } - - return ( -
- - -
-
{locale.COMMON.PREV_POST}
-
{prev.title}
-
-
- - -
-
{locale.COMMON.NEXT_POST}
-
{next.title}
-
- -
-
- ) -} diff --git a/gitbook/components/ArticleInfo.js b/gitbook/components/ArticleInfo.js deleted file mode 100644 index 27def82..0000000 --- a/gitbook/components/ArticleInfo.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * 文章補充資訊 - * @param {*} param0 - * @returns - */ -export default function ArticleInfo({ post }) { - if (!post) { - return null - } - return ( -
- - Last update:{' '} - {post.date?.start_date || post?.publishDay || post?.lastEditedDay} -
- ) -} diff --git a/gitbook/components/ArticleLock.js b/gitbook/components/ArticleLock.js deleted file mode 100644 index 2770739..0000000 --- a/gitbook/components/ArticleLock.js +++ /dev/null @@ -1,67 +0,0 @@ -import { useGlobal } from '@/lib/global' -import { useRouter } from 'next/router' -import { useEffect, useRef } from 'react' - -/** - * 加密文章驗證元件 - * @param {password, validPassword} props - * @param password 正確的密碼 - * @param validPassword(bool) 回呼函式,驗證通過時傳回 true - * @returns - */ -export const ArticleLock = props => { - const { validPassword } = props - const { locale } = useGlobal() - const router = useRouter() - const passwordInputRef = useRef(null) - - /** - * 輸入並送出密碼 - */ - const submitPassword = () => { - const p = document.getElementById('password') - // 驗證失敗提示 - if (!validPassword(p?.value)) { - const tips = document.getElementById('tips') - if (tips) { - tips.innerHTML = '' - tips.innerHTML = `
${locale.COMMON.PASSWORD_ERROR}
` - } - } - } - - useEffect(() => { - // 選取密碼輸入框並聚焦 - passwordInputRef.current.focus() - }, [router]) - - return ( -
-
-
{locale.COMMON.ARTICLE_LOCK_TIPS}
-
- { - if (e.key === 'Enter') { - submitPassword() - } - }} - ref={passwordInputRef} // 綁定 ref 到 passwordInputRef 變數 - className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg dark:text-gray-300 font-light leading-10 text-black bg-gray-100 dark:bg-gray-500'> -
- -  {locale.COMMON.SUBMIT} - -
-
-
-
-
- ) -} diff --git a/gitbook/components/BlogArchiveItem.js b/gitbook/components/BlogArchiveItem.js deleted file mode 100644 index ab11c28..0000000 --- a/gitbook/components/BlogArchiveItem.js +++ /dev/null @@ -1,36 +0,0 @@ -import SmartLink from '@/components/SmartLink' - -/** - * 歸檔分組 - * @param {*} param0 - * @returns - */ -export default function BlogArchiveItem({ archiveTitle, archivePosts }) { - return ( -
-
- {archiveTitle} -
-
    - {archivePosts[archiveTitle]?.map(post => { - return ( -
  • -
    - {post.date?.start_date}{' '} -   - - {post.title} - -
    -
  • - ) - })} -
-
- ) -} diff --git a/gitbook/components/BlogPostCard.js b/gitbook/components/BlogPostCard.js deleted file mode 100644 index 0726d22..0000000 --- a/gitbook/components/BlogPostCard.js +++ /dev/null @@ -1,33 +0,0 @@ -import Badge from '@/components/Badge' -import NotionIcon from '@/components/NotionIcon' -import { siteConfig } from '@/lib/config' -import SmartLink from '@/components/SmartLink' -import { useRouter } from 'next/router' - -const BlogPostCard = ({ post, className }) => { - const router = useRouter() - const currentSelected = - decodeURIComponent(router.asPath.split('?')[0]) === post?.href - - return ( - -
-
- {siteConfig('POST_TITLE_ICON') && ( - - )}{' '} - {post.title} -
- {/* 最新文章加個紅點 */} - {post?.isLatest && siteConfig('GITBOOK_LATEST_POST_RED_BADGE') && ( - - )} -
-
- ) -} - -export default BlogPostCard diff --git a/gitbook/components/BottomMenuBar.js b/gitbook/components/BottomMenuBar.js deleted file mode 100644 index f264d55..0000000 --- a/gitbook/components/BottomMenuBar.js +++ /dev/null @@ -1,50 +0,0 @@ -import { useGlobal } from '@/lib/global' -import { useGitBookGlobal } from '..' - -/** - * 行動版底部導覽 - * @param {*} param0 - * @returns - */ -export default function BottomMenuBar({ post, className }) { - const showTocButton = post?.toc?.length > 1 - const { locale } = useGlobal() - const { pageNavVisible, changePageNavVisible, tocVisible, changeTocVisible } = - useGitBookGlobal() - const togglePageNavVisible = () => { - changePageNavVisible(!pageNavVisible) - } - - const toggleToc = () => { - changeTocVisible(!tocVisible) - } - - return ( -
-
- - - {showTocButton && ( - - )} -
-
- ) -} diff --git a/gitbook/components/Card.js b/gitbook/components/Card.js deleted file mode 100644 index d24c046..0000000 --- a/gitbook/components/Card.js +++ /dev/null @@ -1,9 +0,0 @@ -const Card = ({ children, headerSlot, className }) => { - return
- <>{headerSlot} -
- {children} -
-
-} -export default Card diff --git a/gitbook/components/Catalog.js b/gitbook/components/Catalog.js deleted file mode 100644 index 06c9206..0000000 --- a/gitbook/components/Catalog.js +++ /dev/null @@ -1,104 +0,0 @@ -import { isBrowser } from '@/lib/utils' -import throttle from 'lodash.throttle' -import { uuidToId } from 'notion-utils' -import { useCallback, useEffect, useState } from 'react' - -/** - * 目錄導覽元件 - * @param toc - * @returns {JSX.Element} - * @constructor - */ -const Catalog = ({ post }) => { - const toc = post?.toc - // 同步選取目錄事件 - const [activeSection, setActiveSection] = useState(null) - - // 監聽捲動事件 - useEffect(() => { - window.addEventListener('scroll', actionSectionScrollSpy) - actionSectionScrollSpy() - return () => { - window.removeEventListener('scroll', actionSectionScrollSpy) - } - }, [post]) - - const throttleMs = 200 - const actionSectionScrollSpy = useCallback( - throttle(() => { - const sections = document.getElementsByClassName('notion-h') - let prevBBox = null - let currentSectionId = null - for (let i = 0; i < sections.length; ++i) { - const section = sections[i] - if (!section || !(section instanceof Element)) continue - if (!currentSectionId) { - currentSectionId = section.getAttribute('data-id') - } - const bbox = section.getBoundingClientRect() - const prevHeight = prevBBox ? bbox.top - prevBBox.bottom : 0 - const offset = Math.max(150, prevHeight / 4) - // GetBoundingClientRect returns values relative to viewport - if (bbox.top - offset < 0) { - currentSectionId = section.getAttribute('data-id') - prevBBox = bbox - continue - } - // No need to continue loop, if last element has been detected - break - } - setActiveSection(currentSectionId) - const tocIds = post?.toc?.map(t => uuidToId(t.id)) || [] - const index = tocIds.indexOf(currentSectionId) || 0 - if (isBrowser && tocIds?.length > 0) { - for (const tocWrapper of document?.getElementsByClassName( - 'toc-wrapper' - )) { - tocWrapper?.scrollTo({ top: 28 * index, behavior: 'smooth' }) - } - } - }, throttleMs) - ) - - // 無目錄則直接返回空 - if (!toc || toc?.length < 1) { - return <> - } - - return ( - <> - {/*
- {locale.COMMON.TABLE_OF_CONTENTS} -
*/} - -
- -
- - ) -} - -export default Catalog diff --git a/gitbook/components/CatalogDrawerWrapper.js b/gitbook/components/CatalogDrawerWrapper.js deleted file mode 100644 index 838d000..0000000 --- a/gitbook/components/CatalogDrawerWrapper.js +++ /dev/null @@ -1,66 +0,0 @@ -import { useGlobal } from '@/lib/global' -import { useGitBookGlobal } from '@/themes/gitbook' -import { useRouter } from 'next/router' -import { useEffect } from 'react' -import Catalog from './Catalog' - -/** - * 懸浮抽屜目錄 - * @param toc - * @param post - * @returns {JSX.Element} - * @constructor - */ -const CatalogDrawerWrapper = ({ post, cRef }) => { - const { tocVisible, changeTocVisible } = useGitBookGlobal() - const { locale } = useGlobal() - const router = useRouter() - const switchVisible = () => { - changeTocVisible(!tocVisible) - } - useEffect(() => { - changeTocVisible(false) - }, [router]) - return ( - <> -
- {/* 側邊選單 */} -
- {post && ( - <> -
- {locale.COMMON.TABLE_OF_CONTENTS} - { - changeTocVisible(false) - }}> -
-
- -
- - )} -
-
- {/* 背景遮罩 */} -
- - ) -} -export default CatalogDrawerWrapper diff --git a/gitbook/components/CategoryGroup.js b/gitbook/components/CategoryGroup.js deleted file mode 100644 index ba43437..0000000 --- a/gitbook/components/CategoryGroup.js +++ /dev/null @@ -1,19 +0,0 @@ - -import CategoryItem from './CategoryItem' - -const CategoryGroup = ({ currentCategory, categoryOptions }) => { - if (!categoryOptions) { - return <> - } - return
-
分類
-
- {categoryOptions?.map(category => { - const selected = currentCategory === category.name - return - })} -
-
-} - -export default CategoryGroup diff --git a/gitbook/components/CategoryItem.js b/gitbook/components/CategoryItem.js deleted file mode 100644 index 8b7cbcf..0000000 --- a/gitbook/components/CategoryItem.js +++ /dev/null @@ -1,18 +0,0 @@ -import SmartLink from '@/components/SmartLink' - -export default function CategoryItem ({ selected, category, categoryCount }) { - return ( - - -
{category} {categoryCount && `(${categoryCount})`} -
- -
- ); -} diff --git a/gitbook/components/Footer.js b/gitbook/components/Footer.js deleted file mode 100644 index d850840..0000000 --- a/gitbook/components/Footer.js +++ /dev/null @@ -1,66 +0,0 @@ -import { BeiAnGongAn } from '@/components/BeiAnGongAn' -import { siteConfig } from '@/lib/config' -import SocialButton from './SocialButton' -/** - * 站點也叫 - * @param {*} param0 - * @returns - */ -const Footer = ({ siteInfo }) => { - const d = new Date() - const currentYear = d.getFullYear() - const since = siteConfig('SINCE') - const copyrightDate = - parseInt(since) < currentYear ? since + '-' + currentYear : currentYear - - return ( - - ) -} - -export default Footer diff --git a/gitbook/components/Header.js b/gitbook/components/Header.js deleted file mode 100644 index 2c93034..0000000 --- a/gitbook/components/Header.js +++ /dev/null @@ -1,134 +0,0 @@ -import Collapse from '@/components/Collapse' -import DarkModeButton from '@/components/DarkModeButton' -import { siteConfig } from '@/lib/config' -import { useGlobal } from '@/lib/global' -import { SignInButton, SignedOut, UserButton } from '@clerk/nextjs' -import { useRef, useState } from 'react' -import CONFIG from '../config' -import LogoBar from './LogoBar' -import { MenuBarMobile } from './MenuBarMobile' -import { MenuItemDrop } from './MenuItemDrop' -import SearchInput from './SearchInput' - -/** - * 頁首:頂部導覽列 + 選單 - * @param {} param0 - * @returns - */ -export default function Header(props) { - const { className, customNav, customMenu } = props - const [isOpen, changeShow] = useState(false) - const collapseRef = useRef(null) - - const { locale } = useGlobal() - - const defaultLinks = [ - { - icon: 'fas fa-th', - name: locale.COMMON.CATEGORY, - href: '/category', - show: siteConfig('GITBOOK_MENU_CATEGORY', null, CONFIG) - }, - { - icon: 'fas fa-tag', - name: locale.COMMON.TAGS, - href: '/tag', - show: siteConfig('GITBOOK_BOOK_MENU_TAG', null, CONFIG) - }, - { - icon: 'fas fa-archive', - name: locale.NAV.ARCHIVE, - href: '/archive', - show: siteConfig('GITBOOK_MENU_ARCHIVE', null, CONFIG) - }, - { - icon: 'fas fa-search', - name: locale.NAV.SEARCH, - href: '/search', - show: siteConfig('GITBOOK_MENU_SEARCH', null, CONFIG) - } - ] - - let links = defaultLinks.concat(customNav) - - const toggleMenuOpen = () => { - changeShow(!isOpen) - } - - // 若啟用自訂選單則覆寫頁面內建選單 - if (siteConfig('CUSTOM_MENU')) { - links = customMenu - } - - const enableClerk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - - return ( -
- {/* 桌面端選單 */} -
-
- {/* 左側 */} -
- - - {/* 桌面端頂部選單 */} -
- {links && - links?.map((link, index) => ( - - ))} -
-
- - {/* 右側 */} -
- {/* 登入相關 */} - {enableClerk && ( - <> - - - - - - - - )} - - - {/* 摺疊按鈕,僅行動版顯示 */} -
- -
- {isOpen ? ( - - ) : ( - - )} -
-
-
-
-
- - {/* 行動版摺疊選單 */} - -
- - collapseRef.current?.updateCollapseHeight(param) - } - /> -
-
-
- ) -} diff --git a/gitbook/components/InfoCard.js b/gitbook/components/InfoCard.js deleted file mode 100644 index 4b6e5cf..0000000 --- a/gitbook/components/InfoCard.js +++ /dev/null @@ -1,20 +0,0 @@ -import LazyImage from '@/components/LazyImage' -import Router from 'next/router' -import SocialButton from './SocialButton' -import { siteConfig } from '@/lib/config' - -const InfoCard = (props) => { - const { siteInfo } = props - return
-
-
{ Router.push('/about') }}> - -
-
{siteConfig('AUTHOR')}
-
{siteConfig('BIO')}
- -
-
-} - -export default InfoCard diff --git a/gitbook/components/JumpToTopButton.js b/gitbook/components/JumpToTopButton.js deleted file mode 100644 index 4041851..0000000 --- a/gitbook/components/JumpToTopButton.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 跳轉至網頁頂端 - * 當畫面往下滑 500 像素後會顯示此元件 - * @param targetRef 關聯高度的目標 HTML 標籤 - * @param showPercent 是否顯示百分比 - * @returns {JSX.Element} - * @constructor - */ -const JumpToTopButton = ({ showPercent = false, percent, className }) => { - return ( -
- { - window.scrollTo({ top: 0, behavior: 'smooth' }) - }} - /> -
- ) -} - -export default JumpToTopButton diff --git a/gitbook/components/LeftMenuBar.js b/gitbook/components/LeftMenuBar.js deleted file mode 100644 index 6f9da09..0000000 --- a/gitbook/components/LeftMenuBar.js +++ /dev/null @@ -1,15 +0,0 @@ -import SmartLink from '@/components/SmartLink' - -export default function LeftMenuBar () { - return ( -
-
- -
- -
-
-
-
- ); -} diff --git a/gitbook/components/LogoBar.js b/gitbook/components/LogoBar.js deleted file mode 100644 index 166ded5..0000000 --- a/gitbook/components/LogoBar.js +++ /dev/null @@ -1,29 +0,0 @@ -import LazyImage from '@/components/LazyImage' -import { siteConfig } from '@/lib/config' -import SmartLink from '@/components/SmartLink' -import CONFIG from '../config' - -/** - * Logo 區域 - * @param {*} props - * @returns - */ -export default function LogoBar(props) { - const { siteInfo } = props - return ( -
- - - {siteInfo?.title || siteConfig('TITLE')} - -
- ) -} diff --git a/gitbook/components/MenuBarMobile.js b/gitbook/components/MenuBarMobile.js deleted file mode 100644 index e174fdb..0000000 --- a/gitbook/components/MenuBarMobile.js +++ /dev/null @@ -1,54 +0,0 @@ -import { siteConfig } from '@/lib/config' -import { useGlobal } from '@/lib/global' -import CONFIG from '../config' -import { MenuItemCollapse } from './MenuItemCollapse' - -export const MenuBarMobile = props => { - const { customMenu, customNav } = props - const { locale } = useGlobal() - - let links = [ - // { name: locale.NAV.INDEX, href: '/' || '/', show: true }, - { - name: locale.COMMON.CATEGORY, - href: '/category', - show: siteConfig('GITBOOK_MENU_CATEGORY', null, CONFIG) - }, - { - name: locale.COMMON.TAGS, - href: '/tag', - show: siteConfig('GITBOOK_BOOK_MENU_TAG', null, CONFIG) - }, - { - name: locale.NAV.ARCHIVE, - href: '/archive', - show: siteConfig('GITBOOK_MENU_ARCHIVE', null, CONFIG) - } - // { name: locale.NAV.SEARCH, href: '/search', show: siteConfig('MENU_SEARCH', null, CONFIG) } - ] - - if (customNav) { - links = links.concat(customNav) - } - - // 若啟用自訂選單,則不再使用 Page 生成的選單。 - if (siteConfig('CUSTOM_MENU')) { - links = customMenu - } - - if (!links || links.length === 0) { - return null - } - - return ( - - ) -} diff --git a/gitbook/components/MenuItemCollapse.js b/gitbook/components/MenuItemCollapse.js deleted file mode 100644 index 378cdc5..0000000 --- a/gitbook/components/MenuItemCollapse.js +++ /dev/null @@ -1,97 +0,0 @@ -import Collapse from '@/components/Collapse' -import SmartLink from '@/components/SmartLink' -import { useRouter } from 'next/router' -import { useState } from 'react' - -/** - * 摺疊選單 - * @param {*} param0 - * @returns - */ -export const MenuItemCollapse = props => { - const { link } = props - const [show, changeShow] = useState(false) - const hasSubMenu = link?.subMenus?.length > 0 - - const [isOpen, changeIsOpen] = useState(false) - - const router = useRouter() - - if (!link || !link.show) { - return null - } - - const selected = router.pathname === link.href || router.asPath === link.href - - const toggleShow = () => { - changeShow(!show) - } - - const toggleOpenSubMenu = () => { - changeIsOpen(!isOpen) - } - - return ( - <> -
- {!hasSubMenu && ( - -
-
- {link.name} -
- - )} - - {hasSubMenu && ( -
-
-
- {link.name} -
-
- -
-
- )} -
- - {/* 摺疊子選單 */} - {hasSubMenu && ( - - {link?.subMenus?.map((sLink, index) => { - return ( -
- -
-
- {sLink.title} -
- -
- ) - })} - - )} - - ) -} diff --git a/gitbook/components/MenuItemDrop.js b/gitbook/components/MenuItemDrop.js deleted file mode 100644 index ffbc90e..0000000 --- a/gitbook/components/MenuItemDrop.js +++ /dev/null @@ -1,73 +0,0 @@ -import SmartLink from '@/components/SmartLink' -import { useRouter } from 'next/router' -import { useState } from 'react' - -export const MenuItemDrop = ({ link }) => { - const [show, changeShow] = useState(false) - const router = useRouter() - - if (!link || !link.show) { - return null - } - const hasSubMenu = link?.subMenus?.length > 0 - const selected = router.pathname === link.href || router.asPath === link.href - return ( -
  • changeShow(true)} - onMouseOut={() => changeShow(false)}> - {!hasSubMenu && ( -
    - - {link?.icon && } {link?.name} - -
    - )} - - {/* 包含子選單 */} - {hasSubMenu && ( - <> -
    -
    - {link?.icon && } {link?.name} - {hasSubMenu && ( - - )} -
    -
    - {/* 下拉選單內容 */} -
      - {link?.subMenus?.map((sLink, index) => { - return ( -
    • - - - {link?.icon &&   } - {sLink.title} - - -
    • - ) - })} -
    - - )} -
  • - ) -} diff --git a/gitbook/components/MenuItemMobileNormal.js b/gitbook/components/MenuItemMobileNormal.js deleted file mode 100644 index d04ba79..0000000 --- a/gitbook/components/MenuItemMobileNormal.js +++ /dev/null @@ -1,29 +0,0 @@ -import SmartLink from '@/components/SmartLink' -import { useRouter } from 'next/router' - -export const NormalMenu = props => { - const { link } = props - const router = useRouter() - - if (!link || !link.show) { - return null - } - - const selected = router.pathname === link.href || router.asPath === link.href - - return ( - -
    -
    {link.name}
    -
    - {link.slot} -
    - ) -} diff --git a/gitbook/components/MenuItemPCNormal.js b/gitbook/components/MenuItemPCNormal.js deleted file mode 100644 index 06bee8a..0000000 --- a/gitbook/components/MenuItemPCNormal.js +++ /dev/null @@ -1,30 +0,0 @@ -import SmartLink from '@/components/SmartLink' -import { useRouter } from 'next/router' - -export const MenuItemPCNormal = props => { - const { link } = props - const router = useRouter() - const selected = router.pathname === link.href || router.asPath === link.href - if (!link || !link.show) { - return null - } - - return ( - -
    - -
    {link.name}
    -
    - {link.slot} -
    - ) -} diff --git a/gitbook/components/NavPostItem.js b/gitbook/components/NavPostItem.js deleted file mode 100644 index a6102be..0000000 --- a/gitbook/components/NavPostItem.js +++ /dev/null @@ -1,90 +0,0 @@ -import Badge from '@/components/Badge' -import Collapse from '@/components/Collapse' -import { siteConfig } from '@/lib/config' -import { useEffect, useState } from 'react' -import BlogPostCard from './BlogPostCard' - -/** - * 導覽列表 - * @param posts 所有文章 - * @param tags 所有標籤 - * @returns {JSX.Element} - * @constructor - */ -const NavPostItem = props => { - const { group, expanded, toggleItem } = props // 接收傳入的展開狀態與切換函式 - const hoverExpand = siteConfig('GITBOOK_FOLDER_HOVER_EXPAND') - const [isTouchDevice, setIsTouchDevice] = useState(false) - - // 偵測是否為觸控裝置 - useEffect(() => { - const checkTouchDevice = () => { - if (window.matchMedia('(pointer: coarse)').matches) { - setIsTouchDevice(true) - } - } - checkTouchDevice() - - // 選用:監聽視窗尺寸變化時重新檢測 - window.addEventListener('resize', checkTouchDevice) - return () => { - window.removeEventListener('resize', checkTouchDevice) - } - }, []) - - // 當展開狀態改變時觸發切換函式並同步內部狀態 - const toggleOpenSubMenu = () => { - toggleItem() // 呼叫父元件傳入的切換函式 - } - const onHoverToggle = () => { - // 允許滑鼠懸停時自動展開,而非點擊 - if (!hoverExpand || isTouchDevice) { - return - } - toggleOpenSubMenu() - } - - const groupHasLatest = group?.items?.some(post => post.isLatest) - - if (group?.category) { - return ( - <> -
    - - {group?.category} - -
    - -
    - {groupHasLatest && - siteConfig('GITBOOK_LATEST_POST_RED_BADGE') && - !expanded && } -
    - - {group?.items?.map((post, index) => ( -
    - -
    - ))} -
    - - ) - } else { - return ( - <> - {group?.items?.map((post, index) => ( -
    - -
    - ))} - - ) - } -} - -export default NavPostItem diff --git a/gitbook/components/NavPostList.js b/gitbook/components/NavPostList.js deleted file mode 100644 index 2a5cdac..0000000 --- a/gitbook/components/NavPostList.js +++ /dev/null @@ -1,165 +0,0 @@ -import { siteConfig } from '@/lib/config' -import { useGlobal } from '@/lib/global' -import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' -import CONFIG from '../config' -import BlogPostCard from './BlogPostCard' -import NavPostItem from './NavPostItem' - -/** - * 部落格列表滾動分頁 - * @param posts 所有文章 - * @param tags 所有標籤 - * @returns {JSX.Element} - * @constructor - */ -const NavPostList = props => { - const { filteredNavPages } = props - const { locale, currentSearch } = useGlobal() - const router = useRouter() - - // 依分類將文章歸成資料夾 - const categoryFolders = groupArticles(filteredNavPages) - - // 存放被展開的群組 - const [expandedGroups, setExpandedGroups] = useState([]) - - // 是否採用排他折疊,一次只展開一個資料夾 - const GITBOOK_EXCLUSIVE_COLLAPSE = siteConfig( - 'GITBOOK_EXCLUSIVE_COLLAPSE', - null, - CONFIG - ) - - useEffect(() => { - // 展開資料夾 - setTimeout(() => { - const currentPath = decodeURIComponent(router.asPath.split('?')[0]) - const defaultOpenIndex = getDefaultOpenIndexByPath( - categoryFolders, - currentPath - ) - setExpandedGroups([defaultOpenIndex]) - }, 500) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [router, filteredNavPages]) - - // 切換折疊項目,當陣列狀態改變時觸發 - const toggleItem = index => { - let newExpandedGroups = [...expandedGroups] // 建立新的展開群組陣列 - - // 若 expandedGroups 中不存在則加入,存在則移除 - if (expandedGroups.includes(index)) { - // 若 expandedGroups 中包含 index,則移除 - newExpandedGroups = newExpandedGroups.filter( - expandedIndex => expandedIndex !== index - ) - } else { - // 若 expandedGroups 中不含 index,則加入 - newExpandedGroups.push(index) - } - // 是否排他 - if (GITBOOK_EXCLUSIVE_COLLAPSE) { - // 若折疊選單為排他模式,僅保留目前群組其餘關閉 - newExpandedGroups = newExpandedGroups.filter( - expandedIndex => expandedIndex === index - ) - } - - // 更新展開群組陣列 - setExpandedGroups(newExpandedGroups) - } - - // 無資料時 - if (!categoryFolders || categoryFolders.length === 0) { - // 空白內容 - return ( -
    -

    - {locale.COMMON.NO_RESULTS_FOUND}{' '} - {currentSearch &&

    {currentSearch}
    } -

    -
    - ) - } - // 文章首頁對應路徑 - const href = siteConfig('GITBOOK_INDEX_PAGE') + '' - - const homePost = { - id: '-1', - title: siteConfig('DESCRIPTION'), - href: href.indexOf('/') !== 0 ? '/' + href : href - } - - return ( -
    - {/* 當前文章 */} - - - {/* 文章列表 */} - {categoryFolders?.map((group, index) => ( - toggleItem(index)} // 將切換函式傳給子元件 - /> - ))} -
    - ) -} - -// 依分類將文章歸成資料夾 -function groupArticles(filteredNavPages) { - if (!filteredNavPages) { - return [] - } - const groups = [] - const AUTO_SORT = siteConfig('GITBOOK_AUTO_SORT', true, CONFIG) - - for (let i = 0; i < filteredNavPages.length; i++) { - const item = filteredNavPages[i] - const categoryName = item?.category ? item?.category : '' // 將 category 轉成字串 - - let existingGroup = null - // 啟用自動分組排序;會把相同分類歸到同一個資料夾,忽略 Notion 中的排序 - if (AUTO_SORT) { - existingGroup = groups.find(group => group.category === categoryName) // 尋找同名的最後一個群組 - } else { - existingGroup = groups[groups.length - 1] // 取得最後一個群組 - } - - // 新增資料 - if (existingGroup && existingGroup.category === categoryName) { - existingGroup.items.push(item) - } else { - groups.push({ category: categoryName, items: [item] }) - } - } - return groups -} - -/** - * 查看當前路徑需要展開的選單索引 - * 若皆不符合則回傳 0,即預設展開第一個 - * @param {*} categoryFolders - * @param {*} path - * @returns {number} 需展開的選單索引 - */ -function getDefaultOpenIndexByPath(categoryFolders, path) { - // 找出符合條件的第一個索引 - const index = categoryFolders.findIndex(group => { - return group.items.some(post => path === post.href) - }) - - // 若找到符合條件的索引則回傳 - if (index !== -1) { - return index - } - - return 0 -} -export default NavPostList diff --git a/gitbook/components/PageNavDrawer.js b/gitbook/components/PageNavDrawer.js deleted file mode 100644 index eada964..0000000 --- a/gitbook/components/PageNavDrawer.js +++ /dev/null @@ -1,61 +0,0 @@ -import { useGlobal } from '@/lib/global' -import { useGitBookGlobal } from '@/themes/gitbook' -import { useRouter } from 'next/router' -import { useEffect } from 'react' -import NavPostList from './NavPostList' - -/** - * 懸浮抽屜 頁面內導覽 - * @param toc - * @param post - * @returns {JSX.Element} - * @constructor - */ -const PageNavDrawer = props => { - const { pageNavVisible, changePageNavVisible } = useGitBookGlobal() - const { filteredNavPages } = props - const { locale } = useGlobal() - const router = useRouter() - const switchVisible = () => { - changePageNavVisible(!pageNavVisible) - } - - useEffect(() => { - changePageNavVisible(false) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [router]) - - return ( - <> -
    - {/* 側邊選單 */} -
    -
    - {locale.COMMON.ARTICLE_LIST} - { - changePageNavVisible(false) - }}> -
    - {/* 所有文章列表 */} -
    - -
    -
    -
    - - {/* 背景遮罩 */} -
    - - ) -} -export default PageNavDrawer diff --git a/gitbook/components/PaginationSimple.js b/gitbook/components/PaginationSimple.js deleted file mode 100644 index 7e579df..0000000 --- a/gitbook/components/PaginationSimple.js +++ /dev/null @@ -1,54 +0,0 @@ -import SmartLink from '@/components/SmartLink' -import { useRouter } from 'next/router' -import { useGlobal } from '@/lib/global' - -/** - * 簡易翻頁外掛 - * @param page 當前頁碼 - * @param totalPage 是否有下一頁 - * @returns {JSX.Element} - * @constructor - */ -const PaginationSimple = ({ page, totalPage }) => { - const { locale } = useGlobal() - const router = useRouter() - const currentPage = +page - const showNext = currentPage < totalPage - const pagePrefix = router.asPath.replace(/\/page\/[1-9]\d*/, '').replace(/\/$/, '') - - return ( -
    - - -
    - ) -} - -export default PaginationSimple diff --git a/gitbook/components/Progress.js b/gitbook/components/Progress.js deleted file mode 100644 index 3ae1dcc..0000000 --- a/gitbook/components/Progress.js +++ /dev/null @@ -1,44 +0,0 @@ -import { useEffect, useState } from 'react' -import { isBrowser } from '@/lib/utils' - -/** - * 頂部頁面閱讀進度條 - * @returns {JSX.Element} - * @constructor - */ -const Progress = ({ targetRef, showPercent = true }) => { - const currentRef = targetRef?.current || targetRef - const [percent, changePercent] = useState(0) - const scrollListener = () => { - const target = currentRef || (isBrowser && document.getElementById('posts-wrapper')) - if (target) { - const clientHeight = target.clientHeight - const scrollY = window.pageYOffset - const fullHeight = clientHeight - window.outerHeight - let per = parseFloat(((scrollY / fullHeight) * 100).toFixed(0)) - if (per > 100) per = 100 - if (per < 0) per = 0 - changePercent(per) - } - } - - useEffect(() => { - document.addEventListener('scroll', scrollListener) - return () => document.removeEventListener('scroll', scrollListener) - }, []) - - return ( -
    -
    - {showPercent && ( -
    {percent}%
    - )} -
    -
    - ) -} - -export default Progress diff --git a/gitbook/components/RevolverMaps.js b/gitbook/components/RevolverMaps.js deleted file mode 100644 index b994de5..0000000 --- a/gitbook/components/RevolverMaps.js +++ /dev/null @@ -1,36 +0,0 @@ -import { useEffect, useState } from 'react' - -export default function RevolverMaps () { - const [load, changeLoad] = useState(false) - useEffect(() => { - if (!load) { - initRevolverMaps() - changeLoad(true) - } - }, []) - return
    -} - -function initRevolverMaps () { - if (screen.width >= 768) { - Promise.all([ - loadExternalResource('https://rf.revolvermaps.com/0/0/8.js?i=5jnp1havmh9&m=0&c=ff0000&cr1=ffffff&f=arial&l=33') - ]).then(() => { - console.log('地圖載入完成') - }) - } -} - -// 封裝非同步載入資源的方法 -function loadExternalResource (url) { - return new Promise((resolve, reject) => { - const container = document.getElementById('revolvermaps') - const tag = document.createElement('script') - tag.src = url - if (tag) { - tag.onload = () => resolve(url) - tag.onerror = () => reject(url) - container.appendChild(tag) - } - }) -} diff --git a/gitbook/components/SearchInput.js b/gitbook/components/SearchInput.js deleted file mode 100644 index 5c2cfc9..0000000 --- a/gitbook/components/SearchInput.js +++ /dev/null @@ -1,159 +0,0 @@ -import { siteConfig } from '@/lib/config' -import { deepClone } from '@/lib/utils' -import { useGitBookGlobal } from '@/themes/gitbook' -import { useImperativeHandle, useRef, useState } from 'react' -import { useHotkeys } from 'react-hotkeys-hook' -let lock = false - -/** - * 搜尋列 - */ -const SearchInput = ({ currentSearch, cRef, className }) => { - const searchInputRef = useRef() - const { searchModal, setFilteredNavPages, allNavPages } = useGitBookGlobal() - - useImperativeHandle(cRef, () => { - return { - focus: () => { - searchInputRef?.current?.focus() - } - } - }) - - /** - * 快捷鍵設定 - */ - useHotkeys('ctrl+k', e => { - searchInputRef?.current?.focus() - e.preventDefault() - handleSearch() - }) - - const handleSearch = () => { - // 使用 Algolia - if (siteConfig('ALGOLIA_APP_ID')) { - searchModal?.current?.openSearch() - } - let keyword = searchInputRef.current.value - if (keyword) { - keyword = keyword.trim() - } else { - setFilteredNavPages(allNavPages) - return - } - const filterAllNavPages = deepClone(allNavPages) - - for (let i = filterAllNavPages.length - 1; i >= 0; i--) { - const post = filterAllNavPages[i] - const articleInfo = post.title + '' - const hit = articleInfo.toLowerCase().indexOf(keyword.toLowerCase()) > -1 - if (!hit) { - // 刪除 - filterAllNavPages.splice(i, 1) - } - } - - // 更新完成 - setFilteredNavPages(filterAllNavPages) - } - - /** - * Enter 鍵 - * @param {*} e - */ - const handleKeyUp = e => { - // 使用 Algolia - if (siteConfig('ALGOLIA_APP_ID')) { - searchModal?.current?.openSearch() - return - } - - if (e.keyCode === 13) { - // Enter - handleSearch(searchInputRef.current.value) - } else if (e.keyCode === 27) { - // ESC - cleanSearch() - } - } - - const handleFocus = () => { - // 使用 Algolia - if (siteConfig('ALGOLIA_APP_ID')) { - searchModal?.current?.openSearch() - } - } - - /** - * 清除搜尋 - */ - const cleanSearch = () => { - searchInputRef.current.value = '' - handleSearch() - setShowClean(false) - } - - const [showClean, setShowClean] = useState(false) - const updateSearchKey = val => { - if (lock) { - return - } - searchInputRef.current.value = val - if (val) { - setShowClean(true) - } else { - setShowClean(false) - } - } - - function lockSearchInput() { - lock = true - } - - function unLockSearchInput() { - lock = false - } - - return ( -
    -
    - -
    - updateSearchKey(e.target.value)} - defaultValue={currentSearch} - /> -
    - Ctrl+K -
    - - {showClean && ( -
    - -
    - )} -
    - ) -} - -export default SearchInput diff --git a/gitbook/components/SocialButton.js b/gitbook/components/SocialButton.js deleted file mode 100644 index 5bf8688..0000000 --- a/gitbook/components/SocialButton.js +++ /dev/null @@ -1,188 +0,0 @@ -import QrCode from '@/components/QrCode' -import { siteConfig } from '@/lib/config' -import { useRef, useState } from 'react' -import { handleEmailClick } from '@/lib/plugins/mailEncrypt' - -/** - * 社群聯絡按鈕組 - * @returns {JSX.Element} - * @constructor - */ -const SocialButton = () => { - const CONTACT_GITHUB = siteConfig('CONTACT_GITHUB') - const CONTACT_TWITTER = siteConfig('CONTACT_TWITTER') - const CONTACT_TELEGRAM = siteConfig('CONTACT_TELEGRAM') - - const CONTACT_LINKEDIN = siteConfig('CONTACT_LINKEDIN') - const CONTACT_WEIBO = siteConfig('CONTACT_WEIBO') - const CONTACT_INSTAGRAM = siteConfig('CONTACT_INSTAGRAM') - const CONTACT_EMAIL = siteConfig('CONTACT_EMAIL') - const ENABLE_RSS = siteConfig('ENABLE_RSS') - const CONTACT_BILIBILI = siteConfig('CONTACT_BILIBILI') - const CONTACT_YOUTUBE = siteConfig('CONTACT_YOUTUBE') - - const CONTACT_XIAOHONGSHU = siteConfig('CONTACT_XIAOHONGSHU') - const CONTACT_ZHISHIXINGQIU = siteConfig('CONTACT_ZHISHIXINGQIU') - const CONTACT_WEHCHAT_PUBLIC = siteConfig('CONTACT_WEHCHAT_PUBLIC') - const [qrCodeShow, setQrCodeShow] = useState(false) - - const openPopover = () => { - setQrCodeShow(true) - } - const closePopover = () => { - setQrCodeShow(false) - } - - const emailIcon = useRef(null) - - - return ( -
    -
    - {CONTACT_GITHUB && ( - - - - )} - {CONTACT_TWITTER && ( - - - - )} - {CONTACT_TELEGRAM && ( - - - - )} - {CONTACT_LINKEDIN && ( - - - - )} - {CONTACT_WEIBO && ( - - - - )} - {CONTACT_INSTAGRAM && ( - - - - )} - {CONTACT_EMAIL && ( - handleEmailClick(e, emailIcon, CONTACT_EMAIL)} - title='email' - className='cursor-pointer' - ref={emailIcon}> - - - )} - {ENABLE_RSS && ( - - - - )} - {CONTACT_BILIBILI && ( - - - - )} - {CONTACT_YOUTUBE && ( - - - - )} - {CONTACT_XIAOHONGSHU && ( - - {/* eslint-disable-next-line @next/next/no-img-element */} - 小紅書 - - )} - {CONTACT_ZHISHIXINGQIU && ( - - {/* eslint-disable-next-line @next/next/no-img-element */} - 知識星球{' '} - - )} - {CONTACT_WEHCHAT_PUBLIC && ( - - )} -
    -
    - ) -} -export default SocialButton diff --git a/gitbook/components/TagGroups.js b/gitbook/components/TagGroups.js deleted file mode 100644 index b02594e..0000000 --- a/gitbook/components/TagGroups.js +++ /dev/null @@ -1,27 +0,0 @@ -import TagItemMini from './TagItemMini' - -/** - * 標籤組 - * @param tags - * @param currentTag - * @returns {JSX.Element} - * @constructor - */ -const TagGroups = ({ tagOptions, currentTag }) => { - if (!tagOptions) return <> - return ( -
    -
    標籤
    -
    - { - tagOptions?.map(tag => { - const selected = tag.name === currentTag - return - }) - } -
    -
    - ) -} - -export default TagGroups diff --git a/gitbook/components/TagItemMini.js b/gitbook/components/TagItemMini.js deleted file mode 100644 index e8fde28..0000000 --- a/gitbook/components/TagItemMini.js +++ /dev/null @@ -1,21 +0,0 @@ -import SmartLink from '@/components/SmartLink' - -const TagItemMini = ({ tag, selected = false }) => { - return ( - - -
    {selected && } {tag.name + (tag.count ? `(${tag.count})` : '')}
    - -
    - ) -} - -export default TagItemMini diff --git a/gitbook/config.js b/gitbook/config.js deleted file mode 100644 index dfef470..0000000 --- a/gitbook/config.js +++ /dev/null @@ -1,25 +0,0 @@ -const CONFIG = { - GITBOOK_INDEX_PAGE: 'about', // 文件首頁顯示的文章,請確認此路徑包含在您的 Notion 資料庫中 - - GITBOOK_AUTO_SORT: process.env.NEXT_PUBLIC_GITBOOK_AUTO_SORT || true, // 是否自動依分類名稱分組排序文章;自動分組可能會打亂您在 Notion 中的文章順序 - - GITBOOK_LATEST_POST_RED_BADGE: - process.env.NEXT_PUBLIC_GITBOOK_LATEST_POST_RED_BADGE || true, // 是否替最新文章顯示紅點 - - // 選單 - GITBOOK_MENU_CATEGORY: true, // 顯示分類 - GITBOOK_BOOK_MENU_TAG: true, // 顯示標籤 - GITBOOK_MENU_ARCHIVE: true, // 顯示歸檔 - GITBOOK_MENU_SEARCH: true, // 顯示搜尋 - - // 導覽文章自動排他折疊 - GITBOOK_EXCLUSIVE_COLLAPSE: true, // 一次只展開一個分類,其它資料夾自動關閉。 - - GITBOOK_FOLDER_HOVER_EXPAND: false, // 左側導覽資料夾滑鼠懸停時自動展開;若為 false 則需點擊才會展開 - - // Widget - GITBOOK_WIDGET_REVOLVER_MAPS: - process.env.NEXT_PUBLIC_WIDGET_REVOLVER_MAPS || 'false', // 地圖外掛 - GITBOOK_WIDGET_TO_TOP: true // 回到頂端按鈕 -} -export default CONFIG diff --git a/gitbook/index.js b/gitbook/index.js deleted file mode 100644 index 02e9f69..0000000 --- a/gitbook/index.js +++ /dev/null @@ -1,14 +0,0 @@ -export { LayoutBase, useGitBookGlobal } from './layouts/BaseLayout' -export { - LayoutDashboard, - LayoutIndex, - LayoutPostList, - LayoutSearch -} from './layouts/ListLayouts' -export { LayoutArchive } from './layouts/ArchiveLayout' -export { LayoutSlug } from './layouts/SlugLayout' -export { Layout404 } from './layouts/NotFoundLayout' -export { LayoutCategoryIndex, LayoutTagIndex } from './layouts/TaxonomyLayouts' -export { LayoutSignIn, LayoutSignUp } from './layouts/AuthLayouts' -export { default as CONFIG } from './config' -export { default as THEME_CONFIG } from './config' diff --git a/gitbook/layouts/ArchiveLayout.jsx b/gitbook/layouts/ArchiveLayout.jsx deleted file mode 100644 index 7b3cd46..0000000 --- a/gitbook/layouts/ArchiveLayout.jsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client' - -import BlogArchiveItem from '../components/BlogArchiveItem' - -/** - * 歸檔頁面 - * 主要依靠頁面導覽 - */ -const LayoutArchive = props => { - const { archivePosts } = props - - return ( - <> -
    - {Object.keys(archivePosts)?.map(archiveTitle => ( - - ))} -
    - - ) -} - -export { LayoutArchive } diff --git a/gitbook/layouts/AuthLayouts.jsx b/gitbook/layouts/AuthLayouts.jsx deleted file mode 100644 index b0da5b2..0000000 --- a/gitbook/layouts/AuthLayouts.jsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client' - -import NotionPage from '@/components/NotionPage' -import { SignIn, SignUp } from '@clerk/nextjs' - -/** - * 登入頁面 - */ -const LayoutSignIn = props => { - const { post } = props - const enableClerk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - - return ( - <> -
    - {/* Clerk 預設表單 */} - {enableClerk && ( -
    - -
    - )} -
    - -
    -
    - - ) -} - -/** - * 註冊頁面 - */ -const LayoutSignUp = props => { - const { post } = props - const enableClerk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - - return ( - <> -
    - {/* Clerk 預設表單 */} - {enableClerk && ( -
    - -
    - )} -
    - -
    -
    - - ) -} - -export { LayoutSignIn, LayoutSignUp } diff --git a/gitbook/layouts/BaseLayout.jsx b/gitbook/layouts/BaseLayout.jsx deleted file mode 100644 index 9733332..0000000 --- a/gitbook/layouts/BaseLayout.jsx +++ /dev/null @@ -1,223 +0,0 @@ -'use client' - -import { AdSlot } from '@/components/GoogleAdsense' -import Live2D from '@/components/Live2D' -import LoadingCover from '@/components/LoadingCover' -import dynamic from 'next/dynamic' -import { createContext, useContext, useEffect, useRef, useState } from 'react' -import { siteConfig } from '@/lib/config' -import { useGlobal } from '@/lib/global' -import { useRouter } from 'next/router' -import { getShortId } from '@/lib/utils/pageId' -import Announcement from '../components/Announcement' -import ArticleInfo from '../components/ArticleInfo' -import BottomMenuBar from '../components/BottomMenuBar' -import Catalog from '../components/Catalog' -import Footer from '../components/Footer' -import Header from '../components/Header' -import InfoCard from '../components/InfoCard' -import JumpToTopButton from '../components/JumpToTopButton' -import NavPostList from '../components/NavPostList' -import PageNavDrawer from '../components/PageNavDrawer' -import RevolverMaps from '../components/RevolverMaps' -import CONFIG from '../config' -import { Style } from '../style' - -const AlgoliaSearchModal = dynamic( - () => import('@/components/AlgoliaSearchModal'), - { ssr: false } -) -const WWAds = dynamic(() => import('@/components/WWAds'), { ssr: false }) - -// 主題全域變數 -const ThemeGlobalGitbook = createContext() -const useGitBookGlobal = () => useContext(ThemeGlobalGitbook) - -/** - * 為最新文章加入紅點標記 - */ -function getNavPagesWithLatest(allNavPages, latestPosts, post) { - // localStorage 儲存 id 與上次閱讀時間戳: posts_read_time = {"${post.id}":"Date()"} - const postReadTime = JSON.parse( - localStorage.getItem('post_read_time') || '{}' - ) - if (post) { - postReadTime[getShortId(post.id)] = new Date().getTime() - } - // 更新記錄 - localStorage.setItem('post_read_time', JSON.stringify(postReadTime)) - - return allNavPages?.map(item => { - const res = { - short_id: item.short_id, - title: item.title || '', - pageCoverThumbnail: item.pageCoverThumbnail || '', - category: item.category || null, - tags: item.tags || null, - summary: item.summary || null, - slug: item.slug, - href: item.href, - pageIcon: item.pageIcon || '', - lastEditedDate: item.lastEditedDate - } - // 屬於最新的文章通常 6 篇 &&(無閱讀紀錄 || 最近更新時間大於上次閱讀時間) - if ( - latestPosts.some(post => post?.id.indexOf(item?.short_id) === 14) && - (!postReadTime[item.short_id] || - postReadTime[item.short_id] < new Date(item.lastEditedDate).getTime()) - ) { - return { ...res, isLatest: true } - } else { - return res - } - }) -} - -/** - * 基礎佈局 - * 左右雙欄,手機版改為頂部導覽列 - */ -const LayoutBase = props => { - const { - children, - post, - allNavPages, - latestPosts, - slotLeft, - slotRight, - slotTop - } = props - const { fullWidth } = useGlobal() - const router = useRouter() - const [tocVisible, changeTocVisible] = useState(false) - const [pageNavVisible, changePageNavVisible] = useState(false) - const [filteredNavPages, setFilteredNavPages] = useState(allNavPages) - - const searchModal = useRef(null) - - useEffect(() => { - setFilteredNavPages(getNavPagesWithLatest(allNavPages, latestPosts, post)) - }, [router]) - - const GITBOOK_LOADING_COVER = siteConfig( - 'GITBOOK_LOADING_COVER', - true, - CONFIG - ) - return ( - - - ) -} - -export { Style } diff --git a/mhhung/README.md b/mhhung/README.md deleted file mode 100644 index 9e43796..0000000 --- a/mhhung/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# Typography 主題架構說明 - -本專案是部落格佈景主題(Typography),主要透過 `index.js` 匯出多種佈局組件,方便在 Next.js / Notion-Blog 專案中依照情境載入。下方整理目前的檔案架構與各模組職責,以及常見的使用方式。 - -## 專案結構 - -``` -. -├── components/ # 主題客製化的視覺組件(TopBar、Footer、BlogPostBar…) -├── config.js # 主題對外設定(標題、選單顯示開關…) -├── index.js # 封裝後的佈局出口,對外統一匯出 -├── layouts/ # 各種頁面佈局拆分檔案 -│ ├── ArchiveLayout.jsx # 文章歸檔頁 -│ ├── BaseLayout.jsx # 共同外殼(搜尋、導覽、跳頂按鈕…) -│ ├── ListLayouts.jsx # 首頁、文章列表、搜尋列表 -│ ├── NotFoundLayout.jsx # 404 頁面 -│ ├── SlugLayout.jsx # 單篇文章頁 -│ └── TaxonomyLayouts.jsx # 分類與標籤頁 -├── style.js # 只針對此主題作用的全域樣式 -└── utils/ - └── groupArticlesByYear.js # 文章依年份分組的小工具 -``` - -## 主要佈局與職責 - -- `LayoutBase`:最外層骨架,負責載入樣式、導覽列、頁尾、搜尋與 Loading 狀態,其他頁面內容會以 children 注入。 -- `LayoutIndex` / `LayoutPostList`:顯示文章列表或首頁,會掛上 `BlogPostBar` 與 `BlogListPage`。 -- `LayoutSearch`:在文章列表基礎上,透過 `replaceSearchResult` 將關鍵字高亮。 -- `LayoutArchive`:使用 `groupArticlesByYearArray` 將文章依年份分組後渲染。 -- `LayoutSlug`:單篇文章頁面,負責文章資訊、廣告、前後文推薦與留言區。 -- `LayoutCategoryIndex` / `LayoutTagIndex`:顯示分類與標籤的索引列表。 -- `Layout404`:等待 Notion 內容載入後做導向,或直接顯示 404。 - -## 使用說明 - -1. **在 Next.js 主題系統中載入** - ```jsx - import { LayoutBase, LayoutIndex } from 'mhhung' - - const Home = props => ( - - - - ) - - export default Home - ``` - - `LayoutBase` 會處理共同外殼;`LayoutIndex` 則是此頁的實際內容。 - - 其他頁面可依需求替換為 `LayoutArchive`、`LayoutSlug`…等對應佈局。 - -2. **設定主題參數** - - 編輯 `config.js`,可調整部落格名稱、是否展示分類/標籤/歸檔選單,以及文章推薦、封面等開關。 - - 也可以透過環境變數(如 `NEXT_PUBLIC_TYPOGRAPHY_BLOG_NAME`)覆寫預設值,方便在不同部署環境中使用。 - -3. **擴充樣式或功能** - - 共用樣式可以在 `style.js` 中調整或新增。 - - 若要新增新的頁面佈局,建議在 `layouts/` 下建立新的 JSX 檔案,並於 `index.js` 匯出,以維持統一入口。 - - 共用的邏輯或工具函式,可集中在 `utils/` 目錄內,減少重複程式碼。 - -4. **整合既有元件** - - 所有 `layouts/` 中引用的元件皆放於 `components/`。若要替換視覺元素,如導覽列或頁尾,直接在對應元件修改即可。 - - 動態載入 (`next/dynamic`) 會避免在 SSR 階段載入僅限瀏覽器的套件,若新增依賴,請依照既有範例設定 `ssr: false`。 - -## 開發建議 - -- 調整佈局時,先確認是否為共用邏輯(放在 `BaseLayout` 或工具函式),再決定是否要拆分。 -- 新增功能建議寫在對應的 `layouts/` 或 `components/` 中,並保持檔案職責單一,讓後續維護更容易。 -- 若有多語系或 Theme 變化需求,可以在 `config.js` 中增加新的設定值,並於各佈局透過 `siteConfig` 取得。 - -有任何額外需求都可以在 README 中持續補充,讓後續維護人員快速上手。 diff --git a/mhhung/components/ArticleAround.js b/mhhung/components/ArticleAround.js index c3d2653..1c5eb96 100644 --- a/mhhung/components/ArticleAround.js +++ b/mhhung/components/ArticleAround.js @@ -1,7 +1,7 @@ import SmartLink from '@/components/SmartLink' /** - * 上一則與下一則文章 + * 上一篇、下一篇文章 * @param {prev,next} param0 * @returns */ diff --git a/mhhung/components/ArticleInfo.js b/mhhung/components/ArticleInfo.js index 8175aa0..ade74da 100644 --- a/mhhung/components/ArticleInfo.js +++ b/mhhung/components/ArticleInfo.js @@ -6,7 +6,7 @@ import { formatDateFmt } from '@/lib/utils/formatDate' import NotionIcon from '@/components/NotionIcon' /** - * 文章說明 + * 文章描述 * @param {*} props * @returns */ @@ -27,7 +27,7 @@ export default function ArticleInfo(props) {
    - 發佈於 + 發布於 diff --git a/mhhung/components/ArticleLock.js b/mhhung/components/ArticleLock.js index 3a47720..1b8a1de 100644 --- a/mhhung/components/ArticleLock.js +++ b/mhhung/components/ArticleLock.js @@ -5,7 +5,7 @@ import { useEffect, useRef } from 'react' * 加密文章驗證元件 * @param {password, validPassword} props * @param password 正確的密碼 - * @param validPassword(bool) 回呼函式,驗證正確回呼參數為 true + * @param validPassword(bool) 回呼函式,驗證成功時回傳 true * @returns */ export default function ArticleLock (props) { @@ -24,7 +24,7 @@ export default function ArticleLock (props) { } const passwordInputRef = useRef(null) useEffect(() => { - // 選中密碼輸入框並將其聚焦 + // 選取密碼輸入框並將它聚焦 passwordInputRef.current.focus() }, []) @@ -38,7 +38,7 @@ export default function ArticleLock (props) { submitPassword() } }} - ref={passwordInputRef} // 綁定 ref 到 passwordInputRef 變數 + ref={passwordInputRef} // 將 ref 綁定到 passwordInputRef 變數 className='outline-none w-full text-sm pl-5 rounded-l transition focus:shadow-lg font-light leading-10 text-black dark:bg-gray-500 bg-gray-50' >
    diff --git a/mhhung/components/BlogArchiveItem.js b/mhhung/components/BlogArchiveItem.js index b3d547d..9a4f046 100644 --- a/mhhung/components/BlogArchiveItem.js +++ b/mhhung/components/BlogArchiveItem.js @@ -1,7 +1,7 @@ import SmartLink from '@/components/SmartLink' /** - * 歸檔文章清單 + * 歸檔分組文章 * @param {*} param0 * @returns */ diff --git a/mhhung/components/BlogItem.js b/mhhung/components/BlogItem.js index b51a4ff..fa01f97 100644 --- a/mhhung/components/BlogItem.js +++ b/mhhung/components/BlogItem.js @@ -49,7 +49,7 @@ export const BlogItem = props => {
    - 發佈於 + 發布於 diff --git a/mhhung/components/BlogListPage.js b/mhhung/components/BlogListPage.js index ecb92e4..6c095a1 100644 --- a/mhhung/components/BlogListPage.js +++ b/mhhung/components/BlogListPage.js @@ -7,7 +7,7 @@ import CONFIG from '../config' import { BlogItem } from './BlogItem' /** - * 部落格文章列表 + * 部落格列表 * @param {*} props * @returns */ @@ -19,7 +19,7 @@ export default function BlogListPage(props) { const totalPage = Math.ceil(postCount / POSTS_PER_PAGE) const currentPage = +page - // 部落格列表插入廣告 + // 部落格列表嵌入廣告 const TYPOGRAPHY_POST_AD_ENABLE = siteConfig( 'TYPOGRAPHY_POST_AD_ENABLE', false, diff --git a/mhhung/components/BlogListScroll.js b/mhhung/components/BlogListScroll.js index 710e68b..1cd232d 100644 --- a/mhhung/components/BlogListScroll.js +++ b/mhhung/components/BlogListScroll.js @@ -5,7 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import { BlogItem } from './BlogItem' /** - * 捲動部落格列表 + * 滾動部落格列表 * @param {*} props * @returns */ diff --git a/mhhung/components/BlogPostBar.js b/mhhung/components/BlogPostBar.js index 53ce417..7ac7b91 100644 --- a/mhhung/components/BlogPostBar.js +++ b/mhhung/components/BlogPostBar.js @@ -1,7 +1,7 @@ import { useGlobal } from '@/lib/global' /** - * 文章列表上方區塊 + * 文章列表上方嵌入區塊 * @param {*} props * @returns */ diff --git a/mhhung/components/Catalog.js b/mhhung/components/Catalog.js index 5149bc4..dd389a5 100644 --- a/mhhung/components/Catalog.js +++ b/mhhung/components/Catalog.js @@ -22,8 +22,8 @@ const Catalog = ({ post }) => { if (!post || !post?.toc || post?.toc?.length < 1) { return } - - const throttleMs = 100 // 降低節流時間提高響應速度 + + const throttleMs = 100 // 降低節流時間提升反應速度 const actionSectionScrollSpy = throttle(() => { const sections = document.getElementsByClassName('notion-h') @@ -32,15 +32,15 @@ const Catalog = ({ post }) => { let prevBBox = null let currentSectionId = null - // 先檢查當前視窗中的所有標題 + // 先檢查目前視窗中的所有標題 for (let i = 0; i < sections.length; ++i) { const section = sections[i] if (!section || !(section instanceof Element)) continue - + const bbox = section.getBoundingClientRect() - const offset = 100 // 固定位移量,避免計算不穩定 - - // 如果標題在視窗上方或接近頂部,認為是當前標題 + const offset = 100 // 固定偏移量,避免計算不穩定 + + // 如果標題在視窗上方或接近頂部,視為目前標題 if (bbox.top - offset < 0) { currentSectionId = section.getAttribute('data-id') prevBBox = bbox @@ -49,16 +49,16 @@ const Catalog = ({ post }) => { break } } - - // 如果沒找到任何標題在視窗上方,使用第一個標題 + + // 若沒有找到任何標題位於視窗上方,就使用第一個標題 if (!currentSectionId && sections.length > 0) { currentSectionId = sections[0].getAttribute('data-id') } - + // 只有當 ID 變化時才更新狀態,減少不必要的渲染 if (currentSectionId !== activeSection) { setActiveSection(currentSectionId) - + // 查找目錄中對應的索引並滾動 const index = post?.toc?.findIndex( obj => uuidToId(obj.id) === currentSectionId @@ -71,22 +71,22 @@ const Catalog = ({ post }) => { }, throttleMs) const content = document.querySelector('#container-inner') - if (!content) return // 防止 content 不存在 - - // 添加滾動和內容變化的監聽 + if (!content) return // 避免 content 不存在 + + // 加入滾動與內容變化的監聽 content.addEventListener('scroll', actionSectionScrollSpy) - + // 初始執行一次 setTimeout(() => { actionSectionScrollSpy() - }, 300) // 延遲執行確保 DOM 已完全載入 + }, 300) // 延遲執行以確保 DOM 已完全載入 return () => { content?.removeEventListener('scroll', actionSectionScrollSpy) } }, [post]) - // 無目錄就直接返回空 + // 沒有目錄就直接回傳空值 if (!post || !post?.toc || post?.toc?.length < 1) { return <> } diff --git a/mhhung/components/Footer.js b/mhhung/components/Footer.js index ee404e6..c3836c9 100644 --- a/mhhung/components/Footer.js +++ b/mhhung/components/Footer.js @@ -3,7 +3,7 @@ import DarkModeButton from '@/components/DarkModeButton' import { siteConfig } from '@/lib/config' /** - * 頁腳 + * 頁尾 * @param {*} props * @returns */ diff --git a/mhhung/components/JumpToTopButton.js b/mhhung/components/JumpToTopButton.js index 00ec74b..bcefca5 100644 --- a/mhhung/components/JumpToTopButton.js +++ b/mhhung/components/JumpToTopButton.js @@ -3,8 +3,8 @@ import { useEffect, useState } from 'react' /** * 跳轉到網頁頂部 - * 當畫面下滑 500 像素後會出現此按鈕 - * @param targetRef 連結高度的目標 html 元素 + * 當螢幕下滑 500 像素後會出現該控制項 + * @param targetRef 關聯高度的目標 HTML 標籤 * @param showPercent 是否顯示百分比 * @returns {JSX.Element} * @constructor diff --git a/mhhung/components/MenuItemCollapse.js b/mhhung/components/MenuItemCollapse.js index de470a0..a65cc6f 100644 --- a/mhhung/components/MenuItemCollapse.js +++ b/mhhung/components/MenuItemCollapse.js @@ -3,7 +3,7 @@ import SmartLink from '@/components/SmartLink' import { useState } from 'react' /** - * 收合選單 + * 摺疊選單 * @param {*} param0 * @returns */ @@ -64,7 +64,7 @@ export const MenuItemCollapse = props => { )}
    - {/* 收合子選單 */} + {/* 摺疊子選單 */} {hasSubMenu && ( {link.subMenus.map((sLink, index) => { diff --git a/mhhung/components/MenuItemDrop.js b/mhhung/components/MenuItemDrop.js index 6aaca96..fa718f1 100644 --- a/mhhung/components/MenuItemDrop.js +++ b/mhhung/components/MenuItemDrop.js @@ -49,7 +49,7 @@ export const MenuItemDrop = ({ link }) => { )}
    - {/* 子選單 */} + {/* 子菜單 */}
      {link?.subMenus?.map((sLink, index) => { diff --git a/mhhung/components/MenuList.js b/mhhung/components/MenuList.js index 0b04791..58c6aaa 100644 --- a/mhhung/components/MenuList.js +++ b/mhhung/components/MenuList.js @@ -53,7 +53,7 @@ export const MenuList = ({ customNav, customMenu }) => { links = links.concat(customNav) } - // 如果 開啟自訂選單,則覆蓋 Page 生成的選單 + // 若啟用自訂選單,就覆寫 Page 產生的選單 if (siteConfig('CUSTOM_MENU')) { links = customMenu } @@ -70,7 +70,7 @@ export const MenuList = ({ customNav, customMenu }) => { ))}
    - {/* 行動端小螢幕選單 - 水平排列 */} + {/* 行動版小螢幕選單 - 水平排列 */}