This commit is contained in:
tangly1024.com
2024-11-27 14:14:07 +08:00
42 changed files with 197 additions and 183 deletions

View File

@@ -173,3 +173,4 @@
# ENABLE_CACHE= # ENABLE_CACHE=
# VERCEL_ENV= # VERCEL_ENV=
# NEXT_PUBLIC_VERSION= # NEXT_PUBLIC_VERSION=
# NEXT_BUILD_STANDALONE=

View File

@@ -1,23 +1,42 @@
ARG NOTION_PAGE_ID ARG NOTION_PAGE_ID
ARG NEXT_PUBLIC_THEME ARG NEXT_PUBLIC_THEME
# Install dependencies only when needed FROM node:18-alpine3.18 AS base
FROM node:18-alpine3.18 AS deps
# 1. Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat
WORKDIR /app WORKDIR /app
COPY package.json ./ COPY package.json ./
RUN yarn install --frozen-lockfile RUN yarn install --frozen-lockfile
# Rebuild the source code only when needed # 2. Rebuild the source code only when needed
FROM node:18-alpine3.18 AS builder FROM base AS builder
ARG NOTION_PAGE_ID ARG NOTION_PAGE_ID
ENV NEXT_BUILD_STANDALONE=true
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
RUN yarn build RUN yarn build
ENV NODE_ENV production # 3. Production image, copy all the files and run next
FROM base AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder /app/public ./public
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# 个人仓库把将配置好的.env.local文件放到项目根目录可自动使用环境变量
# COPY --from=builder /app/.env.local ./
EXPOSE 3000 EXPOSE 3000
@@ -26,4 +45,4 @@ EXPOSE 3000
# Uncomment the following line in case you want to disable telemetry. # Uncomment the following line in case you want to disable telemetry.
# ENV NEXT_TELEMETRY_DISABLED 1 # ENV NEXT_TELEMETRY_DISABLED 1
CMD ["yarn", "start"] CMD ["node", "server.js"]

View File

@@ -8,6 +8,7 @@ import { GlobalStyle } from './GlobalStyle'
import { initGoogleAdsense } from './GoogleAdsense' import { initGoogleAdsense } from './GoogleAdsense'
import Head from 'next/head' import Head from 'next/head'
import ExternalScript from './ExternalScript'
import WebWhiz from './Webwhiz' import WebWhiz from './Webwhiz'
/** /**
@@ -258,10 +259,10 @@ const ExternalPlugin = props => {
{/* 提前连接到广告服务器 */} {/* 提前连接到广告服务器 */}
<link rel='preconnect' href='https://cdn.wwads.cn' /> <link rel='preconnect' href='https://cdn.wwads.cn' />
</Head> </Head>
<script <ExternalScript
type='text/javascript' type='text/javascript'
src='https://cdn.wwads.cn/js/makemoney.js' src='https://cdn.wwads.cn/js/makemoney.js'
async></script> />
</> </>
)} )}

View File

@@ -7,7 +7,7 @@ import { isBrowser } from '@/lib/utils'
* 传入参数将转为 <script>标签。 * 传入参数将转为 <script>标签。
* @returns * @returns
*/ */
const ExternalScript = (props) => { const ExternalScript = props => {
const { src } = props const { src } = props
if (!isBrowser || !src) { if (!isBrowser || !src) {
return null return null
@@ -22,7 +22,7 @@ const ExternalScript = (props) => {
script.setAttribute(key, value) script.setAttribute(key, value)
}) })
document.head.appendChild(script) document.head.appendChild(script)
console.log('加载外部脚本', props, script) // console.log('加载外部脚本', props, script)
return null return null
} }

View File

