Skip to content

Commit 2eb95b2

Browse files
committed
自动保存密码,并添加解密通知弹框
1 parent 3b06203 commit 2eb95b2

File tree

9 files changed

+166
-38
lines changed

9 files changed

+166
-38
lines changed

components/Notification.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { useState } from 'react'
2+
3+
const useNotification = () => {
4+
const [message, setMessage] = useState('')
5+
const [isVisible, setIsVisible] = useState(false)
6+
7+
const showNotification = msg => {
8+
setMessage(msg)
9+
setIsVisible(true)
10+
setTimeout(() => {
11+
setIsVisible(false)
12+
}, 3000)
13+
}
14+
15+
const closeNotification = () => {
16+
setIsVisible(false)
17+
}
18+
19+
// 测试通知效果
20+
// const toggleVisible = () => {
21+
// setIsVisible(prev => !prev) // 使用函数式更新
22+
// }
23+
// useEffect(() => {
24+
// document?.addEventListener('click', toggleVisible)
25+
// return () => {
26+
// document?.removeEventListener('click', toggleVisible)
27+
// }
28+
// }, [])
29+
30+
/**
31+
* 通知组件
32+
* @returns
33+
*/
34+
const Notification = () => {
35+
return (
36+
<div
37+
className={`notification fixed left-0 w-full px-2 z-50 transform transition-all duration-300 ${
38+
isVisible ? 'bottom-20 animate__animated animate__fadeIn' : ''
39+
} `}>
40+
<div className='max-w-3xl mx-auto bg-green-500 flex items-center justify-between px-4 py-2 text-white rounded-lg shadow-lg'>
41+
{message}
42+
<button
43+
onClick={closeNotification}
44+
className='ml-4 p-2 cursor-pointer bg-transparent text-white border-none'>
45+
<i className='fas fa-times' />
46+
</button>
47+
</div>
48+
</div>
49+
)
50+
}
51+
52+
return {
53+
showNotification,
54+
closeNotification,
55+
Notification
56+
}
57+
}
58+
59+
export default useNotification

