Skip to content

Commit

Permalink
Merge branch 'tangly1024:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
snakexgc authored Mar 6, 2024
2 parents 31a9142 + a33bf9f commit cda853b
Show file tree
Hide file tree
Showing 36 changed files with 573 additions and 1,450 deletions.
2 changes: 1 addition & 1 deletion .env.local
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# 环境变量 @see https://www.nextjs.cn/docs/basic-features/environment-variables
NEXT_PUBLIC_VERSION=4.3.0
NEXT_PUBLIC_VERSION=4.3.1


# 可在此添加环境变量,去掉最左边的(# )注释即可
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/docker-ghcr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
Expand All @@ -54,6 +57,7 @@ jobs:
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
23 changes: 16 additions & 7 deletions blog.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,14 @@ const BLOG = {
},

CAN_COPY: process.env.NEXT_PUBLIC_CAN_COPY || true, // 是否允许复制页面内容 默认允许,如果设置为false、则全栈禁止复制内容。
// 自定义右键菜单
CUSTOM_RIGHT_CLICK_CONTEXT_MENU: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU || true, // 自定义右键菜单,覆盖系统菜单
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH || true, // 右键菜单是否允许切换主题
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_SWITCH || true, // 是否显示切换主题
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_DARK_MODE || true, // 是否显示深色模式
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_SHARE_LINK || true, // 是否显示分享链接
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_RANDOM_POST || true, // 是否显示随机博客
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_CATEGORY || true, // 是否显示分类
CUSTOM_RIGHT_CLICK_CONTEXT_MENU_TAG: process.env.NEXT_PUBLIC_CUSTOM_RIGHT_CLICK_CONTEXT_MENU_THEME_TAG || true, // 是否显示标签

