diff --git a/components/DisableCopy.js b/components/DisableCopy.js index 80860714..9bc2cb9e 100644 --- a/components/DisableCopy.js +++ b/components/DisableCopy.js @@ -1,4 +1,4 @@ -import BLOG from '@/blog.config' +import { siteConfig } from '@/lib/config' import { useEffect } from 'react' /** @@ -6,7 +6,7 @@ import { useEffect } from 'react' */ export default function DisableCopy() { useEffect(() => { - if (!JSON.parse(BLOG.CAN_COPY)) { + if (!JSON.parse(siteConfig('CAN_COPY'))) { // 全栈添加禁止复制的样式 document.getElementsByTagName('html')[0].classList.add('forbid-copy') // 监听复制事件 diff --git a/lib/config.js b/lib/config.js index d5d6a8cd..69c8fd14 100644 --- a/lib/config.js +++ b/lib/config.js @@ -9,10 +9,11 @@ import { deepClone } from './utils' * 1. 优先读取NotionConfig表 * 2. 其次读取环境变量 * 3. 再读取blog.config.js文件 - * @param {*} key + * @param {*} key ; 参数名 + * @param {*} defaultVal ; 参数不存在默认返回值 * @returns */ -export const siteConfig = (key) => { +export const siteConfig = (key, defaultVal = null) => { let global = null try { // eslint-disable-next-line react-hooks/rules-of-hooks @@ -36,7 +37,7 @@ export const siteConfig = (key) => { val = siteInfo?.pageCover // 封面图取Notion的封面 break case 'AVATAR': - val = siteInfo?.icon // 封面图取Notion的封面 + val = siteInfo?.icon // 封面图取Notion的头像 break case 'TITLE': val = siteInfo?.title // 标题取Notion中的标题 @@ -48,7 +49,10 @@ export const siteConfig = (key) => { if (!val) { val = BLOG[key] } - console.log('实际配置', key, val) + if (!val) { + val = defaultVal + } + // console.log('实际配置', key, val) return val } diff --git a/lib/utils.js b/lib/utils.js index 75278521..30e0e038 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -75,7 +75,6 @@ export function getQueryVariable(key) { } return (false) } - /** * 获取 URL 中指定参数的值 * @param {string} url @@ -83,8 +82,10 @@ export function getQueryVariable(key) { * @returns {string|null} */ export function getQueryParam(url, param) { - const searchParams = new URLSearchParams(url.split('?')[1]) - return searchParams.get(param) + // 移除哈希部分 + const urlWithoutHash = url.split('#')[0]; + const searchParams = new URLSearchParams(urlWithoutHash.split('?')[1]); + return searchParams.get(param); } /** @@ -202,3 +203,46 @@ export const isMobile = () => { return isMobile } + +/** + * 扫描页面上的所有文本节点,将url格式的文本转为可点击链接 + * @param {*} node + */ +export const scanAndConvertToLinks = (node) => { + if (node.nodeType === Node.TEXT_NODE) { + const text = node.textContent; + const urlRegex = /https?:\/\/[^\s]+/g; + let lastIndex = 0; + let match; + + const newNode = document.createElement('span'); + + while ((match = urlRegex.exec(text)) !== null) { + const beforeText = text.substring(lastIndex, match.index); + const url = match[0]; + + if (beforeText) { + newNode.appendChild(document.createTextNode(beforeText)); + } + + const link = document.createElement('a'); + link.href = url; + link.target = '_blank' + link.textContent = url; + + newNode.appendChild(link); + + lastIndex = urlRegex.lastIndex; + } + + if (lastIndex < text.length) { + newNode.appendChild(document.createTextNode(text.substring(lastIndex))); + } + + node.parentNode.replaceChild(newNode, node); + } else if (node.nodeType === Node.ELEMENT_NODE) { + for (const childNode of node.childNodes) { + scanAndConvertToLinks(childNode); + } + } +} diff --git a/pages/_app.js b/pages/_app.js index 60535cc2..a19933cb 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -44,7 +44,6 @@ const MyApp = ({ Component, pageProps }) => { loadExternalResource(url, 'css') } } - checkThemeDOM() } return ( @@ -55,18 +54,4 @@ const MyApp = ({ Component, pageProps }) => { ) } -/** - * 切换主题时的特殊处理 - */ -const checkThemeDOM = () => { - const elements = document.querySelectorAll('[id^="theme-"]') - if (elements?.length > 1) { - elements[elements.length - 1].scrollIntoView() - // 删除前面的元素,只保留最后一个元素 - for (let i = 0; i < elements.length - 1; i++) { - elements[i].parentNode.removeChild(elements[i]) - } - } -} - export default MyApp diff --git a/themes/commerce/components/Footer.js b/themes/commerce/components/Footer.js index ced637e9..c7d968b2 100644 --- a/themes/commerce/components/Footer.js +++ b/themes/commerce/components/Footer.js @@ -1,36 +1,101 @@ import React from 'react' -import BLOG from '@/blog.config' import { siteConfig } from '@/lib/config' +import Link from 'next/link' -const Footer = ({ title }) => { +/** + * 页脚 + * @param {*} param0 + * @returns + */ +const Footer = (props) => { const d = new Date() const currentYear = d.getFullYear() - const copyrightDate = (function() { - if (Number.isInteger(BLOG.SINCE) && BLOG.SINCE < currentYear) { - return BLOG.SINCE + '-' + currentYear - } - return currentYear - })() + const since = siteConfig('SINCE') + const copyrightDate = parseInt(since) < currentYear ? since + '-' + currentYear : currentYear + const { categoryOptions, customMenu } = props - return ( - } export default Footer diff --git a/themes/commerce/components/Hero.js b/themes/commerce/components/Hero.js index ef50465f..34ddbd57 100644 --- a/themes/commerce/components/Hero.js +++ b/themes/commerce/components/Hero.js @@ -1,65 +1,24 @@ // import Image from 'next/image' -import { useEffect, useState } from 'react' -import Typed from 'typed.js' import CONFIG from '../config' -import NavButtonGroup from './NavButtonGroup' -import { useGlobal } from '@/lib/global' import LazyImage from '@/components/LazyImage' -import { siteConfig } from '@/lib/config' - -let wrapperTop = 0 /** * 顶部全屏大图 * @returns */ const Hero = props => { - const [typed, changeType] = useState() const { siteInfo } = props - const { locale } = useGlobal() - const scrollToWrapper = () => { - window.scrollTo({ top: wrapperTop, behavior: 'smooth' }) - } - const GREETING_WORDS = siteConfig('GREETING_WORDS').split(',') - useEffect(() => { - updateHeaderHeight() - - if (!typed && window && document.getElementById('typed')) { - changeType( - new Typed('#typed', { - strings: GREETING_WORDS, - typeSpeed: 200, - backSpeed: 100, - backDelay: 400, - showCursor: true, - smartBackspace: true - }) - ) - } - - window.addEventListener('resize', updateHeaderHeight) - return () => { - window.removeEventListener('resize', updateHeaderHeight) - } - }) - - function updateHeaderHeight() { - requestAnimationFrame(() => { - const wrapperElement = document.getElementById('wrapper') - wrapperTop = wrapperElement?.offsetTop - }) - } return ( ) diff --git a/themes/commerce/components/LogoBar.js b/themes/commerce/components/LogoBar.js index 925f1063..de5a5bfe 100644 --- a/themes/commerce/components/LogoBar.js +++ b/themes/commerce/components/LogoBar.js @@ -1,5 +1,5 @@ import Link from 'next/link' -import { siteConfig } from '@/lib/config' +// import { siteConfig } from '@/lib/config' import LazyImage from '@/components/LazyImage'; /** @@ -14,7 +14,7 @@ export default function LogoBar (props) { -
{siteConfig('TITLE')}
+ {/*
{siteConfig('TITLE')}
*/} ); } diff --git a/themes/commerce/components/MenuItemDrop.js b/themes/commerce/components/MenuItemDrop.js index 05a6b4e6..cec4b854 100644 --- a/themes/commerce/components/MenuItemDrop.js +++ b/themes/commerce/components/MenuItemDrop.js @@ -16,14 +16,14 @@ export const MenuItemDrop = ({ link }) => { {!hasSubMenu && - {link?.icon && } {link?.name} + className={`${selected && 'border-b-2 border-[#D2232A]'} h-full flex space-x-1 whitespace-nowrap items-center font-sans menu-link pl-2 pr-4 dark:text-gray-200 no-underline tracking-widest pb-1`}> + {link?.icon && }
{link?.name}
{/* {hasSubMenu && } */} } {hasSubMenu && <> -
- {link?.icon && } {link?.name} +
+ {link?.icon && }
{link?.name}
{/* */}
} diff --git a/themes/commerce/components/MenuListSide.js b/themes/commerce/components/MenuListSide.js index 789d07d2..a60ba77e 100644 --- a/themes/commerce/components/MenuListSide.js +++ b/themes/commerce/components/MenuListSide.js @@ -18,7 +18,7 @@ export const MenuListSide = (props) => { if (customNav) { links = customNav.concat(links) } - + for (let i = 0; i < links.length; i++) { if (links[i].id !== i) { links[i].id = i diff --git a/themes/commerce/components/TopNavBar.js b/themes/commerce/components/TopNavBar.js index 00a232fa..c2bf6065 100644 --- a/themes/commerce/components/TopNavBar.js +++ b/themes/commerce/components/TopNavBar.js @@ -17,7 +17,6 @@ export default function TopNavBar(props) { const { customNav, customMenu } = props const [isOpen, changeShow] = useState(false) const collapseRef = useRef(null) - let windowTop = 0 const { locale } = useGlobal() @@ -43,19 +42,17 @@ export default function TopNavBar(props) { } }, []) - const throttleMs = 200 + const throttleMs = 150 const scrollTrigger = throttle(() => { const scrollS = window.scrollY - const nav = document.querySelector('#navbar') + const nav = document.querySelector('#top-navbar') - const narrowNav = scrollS >= windowTop || scrollS > 200 + const narrowNav = scrollS > 50 if (narrowNav) { - nav && nav.classList.replace('h-24', 'h-16') - windowTop = scrollS + nav && nav.classList.replace('h-24', 'h-14') } else { - nav && nav.classList.replace('h-16', 'h-24') - windowTop = scrollS + nav && nav.classList.replace('h-14', 'h-24') } }, throttleMs) @@ -68,34 +65,32 @@ export default function TopNavBar(props) { return null } - return ( -
+ return
- {/* 移动端折叠菜单 */} - -
- collapseRef.current?.updateCollapseHeight(param)} /> -
-
+ {/* 移动端折叠菜单 */} + +
+ collapseRef.current?.updateCollapseHeight(param)} /> +
+
- {/* 导航栏菜单 */} -