@@ -57,32 +57,35 @@ export default function LazyImage({
} else { } else {
imageRef.current.src = defaultPlaceholderSrc imageRef.current.src = defaultPlaceholderSrc
} }
imageRef.current.classList.remove('lazy-image-placeholder') // 移除占位符类名
if (imageRef.current) {
imageRef.current.classList.remove('lazy-image-placeholder')
}
} }
} }
useEffect(() => { useEffect(() => {
const adjustedImageSrc = const adjustedImageSrc =
adjustImgSize(src, maxWidth) || defaultPlaceholderSrc adjustImgSize(src, maxWidth) || defaultPlaceholderSrc
// 加载原图
const img = new Image()
img.src = adjustedImageSrc
img.onload = () => {
setCurrentSrc(adjustedImageSrc)
handleImageLoaded(adjustedImageSrc)
}
img.onerror = handleImageError
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
entries => { entries => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
const lazyImage = entry.target // 拉取图片
lazyImage.src = adjustedImageSrc const img = new Image()
observer.unobserve(lazyImage) img.src = adjustedImageSrc
img.onload = () => {
setCurrentSrc(adjustedImageSrc)
handleImageLoaded(adjustedImageSrc)
}
img.onerror = handleImageError
observer.unobserve(entry.target)
} }
}) })
}, },
{ rootMargin: '50px 0px' } // Adjust the rootMargin as needed to trigger the loading earlier or later { rootMargin: '50px 0px' } // 轻微提前加载
) )
if (imageRef.current) { if (imageRef.current) {
observer.observe(imageRef.current) observer.observe(imageRef.current)
@@ -98,36 +101,19 @@ export default function LazyImage({
const imgProps = { const imgProps = {
ref: imageRef, ref: imageRef,
src: currentSrc, src: currentSrc,
'data-src': src, 'data-src': src, // 存储原始图片地址
alt: alt, alt: alt || 'Lazy loaded image',
onLoad: handleThumbnailLoaded, // 缩略图加载完成 onLoad: handleThumbnailLoaded,
onError: handleImageError // 添加onError处理函数 onError: handleImageError,
className: `${className || ''} lazy-image-placeholder`,
style,
width: width || 'auto',
height: height || 'auto',
onClick
} }
if (id) { if (id) imgProps.id = id
imgProps.id = id if (title) imgProps.title = title
}
if (title) {
imgProps.title = title
}
if (width && width !== 'auto') {
imgProps.width = width
}
if (height && height !== 'auto') {
imgProps.height = height
}
if (className) {
imgProps.className = className + ' lazy-image-placeholder'
}
if (style) {
imgProps.style = style
}
if (onClick) {
imgProps.onClick = onClick
}
if (!src) { if (!src) {
return null return null

View File

@@ -14,8 +14,7 @@ export default function LoadingCover() {
if (onLoading) { if (onLoading) {
setIsVisible(true) setIsVisible(true)
} else { } else {
const timeout = setTimeout(() => setIsVisible(false), 1800) // 等待淡出动画结束 setIsVisible(false)
return () => clearTimeout(timeout)
} }
}, [onLoading]) }, [onLoading])

View File

@@ -149,9 +149,11 @@ const processGalleryImg = zoom => {
const autoScrollToHash = () => { const autoScrollToHash = () => {
setTimeout(() => { setTimeout(() => {
// 跳转到指定标题 // 跳转到指定标题
const needToJumpToTitle = window.location.hash const hash = window?.location?.hash
const needToJumpToTitle = hash && hash > 0
if (needToJumpToTitle) { if (needToJumpToTitle) {
const tocNode = document.getElementById(window.location.hash.substring(1)) console.log('jump to hash', hash)
const tocNode = document.getElementById(hash.substring(1))
if (tocNode && tocNode?.className?.indexOf('notion') > -1) { if (tocNode && tocNode?.className?.indexOf('notion') > -1) {
tocNode.scrollIntoView({ block: 'start', behavior: 'smooth' }) tocNode.scrollIntoView({ block: 'start', behavior: 'smooth' })
} }

View File

@@ -7,7 +7,7 @@ import { siteConfig } from '@/lib/config'
export default function PoweredBy(props) { export default function PoweredBy(props) {
return ( return (
<div <div
className={`gap-x-1 flex flex-wrap text-sm font-serif ${props.className}`}> className={`gap-x-1 flex flex-wrap text-sm font-serif ${props.className || ''}`}>
<span>Powered by</span> <span>Powered by</span>
<a <a
href='https://github.com/tangly1024/NotionNext' href='https://github.com/tangly1024/NotionNext'

View File

@@ -18,6 +18,32 @@ const SEO = props => {
let image let image
const router = useRouter() const router = useRouter()
const meta = getSEOMeta(props, router, useGlobal()?.locale) const meta = getSEOMeta(props, router, useGlobal()?.locale)
const webFontUrl = siteConfig('FONT_URL')
useEffect(() => {
// 使用WebFontLoader字体加载
loadExternalResource(
'https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.28/webfontloader.js',
'js'
).then(url => {
const WebFont = window?.WebFont
if (WebFont) {
// console.log('LoadWebFont', webFontUrl)
WebFont.load({
custom: {
// families: ['"LXGW WenKai"'],
urls: webFontUrl
}
})
}
})
}, [])
// SEO关键词
let keywords = meta?.tags || siteConfig('KEYWORDS')
if (post?.tags && post?.tags?.length > 0) {
keywords = post?.tags?.join(',')
}
if (meta) { if (meta) {
url = `${url}/${meta.slug}` url = `${url}/${meta.slug}`
image = meta.image || '/bg_image.jpg' image = meta.image || '/bg_image.jpg'
@@ -28,7 +54,6 @@ const SEO = props => {
const lang = siteConfig('LANG').replace('-', '_') // Facebook OpenGraph 要 zh_CN 這樣的格式才抓得到語言 const lang = siteConfig('LANG').replace('-', '_') // Facebook OpenGraph 要 zh_CN 這樣的格式才抓得到語言
const category = meta?.category || siteConfig('KEYWORDS') // section 主要是像是 category 這樣的分類Facebook 用這個來抓連結的分類 const category = meta?.category || siteConfig('KEYWORDS') // section 主要是像是 category 這樣的分類Facebook 用這個來抓連結的分類
const favicon = siteConfig('BLOG_FAVICON') const favicon = siteConfig('BLOG_FAVICON')
const webFontUrl = siteConfig('FONT_URL')
const BACKGROUND_DARK = siteConfig('BACKGROUND_DARK', '', NOTION_CONFIG) const BACKGROUND_DARK = siteConfig('BACKGROUND_DARK', '', NOTION_CONFIG)
const SEO_BAIDU_SITE_VERIFICATION = siteConfig( const SEO_BAIDU_SITE_VERIFICATION = siteConfig(
@@ -68,30 +93,6 @@ const SEO = props => {
) )
const FACEBOOK_PAGE = siteConfig('FACEBOOK_PAGE', null, NOTION_CONFIG) const FACEBOOK_PAGE = siteConfig('FACEBOOK_PAGE', null, NOTION_CONFIG)
// SEO关键词
let keywords = meta?.tags || siteConfig('KEYWORDS')
if (post?.tags && post?.tags?.length > 0) {
keywords = post?.tags?.join(',')
}
useEffect(() => {
// 使用WebFontLoader字体加载
loadExternalResource(
'https://cdnjs.cloudflare.com/ajax/libs/webfont/1.6.28/webfontloader.js',
'js'
).then(url => {
const WebFont = window?.WebFont
if (WebFont) {
console.log('LoadWebFont', webFontUrl)
WebFont.load({
custom: {
// families: ['"LXGW WenKai"'],
urls: webFontUrl
}
})
}
})
}, [])
return ( return (
<Head> <Head>

View File

@@ -6,14 +6,23 @@ import { siteConfig } from '@/lib/config'
* @param {boolean} sticky - 是否粘性定位 * @param {boolean} sticky - 是否粘性定位
* @returns {JSX.Element | null} - 返回渲染的 JSX 元素或 null * @returns {JSX.Element | null} - 返回渲染的 JSX 元素或 null
*/ */
export default function WWAds({ orientation = 'vertical', sticky = false, className }) { export default function WWAds({
orientation = 'vertical',
sticky = false,
className
}) {
const AD_WWADS_ID = siteConfig('AD_WWADS_ID') const AD_WWADS_ID = siteConfig('AD_WWADS_ID')
if (!AD_WWADS_ID) { if (!AD_WWADS_ID) {
return null return null
} }
return <div data-id={AD_WWADS_ID} className={`wwads-cn return (
<div
data-id={AD_WWADS_ID}
className={`wwads-cn
${orientation === 'vertical' ? 'wwads-vertical' : 'wwads-horizontal'} ${orientation === 'vertical' ? 'wwads-vertical' : 'wwads-horizontal'}
${sticky ? 'wwads-sticky' : ''} z-10 ${className || ''}`} /> ${sticky ? 'wwads-sticky' : ''} z-10 ${className || ''}`}
/>
)
} }

View File

@@ -169,11 +169,11 @@ export function loadExternalResource(url, type = 'js') {
} }
if (tag) { if (tag) {
tag.onload = () => { tag.onload = () => {
console.log('Load Success', url) // console.log('Load Success', url)
resolve(url) resolve(url)
} }
tag.onerror = () => { tag.onerror = () => {
console.log('Load Error', url) console.warn('Load Error', url)
reject(url) reject(url)
} }
document.head.appendChild(tag) document.head.appendChild(tag)
@@ -204,6 +204,9 @@ export function getQueryVariable(key) {
* @returns {string|null} * @returns {string|null}
*/ */
export function getQueryParam(url, param) { export function getQueryParam(url, param) {
if (!url) {
return ''
}
// 移除哈希部分 // 移除哈希部分
const urlWithoutHash = url.split('#')[0] const urlWithoutHash = url.split('#')[0]
const searchParams = new URLSearchParams(urlWithoutHash.split('?')[1]) const searchParams = new URLSearchParams(urlWithoutHash.split('?')[1])

View File

@@ -84,7 +84,7 @@ const nextConfig = {
eslint: { eslint: {
ignoreDuringBuilds: true ignoreDuringBuilds: true
}, },
output: process.env.EXPORT ? 'export' : undefined, output: process.env.EXPORT ? 'export' : process.env.NEXT_BUILD_STANDALONE === 'true' ? 'standalone' : undefined,
staticPageGenerationTimeout: 120, staticPageGenerationTimeout: 120,
// 多语言, 在export时禁用 // 多语言, 在export时禁用
i18n: process.env.EXPORT i18n: process.env.EXPORT

View File

@@ -1,6 +1,6 @@
{ {
"name": "notion-next", "name": "notion-next",
"version": "4.7.10", "version": "4.7.11",
"homepage": "https://github.com/tangly1024/NotionNext.git", "homepage": "https://github.com/tangly1024/NotionNext.git",
"license": "MIT", "license": "MIT",
"repository": { "repository": {

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 404 * 404
@@ -10,9 +9,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
const NoFound = props => { const NoFound = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='Layout404' {...props} />
} }
export async function getStaticProps(req) { export async function getStaticProps(req) {

View File

@@ -87,7 +87,7 @@ const Slug = props => {
return ( return (
<> <>
{/* 文章布局 */} {/* 文章布局 */}
<DynamicLayout theme={theme} router={router} {...props} /> <DynamicLayout theme={theme} layoutName='LayoutSlug' {...props} />
{/* 解锁密码提示框 */} {/* 解锁密码提示框 */}
{post?.password && post?.password !== '' && !lock && <Notification />} {post?.password && post?.password !== '' && !lock && <Notification />}
{/* 导流工具 */} {/* 导流工具 */}

View File

@@ -8,7 +8,7 @@ import 'react-notion-x/src/styles.css' // 原版的react-notion-x
import useAdjustStyle from '@/hooks/useAdjustStyle' import useAdjustStyle from '@/hooks/useAdjustStyle'
import { GlobalContextProvider } from '@/lib/global' import { GlobalContextProvider } from '@/lib/global'
import { getGlobalLayoutByTheme } from '@/themes/theme' import { getBaseLayoutByTheme } from '@/themes/theme'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useCallback, useMemo } from 'react' import { useCallback, useMemo } from 'react'
import { getQueryParam } from '../lib/utils' import { getQueryParam } from '../lib/utils'
@@ -34,7 +34,7 @@ const MyApp = ({ Component, pageProps }) => {
useAdjustStyle() useAdjustStyle()
const route = useRouter() const route = useRouter()
const queryParam = useMemo(() => { const theme = useMemo(() => {
return ( return (
getQueryParam(route.asPath, 'theme') || getQueryParam(route.asPath, 'theme') ||
pageProps?.NOTION_CONFIG?.THEME || pageProps?.NOTION_CONFIG?.THEME ||
@@ -45,10 +45,10 @@ const MyApp = ({ Component, pageProps }) => {
// 整体布局 // 整体布局
const GLayout = useCallback( const GLayout = useCallback(
props => { props => {
const Layout = getGlobalLayoutByTheme(queryParam) const Layout = getBaseLayoutByTheme(theme)
return <Layout {...props} /> return <Layout {...props} />
}, },
[queryParam] [theme]
) )
const enableClerk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY const enableClerk = process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY

View File

@@ -4,7 +4,6 @@ import { getGlobalData } from '@/lib/db/getSiteData'
import { isBrowser } from '@/lib/utils' import { isBrowser } from '@/lib/utils'
import { formatDateFmt } from '@/lib/utils/formatDate' import { formatDateFmt } from '@/lib/utils/formatDate'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
import { useEffect } from 'react' import { useEffect } from 'react'
/** /**
@@ -27,9 +26,8 @@ const ArchiveIndex = props => {
} }
}, []) }, [])
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutArchive' {...props} />
} }
export async function getStaticProps({ locale }) { export async function getStaticProps({ locale }) {

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 分类页 * 分类页
@@ -10,9 +9,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
export default function Category(props) { export default function Category(props) {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutPostList' {...props} />
} }
export async function getStaticProps({ params: { category }, locale }) { export async function getStaticProps({ params: { category }, locale }) {

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 分类页 * 分类页
@@ -11,9 +10,8 @@ import { useRouter } from 'next/router'
*/ */
export default function Category(props) { export default function Category(props) {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutPostList' {...props} />
} }
export async function getStaticProps({ params: { category, page } }) { export async function getStaticProps({ params: { category, page } }) {

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 分类首页 * 分类首页
@@ -10,9 +9,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
export default function Category(props) { export default function Category(props) {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutPostList' {...props} />
} }
export async function getStaticProps({ locale }) { export async function getStaticProps({ locale }) {

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData' import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 根据notion的slug访问页面 * 根据notion的slug访问页面
@@ -11,9 +10,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
const Dashboard = props => { const Dashboard = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutDashboard' {...props} />
} }
export async function getStaticProps({ locale }) { export async function getStaticProps({ locale }) {

View File

@@ -5,7 +5,6 @@ import { generateRobotsTxt } from '@/lib/robots.txt'
import { generateRss } from '@/lib/rss' import { generateRss } from '@/lib/rss'
import { generateSitemapXml } from '@/lib/sitemap.xml' import { generateSitemapXml } from '@/lib/sitemap.xml'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 首页布局 * 首页布局
@@ -13,9 +12,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
const Index = props => { const Index = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutIndex' {...props} />
} }
/** /**

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData, getPostBlocks } from '@/lib/db/getSiteData' import { getGlobalData, getPostBlocks } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 文章列表分页 * 文章列表分页
@@ -10,9 +9,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
const Page = props => { const Page = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutPostList' {...props} />
} }
export async function getStaticPaths({ locale }) { export async function getStaticPaths({ locale }) {

View File

@@ -3,12 +3,10 @@ import { getDataFromCache } from '@/lib/cache/cache_manager'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
const Index = props => { const Index = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutSearch' {...props} />
} }
/** /**

View File

@@ -3,15 +3,13 @@ import { getDataFromCache } from '@/lib/cache/cache_manager'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
const Index = props => { const Index = props => {
const { keyword } = props const { keyword } = props
props = { ...props, currentSearch: keyword } props = { ...props, currentSearch: keyword }
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutSearch' {...props} />
} }
/** /**

View File

@@ -32,7 +32,7 @@ const Search = props => {
props = { ...props, posts: filteredPosts } props = { ...props, posts: filteredPosts }
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutSearch' {...props} />
} }
/** /**

View File

@@ -3,7 +3,6 @@ import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
// import { getGlobalData } from '@/lib/db/getSiteData' // import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 登录 * 登录
@@ -11,9 +10,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
const SignIn = props => { const SignIn = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutSignIn' {...props} />
} }
export async function getStaticProps(req) { export async function getStaticProps(req) {

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 注册 * 注册
@@ -10,9 +9,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
const SignUp = props => { const SignUp = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutSignUp' {...props} />
} }
export async function getStaticProps(req) { export async function getStaticProps(req) {

View File

@@ -2,7 +2,6 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
/** /**
* 标签下的文章列表 * 标签下的文章列表
@@ -10,9 +9,8 @@ import { useRouter } from 'next/router'
* @returns * @returns
*/ */
const Tag = props => { const Tag = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutPostList' {...props} />
} }
export async function getStaticProps({ params: { tag }, locale }) { export async function getStaticProps({ params: { tag }, locale }) {

View File

@@ -2,12 +2,10 @@ import BLOG from '@/blog.config'
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import { getGlobalData } from '@/lib/db/getSiteData' import { getGlobalData } from '@/lib/db/getSiteData'
import { DynamicLayout } from '@/themes/theme' import { DynamicLayout } from '@/themes/theme'
import { useRouter } from 'next/router'
const Tag = props => { const Tag = props => {
const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutPostList' {...props} />
} }
export async function getStaticProps({ params: { tag, page }, locale }) { export async function getStaticProps({ params: { tag, page }, locale }) {

View File

@@ -12,7 +12,7 @@ import { useRouter } from 'next/router'
const TagIndex = props => { const TagIndex = props => {
const router = useRouter() const router = useRouter()
const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG) const theme = siteConfig('THEME', BLOG.THEME, props.NOTION_CONFIG)
return <DynamicLayout theme={theme} router={router} {...props} /> return <DynamicLayout theme={theme} layoutName='LayoutPostList' {...props} />
} }
export async function getStaticProps(req) { export async function getStaticProps(req) {

View File

@@ -19,6 +19,7 @@ html {
position: sticky; position: sticky;
z-index: 10; z-index: 10;
top: -1px; top: -1px;
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px); backdrop-filter: blur(5px);
transition: all 0.5s cubic-bezier(0.4, 0, 0, 1); transition: all 0.5s cubic-bezier(0.4, 0, 0, 1);
border-bottom-color: transparent; border-bottom-color: transparent;
@@ -74,6 +75,7 @@ nav {
@supports not (backdrop-filter: none) { @supports not (backdrop-filter: none) {
.sticky-nav { .sticky-nav {
-webkit-backdrop-filter: none;
backdrop-filter: none; backdrop-filter: none;
@apply bg-day dark:bg-gray-800; @apply bg-day dark:bg-gray-800;
} }

View File

@@ -27,7 +27,7 @@ const BlogPostCard = ({ index, post, showSummary, siteInfo }) => {
key={post.id} key={post.id}
data-aos='fade-up' data-aos='fade-up'
data-aos-easing='ease-in-out' data-aos-easing='ease-in-out'
data-aos-duration='800' data-aos-duration='500'
data-aos-once='false' data-aos-once='false'
data-aos-anchor-placement='top-bottom' data-aos-anchor-placement='top-bottom'
id='blog-post-card' id='blog-post-card'

View File

@@ -35,7 +35,7 @@ const Footer = ({ title }) => {
<h1 className='text-xs pt-4 text-light-400 dark:text-gray-400'> <h1 className='text-xs pt-4 text-light-400 dark:text-gray-400'>
{title} {siteConfig('BIO') && <>|</>} {siteConfig('BIO')} {title} {siteConfig('BIO') && <>|</>} {siteConfig('BIO')}
</h1> </h1>
<PoweredBy /> <PoweredBy className='justify-center' />
</span> </span>
<br /> <br />
</footer> </footer>

View File

@@ -147,10 +147,10 @@ export default function Header(props) {
{!showSearchInput && ( {!showSearchInput && (
<> <>
{/* 左侧图标Logo */} {/* 左侧图标Logo */}
<div className='flex gap-x-8 h-full'> <div className='flex gap-x-2 lg:gap-x-4 h-full'>
<LogoBar {...props} /> <LogoBar className={'text-sm md:text-md lg:text-lg'} />
{/* 桌面端顶部菜单 */} {/* 桌面端顶部菜单 */}
<ul className='hidden md:flex items-center gap-x-4 py-1'> <ul className='hidden md:flex items-center gap-x-4 py-1 text-sm md:text-md'>
{links && {links &&
links?.map((link, index) => ( links?.map((link, index) => (
<MenuItemDrop key={index} link={link} /> <MenuItemDrop key={index} link={link} />

View File

@@ -1,12 +1,14 @@
import { siteConfig } from '@/lib/config' import { siteConfig } from '@/lib/config'
import Link from 'next/link' import Link from 'next/link'
export default function LogoBar(props) { export default function LogoBar({ className }) {
return ( return (
<div id='top-wrapper' className='w-full flex items-center '> <div
id='top-wrapper'
className={`w-full flex items-center ${className || ''}`}>
<Link <Link
href='/' href='/'
className='logo flex text-md font-semibold md:text-xl hover:bg-black hover:text-white p-2 rounded-xl duration-200 dark:text-gray-200'> className='logo flex font-semibold hover:bg-black hover:text-white p-2 rounded-xl duration-200 dark:text-gray-200'>
{/* <LazyImage {/* <LazyImage
src={siteInfo?.icon} src={siteInfo?.icon}
width={24} width={24}

View File

@@ -17,10 +17,6 @@ export const MenuItemCollapse = props => {
const router = useRouter() const router = useRouter()
if (!link || !link.show) {
return null
}
const selected = router.pathname === link.href || router.asPath === link.href const selected = router.pathname === link.href || router.asPath === link.href
const toggleShow = () => { const toggleShow = () => {
@@ -36,6 +32,10 @@ export const MenuItemCollapse = props => {
setOpen(false) setOpen(false)
}, [router]) }, [router])
if (!link || !link.show) {
return null
}
return ( return (
<> <>
<div <div

View File

@@ -22,13 +22,13 @@ export const MenuItemDrop = ({ link }) => {
{hasSubMenu && ( {hasSubMenu && (
<div <div
className={ className={
'px-2 h-full whitespace-nowrap duration-300 text-md justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' + 'px-2 h-full whitespace-nowrap duration-300 justify-between dark:text-gray-300 cursor-pointer flex flex-nowrap items-center ' +
(selected (selected
? 'bg-gray-600 text-white hover:text-white' ? 'bg-gray-600 text-white hover:text-white'
: 'hover:text-gray-600') : 'hover:text-gray-600')
}> }>
<div className='items-center flex gap-x-1'> <div className='items-center flex'>
{link?.icon && <i className={link?.icon} />} {link?.name} {link?.icon && <i className={`${link?.icon} pr-2`} />} {link?.name}
<i <i
className={`px-1 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i> className={`px-1 fas fa-chevron-down duration-500 transition-all ${show ? ' rotate-180' : ''}`}></i>
</div> </div>
@@ -52,15 +52,15 @@ export const MenuItemDrop = ({ link }) => {
{/* 子菜单 */} {/* 子菜单 */}
{hasSubMenu && ( {hasSubMenu && (
<ul <ul
className={`${show ? 'visible opacity-100 top-14' : 'invisible opacity-0 top-20'} absolute border bg-white dark:bg-black dark:border-gray-800 transition-all duration-150 z-20 block rounded-lg drop-shadow-lg p-4 `}> className={`${show ? 'visible opacity-100 top-14' : 'invisible opacity-0 top-20'} p-1 absolute border bg-white dark:bg-black dark:border-gray-800 transition-all duration-150 z-20 block rounded-lg drop-shadow-lg`}>
{link?.subMenus?.map(sLink => { {link?.subMenus?.map(sLink => {
return ( return (
<li <li
key={sLink.id} key={sLink.id}
className='dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-900 tracking-widest transition-all duration-200 dark:border-gray-800 py-3 pr-6 pl-3'> className='py-3 pr-6 hover:bg-gray-100 dark:hover:bg-gray-900 dark:text-gray-200 tracking-widest transition-color duration-200 dark:border-gray-800 '>
<Link href={sLink.href} target={link?.target}> <Link href={sLink.href} target={link?.target}>
<span className='text-sm'> <span className='text-sm ml-2'>
{link?.icon && <i className={sLink?.icon}> &nbsp; </i>} {link?.icon && <i className={`${sLink?.icon} pr-2`}> </i>}
{sLink.title} {sLink.title}
</span> </span>
</Link> </Link>

View File

@@ -14,10 +14,11 @@ import Swiper from './Swiper'
const PostListRecommend = ({ latestPosts, allNavPages }) => { const PostListRecommend = ({ latestPosts, allNavPages }) => {
// 获取推荐文章 // 获取推荐文章
const recommendPosts = getTopPosts({ latestPosts, allNavPages }) const recommendPosts = getTopPosts({ latestPosts, allNavPages })
const title = siteConfig('MAGZINE_RECOMMEND_POST_TITLE', '', CONFIG)
if (!recommendPosts || recommendPosts.length === 0) { if (!recommendPosts || recommendPosts.length === 0) {
return <PostListEmpty /> return <PostListEmpty />
} }
const title = siteConfig('MAGZINE_RECOMMEND_POST_TITLE', '', CONFIG)
return ( return (
<div className={`w-full py-10 px-2 bg-[#F6F6F1] dark:bg-black`}> <div className={`w-full py-10 px-2 bg-[#F6F6F1] dark:bg-black`}>

View File

@@ -42,7 +42,7 @@ export const MenuItem = ({ link }) => {
<li className='submenu-item group relative whitespace-nowrap'> <li className='submenu-item group relative whitespace-nowrap'>
<button <button
onClick={toggleSubMenu} onClick={toggleSubMenu}
className={`cursor-pointer relative w-full px-8 flex items-center justify-between py-2 text-base font-medium text-dark group-hover:text-primary dark:text-white lg:ml-8 lg:mr-0 lg:inline-flex lg:py-6 lg:pl-0 lg:pr-4 ${ className={`cursor-pointer relative px-8 flex items-center justify-between py-2 text-base font-medium text-dark group-hover:text-primary dark:text-white lg:ml-8 lg:mr-0 lg:inline-flex lg:py-6 lg:pl-0 lg:pr-4 ${
router.route === '/' router.route === '/'
? 'lg:text-white lg:group-hover:text-white' ? 'lg:text-white lg:group-hover:text-white'
: '' : ''

View File

@@ -54,6 +54,11 @@ const Style = () => {
color: rgb(55 88 249 / var(--tw-text-opacity)); color: rgb(55 88 249 / var(--tw-text-opacity));
opacity: 1; opacity: 1;
} }
#theme-starter .sticky #navbarCollapse li > button{
--tw-text-opacity: 1;
color: rgb(17 25 40 / var(--tw-text-opacity));
}
:is(.dark #theme-starter .sticky #navbarCollapse li > a){ :is(.dark #theme-starter .sticky #navbarCollapse li > a){
--tw-text-opacity: 1; --tw-text-opacity: 1;
@@ -64,7 +69,12 @@ const Style = () => {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(55 88 249 / var(--tw-text-opacity)); color: rgb(55 88 249 / var(--tw-text-opacity));
} }
:is(.dark #theme-starter .sticky #navbarCollapse li > button){
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
#navbarCollapse li .ud-menu-scroll.active{ #navbarCollapse li .ud-menu-scroll.active{
opacity: 0.7; opacity: 0.7;
} }

View File

@@ -2,6 +2,7 @@ import BLOG, { LAYOUT_MAPPINGS } from '@/blog.config'
import * as ThemeComponents from '@theme-components' import * as ThemeComponents from '@theme-components'
import getConfig from 'next/config' import getConfig from 'next/config'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { getQueryParam, getQueryVariable, isBrowser } from '../lib/utils' import { getQueryParam, getQueryVariable, isBrowser } from '../lib/utils'
// 在next.config.js中扫描所有主题 // 在next.config.js中扫描所有主题
@@ -56,19 +57,20 @@ export const getThemeConfig = async themeQuery => {
/** /**
* 加载全局布局 * 加载全局布局
* @param {*} themeQuery * @param {*} theme
* @returns * @returns
*/ */
export const getGlobalLayoutByTheme = themeQuery => { export const getBaseLayoutByTheme = theme => {
if (themeQuery !== BLOG.THEME) { const LayoutBase = ThemeComponents['LayoutBase']
const isDefaultTheme = !theme || theme === BLOG.THEME
if (!isDefaultTheme) {
return dynamic( return dynamic(
() => () => import(`@/themes/${theme}`).then(m => m['LayoutBase']),
import(`@/themes/${themeQuery}`).then(m => m[getLayoutNameByPath(-1)]),
{ ssr: true } { ssr: true }
) )
} else {
return ThemeComponents[getLayoutNameByPath('-1')]
} }
return LayoutBase
} }
/** /**
@@ -76,8 +78,8 @@ export const getGlobalLayoutByTheme = themeQuery => {
* @param {*} props * @param {*} props
*/ */
export const DynamicLayout = props => { export const DynamicLayout = props => {
const { router, theme } = props const { theme, layoutName } = props
const SelectedLayout = getLayoutByTheme({ router, theme }) const SelectedLayout = getLayoutByTheme({ layoutName, theme })
return <SelectedLayout {...props} /> return <SelectedLayout {...props} />
} }
@@ -87,26 +89,31 @@ export const DynamicLayout = props => {
* @param {*} theme * @param {*} theme
* @returns * @returns
*/ */
export const getLayoutByTheme = ({ router, theme }) => { export const getLayoutByTheme = ({ layoutName, theme }) => {
const themeQuery = getQueryParam(router.asPath, 'theme') || theme // const layoutName = getLayoutNameByPath(router.pathname, router.asPath)
const layoutName = getLayoutNameByPath(router.pathname, router.asPath) const LayoutComponents =
ThemeComponents[layoutName] || ThemeComponents.LayoutSlug
const router = useRouter()
const themeQuery = getQueryParam(router?.asPath, 'theme') || theme
const isDefaultTheme = !themeQuery || themeQuery === BLOG.THEME const isDefaultTheme = !themeQuery || themeQuery === BLOG.THEME
const loadThemeComponents = componentsSource => { // 加载非当前默认主题
const components = if (!isDefaultTheme) {
componentsSource[layoutName] || componentsSource.LayoutSlug const loadThemeComponents = componentsSource => {
setTimeout(fixThemeDOM, isDefaultTheme ? 100 : 500) // 根据主题选择延迟时间 const components =
return components componentsSource[layoutName] || componentsSource.LayoutSlug
} setTimeout(fixThemeDOM, 500)
return components
if (isDefaultTheme) { }
return loadThemeComponents(ThemeComponents)
} else {
return dynamic( return dynamic(
() => import(`@/themes/${themeQuery}`).then(m => loadThemeComponents(m)), () => import(`@/themes/${themeQuery}`).then(m => loadThemeComponents(m)),
{ ssr: true } { ssr: true }
) )
} }
setTimeout(fixThemeDOM, 100)
return LayoutComponents
} }
/** /**
@@ -128,8 +135,6 @@ const fixThemeDOM = () => {
if (isBrowser) { if (isBrowser) {
const elements = document.querySelectorAll('[id^="theme-"]') const elements = document.querySelectorAll('[id^="theme-"]')
if (elements?.length > 1) { if (elements?.length > 1) {
elements[elements.length - 1].scrollIntoView()
// 删除前面的元素,只保留最后一个元素
for (let i = 0; i < elements.length - 1; i++) { for (let i = 0; i < elements.length - 1; i++) {
if ( if (
elements[i] && elements[i] &&
@@ -139,6 +144,7 @@ const fixThemeDOM = () => {
elements[i].parentNode.removeChild(elements[i]) elements[i].parentNode.removeChild(elements[i])
} }
} }
elements[0]?.scrollIntoView()
} }
} }
} }