// 自定义外部脚本,外部样式
CUSTOM_EXTERNAL_JS: [''], // e.g. ['http://xx.com/script.js','http://xx.com/script.js']
Expand Down Expand Up @@ -432,6 +438,15 @@ const BLOG = {
MAILCHIMP_LIST_ID: process.env.MAILCHIMP_LIST_ID || null, // 开启mailichimp邮件订阅 客户列表ID ,具体使用方法参阅文档
MAILCHIMP_API_KEY: process.env.MAILCHIMP_API_KEY || null, // 开启mailichimp邮件订阅 APIkey

// ANIMATE.css 动画
ANIMATE_CSS_URL: process.env.NEXT_PUBLIC_ANIMATE_CSS_URL || 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css', // 动画CDN

// 网站图片
IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址,支持base64或url
IMG_URL_TYPE: process.env.NEXT_PUBLIC_IMG_TYPE || 'Notion', // 此配置已失效,请勿使用;AMAZON方案不再支持,仅支持Notion方案。 ['Notion','AMAZON'] 站点图片前缀 默认 Notion:(https://notion.so/images/xx) , AMAZON(https://s3.us-west-2.amazonaws.com/xxx)
IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影
IMG_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMG_COMPRESS_WIDTH || 800, // Notion图片压缩宽度

// 作废配置
AVATAR: process.env.NEXT_PUBLIC_AVATAR || '/avatar.svg', // 作者头像,被notion中的ICON覆盖。若无ICON则取public目录下的avatar.png
TITLE: process.env.NEXT_PUBLIC_TITLE || 'NotionNext BLOG', // 站点标题 ,被notion中的页面标题覆盖;此处请勿留空白,否则服务器无法编译
Expand All @@ -440,12 +455,6 @@ const BLOG = {
DESCRIPTION:
process.env.NEXT_PUBLIC_DESCRIPTION || '这是一个由NotionNext生成的站点', // 站点描述,被notion中的页面描述覆盖

// 网站图片
IMG_LAZY_LOAD_PLACEHOLDER: process.env.NEXT_PUBLIC_IMG_LAZY_LOAD_PLACEHOLDER || 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==', // 懒加载占位图片地址,支持base64或url
IMG_URL_TYPE: process.env.NEXT_PUBLIC_IMG_TYPE || 'Notion', // 此配置已失效,请勿使用;AMAZON方案不再支持,仅支持Notion方案。 ['Notion','AMAZON'] 站点图片前缀 默认 Notion:(https://notion.so/images/xx) , AMAZON(https://s3.us-west-2.amazonaws.com/xxx)
IMG_SHADOW: process.env.NEXT_PUBLIC_IMG_SHADOW || false, // 文章图片是否自动添加阴影
IMG_COMPRESS_WIDTH: process.env.NEXT_PUBLIC_IMG_COMPRESS_WIDTH || 800, // Notion图片压缩宽度

// 开发相关
NOTION_ACCESS_TOKEN: process.env.NOTION_ACCESS_TOKEN || '', // Useful if you prefer not to make your database public
DEBUG: process.env.NEXT_PUBLIC_DEBUG || false, // 是否显示调试按钮
Expand Down
202 changes: 136 additions & 66 deletions components/AlgoliaSearchModal.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useState, useImperativeHandle, useRef } from 'react'
import { useState, useImperativeHandle, useRef, useEffect } from 'react'
import algoliasearch from 'algoliasearch'
import replaceSearchResult from '@/components/Mark'
import Link from 'next/link'
import { useGlobal } from '@/lib/global'
import throttle from 'lodash/throttle'
import { siteConfig } from '@/lib/config'
import { useHotkeys } from 'react-hotkeys-hook';

/**
* 结合 Algolia 实现的弹出式搜索框
Expand All @@ -19,7 +20,63 @@ export default function AlgoliaSearchModal({ cRef }) {
const [totalPage, setTotalPage] = useState(0)
const [totalHit, setTotalHit] = useState(0)
const [useTime, setUseTime] = useState(0)
const inputRef = useRef(null)
const [activeIndex, setActiveIndex] = useState(0)
const [isLoading, setIsLoading] = useState(false)
useHotkeys('ctrl+k', (e) => {
e.preventDefault()
setIsModalOpen(true)
})
// 方向键调整选中
useHotkeys('down', (e) => {
e.preventDefault()
if (activeIndex < searchResults.length - 1) {
setActiveIndex(activeIndex + 1)
}
}, { enableOnFormTags: true })
useHotkeys('up', (e) => {
e.preventDefault()
if (activeIndex > 0) {
setActiveIndex(activeIndex - 1)
}
}, { enableOnFormTags: true })
// esc关闭
useHotkeys('esc', (e) => {
e.preventDefault()
setIsModalOpen(false)
}, { enableOnFormTags: true })
// 跳转Search结果
const onJumpSearchResult = () => {
if (searchResults.length > 0) {
window.location.href = `${siteConfig('SUB_PATH', '')}/${searchResults[activeIndex].slug}`
}
}
// enter跳转
useHotkeys('enter', (e) => {
if (searchResults.length > 0) {
onJumpSearchResult(index)
}
}, { enableOnFormTags: true })

const resetSearch = () => {
setActiveIndex(0)
setKeyword('')
setSearchResults([])
setUseTime(0)
setTotalPage(0)
setTotalHit(0)
if (inputRef.current) inputRef.current.value = ''
}

useEffect(() => {
if (isModalOpen) {
setTimeout(() => {
inputRef.current?.focus()
}, 100)
} else {
resetSearch()
}
}, [isModalOpen])
/**
* 对外暴露方法
*/
Expand All @@ -45,18 +102,19 @@ export default function AlgoliaSearchModal({ cRef }) {
setUseTime(0)
setTotalPage(0)
setTotalHit(0)
setActiveIndex(0)
if (!query || query === '') {
return
}

setIsLoading(true)
try {
const res = await index.search(query, { page, hitsPerPage: 10 })
const { hits, nbHits, nbPages, processingTimeMS } = res
setUseTime(processingTimeMS)
setTotalPage(nbPages)
setTotalHit(nbHits)
setSearchResults(hits)

setIsLoading(false)
const doms = document.getElementById('search-wrapper').getElementsByClassName('replace')

setTimeout(() => {
Expand All @@ -65,7 +123,7 @@ export default function AlgoliaSearchModal({ cRef }) {
search: query,
target: {
element: 'span',
className: 'text-blue-600 border-b border-dashed'
className: 'font-bold border-b border-dashed'
}
})
}, 200) // 延时高亮
Expand All @@ -75,8 +133,8 @@ export default function AlgoliaSearchModal({ cRef }) {
}

// 定义节流函数,确保在用户停止输入一段时间后才会调用处理搜索的方法
const throttledHandleInputChange = useRef(throttle((query) => {
handleSearch(query, 0);
const throttledHandleInputChange = useRef(throttle((query, page = 0) => {
handleSearch(query, page);
}, 1000));

// 用于存储搜索延迟的计时器
Expand Down Expand Up @@ -115,22 +173,19 @@ export default function AlgoliaSearchModal({ cRef }) {
if (!siteConfig('ALGOLIA_APP_ID')) {
return <></>
}

return (
<div
id="search-wrapper"
className={`${
isModalOpen ? 'opacity-100' : 'invisible opacity-0 pointer-events-none'
} z-30 fixed h-screen w-screen left-0 top-0 mt-12 flex items-start justify-center`}
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`}
>
{/* 模态框 */}
<div
className={`${
isModalOpen ? 'opacity-100' : 'invisible opacity-0 translate-y-10'
} flex flex-col justify-between w-full min-h-[10rem] 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 `}
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 `}
>
<div className="flex justify-between items-center">
<div className="text-2xl text-blue-600 font-bold">搜索</div>
<div className="text-2xl text-blue-600 dark:text-yellow-600 font-bold">搜索</div>
<div>
<i
className="text-gray-600 fa-solid fa-xmark p-1 cursor-pointer hover:text-blue-600"
Expand All @@ -144,39 +199,55 @@ export default function AlgoliaSearchModal({ cRef }) {
placeholder="在这里输入搜索关键词..."
onChange={e => handleInputChange(e)}
className="text-black dark:text-gray-200 bg-gray-50 dark:bg-gray-600 outline-blue-500 w-full px-4 my-2 py-1 mb-4 border rounded-md"
ref={inputRef}
/>

{/* 标签组 */}
<div className="mb-4">
<TagGroups />
</div>

<ul>
{searchResults.map(result => (
<li key={result.objectID} className="replace my-2">
{
searchResults.length === 0 && keyword && !isLoading && (
<div>
<p className=" text-slate-600 text-center my-4 text-base"> 无法找到相关结果
<span
className='font-semibold'
>&quot;{keyword}&quot;</span></p>
</div>
)
}
<ul className='flex-1 overflow-auto'>
{searchResults.map((result, index) => (
<li key={result.objectID}
onMouseEnter={() => setActiveIndex(index)}
onClick={() => onJumpSearchResult(index)}
className={`cursor-pointer replace my-2 p-2 duration-100
rounded-lg
${activeIndex === index ? 'bg-blue-600 dark:bg-yellow-600' : ''}`}>
<a
href={`${siteConfig('SUB_PATH', '')}/${result.slug}`}
className="font-bold hover:text-blue-600 text-black dark:text-gray-200"
className={`${activeIndex === index ? ' text-white' : ' text-black dark:text-gray-300 '}`}
>
{result.title}
</a>
</li>
))}
</ul>

<Pagination totalPage={totalPage} page={page} switchPage={switchPage} />
<div>
{totalHit > 0 && (
<div>
共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒
</div>
)}
</div>
<div className="text-gray-600 mt-2">
<span>
<i className="fa-brands fa-algolia"></i> Algolia 提供搜索服务
</span>{' '}
<div className='flex items-center justify-between mt-2 sm:text-sm text-xs dark:text-gray-300'>
<div>
{totalHit > 0 && (
<p>
共搜索到 {totalHit} 条结果,用时 {useTime} 毫秒
</p>
)}
</div>
<div className="text-gray-600 dark:text-gray-300 text-right">
<span >
<i className="fa-brands fa-algolia"></i> Algolia 提供搜索服务
</span>
</div>
</div>

</div>

{/* 遮罩 */}
Expand All @@ -191,26 +262,26 @@ export default function AlgoliaSearchModal({ cRef }) {
/**
* 标签组
*/
function TagGroups(props) {
function TagGroups() {
const { tagOptions } = useGlobal()
// 获取tagOptions数组前十个
const firstTenTags = tagOptions?.slice(0, 10)

return <div id='tags-group' className='dark:border-gray-700 space-y-2'>
{
firstTenTags?.map((tag, index) => {
return <Link passHref
key={index}
href={`/tag/${encodeURIComponent(tag.name)}`}
className={'cursor-pointer inline-block whitespace-nowrap'}>
<div className={' flex items-center text-black dark:text-gray-300 hover:bg-blue-600 dark:hover:bg-yellow-600 hover:scale-110 hover:text-white rounded-lg px-2 py-0.5 duration-150 transition-all'}>
<div className='text-lg'>{tag.name} </div>{tag.count ? <sup className='relative ml-1'>{tag.count}</sup> : <></>}
</div>

</Link>
})
}
</div>
{
firstTenTags?.map((tag, index) => {
return <Link passHref
key={index}
href={`/tag/${encodeURIComponent(tag.name)}`}
className={'cursor-pointer inline-block whitespace-nowrap'}>
<div className={'flex items-center text-black dark:text-gray-300 hover:bg-blue-600 dark:hover:bg-yellow-600 hover:scale-110 hover:text-white rounded-lg px-2 py-0.5 duration-150 transition-all'}>
<div className='text-lg'>{tag.name} </div>{tag.count ? <sup className='relative ml-1'>{tag.count}</sup> : <></>}
</div>

</Link>
})
}
</div>
}

/**
Expand All @@ -222,24 +293,23 @@ function Pagination(props) {
if (totalPage <= 0) {
return <></>
}
const pagesElement = []

for (let i = 0; i < totalPage; i++) {
const selected = page === i
pagesElement.push(getPageElement(i, selected, switchPage))
}
return <div className='flex space-x-1 w-full justify-center py-1'>
{pagesElement.map(p => p)}
</div>
}
return (
<div className='flex space-x-1 w-full justify-center py-1'>
{Array.from({ length: totalPage }, (_, i) => {
const classNames = page === i
? 'font-bold text-white bg-blue-600 dark:bg-yellow-600 rounded'
: 'hover:text-blue-600 hover:font-bold dark:text-gray-300'

/**
* 获取分页按钮
* @param {*} i
* @param {*} selected
*/
function getPageElement(i, selected, switchPage) {
return <div onClick={() => switchPage(i)} className={`${selected ? 'font-bold text-white bg-blue-600 rounded' : 'hover:text-blue-600 hover:font-bold'} text-center cursor-pointer w-6 h-6 `}>
{i + 1}
</div>
return (
<div
onClick={() => switchPage(i)}
className={`text-center cursor-pointer w-6 h-6 ${classNames}`}
key={i}
>
{i + 1}
</div>
)
})}
</div>
)
}
11 changes: 11 additions & 0 deletions components/Badge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* 红点
*/
export default function Badge() {
return <>
{/* 红点 */}
<span class="absolute right-1 top-1 flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-red-500"></span>
</span></>
}
Loading

0 comments on commit cda853b

Please sign in to comment.