feat(theme): add password support for post frontmatter (#689)

This commit is contained in:
pengzhanbo 2025-09-10 12:44:01 +08:00 committed by GitHub
parent 2757301d61
commit 086528606e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 77 additions and 21 deletions

View File

@ -173,6 +173,7 @@ npm run docs:dev
{ github: 'shuoliuchn', name: 'Shuo Liu' },
'Hammuu1112',
'SherkeyXD',
{ github: 'Kinneyzhang', name: 'Geekinney' },
]"
/>

View File

@ -89,13 +89,34 @@ export default defineUserConfig({
- 使用 `encrypt.admin` 解锁后,被认为是管理员访问,其它未解锁页面也默认解锁。
:::
### Frontmatter
在 Markdown 文件的 `Frontmatter` 中,可以使用 `password` 设置文章的密码。
```md
---
title: 加密的文章
password: 123456
---
```
还可以添加 `passwordHint` 选项,用于设置密码提示信息。
```md
---
title: 加密的文章
password: 123456
passwordHint: 密码是 123456
---
```
## 示例
点击访问 [加密文章密码123456](/article/enx7c9s/)
## 相关配置
以下配置支持在多语言配置中使用。
以下配置支持在 [多语言配置](../../config/locales.md) 中使用。
### encryptGlobalText

View File

@ -40,11 +40,8 @@ export function inlineSyntaxPlugin(
md.use(abbrPlugin)
}
if (
options.plot === true
|| (isPlainObject(options.plot) && options.plot.tag !== false)
) {
// !!plot!!
// !!plot!!
if (options.plot === true || isPlainObject(options.plot)) {
md.use(plotPlugin)
}
}

View File

@ -2,7 +2,7 @@
import VPEncryptForm from '@theme/VPEncryptForm.vue'
import { useData } from '../composables/index.js'
const { theme } = useData()
const { theme, frontmatter } = useData<'post'>()
</script>
<template>
@ -11,7 +11,7 @@ const { theme } = useData()
<div class="logo">
<span class="vpi-lock icon-lock-head" />
</div>
<VPEncryptForm :info="theme.encryptPageText" />
<VPEncryptForm :info="frontmatter.passwordHint || theme.encryptPageText" />
</div>
</ClientOnly>
</template>

View File

@ -89,6 +89,9 @@ export function setupEncrypt(): void {
const hasPageEncrypt = computed(() => {
const pagePath = route.path
const filePathRelative = page.value.filePathRelative
if (page.value._e)
return true
return encrypt.value.ruleList.length
? encrypt.value.matches.some(match => toMatch(match, pagePath, filePathRelative))
: false
@ -106,10 +109,16 @@ export function setupEncrypt(): void {
const hashList = computed(() => {
const pagePath = route.path
const filePathRelative = page.value.filePathRelative
return encrypt.value.ruleList.length
const passwords = typeof page.value._e === 'string' ? page.value._e.split(':') : []
const pageRule: EncryptDataRule | undefined = passwords.length
? { key: pagePath.replace(/\//g, '').replace(/\.html$/, ''), match: pagePath, rules: passwords }
: undefined
const rules = encrypt.value.ruleList.length
? encrypt.value.ruleList
.filter(item => toMatch(item.match, pagePath, filePathRelative))
: []
return [pageRule, ...rules].filter(Boolean) as EncryptDataRule[]
})
const isPageDecrypted = computed(() => {

View File

@ -0,0 +1,14 @@
import type { Page } from 'vuepress/core'
import type { ThemePageData } from '../../shared/index.js'
import { toArray } from '@pengzhanbo/utils'
import { genEncrypt } from '../utils/index.js'
export function encryptPage(
page: Page<ThemePageData>,
): void {
const password = toArray(page.frontmatter.password)
if (password.length) {
page.data._e = password.map(pwd => genEncrypt(pwd as string)).join(':')
}
delete page.frontmatter.password
}

View File

@ -2,6 +2,7 @@ import type { Page } from 'vuepress/core'
import type { ThemePageData } from '../../shared/index.js'
import { getThemeConfig } from '../loadConfig/index.js'
import { autoCategory } from './autoCategory.js'
import { encryptPage } from './encryptPage.js'
import { enableBulletin } from './pageBulletin.js'
export function extendsPageData(
@ -9,6 +10,7 @@ export function extendsPageData(
): void {
const options = getThemeConfig()
cleanPageData(page)
encryptPage(page)
autoCategory(page, options)
enableBulletin(page, options)
}
@ -30,11 +32,6 @@ function cleanPageData(page: Page<ThemePageData>) {
delete page.frontmatter.home
}
// if (page.frontmatter.article === false) {
// page.frontmatter.draft = true
// }
// delete page.frontmatter.article
if (page.headers) {
page.data.headers = []
}

View File

@ -2,10 +2,9 @@ import type { App } from 'vuepress'
import type { Page } from 'vuepress/core'
import type { EncryptOptions, ThemePageData } from '../../shared/index.js'
import type { FsCache } from '../utils/index.js'
import { isNumber, isString, random, toArray } from '@pengzhanbo/utils'
import { genSaltSync, hashSync } from 'bcrypt-ts'
import { isNumber, isString, toArray } from '@pengzhanbo/utils'
import { getThemeConfig } from '../loadConfig/index.js'
import { createFsCache, hash, perf, resolveContent, writeTemp } from '../utils/index.js'
import { createFsCache, genEncrypt, hash, perf, resolveContent, writeTemp } from '../utils/index.js'
export type EncryptConfig = readonly [
boolean, // global
@ -45,13 +44,11 @@ export async function prepareEncrypt(app: App): Promise<void> {
perf.log('prepare:encrypt')
}
const salt = () => genSaltSync(random(8, 16))
function resolveEncrypt(encrypt?: EncryptOptions): EncryptConfig {
const admin = encrypt?.admin
? toArray(encrypt.admin)
.filter(isStringLike)
.map(item => hashSync(String(item), salt()))
.map(item => genEncrypt(item))
.join(separator)
: ''
@ -64,7 +61,7 @@ function resolveEncrypt(encrypt?: EncryptOptions): EncryptConfig {
rules[String(index)] = toArray(encrypt.rules![key])
.filter(isStringLike)
.map(item => hashSync(String(item), salt()))
.map(item => genEncrypt(item))
.join(separator)
})
}
@ -76,6 +73,9 @@ export function isEncryptPage(page: Page<ThemePageData>, encrypt?: EncryptOption
if (!encrypt)
return false
if (page.data._e)
return true
const rules = encrypt.rules ?? {}
return Object.keys(rules).some((match) => {

View File

@ -0,0 +1,6 @@
import { random } from '@pengzhanbo/utils'
import { genSaltSync, hashSync } from 'bcrypt-ts'
export function genEncrypt(pwd: string): string {
return hashSync(String(pwd), genSaltSync(random(8, 16)))
}

View File

@ -1,5 +1,6 @@
export * from './constants.js'
export * from './createFsCache.js'
export * from './encrypt.js'
export * from './hash.js'
export * from './interopDefault.js'
export * from './logger.js'

View File

@ -52,6 +52,16 @@ export interface ThemePostFrontmatter extends ThemePageFrontmatter {
*
*/
copyright?: boolean | CopyrightLicense | CopyrightFrontmatter
/**
*
*/
password?: string | string[]
/**
*
*/
passwordHint?: string
}
export interface CopyrightFrontmatter extends CopyrightOptions {