mirror of
https://github.com/pengzhanbo/vuepress-theme-plume.git
synced 2026-04-23 10:58:13 +08:00
feat(theme): optimize page encrypt (#750)
This commit is contained in:
parent
87cda0c824
commit
a5dfef7202
@ -293,7 +293,7 @@ watch(
|
||||
|
||||
@media (min-width: 960px) {
|
||||
.content {
|
||||
padding: 0 32px 128px;
|
||||
padding: 0 32px 88px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,10 @@ const { global, info } = defineProps<{
|
||||
info?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'validate', isValidate: boolean): void
|
||||
}>()
|
||||
|
||||
const { theme } = useData()
|
||||
const { compareGlobal, comparePage } = useEncryptCompare()
|
||||
|
||||
@ -29,6 +33,7 @@ async function onSubmit() {
|
||||
errorCode.value = 0
|
||||
password.value = ''
|
||||
}
|
||||
emit('validate', errorCode.value === 0)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -44,8 +49,10 @@ async function onSubmit() {
|
||||
class="encrypt-input"
|
||||
:class="{ error: errorCode === 1 }"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
:placeholder="theme.encryptPlaceholder ?? 'Enter Password'"
|
||||
@keyup.enter="onSubmit"
|
||||
@focus="!password && (errorCode = 0)"
|
||||
@input="password && (errorCode = 0)"
|
||||
>
|
||||
</label>
|
||||
@ -83,9 +90,9 @@ async function onSubmit() {
|
||||
.encrypt-input {
|
||||
width: 100%;
|
||||
padding: 8px 12px 8px 32px;
|
||||
background-color: transparent;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 21px;
|
||||
outline: none;
|
||||
transition: border-color var(--vp-t-color), background-color var(--vp-t-color);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import VPEncryptForm from '@theme/VPEncryptForm.vue'
|
||||
import { useTemplateRef } from 'vue'
|
||||
import { useData } from '../composables/index.js'
|
||||
|
||||
defineOptions({
|
||||
@ -7,20 +8,42 @@ defineOptions({
|
||||
})
|
||||
|
||||
const { theme, frontmatter } = useData<'post'>()
|
||||
|
||||
const el = useTemplateRef<HTMLElement>('el')
|
||||
function onValidate(isValidate: boolean) {
|
||||
if (!isValidate) {
|
||||
el.value?.classList.add('animation')
|
||||
setTimeout(() => {
|
||||
el.value?.classList.remove('animation')
|
||||
}, 800)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ClientOnly>
|
||||
<div class="vp-page-encrypt" v-bind="$attrs">
|
||||
<div ref="el" class="vp-page-encrypt" v-bind="$attrs">
|
||||
<div class="logo">
|
||||
<span class="vpi-lock icon-lock-head" />
|
||||
</div>
|
||||
<VPEncryptForm :info="frontmatter.passwordHint || theme.encryptPageText" />
|
||||
<VPEncryptForm :info="frontmatter.passwordHint || theme.encryptPageText" @validate="onValidate" />
|
||||
</div>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-page-encrypt {
|
||||
transition: var(--vp-t-color);
|
||||
transition-property: box-shadow, border-color, transform;
|
||||
}
|
||||
|
||||
.vp-page-encrypt.animation {
|
||||
animation-name: encrypt-error;
|
||||
animation-duration: 0.15s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: 4;
|
||||
}
|
||||
|
||||
.vp-page-encrypt .logo {
|
||||
text-align: center;
|
||||
}
|
||||
@ -37,15 +60,26 @@ const { theme, frontmatter } = useData<'post'>()
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
margin: 40px auto 0;
|
||||
border: solid 1px var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--vp-shadow-1);
|
||||
transition: var(--vp-t-color);
|
||||
transition-property: box-shadow, border-color;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes encrypt-error {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.vp-page-encrypt:hover {
|
||||
box-shadow: var(--vp-shadow-2);
|
||||
33% {
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
|
||||
67% {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import VPEncryptPage from '@theme/VPEncryptPage.vue'
|
||||
import { useEncrypt } from '../composables/index.js'
|
||||
|
||||
const { isPageDecrypted } = useEncrypt()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vp-page">
|
||||
<slot name="page-top" />
|
||||
<Content class="vp-doc plume-content" vp-content />
|
||||
<slot name="page-bottom" />
|
||||
<VPEncryptPage v-if="!isPageDecrypted" />
|
||||
<template v-else>
|
||||
<slot name="page-top" />
|
||||
<Content class="vp-doc plume-content" vp-content />
|
||||
<slot name="page-bottom" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { encrypt as rawEncrypt } from '@internal/encrypt'
|
||||
import { decodeData } from '@vuepress/helper/client'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type EncryptConfig = readonly [
|
||||
@ -35,14 +36,15 @@ export function useEncryptData(): EncryptRef {
|
||||
function resolveEncryptData(
|
||||
[global, separator, admin, matches, rules]: EncryptConfig,
|
||||
): EncryptData {
|
||||
const keys = matches.map(match => decodeData(match))
|
||||
return {
|
||||
global,
|
||||
separator,
|
||||
matches,
|
||||
matches: keys,
|
||||
admins: admin.split(separator),
|
||||
ruleList: Object.keys(rules).map(key => ({
|
||||
key,
|
||||
match: matches[key] as string,
|
||||
match: keys[key] as string,
|
||||
rules: rules[key].split(separator),
|
||||
})),
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { hasOwn, useSessionStorage } from '@vueuse/core'
|
||||
import { compare, genSaltSync } from 'bcrypt-ts/browser'
|
||||
import { computed, inject, provide } from 'vue'
|
||||
import { useRoute } from 'vuepress/client'
|
||||
import { removeLeadingSlash } from 'vuepress/shared'
|
||||
import { useData } from './data.js'
|
||||
import { useEncryptData } from './encrypt-data.js'
|
||||
|
||||
@ -73,12 +74,12 @@ function toMatch(match: string, pagePath: string, filePathRelative: string | nul
|
||||
const relativePath = filePathRelative || ''
|
||||
if (match[0] === '^') {
|
||||
const regex = createMatchRegex(match)
|
||||
return regex.test(pagePath) || (relativePath && regex.test(relativePath))
|
||||
return regex.test(pagePath) || regex.test(relativePath)
|
||||
}
|
||||
if (match.endsWith('.md'))
|
||||
return relativePath && relativePath.endsWith(match)
|
||||
|
||||
return pagePath.startsWith(match) || relativePath.startsWith(match)
|
||||
return pagePath.startsWith(match) || relativePath.startsWith(removeLeadingSlash(match))
|
||||
}
|
||||
|
||||
export function setupEncrypt(): void {
|
||||
|
||||
@ -2,7 +2,8 @@ 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, toArray } from '@pengzhanbo/utils'
|
||||
import { isEmptyObject, isNumber, isString, toArray } from '@pengzhanbo/utils'
|
||||
import { encodeData, removeLeadingSlash } from '@vuepress/helper'
|
||||
import { getThemeConfig } from '../loadConfig/index.js'
|
||||
import { createFsCache, genEncrypt, hash, perf, resolveContent, writeTemp } from '../utils/index.js'
|
||||
|
||||
@ -15,6 +16,7 @@ export type EncryptConfig = readonly [
|
||||
]
|
||||
|
||||
const isStringLike = (value: unknown): boolean => isString(value) || isNumber(value)
|
||||
|
||||
const separator = ':'
|
||||
let contentHash = ''
|
||||
let fsCache: FsCache<[string, EncryptConfig]> | null = null
|
||||
@ -52,14 +54,19 @@ function resolveEncrypt(encrypt?: EncryptOptions): EncryptConfig {
|
||||
.join(separator)
|
||||
: ''
|
||||
|
||||
const rules: Record<string, string> = {}
|
||||
const keys = Object.keys(encrypt?.rules ?? {})
|
||||
const encryptRules = Object.keys(encrypt?.rules ?? {}).reduce((acc, key) => {
|
||||
acc[encodeData(key)] = encrypt!.rules![key]
|
||||
return acc
|
||||
}, {} as Record<string, string | string[]>)
|
||||
|
||||
if (encrypt?.rules) {
|
||||
Object.keys(encrypt.rules).forEach((key) => {
|
||||
const rules: Record<string, string> = {}
|
||||
const keys = Object.keys(encryptRules)
|
||||
|
||||
if (!isEmptyObject(encryptRules)) {
|
||||
Object.keys(encryptRules).forEach((key) => {
|
||||
const index = keys.indexOf(key)
|
||||
|
||||
rules[String(index)] = toArray(encrypt.rules![key])
|
||||
rules[String(index)] = toArray(encryptRules[key])
|
||||
.filter(isStringLike)
|
||||
.map(item => genEncrypt(item))
|
||||
.join(separator)
|
||||
@ -82,11 +89,11 @@ export function isEncryptPage(page: Page<ThemePageData>, encrypt?: EncryptOption
|
||||
const relativePath = page.data.filePathRelative || ''
|
||||
if (match[0] === '^') {
|
||||
const regex = new RegExp(match)
|
||||
return regex.test(page.path) || (relativePath && regex.test(relativePath))
|
||||
return regex.test(page.path) || regex.test(relativePath)
|
||||
}
|
||||
if (match.endsWith('.md'))
|
||||
return relativePath && relativePath.endsWith(match)
|
||||
return relativePath.endsWith(match)
|
||||
|
||||
return page.path.startsWith(match) || relativePath.startsWith(match)
|
||||
return page.path.startsWith(match) || relativePath.startsWith(removeLeadingSlash(match))
|
||||
})
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user