mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-17 23:16:50 +00:00
Merge branch 'main' into feat/mail-encypt
This commit is contained in:
@@ -3,7 +3,7 @@ import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import algoliasearch from 'algoliasearch'
|
||||
import throttle from 'lodash/throttle'
|
||||
import Link from 'next/link'
|
||||
import SmartLink from '@/components/SmartLink'
|
||||
import { useRouter } from 'next/router'
|
||||
import {
|
||||
Fragment,
|
||||
@@ -104,7 +104,8 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
// 跳转Search结果
|
||||
const onJumpSearchResult = () => {
|
||||
if (searchResults.length > 0) {
|
||||
window.location.href = `${siteConfig('SUB_PATH', '')}/${searchResults[activeIndex].slug}`
|
||||
const searchResult = searchResults[activeIndex]
|
||||
window.location.href = `${siteConfig('SUB_PATH', '')}/${searchResult.slug || searchResult.objectID}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,12 +247,12 @@ export default function AlgoliaSearchModal({ cRef }) {
|
||||
id='search-wrapper'
|
||||
className={`${
|
||||
isModalOpen ? 'opacity-100' : 'invisible opacity-0 pointer-events-none'
|
||||
} z-30 fixed h-screen w-screen left-0 top-0 sm:mt-12 flex items-start justify-center mt-0`}>
|
||||
} z-30 fixed h-screen w-screen left-0 top-0 sm:mt-[10vh] flex items-start justify-center mt-0`}>
|
||||
{/* 模态框 */}
|
||||
<div
|
||||
className={`${
|
||||
isModalOpen ? 'opacity-100' : 'invisible opacity-0 translate-y-10'
|
||||
} flex flex-col justify-between w-full min-h-[10rem] h-full md:h-fit max-w-xl dark:bg-hexo-black-gray dark:border-gray-800 bg-white dark:bg- p-5 rounded-lg z-50 shadow border hover:border-blue-600 duration-300 transition-all `}>
|
||||
} max-h-[80vh] flex flex-col justify-between w-full min-h-[10rem] h-full md:h-fit max-w-xl dark:bg-hexo-black-gray dark:border-gray-800 bg-white dark:bg- p-5 rounded-lg z-50 shadow border hover:border-blue-600 duration-300 transition-all `}>
|
||||
<div className='flex justify-between items-center'>
|
||||
<div className='text-2xl text-blue-600 dark:text-yellow-600 font-bold'>
|
||||
搜索
|
||||
@@ -356,7 +357,7 @@ function TagGroups() {
|
||||
<div id='tags-group' className='dark:border-gray-700 space-y-2'>
|
||||
{firstTenTags?.map((tag, index) => {
|
||||
return (
|
||||
<Link
|
||||
<SmartLink
|
||||
passHref
|
||||
key={index}
|
||||
href={`/tag/${encodeURIComponent(tag.name)}`}
|
||||
@@ -372,7 +373,7 @@ function TagGroups() {
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</SmartLink>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
73
components/ArticleExpirationNotice.js
Normal file
73
components/ArticleExpirationNotice.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
|
||||
/**
|
||||
* 文章过期提醒组件
|
||||
* 当文章超过指定天数时显示提醒
|
||||
* @param {Object} props - 组件属性
|
||||
* @param {Object} props.post - 文章数据
|
||||
* @param {number} [props.daysThreshold=90] - 过期阈值(天)
|
||||
* @returns {JSX.Element|null}
|
||||
*/
|
||||
export default function ArticleExpirationNotice({
|
||||
post,
|
||||
daysThreshold = siteConfig('ARTICLE_EXPIRATION_DAYS', 90)
|
||||
}) {
|
||||
const articleExpirationEnabled = siteConfig(
|
||||
'ARTICLE_EXPIRATION_ENABLED',
|
||||
false
|
||||
)
|
||||
if (!articleExpirationEnabled || !post?.lastEditedDay) {
|
||||
return null
|
||||
}
|
||||
|
||||
const postDate = new Date(post.lastEditedDay)
|
||||
const today = new Date()
|
||||
const diffTime = Math.abs(today - postDate)
|
||||
const daysOld = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||
const isVisible = daysOld >= daysThreshold
|
||||
|
||||
if (!isVisible) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 使用 %%DAYS%% 作为占位符
|
||||
const articleExpirationMessage = siteConfig(
|
||||
'ARTICLE_EXPIRATION_MESSAGE',
|
||||
'这篇文章发布于 %%DAYS%% 天前,内容可能已过时,请谨慎参考。'
|
||||
)
|
||||
const articleExpirationMessageParts =
|
||||
articleExpirationMessage.split('%%DAYS%%')
|
||||
|
||||
// 直接返回 JSX 内容
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
'p-4 rounded-lg border border-blue-300 bg-blue-50 dark:bg-blue-900/20 text-gray-800 dark:text-gray-200 shadow-sm'
|
||||
}>
|
||||
<div className='flex items-start'>
|
||||
<i className='fas fa-exclamation-triangle text-blue-500 dark:text-blue-400 mt-0.5 mr-2 flex-shrink-0' />
|
||||
<div className='ml-1'>
|
||||
<div className='text-blue-600 dark:text-blue-400 font-medium'>
|
||||
{siteConfig('ARTICLE_EXPIRATION_TITLE', '温馨提醒')}
|
||||
</div>
|
||||
<div className='flex items-center mt-1 text-sm text-gray-700 dark:text-gray-300'>
|
||||
<i className='far fa-clock text-red-500 dark:text-red-400 mr-1' />
|
||||
<span>
|
||||
{(() => {
|
||||
return (
|
||||
<>
|
||||
{articleExpirationMessageParts[0]}
|
||||
<span className='text-red-500 dark:text-red-400 font-bold'>
|
||||
{daysOld}
|
||||
</span>
|
||||
{articleExpirationMessageParts[1]}
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import useWindowSize from '@/hooks/useWindowSize'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import { THEMES, saveDarkModeToLocalStorage } from '@/themes/theme'
|
||||
import Link from 'next/link'
|
||||
import SmartLink from '@/components/SmartLink'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
|
||||
@@ -198,23 +198,23 @@ export default function CustomContextMenu(props) {
|
||||
)}
|
||||
|
||||
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY && (
|
||||
<Link
|
||||
<SmartLink
|
||||
href='/category'
|
||||
title={locale.MENU.CATEGORY}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-square-minus mr-2' />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.CATEGORY}</div>
|
||||
</Link>
|
||||
</SmartLink>
|
||||
)}
|
||||
|
||||
{CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG && (
|
||||
<Link
|
||||
<SmartLink
|
||||
href='/tag'
|
||||
title={locale.MENU.TAGS}
|
||||
className='w-full px-2 h-10 flex justify-start items-center flex-nowrap cursor-pointer hover:bg-blue-600 hover:text-white rounded-lg duration-200 transition-all'>
|
||||
<i className='fa-solid fa-tag mr-2' />
|
||||
<div className='whitespace-nowrap'>{locale.MENU.TAGS}</div>
|
||||
</Link>
|
||||
</SmartLink>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
66
components/NotByAI.js
Normal file
66
components/NotByAI.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useGlobal } from '@/lib/global'
|
||||
|
||||
const LANGS = {
|
||||
'en-US': 'en',
|
||||
'zh-CN': 'zh',
|
||||
'zh-HK': 'zh-HK',
|
||||
'zh-TW': 'zh-TW',
|
||||
'fr-FR': 'fr',
|
||||
'tr-TR': 'tr',
|
||||
'ja-JP': 'ja'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前not-by-ai文件路径
|
||||
* 如果匹配到完整的“国家-地区”语言,则显示国家的语言
|
||||
* @returns string
|
||||
*/
|
||||
export function generateNotByAiPath(langString) {
|
||||
const supportedLocales = Object.keys(LANGS)
|
||||
let userLocale
|
||||
|
||||
// 将语言字符串拆分为语言和地区代码,例如将 "zh-CN" 拆分为 "zh" 和 "CN"
|
||||
const [language, region] = langString?.split(/[-_]/)
|
||||
|
||||
// 优先匹配语言和地区都匹配的情况
|
||||
const specificLocale = `${language}-${region}`
|
||||
if (supportedLocales.includes(specificLocale)) {
|
||||
userLocale = LANGS[specificLocale]
|
||||
}
|
||||
|
||||
// 然后尝试匹配只有语言匹配的情况
|
||||
if (!userLocale) {
|
||||
const languageOnlyLocales = supportedLocales.filter(locale =>
|
||||
locale.startsWith(language)
|
||||
)
|
||||
if (languageOnlyLocales.length > 0) {
|
||||
userLocale = LANGS[languageOnlyLocales[0]]
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还没匹配到,则返回最接近的
|
||||
if (!userLocale) {
|
||||
const fallbackLocale = supportedLocales.find(locale =>
|
||||
locale.startsWith('en')
|
||||
)
|
||||
userLocale = LANGS[fallbackLocale]
|
||||
}
|
||||
|
||||
return userLocale ?? 'zh'
|
||||
}
|
||||
|
||||
/**
|
||||
* 版权声明
|
||||
* @returns
|
||||
*/
|
||||
export default function NotByAI() {
|
||||
const { lang, isDarkMode } = useGlobal()
|
||||
|
||||
return (
|
||||
<img
|
||||
className='transform hover:scale-110 duration-150'
|
||||
src={`/svg/not-by-ai/${generateNotByAiPath(lang)}/Written-By-Human-Not-By-AI-Badge-${isDarkMode ? 'black' : 'white'}.svg`}
|
||||
alt='not-by-ai'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -64,7 +64,7 @@ const Player = () => {
|
||||
<link
|
||||
rel='stylesheet'
|
||||
type='text/css'
|
||||
href='https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/aplayer/1.10.1/APlayer.min.css'
|
||||
href='https://cdn.jsdelivr.net/npm/aplayer@1.10.0/dist/APlayer.min.css'
|
||||
/>
|
||||
{meting ? (
|
||||
<meting-js
|
||||
|
||||
52
components/SmartLink.js
Normal file
52
components/SmartLink.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import Link from 'next/link'
|
||||
import { siteConfig } from '@/lib/config'
|
||||
|
||||
// 过滤 <a> 标签不能识别的 props
|
||||
const filterDOMProps = props => {
|
||||
const { passHref, legacyBehavior, ...rest } = props
|
||||
return rest
|
||||
}
|
||||
|
||||
const SmartLink = ({ href, children, ...rest }) => {
|
||||
const LINK = siteConfig('LINK')
|
||||
|
||||
// 获取 URL 字符串用于判断是否是外链
|
||||
let urlString = ''
|
||||
|
||||
if (typeof href === 'string') {
|
||||
urlString = href
|
||||
} else if (
|
||||
typeof href === 'object' &&
|
||||
href !== null &&
|
||||
typeof href.pathname === 'string'
|
||||
) {
|
||||
urlString = href.pathname
|
||||
}
|
||||
|
||||
const isExternal = urlString.startsWith('http') && !urlString.startsWith(LINK)
|
||||
|
||||
if (isExternal) {
|
||||
// 对于外部链接,必须是 string 类型
|
||||
const externalUrl =
|
||||
typeof href === 'string' ? href : new URL(href.pathname, LINK).toString()
|
||||
|
||||
return (
|
||||
<a
|
||||
href={externalUrl}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
{...filterDOMProps(rest)}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
// 内部链接(可为对象形式)
|
||||
return (
|
||||
<Link href={href} {...rest}>
|
||||
{children}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default SmartLink
|
||||
@@ -1,5 +1,5 @@
|
||||
import { siteConfig } from '@/lib/config'
|
||||
import Link from 'next/link'
|
||||
import SmartLink from '@/components/SmartLink'
|
||||
import { useRouter } from 'next/router'
|
||||
/**
|
||||
* 跳转仪表盘的按钮
|
||||
@@ -24,7 +24,7 @@ export default function DashboardButton({ className }) {
|
||||
<button
|
||||
type='button'
|
||||
className={`${className || ''} text-white bg-gray-800 hover:bg-gray-900 hover:ring-4 hover:ring-gray-300 focus:outline-none focus:ring-4 focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2 me-2 dark:bg-gray-800 dark:hover:bg-gray-700 dark:focus:ring-gray-700 dark:border-gray-700`}>
|
||||
<Link href='/dashboard'>仪表盘</Link>
|
||||
<SmartLink href='/dashboard'>仪表盘</SmartLink>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import LazyImage from '@/components/LazyImage'
|
||||
import { useGlobal } from '@/lib/global'
|
||||
import formatDate from '@/lib/utils/formatDate'
|
||||
import Link from 'next/link'
|
||||
import SmartLink from '@/components/SmartLink'
|
||||
import DashboardSignOutButton from './DashboardSignOutButton'
|
||||
|
||||
/**
|
||||
@@ -25,11 +25,11 @@ export default function DashboardHeader() {
|
||||
<div class='font-medium dark:text-white'>
|
||||
<div className='flex items-center gap-x-2'>
|
||||
<span>{user?.fullName}</span>
|
||||
<Link href='/dashboard/membership'>
|
||||
<SmartLink href='/dashboard/membership'>
|
||||
<span class='bg-gray-100 text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-gray-300'>
|
||||
普通用户
|
||||
</span>
|
||||
</Link>
|
||||
</SmartLink>
|
||||
</div>
|
||||
<div className='text-sm text-gray-500 gap-x-2 flex dark:text-gray-400'>
|
||||
<span>{user?.username}</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Link from 'next/link'
|
||||
import SmartLink from '@/components/SmartLink'
|
||||
|
||||
/**
|
||||
* 联盟行销
|
||||
@@ -140,11 +140,11 @@ export default function DashboardItemAffliate() {
|
||||
for='remember'
|
||||
className='ms-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||
我以阅读并同意{' '}
|
||||
<Link
|
||||
<SmartLink
|
||||
href='/terms-of-use'
|
||||
className='text-blue-600 hover:underline dark:text-blue-500'>
|
||||
服务协议
|
||||
</Link>
|
||||
</SmartLink>
|
||||
.
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Link from 'next/link'
|
||||
import SmartLink from '@/components/SmartLink'
|
||||
|
||||
/**
|
||||
* 仪表盘菜单
|
||||
@@ -43,12 +43,12 @@ export default function DashboardMenuList() {
|
||||
className={`rounded-lg cursor-pointer block ${
|
||||
isActive ? 'bg-blue-100 text-blue-600' : 'hover:bg-gray-100'
|
||||
}`}>
|
||||
<Link
|
||||
<SmartLink
|
||||
href={item.href}
|
||||
className='block py-2 px-4 w-full items-center justify-center'>
|
||||
<i className={`${item.icon} w-6 mr-2`}></i>
|
||||
<span className='whitespace-nowrap'>{item.title}</span>
|
||||
</Link>
|
||||
</SmartLink>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user