diff --git a/docs/.vuepress/configs/navbar/en.ts b/docs/.vuepress/configs/navbar/en.ts
index 245a503c5e..8f45b0bc03 100644
--- a/docs/.vuepress/configs/navbar/en.ts
+++ b/docs/.vuepress/configs/navbar/en.ts
@@ -31,6 +31,7 @@ export const navbarEn: NavbarConfig = [
'/plugins/photo-swipe',
'/plugins/redirect',
'/plugins/register-components',
+ '/plugins/watermark',
],
},
{
diff --git a/docs/.vuepress/configs/navbar/zh.ts b/docs/.vuepress/configs/navbar/zh.ts
index ebe3482770..38798500e3 100644
--- a/docs/.vuepress/configs/navbar/zh.ts
+++ b/docs/.vuepress/configs/navbar/zh.ts
@@ -31,6 +31,7 @@ export const navbarZh: NavbarConfig = [
'/zh/plugins/photo-swipe',
'/zh/plugins/redirect',
'/zh/plugins/register-components',
+ '/zh/plugins/watermark',
],
},
{
diff --git a/docs/.vuepress/configs/sidebar/en.ts b/docs/.vuepress/configs/sidebar/en.ts
index 44e58f7b75..d082a4fd45 100644
--- a/docs/.vuepress/configs/sidebar/en.ts
+++ b/docs/.vuepress/configs/sidebar/en.ts
@@ -16,6 +16,7 @@ export const sidebarEn: SidebarConfig = {
'/plugins/photo-swipe',
'/plugins/redirect',
'/plugins/register-components',
+ '/plugins/watermark',
],
},
{
diff --git a/docs/.vuepress/configs/sidebar/zh.ts b/docs/.vuepress/configs/sidebar/zh.ts
index 7df99c8007..b1e5298345 100644
--- a/docs/.vuepress/configs/sidebar/zh.ts
+++ b/docs/.vuepress/configs/sidebar/zh.ts
@@ -16,6 +16,7 @@ export const sidebarZh: SidebarConfig = {
'/zh/plugins/photo-swipe',
'/zh/plugins/redirect',
'/zh/plugins/register-components',
+ '/zh/plugins/watermark',
],
},
{
diff --git a/docs/plugins/watermark.md b/docs/plugins/watermark.md
new file mode 100644
index 0000000000..5e2f7a83b8
--- /dev/null
+++ b/docs/plugins/watermark.md
@@ -0,0 +1,133 @@
+# watermark
+
+
+
+Integrate [watermark-js-plus](https://github.com/zhensherlock/watermark-js-plus) into VuePress。
+
+This plugin can add watermark to the pages, you can choose between add watermark globally or on specific pages. You can also choose between add text watermark or image watermark.
+
+## Usage
+
+```sh
+npm i -D @vuepress/plugin-watermark@next
+```
+
+```ts
+import { watermarkPlugin } from '@vuepress/plugin-watermark'
+
+export default {
+ plugins: [
+ watermarkPlugin({
+ // options
+ }),
+ ],
+}
+```
+
+## Options
+
+### enabled
+
+- Type: `boolean | ((page: Page) => boolean)`
+
+- Default: `false`
+
+- Details:
+
+ Specify which pages need to have watermarks added.
+
+ Pages with `true` value will have watermarks added.
+
+### watermarkOptions
+
+- Type: `WatermarkOptions`
+
+- Default: `undefined`
+
+- Details: Please refer to the [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/zh/config/) configuration options.
+
+#### watermarkOptions.parent
+
+- Type: `string`
+
+- Default: `body`
+
+- Details: Parent element selector for adding watermark.
+
+ By default, it is inserted into the body, but you can specify inserting it into a specific element on the page.
+
+### delay
+
+- Type: `number`
+
+- Default: `500`
+
+- Details: Delay for adding watermarks. In milliseconds.
+
+ This delay will only take effect when adding watermarks to a specific element on the page.
+
+ A delay is required when the watermark parent is rerendered when switching pages.
+
+## Frontmatter
+
+### watermark
+
+- Type: `boolean | WatermarkOptions`
+
+- Details:
+
+ When the type is `boolean`, it indicates whether the watermark is enabled.
+
+ When the type is `WatermarkOptions`, it represents the current page's watermark configuration.
+
+ You can refer to [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/zh/config/).
+
+```md
+---
+watermark:
+ width: 200
+ height: 200
+ content: Your content
+ opacity: 0.5
+---
+```
+
+## Client Config
+
+### defineWatermarkConfig(config)
+
+- Type: `(config: MaybeRefOrGetter) => void`
+
+Additional configuration passed to [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/en/config/).
+
+```ts
+import { defineWatermarkConfig } from '@vuepress/plugin-watermark/client'
+
+defineWatermarkConfig({
+ // Set up additional watermark configurations here.
+})
+```
+
+In most cases, the majority of options should be defined in Node,
+but there are some special situations. For example,
+it may be necessary to control different watermark opacities, font colors,
+etc., in **dark/light mode** , or to pass in callbacks such as `onSuccess`, `extraDrawFunc`, and so on.
+
+```ts
+import { computed } from 'vue'
+
+export default defineClientConfig({
+ setup() {
+ const isDark = useDarkMode()
+
+ const watermarkConfig = computed(() => ({
+ fontColor: isDark.value ? '#fff' : '#000',
+ onSuccess: () => {
+ console.log('success')
+ },
+ }))
+
+ defineWatermarkConfig(watermarkConfig)
+ },
+})
+```
diff --git a/docs/zh/plugins/watermark.md b/docs/zh/plugins/watermark.md
new file mode 100644
index 0000000000..25ca0cbf46
--- /dev/null
+++ b/docs/zh/plugins/watermark.md
@@ -0,0 +1,132 @@
+# watermark
+
+
+
+将 [watermark-js-plus](https://github.com/zhensherlock/watermark-js-plus) 到 VuePress 中。
+
+此插件可在在页面中添加水印,可以选择为 全局页面 或 部分页面添加水印,还可以选择添加 文字水印 或 图片水印。
+
+## 使用
+
+```sh
+npm i -D @vuepress/plugin-watermark@next
+```
+
+```ts
+import { watermarkPlugin } from '@vuepress/plugin-watermark'
+
+export default {
+ plugins: [
+ watermarkPlugin({
+ // options
+ }),
+ ],
+}
+```
+
+## 配置项
+
+### enabled
+
+- 类型: `boolean | ((page: Page) => boolean)`
+
+- 默认值: `false`
+
+- 详情:
+
+ 指定哪些页面需要添加水印。
+
+ 拥有 `true` 值的页面将会被添加水印。
+
+### watermarkOptions
+
+- 类型: `WatermarkOptions`
+
+- 默认值: `undefined`
+
+- 详情: 配置项请参考 [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/zh/config/)。
+
+#### watermarkOptions.parent
+
+- 类型: `string`
+
+- 默认值: `body`
+
+- 详情:添加水印的父元素选择器。
+
+ 默认插入到 body 中,可以指定插入到页面的某个元素中。
+
+### delay
+
+- 类型: `number`
+
+- 默认值: `500`
+
+- 详情:添加水印的延时。以毫秒为单位。
+
+ 该延迟仅会在添加水印到页面某个元素时生效。
+
+ 如果水印的父元素在切换页面时被重新渲染,那么需要延迟一段时间才能重新添加水印。
+
+## Frontmatter
+
+### watermark
+
+- 类型: `boolean | WatermarkOptions`
+
+- 详情:
+
+ 当类型为 `boolean` 时,表示是否启用水印。
+
+ 当类型为 `WatermarkOptions` 时,表示当前页面水印配置。
+
+ 可以参考 [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/zh/config/) 。
+
+```md
+---
+watermark:
+ width: 200
+ height: 200
+ content: Your content
+ opacity: 0.5
+---
+```
+
+## 客户端配置
+
+### defineWatermarkConfig(config)
+
+- 类型: `(config: MaybeRefOrGetter) => void`
+
+传递给 [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/zh/config/) 的额外配置。
+
+```ts
+import { defineWatermarkConfig } from '@vuepress/plugin-watermark/client'
+
+defineWatermarkConfig({
+ // 在此设置额外的 watermark 配置
+})
+```
+
+通常来说,大部分选项应该在 Node 中定义,但存在一些特殊情况。
+比如需要在 **深色/浅色 模式** 下控制不同的 水印 透明度、字体颜色等,
+或者需要传入如 `onSuccess`、`extraDrawFunc` 等回调函数。
+
+```ts
+import { computed } from 'vue'
+
+export default defineClientConfig({
+ setup() {
+ const isDark = useDarkMode()
+
+ const watermarkConfig = computed(() => ({
+ fontColor: isDark.value ? '#fff' : '#000',
+ onSuccess: () => {
+ console.log('success')
+ },
+ }))
+
+ defineWatermarkConfig(watermarkConfig)
+ },
+})
+```
diff --git a/e2e/docs/.vuepress/config.ts b/e2e/docs/.vuepress/config.ts
index 9087029387..1722521936 100644
--- a/e2e/docs/.vuepress/config.ts
+++ b/e2e/docs/.vuepress/config.ts
@@ -7,6 +7,7 @@ import { copyrightPlugin } from '@vuepress/plugin-copyright'
import { feedPlugin } from '@vuepress/plugin-feed'
import { pwaPlugin } from '@vuepress/plugin-pwa'
import { redirectPlugin } from '@vuepress/plugin-redirect'
+import { watermarkPlugin } from '@vuepress/plugin-watermark'
import { defaultTheme } from '@vuepress/theme-default'
import { defineUserConfig } from 'vuepress/cli'
import type { UserConfig } from 'vuepress/cli'
@@ -223,5 +224,14 @@ export default defineUserConfig({
'/redirect/config/': '/redirect/final.html',
},
}),
+ watermarkPlugin({
+ enable: (page) => page.path.startsWith('/watermark/'),
+
+ watermarkOptions: {
+ content: 'VuePress Watermark',
+ width: 200,
+ height: 200,
+ },
+ }),
],
}) as UserConfig
diff --git a/e2e/docs/watermark/README.md b/e2e/docs/watermark/README.md
new file mode 100644
index 0000000000..ca436db062
--- /dev/null
+++ b/e2e/docs/watermark/README.md
@@ -0,0 +1,5 @@
+---
+watermark: true
+---
+
+# Default Watermark
diff --git a/e2e/docs/watermark/disabled.md b/e2e/docs/watermark/disabled.md
new file mode 100644
index 0000000000..b51ef8bac0
--- /dev/null
+++ b/e2e/docs/watermark/disabled.md
@@ -0,0 +1,5 @@
+---
+watermark: false
+---
+
+# disabled watermark
diff --git a/e2e/package.json b/e2e/package.json
index 176a451821..e71464c34d 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -23,6 +23,7 @@
"@vuepress/plugin-feed": "workspace:*",
"@vuepress/plugin-pwa": "workspace:*",
"@vuepress/plugin-redirect": "workspace:*",
+ "@vuepress/plugin-watermark": "workspace:*",
"@vuepress/theme-default": "workspace:*",
"sass": "^1.75.0",
"sass-loader": "^14.2.1",
diff --git a/e2e/tests/plugin-watermark/watermark.spec.ts b/e2e/tests/plugin-watermark/watermark.spec.ts
new file mode 100644
index 0000000000..4aa45563b5
--- /dev/null
+++ b/e2e/tests/plugin-watermark/watermark.spec.ts
@@ -0,0 +1,21 @@
+import { expect, test } from '@playwright/test'
+
+test.describe('plugin-watermark', () => {
+ test('enabled watermark', async ({ page }) => {
+ await page.goto('watermark/')
+
+ expect(
+ await page.locator('//html/body/div[2]').getAttribute('style'),
+ ).toContain('z-index: 2147483647 !important;')
+
+ expect(
+ await page.locator('//html/body/div[2]/div').getAttribute('style'),
+ ).toContain('background-image: url("data:image/png;base64,')
+ })
+
+ test('disabled watermark', async ({ page }) => {
+ await page.goto('watermark/disabled.html')
+
+ await expect(page.locator('//html/body/div[2]')).not.toBeVisible()
+ })
+})
diff --git a/plugins/plugin-watermark/CHANGELOG.md b/plugins/plugin-watermark/CHANGELOG.md
new file mode 100644
index 0000000000..e4d87c4d45
--- /dev/null
+++ b/plugins/plugin-watermark/CHANGELOG.md
@@ -0,0 +1,4 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
diff --git a/plugins/plugin-watermark/package.json b/plugins/plugin-watermark/package.json
new file mode 100644
index 0000000000..a2ec5ce928
--- /dev/null
+++ b/plugins/plugin-watermark/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "@vuepress/plugin-watermark",
+ "version": "2.0.0-rc.26",
+ "description": "VuePress plugin - watermark",
+ "keywords": [
+ "vuepress-plugin",
+ "vuepress",
+ "plugin",
+ "watermark"
+ ],
+ "homepage": "https://ecosystem.vuejs.press/plugins/watermark.html",
+ "bugs": {
+ "url": "https://github.com/vuepress/ecosystem/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/vuepress/ecosystem.git",
+ "directory": "plugins/plugin-watermark"
+ },
+ "license": "MIT",
+ "author": "pengzhanbo",
+ "type": "module",
+ "exports": {
+ ".": "./lib/node/index.js",
+ "./client": "./lib/client/index.js",
+ "./package.json": "./package.json"
+ },
+ "main": "./lib/node/index.js",
+ "types": "./lib/node/index.d.ts",
+ "files": [
+ "lib"
+ ],
+ "scripts": {
+ "build": "tsc -b tsconfig.build.json",
+ "clean": "rimraf --glob ./lib ./*.tsbuildinfo"
+ },
+ "dependencies": {
+ "@vuepress/helper": "workspace:*",
+ "vue": "^3.4.26",
+ "watermark-js-plus": "^1.4.22"
+ },
+ "peerDependencies": {
+ "vuepress": "2.0.0-rc.9"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/plugins/plugin-watermark/src/client/composables/index.ts b/plugins/plugin-watermark/src/client/composables/index.ts
new file mode 100644
index 0000000000..98d99d05f5
--- /dev/null
+++ b/plugins/plugin-watermark/src/client/composables/index.ts
@@ -0,0 +1 @@
+export * from './setupWatermark.js'
diff --git a/plugins/plugin-watermark/src/client/composables/setupWatermark.ts b/plugins/plugin-watermark/src/client/composables/setupWatermark.ts
new file mode 100644
index 0000000000..b14a8f40df
--- /dev/null
+++ b/plugins/plugin-watermark/src/client/composables/setupWatermark.ts
@@ -0,0 +1,79 @@
+import { wait } from '@vuepress/helper/client'
+import {
+ getCurrentInstance,
+ isRef,
+ nextTick,
+ onMounted,
+ toValue,
+ watch,
+} from 'vue'
+import type { MaybeRef, Ref } from 'vue'
+import { withBase } from 'vuepress/client'
+import { Watermark } from 'watermark-js-plus'
+import type { WatermarkOptions } from '../helper/index.js'
+
+export const setupWatermark = (
+ options: MaybeRef,
+ enabled: Ref,
+ delay = 500,
+): void => {
+ const isInsideApp = (target?: string | Element): boolean => {
+ const el =
+ typeof target === 'string' ? document.querySelector(target) : target
+
+ return Boolean(
+ el &&
+ (getCurrentInstance()?.appContext.app._container as Element).contains?.(
+ el,
+ ),
+ )
+ }
+
+ onMounted(() => {
+ const watermark = new Watermark()
+
+ const updateWaterMark = (
+ // shadow clone options object so that we can modify later
+ { ...options }: WatermarkOptions,
+ ): void => {
+ // Blind mode default alpha is 0.005
+ if (options.mode === 'blind' && !options.globalAlpha) {
+ options.globalAlpha = 0.005
+ }
+
+ if (options.image?.startsWith('/')) {
+ options.image = withBase(options.image)
+ }
+
+ if (toValue(enabled))
+ nextTick(() => watermark.changeOptions(options, 'overwrite'))
+ else watermark.changeOptions(options, 'overwrite', false)
+ }
+
+ if (isRef(options))
+ watch(
+ () => options,
+ () => {
+ updateWaterMark(options.value)
+ },
+ { immediate: true },
+ )
+ else updateWaterMark(options)
+
+ watch(enabled, () =>
+ nextTick(() => {
+ if (enabled.value) {
+ if (isInsideApp(toValue(options).parent)) {
+ wait(delay).then(() => {
+ watermark.create()
+ })
+ } else {
+ watermark.create()
+ }
+ } else {
+ watermark.destroy()
+ }
+ }),
+ )
+ })
+}
diff --git a/plugins/plugin-watermark/src/client/config.ts b/plugins/plugin-watermark/src/client/config.ts
new file mode 100644
index 0000000000..5482516235
--- /dev/null
+++ b/plugins/plugin-watermark/src/client/config.ts
@@ -0,0 +1,32 @@
+import { computed } from 'vue'
+import { defineClientConfig, usePageFrontmatter } from 'vuepress/client'
+import type { ClientConfig } from 'vuepress/client'
+import type { WatermarkPluginFrontmatter } from '../shared/options.js'
+import { setupWatermark } from './composables/index.js'
+import { injectWatermarkConfig, useWatermarkOptions } from './helper/index.js'
+import type { WatermarkOptions } from './helper/index.js'
+
+declare const __WM_DELAY__: number
+declare const __WM_GLOBAL__: boolean
+declare const __WM_OPTIONS__: WatermarkOptions
+
+export default defineClientConfig({
+ enhance({ app }) {
+ injectWatermarkConfig(app)
+ },
+
+ setup() {
+ if (__VUEPRESS_SSR__) return
+
+ const frontmatter = usePageFrontmatter()
+ const watermarkOptions = useWatermarkOptions(__WM_OPTIONS__)
+
+ const enabled = computed(() => {
+ const watermark = frontmatter.value.watermark
+
+ return Boolean(watermark ?? __WM_GLOBAL__ ?? false)
+ })
+
+ setupWatermark(watermarkOptions, enabled, __WM_DELAY__)
+ },
+}) as ClientConfig
diff --git a/plugins/plugin-watermark/src/client/helper/index.ts b/plugins/plugin-watermark/src/client/helper/index.ts
new file mode 100644
index 0000000000..69d2f191f9
--- /dev/null
+++ b/plugins/plugin-watermark/src/client/helper/index.ts
@@ -0,0 +1 @@
+export * from './watermark.js'
diff --git a/plugins/plugin-watermark/src/client/helper/watermark.ts b/plugins/plugin-watermark/src/client/helper/watermark.ts
new file mode 100644
index 0000000000..ba5dc3a2f5
--- /dev/null
+++ b/plugins/plugin-watermark/src/client/helper/watermark.ts
@@ -0,0 +1,95 @@
+import { isFunction, isPlainObject } from '@vuepress/helper/client'
+import { computed, inject, isRef, ref, toValue, watch } from 'vue'
+import type {
+ App,
+ ComputedRef,
+ InjectionKey,
+ MaybeRef,
+ MaybeRefOrGetter,
+ Ref,
+} from 'vue'
+import { usePageFrontmatter } from 'vuepress/client'
+import type { WatermarkOptions as WatermarkRawOptions } from 'watermark-js-plus/dist/types/src/types/index.js'
+import type { WatermarkPluginFrontmatter } from '../../shared/index.js'
+
+export type WatermarkOptions = Partial
+
+const watermarkSymbol: InjectionKey[> = Symbol(
+ __VUEPRESS_DEV__ ? 'watermark' : '',
+)
+
+const watermarkOptions = ref({})
+
+/**
+ * Define additional watermark configurations in the client-side.
+ *
+ * In most cases, the majority of options should be defined in Node,
+ * but there are some special situations. For example,
+ * it may be necessary to control different watermark opacities, font colors,
+ * etc., in dark/light mode, or to pass in callbacks such as `onSuccess`, `extraDrawFunc`, and so on.
+ *
+ * 在客户端中定义额外的水印配置。
+ *
+ * 通常来说,大部分选项应该在 Node 中定义,但存在一些特殊情况。
+ * 比如需要在 深色/浅色 模式下控制不同的 水印 透明度、字体颜色等,
+ * 或者需要传入如 `onSuccess`、`extraDrawFunc` 等回调函数。
+ *
+ * @example
+ * ```ts
+ * import { computed } from 'vue'
+ *
+ * const isDark = useDarkMode()
+ *
+ * const watermarkConfig = computed(() => ({
+ * fontColor: isDark.value ? '#fff' : '#000',
+ * onSuccess: () => {
+ * console.log('success')
+ * },
+ * }))
+ *
+ * defineWatermarkConfig(watermarkConfig)
+ * ```
+ *
+ * @param userConfig Watermark options
+ *
+ */
+export const defineWatermarkConfig = (
+ userConfig: MaybeRefOrGetter,
+): void => {
+ if (isRef(userConfig)) {
+ watch(
+ userConfig,
+ (value) => {
+ watermarkOptions.value = value
+ },
+ { immediate: true },
+ )
+ } else if (isFunction(userConfig)) {
+ watch(userConfig, (value) => {
+ watermarkOptions.value = value
+ })
+ } else {
+ watermarkOptions.value = userConfig
+ }
+}
+
+export const useWatermarkOptions = (
+ options: MaybeRef,
+): ComputedRef => {
+ const globalOptions = inject(watermarkSymbol)!
+ const frontmatter = usePageFrontmatter()
+
+ return computed(() => {
+ const { watermark } = frontmatter.value
+
+ return {
+ ...toValue(options),
+ ...globalOptions.value,
+ ...(isPlainObject(watermark) ? watermark : {}),
+ }
+ })
+}
+
+export const injectWatermarkConfig = (app: App): void => {
+ app.provide(watermarkSymbol, watermarkOptions)
+}
diff --git a/plugins/plugin-watermark/src/client/index.ts b/plugins/plugin-watermark/src/client/index.ts
new file mode 100644
index 0000000000..cd566e1947
--- /dev/null
+++ b/plugins/plugin-watermark/src/client/index.ts
@@ -0,0 +1,3 @@
+export * from './composables/index.js'
+export * from './helper/index.js'
+export * from '../shared/index.js'
diff --git a/plugins/plugin-watermark/src/node/index.ts b/plugins/plugin-watermark/src/node/index.ts
new file mode 100644
index 0000000000..5ace105e6c
--- /dev/null
+++ b/plugins/plugin-watermark/src/node/index.ts
@@ -0,0 +1,3 @@
+export * from './options.js'
+export * from './watermarkPlugin.js'
+export * from '../shared/index.js'
diff --git a/plugins/plugin-watermark/src/node/logger.ts b/plugins/plugin-watermark/src/node/logger.ts
new file mode 100644
index 0000000000..3cf328d7f7
--- /dev/null
+++ b/plugins/plugin-watermark/src/node/logger.ts
@@ -0,0 +1,5 @@
+import { Logger } from '@vuepress/helper'
+
+export const PLUGIN_NAME = '@vuepress/plugin-watermark'
+
+export const logger = new Logger(PLUGIN_NAME)
diff --git a/plugins/plugin-watermark/src/node/options.ts b/plugins/plugin-watermark/src/node/options.ts
new file mode 100644
index 0000000000..5809e2ea4c
--- /dev/null
+++ b/plugins/plugin-watermark/src/node/options.ts
@@ -0,0 +1,41 @@
+import type { Page } from 'vuepress'
+import type { WatermarkPureOptions } from '../shared/index.js'
+
+export interface WatermarkPluginOptions {
+ /**
+ * Specify which pages need to have watermarks added.
+ *
+ * Pages with `true` value will have watermarks added.
+ *
+ * 指定哪些页面需要添加水印。
+ *
+ * 拥有 `true` 值的页面将会被添加水印。
+ *
+ * @default false
+ */
+ enabled?: boolean | ((page: Page) => boolean)
+
+ /**
+ * Watermark options
+ *
+ * 水印配置选项
+ *
+ * @see [watermark-js-plus](https://zhensherlock.github.io/watermark-js-plus/config/)
+ */
+ watermarkOptions?: WatermarkPureOptions
+
+ /**
+ * Delay for adding watermarks. In milliseconds.
+ *
+ * This delay will only take effect when adding watermarks to a specific element on the page.
+ *
+ * A delay may be required when the watermark parent is rerendered when switching pages.
+ *
+ * 添加水印的延时。以毫秒为单位。
+ * 该延迟仅会在添加水印到页面某个元素时生效。
+ * 在切换路由后,需要延迟一段时间后才能添加水印。
+ *
+ * @default 500
+ */
+ delay?: number
+}
diff --git a/plugins/plugin-watermark/src/node/watermarkPlugin.ts b/plugins/plugin-watermark/src/node/watermarkPlugin.ts
new file mode 100644
index 0000000000..415f45a32b
--- /dev/null
+++ b/plugins/plugin-watermark/src/node/watermarkPlugin.ts
@@ -0,0 +1,39 @@
+import type { Plugin } from 'vuepress/core'
+import { isFunction } from 'vuepress/shared'
+import { getDirname, path } from 'vuepress/utils'
+import { logger, PLUGIN_NAME } from './logger.js'
+import type { WatermarkPluginOptions } from './options.js'
+
+const __dirname = getDirname(import.meta.url)
+
+export const watermarkPlugin =
+ ({
+ enabled: enable = true,
+ ...options
+ }: WatermarkPluginOptions = {}): Plugin =>
+ (app) => {
+ if (app.env.isDebug) logger.info('Options:', options)
+
+ return {
+ name: PLUGIN_NAME,
+
+ define: {
+ __WM_DELAY__: options.delay ?? 500,
+ __WM_GLOBAL__: enable === true,
+ __WM_OPTIONS__: options.watermarkOptions ?? {},
+ },
+
+ extendsPage: (page) => {
+ // When watermark is a filter function, enable watermark for matching pages.
+ if (isFunction(enable)) {
+ const { frontmatter } = page
+
+ if (!('watermark' in frontmatter) && enable(page)) {
+ frontmatter.watermark = true
+ }
+ }
+ },
+
+ clientConfigFile: path.resolve(__dirname, '../client/config.js'),
+ }
+ }
diff --git a/plugins/plugin-watermark/src/shared/index.ts b/plugins/plugin-watermark/src/shared/index.ts
new file mode 100644
index 0000000000..a66c7e8d47
--- /dev/null
+++ b/plugins/plugin-watermark/src/shared/index.ts
@@ -0,0 +1 @@
+export * from './options.js'
diff --git a/plugins/plugin-watermark/src/shared/options.ts b/plugins/plugin-watermark/src/shared/options.ts
new file mode 100644
index 0000000000..1edeaf9ba8
--- /dev/null
+++ b/plugins/plugin-watermark/src/shared/options.ts
@@ -0,0 +1,24 @@
+import type { WatermarkOptions as _WatermarkRawOptions } from 'watermark-js-plus/dist/types/src/types/index.js'
+
+export type WatermarkPureOptions = Omit<
+ Partial<_WatermarkRawOptions>,
+ | 'onSuccess'
+ | 'onBeforeDestroy'
+ | 'onDestroyed'
+ | 'extraDrawFunc'
+ | 'onObserveError'
+ | 'parent'
+> & {
+ /**
+ * Watermark parent selector
+ *
+ * 水印父元素选择器
+ *
+ * @default 'body'
+ */
+ parent?: 'body' | (string & { __z_ignore?: never })
+}
+
+export interface WatermarkPluginFrontmatter {
+ watermark?: WatermarkPureOptions | boolean
+}
diff --git a/plugins/plugin-watermark/tsconfig.build.json b/plugins/plugin-watermark/tsconfig.build.json
new file mode 100644
index 0000000000..fd3f9ed86a
--- /dev/null
+++ b/plugins/plugin-watermark/tsconfig.build.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../tsconfig.build.json",
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./lib",
+ "types": ["vuepress/client-types"]
+ },
+ "include": ["./src"],
+ "references": [
+ {
+ "path": "../../tools/helper/tsconfig.build.json"
+ }
+ ]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5cfadd0c2e..58173c7c3c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -188,6 +188,9 @@ importers:
'@vuepress/plugin-redirect':
specifier: workspace:*
version: link:../plugins/plugin-redirect
+ '@vuepress/plugin-watermark':
+ specifier: workspace:*
+ version: link:../plugins/plugin-watermark
'@vuepress/theme-default':
specifier: workspace:*
version: link:../themes/theme-default
@@ -653,6 +656,21 @@ importers:
specifier: 2.0.0-rc.9
version: 2.0.0-rc.9(@vuepress/bundler-vite@2.0.0-rc.9(@types/node@20.12.7)(jiti@1.21.0)(sass@1.75.0)(terser@5.30.4)(typescript@5.4.5))(@vuepress/bundler-webpack@2.0.0-rc.9(typescript@5.4.5))(typescript@5.4.5)(vue@3.4.26(typescript@5.4.5))
+ plugins/plugin-watermark:
+ dependencies:
+ '@vuepress/helper':
+ specifier: workspace:*
+ version: link:../../tools/helper
+ vue:
+ specifier: ^3.4.26
+ version: 3.4.26(typescript@5.4.5)
+ vuepress:
+ specifier: 2.0.0-rc.9
+ version: 2.0.0-rc.9(@vuepress/bundler-vite@2.0.0-rc.9(@types/node@20.12.7)(jiti@1.21.0)(sass@1.75.0)(terser@5.30.4)(typescript@5.4.5))(@vuepress/bundler-webpack@2.0.0-rc.9(typescript@5.4.5))(typescript@5.4.5)(vue@3.4.26(typescript@5.4.5))
+ watermark-js-plus:
+ specifier: ^1.4.22
+ version: 1.4.22
+
themes/theme-default:
dependencies:
'@vuepress/helper':
@@ -6477,6 +6495,10 @@ packages:
resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==}
engines: {node: '>=10.13.0'}
+ watermark-js-plus@1.4.22:
+ resolution: {integrity: sha512-/UquC1Wkj3UuraGDmx29Ri3tu4gla05k9u0e+l4T1Ms69bWvLi/K74y9iyzp502gL55gm4BV8jUjvs6LvAzKmA==}
+ engines: {node: '>=16.0.0'}
+
wbuf@1.7.3:
resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==}
@@ -8516,7 +8538,7 @@ snapshots:
'@types/markdown-it-emoji@2.0.5':
dependencies:
- '@types/markdown-it': 13.0.7
+ '@types/markdown-it': 14.0.1
'@types/markdown-it@13.0.7':
dependencies:
@@ -13218,6 +13240,8 @@ snapshots:
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
+ watermark-js-plus@1.4.22: {}
+
wbuf@1.7.3:
dependencies:
minimalistic-assert: 1.0.1
diff --git a/tsconfig.build.json b/tsconfig.build.json
index 44e2d38062..322cc6ce0c 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -53,6 +53,7 @@
{ "path": "./plugins/plugin-sitemap/tsconfig.build.json" },
{ "path": "./plugins/plugin-theme-data/tsconfig.build.json" },
{ "path": "./plugins/plugin-toc/tsconfig.build.json" },
+ { "path": "./plugins/plugin-watermark/tsconfig.build.json" },
{ "path": "./themes/theme-default/tsconfig.build.json" },
{ "path": "./tools/create-vuepress/tsconfig.build.json" },
{ "path": "./tools/helper/tsconfig.build.json" },
]