mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
feat(theme): add password support for post frontmatter (#689)
This commit is contained in:
parent
2757301d61
commit
086528606e
@ -173,6 +173,7 @@ npm run docs:dev
|
||||
{ github: 'shuoliuchn', name: 'Shuo Liu' },
|
||||
'Hammuu1112',
|
||||
'SherkeyXD',
|
||||
{ github: 'Kinneyzhang', name: 'Geekinney' },
|
||||
]"
|
||||
/>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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(() => {
|
||||
|
||||
14
theme/src/node/pages/encryptPage.ts
Normal file
14
theme/src/node/pages/encryptPage.ts
Normal 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
|
||||
}
|
||||
@ -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 = []
|
||||
}
|
||||
|
||||
@ -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) => {
|
||||
|
||||
6
theme/src/node/utils/encrypt.ts
Normal file
6
theme/src/node/utils/encrypt.ts
Normal 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)))
|
||||
}
|
||||
@ -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'
|
||||
|
||||
@ -52,6 +52,16 @@ export interface ThemePostFrontmatter extends ThemePageFrontmatter {
|
||||
* 版权信息
|
||||
*/
|
||||
copyright?: boolean | CopyrightLicense | CopyrightFrontmatter
|
||||
|
||||
/**
|
||||
* 文章加密密码
|
||||
*/
|
||||
password?: string | string[]
|
||||
|
||||
/**
|
||||
* 文章加密密码提示文本
|
||||
*/
|
||||
passwordHint?: string
|
||||
}
|
||||
|
||||
export interface CopyrightFrontmatter extends CopyrightOptions {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user