diff --git a/docs/README.md b/docs/README.md index e35e6f51..330bf4de 100644 --- a/docs/README.md +++ b/docs/README.md @@ -173,6 +173,7 @@ npm run docs:dev { github: 'shuoliuchn', name: 'Shuo Liu' }, 'Hammuu1112', 'SherkeyXD', + { github: 'Kinneyzhang', name: 'Geekinney' }, ]" /> diff --git a/docs/notes/theme/guide/features/encryption.md b/docs/notes/theme/guide/features/encryption.md index 00a76b1f..028e4f32 100644 --- a/docs/notes/theme/guide/features/encryption.md +++ b/docs/notes/theme/guide/features/encryption.md @@ -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 diff --git a/plugins/plugin-md-power/src/node/inline/index.ts b/plugins/plugin-md-power/src/node/inline/index.ts index 9ede681e..11eeb153 100644 --- a/plugins/plugin-md-power/src/node/inline/index.ts +++ b/plugins/plugin-md-power/src/node/inline/index.ts @@ -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) } } diff --git a/theme/src/client/components/VPEncryptPage.vue b/theme/src/client/components/VPEncryptPage.vue index 7b616afb..d8be5c5a 100644 --- a/theme/src/client/components/VPEncryptPage.vue +++ b/theme/src/client/components/VPEncryptPage.vue @@ -2,7 +2,7 @@ import VPEncryptForm from '@theme/VPEncryptForm.vue' import { useData } from '../composables/index.js' -const { theme } = useData() +const { theme, frontmatter } = useData<'post'>() diff --git a/theme/src/client/composables/encrypt.ts b/theme/src/client/composables/encrypt.ts index cd8cdf41..35c8afde 100644 --- a/theme/src/client/composables/encrypt.ts +++ b/theme/src/client/composables/encrypt.ts @@ -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(() => { diff --git a/theme/src/node/pages/encryptPage.ts b/theme/src/node/pages/encryptPage.ts new file mode 100644 index 00000000..ffdb8b1c --- /dev/null +++ b/theme/src/node/pages/encryptPage.ts @@ -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, +): 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 +} diff --git a/theme/src/node/pages/extendsPage.ts b/theme/src/node/pages/extendsPage.ts index f34b7339..fc617f3e 100644 --- a/theme/src/node/pages/extendsPage.ts +++ b/theme/src/node/pages/extendsPage.ts @@ -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) { delete page.frontmatter.home } - // if (page.frontmatter.article === false) { - // page.frontmatter.draft = true - // } - // delete page.frontmatter.article - if (page.headers) { page.data.headers = [] } diff --git a/theme/src/node/prepare/prepareEncrypt.ts b/theme/src/node/prepare/prepareEncrypt.ts index e098e731..3cba5539 100644 --- a/theme/src/node/prepare/prepareEncrypt.ts +++ b/theme/src/node/prepare/prepareEncrypt.ts @@ -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 { 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, encrypt?: EncryptOption if (!encrypt) return false + if (page.data._e) + return true + const rules = encrypt.rules ?? {} return Object.keys(rules).some((match) => { diff --git a/theme/src/node/utils/encrypt.ts b/theme/src/node/utils/encrypt.ts new file mode 100644 index 00000000..1313efcc --- /dev/null +++ b/theme/src/node/utils/encrypt.ts @@ -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))) +} diff --git a/theme/src/node/utils/index.ts b/theme/src/node/utils/index.ts index fea79631..f043c77c 100644 --- a/theme/src/node/utils/index.ts +++ b/theme/src/node/utils/index.ts @@ -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' diff --git a/theme/src/shared/frontmatter/post.ts b/theme/src/shared/frontmatter/post.ts index c3a176af..b25a709f 100644 --- a/theme/src/shared/frontmatter/post.ts +++ b/theme/src/shared/frontmatter/post.ts @@ -52,6 +52,16 @@ export interface ThemePostFrontmatter extends ThemePageFrontmatter { * 版权信息 */ copyright?: boolean | CopyrightLicense | CopyrightFrontmatter + + /** + * 文章加密密码 + */ + password?: string | string[] + + /** + * 文章加密密码提示文本 + */ + passwordHint?: string } export interface CopyrightFrontmatter extends CopyrightOptions {