lib/lang/en-US.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,13 @@ export default {
3737
ARTICLE: 'Article',
3838
VISITORS: 'Visitors',
3939
VIEWS: 'Views',
40-
COPYRIGHT_NOTICE: 'All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!',
40+
COPYRIGHT_NOTICE:
41+
'All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!',
4142
RESULT_OF_SEARCH: 'Results Found',
4243
ARTICLE_DETAIL: 'Article Details',
4344
PASSWORD_ERROR: 'Password Error!',
4445
ARTICLE_LOCK_TIPS: 'Please Enter the password:',
46+
ARTICLE_UNLOCK_TIPS: 'Article Unlocked',
4547
NO_RESULTS_FOUND: 'No results found.',
4648
SUBMIT: 'Submit',
4749
POST_TIME: 'Post on',

lib/lang/ja-JP.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ export default {
2929
ARTICLE: '記事',
3030
VISITORS: '人の訪問者',
3131
VIEWS: '回の閲覧',
32-
COPYRIGHT_NOTICE: 'この記事はCC BY-NC-SA 4.0 ライセンスの下でライセンスされています。転載する場合には出典を明らかにしてください。',
32+
COPYRIGHT_NOTICE:
33+
'この記事はCC BY-NC-SA 4.0 ライセンスの下でライセンスされています。転載する場合には出典を明らかにしてください。',
3334
RESULT_OF_SEARCH: '個の検索結果',
3435
ARTICLE_DETAIL: '記事の詳細',
3536
PASSWORD_ERROR: 'パスワードが違います!',
36-
ARTICLE_LOCK_TIPS: 'この記事はロックされています。アクセスパスワードを入力してください。',
37+
ARTICLE_LOCK_TIPS:
38+
'この記事はロックされています。アクセスパスワードを入力してください。',
39+
ARTICLE_UNLOCK_TIPS: '記事がロック解除されました',
3740
SUBMIT: '送信',
3841
POST_TIME: '公開日',
3942
LAST_EDITED_TIME: '最終更新日',

lib/lang/zh-CN.js

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default {
4545
ARTICLE_DETAIL: '文章详情',
4646
PASSWORD_ERROR: '密码错误!',
4747
ARTICLE_LOCK_TIPS: '文章已上锁,请输入访问密码',
48+
ARTICLE_UNLOCK_TIPS: '文章已解锁',
4849
SUBMIT: '提交',
4950
POST_TIME: '发布于',
5051
LAST_EDITED_TIME: '最后更新',

lib/password.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { isBrowser } from './utils'
2+
3+
/**
4+
* 获取默认密码
5+
* 用户可以通过url中拼接参数,输入密码
6+
* 输入过一次的密码会被存储在浏览器中,便于下一次免密访问
7+
* 返回的是一组历史密码,便于客户端多次尝试
8+
*/
9+
export const getPasswordQuery = path => {
10+
// 使用 URL 对象解析 URL
11+
const url = new URL(path, isBrowser ? window.location.origin : '')
12+
13+
// 获取查询参数
14+
const queryParams = Object.fromEntries(url.searchParams.entries())
15+
16+
// 请求中带着密码
17+
if (queryParams.password) {
18+
// 将已输入密码作为默认密码存放在 localStorage,便于下次读取并自动尝试
19+
localStorage.setItem('password_default', queryParams.password)
20+
}
21+
22+
// 获取路径部分
23+
const cleanedPath = url.pathname
24+
25+
// 从 localStorage 中获取相关密码
26+
const storedPassword = localStorage.getItem('password_' + cleanedPath)
27+
const defaultPassword = localStorage.getItem('password_default')
28+
29+
// 将所有密码存储在一个数组中,并过滤掉无效值
30+
const passwords = [
31+
queryParams.password,
32+
storedPassword,
33+
defaultPassword
34+
].filter(Boolean)
35+
36+
return passwords
37+
}

pages/[prefix]/index.js

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import BLOG from '@/blog.config'
2+
import useNotification from '@/components/Notification'
23
import { siteConfig } from '@/lib/config'
34
import { getGlobalData, getPost, getPostBlocks } from '@/lib/db/getSiteData'
5+
import { useGlobal } from '@/lib/global'
46
import { getPageTableOfContents } from '@/lib/notion/getPageTableOfContents'
7+
import { getPasswordQuery } from '@/lib/password'
58
import { uploadDataToAlgolia } from '@/lib/plugins/algolia'
69
import { checkSlugHasNoSlash, getRecommendPost } from '@/lib/utils/post'
710
import { getLayoutByTheme } from '@/themes/theme'
@@ -19,9 +22,11 @@ import { useEffect, useState } from 'react'
1922
const Slug = props => {
2023
const { post } = props
2124
const router = useRouter()
25+
const { locale } = useGlobal()
2226

2327
// 文章锁🔐
2428
const [lock, setLock] = useState(post?.password && post?.password !== '')
29+
const { showNotification, Notification } = useNotification()
2530

2631
/**
2732
* 验证文章密码
@@ -36,6 +41,7 @@ const Slug = props => {
3641
setLock(false)
3742
// 输入密码存入localStorage,下次自动提交
3843
localStorage.setItem('password_' + router.asPath, passInput)
44+
showNotification(locale.COMMON.ARTICLE_UNLOCK_TIPS) // 设置解锁成功提示显示
3945
return true
4046
}
4147
return false
@@ -56,10 +62,14 @@ const Slug = props => {
5662
}
5763
}
5864

59-
// 从localStorage中读取上次记录 自动提交密码
60-
const passInput = localStorage.getItem('password_' + router.asPath)
61-
if (passInput) {
62-
validPassword(passInput)
65+
// 读取上次记录 自动提交密码
66+
const passInputs = getPasswordQuery(router.asPath)
67+
if (passInputs.length > 0) {
68+
for (const passInput of passInputs) {
69+
if (validPassword(passInput)) {
70+
break // 密码验证成功,停止尝试
71+
}
72+
}
6373
}
6474
}, [post])
6575

@@ -83,7 +93,12 @@ const Slug = props => {
8393
theme: siteConfig('THEME'),
8494
router: useRouter()
8595
})
86-
return <Layout {...props} />
96+
return (
97+
<>
98+
<Layout {...props} />
99+
{!lock && <Notification />}
100+
</>
101+
)
87102
}
88103

89104
export async function getStaticPaths() {

themes/gitbook/components/NavPostItem.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ const NavPostItem = props => {
4040
!expanded && <Badge />}
4141
</div>
4242
<Collapse isOpen={expanded} onHeightChange={props.onHeightChange}>
43-
{group?.items?.map(post => (
44-
<div key={post.id} className='ml-3 border-l'>
43+
{group?.items?.map((post, index) => (
44+
<div key={index} className='ml-3 border-l'>
4545
<BlogPostCard className='text-sm ml-3' post={post} />
4646
</div>
4747
))}
@@ -51,8 +51,8 @@ const NavPostItem = props => {
5151
} else {
5252
return (
5353
<>
54-
{group?.items?.map(post => (
55-
<div key={post.id}>
54+
{group?.items?.map((post, index) => (
55+
<div key={index}>
5656
<BlogPostCard className='text-sm py-2' post={post} />
5757
</div>
5858
))}

themes/gitbook/index.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { siteConfig } from '@/lib/config'
1010
import { useGlobal } from '@/lib/global'
1111
import { isBrowser } from '@/lib/utils'
1212
import { getShortId } from '@/lib/utils/pageId'
13-
import { Transition } from '@headlessui/react'
1413
import dynamic from 'next/dynamic'
1514
import Link from 'next/link'
1615
import { useRouter } from 'next/router'
@@ -102,7 +101,7 @@ const LayoutBase = props => {
102101
slotRight,
103102
slotTop
104103
} = props
105-
const { onLoading, fullWidth } = useGlobal()
104+
const { fullWidth } = useGlobal()
106105
const router = useRouter()
107106
const [tocVisible, changeTocVisible] = useState(false)
108107
const [pageNavVisible, changePageNavVisible] = useState(false)
@@ -174,7 +173,7 @@ const LayoutBase = props => {
174173
{slotTop}
175174
<WWAds className='w-full' orientation='horizontal' />
176175

177-
<Transition
176+
{/* <Transition
178177
show={!onLoading}
179178
appear={true}
180179
enter='transition ease-in-out duration-700 transform order-first'
@@ -183,9 +182,9 @@ const LayoutBase = props => {
183182
leave='transition ease-in-out duration-300 transform'
184183
leaveFrom='opacity-100 translate-y-0'
185184
leaveTo='opacity-0 -translate-y-16'
186-
unmount={false}>
187-
{children}
188-
</Transition>
185+
unmount={false}> */}
186+
{children}
187+
{/* </Transition> */}
189188

190189
{/* Google广告 */}
191190
<AdSlot type='in-article' />

themes/nav/components/NavPostItem.js

+32-20
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import BlogPostCard from './BlogPostCard'
2-
import { useState } from 'react'
31
import Collapse from '@/components/Collapse'
2+
import { useState } from 'react'
3+
import BlogPostCard from './BlogPostCard'
44

55
/**
66
* 导航列表
@@ -9,7 +9,7 @@ import Collapse from '@/components/Collapse'
99
* @returns {JSX.Element}
1010
* @constructor
1111
*/
12-
const NavPostItem = (props) => {
12+
const NavPostItem = props => {
1313
const { group } = props
1414
const [isOpen, changeIsOpen] = useState(group?.selected)
1515

@@ -20,25 +20,37 @@ const NavPostItem = (props) => {
2020
console.log(group)
2121

2222
if (group?.category) {
23-
return <>
24-
<div
25-
onClick={toggleOpenSubMenu}
26-
className='select-none flex justify-between text-sm cursor-pointer p-2 hover:bg-gray-50 rounded-md dark:hover:bg-gray-600' key={group?.category}>
27-
<span>{group?.category}</span>
28-
<div className='inline-flex items-center select-none pointer-events-none '><i className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''}`}></i></div>
23+
return (
24+
<>
25+
<div
26+
onClick={toggleOpenSubMenu}
27+
className='select-none flex justify-between text-sm cursor-pointer p-2 hover:bg-gray-50 rounded-md dark:hover:bg-gray-600'
28+
key={group?.category}>
29+
<span>{group?.category}</span>
30+
<div className='inline-flex items-center select-none pointer-events-none '>
31+
<i
32+
className={`px-2 fas fa-chevron-left transition-all duration-200 ${isOpen ? '-rotate-90' : ''}`}></i>
33+
</div>
34+
</div>
35+
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
36+
{group?.items?.map((post, index) => (
37+
<div key={index} className='ml-3 border-l'>
38+
<BlogPostCard className='text-sm ml-3' post={post} />
2939
</div>
30-
<Collapse isOpen={isOpen} onHeightChange={props.onHeightChange}>
31-
{group?.items?.map(post => (<div key={post.id} className='ml-3 border-l'>
32-
<BlogPostCard className='text-sm ml-3' post={post} /></div>))
33-
}
34-
</Collapse>
35-
</>
40+
))}
41+
</Collapse>
42+
</>
43+
)
3644
} else {
37-
return <>
38-
{group?.items?.map(post => (<div key={post.id} >
39-
<BlogPostCard className='text-sm py-2' post={post} /></div>))
40-
}
41-
</>
45+
return (
46+
<>
47+
{group?.items?.map((post, index) => (
48+
<div key={index}>
49+
<BlogPostCard className='text-sm py-2' post={post} />
50+
</div>
51+
))}
52+
</>
53+
)
4254
}
4355
}
4456

0 commit comments

Comments
 (0)