diff --git a/.vscode/settings.json b/.vscode/settings.json
index ebe86f5e9c..1daa0feb53 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -32,6 +32,7 @@
"mdit",
"nord",
"nprogress",
+ "photoswipe",
"prefetch",
"preload",
"prismjs",
diff --git a/docs/.vuepress/configs/navbar/en.ts b/docs/.vuepress/configs/navbar/en.ts
index 987730089e..8ccae8a4b5 100644
--- a/docs/.vuepress/configs/navbar/en.ts
+++ b/docs/.vuepress/configs/navbar/en.ts
@@ -29,6 +29,7 @@ export const navbarEn: NavbarConfig = [
'/plugins/google-analytics',
'/plugins/medium-zoom',
'/plugins/nprogress',
+ '/plugins/photo-swipe',
'/plugins/redirect',
'/plugins/register-components',
],
diff --git a/docs/.vuepress/configs/navbar/zh.ts b/docs/.vuepress/configs/navbar/zh.ts
index 602a9186d9..4727c0b64d 100644
--- a/docs/.vuepress/configs/navbar/zh.ts
+++ b/docs/.vuepress/configs/navbar/zh.ts
@@ -29,6 +29,7 @@ export const navbarZh: NavbarConfig = [
'/zh/plugins/google-analytics',
'/zh/plugins/medium-zoom',
'/zh/plugins/nprogress',
+ '/zh/plugins/photo-swipe',
'/zh/plugins/redirect',
'/zh/plugins/register-components',
],
diff --git a/docs/.vuepress/configs/sidebar/en.ts b/docs/.vuepress/configs/sidebar/en.ts
index 0c34c63e11..8e424ca845 100644
--- a/docs/.vuepress/configs/sidebar/en.ts
+++ b/docs/.vuepress/configs/sidebar/en.ts
@@ -14,6 +14,7 @@ export const sidebarEn: SidebarConfig = {
'/plugins/google-analytics',
'/plugins/medium-zoom',
'/plugins/nprogress',
+ '/plugins/photo-swipe',
'/plugins/redirect',
'/plugins/register-components',
],
diff --git a/docs/.vuepress/configs/sidebar/zh.ts b/docs/.vuepress/configs/sidebar/zh.ts
index 5489a6ff5c..4a71f15f09 100644
--- a/docs/.vuepress/configs/sidebar/zh.ts
+++ b/docs/.vuepress/configs/sidebar/zh.ts
@@ -14,6 +14,7 @@ export const sidebarZh: SidebarConfig = {
'/zh/plugins/google-analytics',
'/zh/plugins/medium-zoom',
'/zh/plugins/nprogress',
+ '/zh/plugins/photo-swipe',
'/zh/plugins/redirect',
'/zh/plugins/register-components',
],
diff --git a/docs/package.json b/docs/package.json
index 0a4c547631..6cdb02f7dc 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -19,6 +19,7 @@
"@vuepress/plugin-google-analytics": "workspace:*",
"@vuepress/plugin-medium-zoom": "workspace:*",
"@vuepress/plugin-nprogress": "workspace:*",
+ "@vuepress/plugin-photo-swipe": "workspace:*",
"@vuepress/plugin-pwa-popup": "workspace:*",
"@vuepress/plugin-redirect": "workspace:*",
"@vuepress/plugin-register-components": "workspace:*",
diff --git a/docs/plugins/photo-swipe.md b/docs/plugins/photo-swipe.md
new file mode 100644
index 0000000000..a0ac80b59f
--- /dev/null
+++ b/docs/plugins/photo-swipe.md
@@ -0,0 +1,271 @@
+# photo-swipe
+
+
+
+This plugin will make the pictures in the body of the page enter the preview mode when clicked.
+
+## Usage
+
+```bash
+npm i -D @vuepress/plugin-photo-swipe@next
+```
+
+```ts
+import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
+
+export default {
+ plugins: [
+ photoSwipePlugin({
+ // options
+ }),
+ ],
+}
+```
+
+In preview mode, you can:
+
+- Swipe left and right to preview other pictures on the page in order
+- View the description of the picture
+- Zoom in and zoom out the picture
+- View pictures in full screen
+- Download pictures
+- Share pictures
+
+::: tip
+
+- Besides clicking "×" in the upper right corner to exit the preview mode, scrolling up and down more than a certain distance will also exit preview mode.
+- On mobile devices, or using the PC trackpad, you can use pan and zoom gestures to pan and zoom in the preview mode.
+
+:::
+
+## Config
+
+### selector
+
+- Type: `string | string[]`
+- Default: `".theme-default-content :not(a) > img:not([no-view])"`
+- Details: Image selector
+
+### scrollToClose
+
+- Type: `boolean`
+- Default: `true`
+- Details: Whether close the current image when scrolling.
+
+### delay
+
+- Type: `number`
+- Default: `800`
+- Details:
+
+ The delay of operating dom, in ms.
+
+ ::: tip
+
+ If the theme you are using has a switching animation, it is recommended to configure this option to `Switch animation duration + 200`.
+
+ :::
+
+### locales
+
+- Type: `PhotoSwipeLocaleConfig`
+
+ ```ts
+ interface PhotoSwipeLocaleData {
+ /**
+ * Close button label text
+ */
+ close: string
+
+ /**
+ * Full screen button label text
+ */
+ fullscreen: string
+
+ /**
+ * Share button label text
+ */
+ share: string
+
+ /**
+ * Zoom button label text
+ */
+ zoom: string
+
+ /**
+ * Previous image button label text
+ */
+ prev: string
+
+ /**
+ * Next image button label text
+ */
+ next: string
+
+ /**
+ * Share button config
+ */
+ buttons: PhotoSwipeDefaultUI.ShareButtonData[]
+ }
+
+ interface PhotoSwipeLocaleConfig {
+ [localePath: string]: PhotoSwipeLocaleData
+ }
+ ```
+
+- Details: Locales config for photo-swipe plugin.
+
+- Example:
+
+ ```ts
+ import { defineUserConfig } from 'vuepress'
+ import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
+
+ export default defineUserConfig({
+ locales: {
+ '/': {
+ // this is a supported language
+ lang: 'en-US',
+ },
+ '/xx/': {
+ // the plugin does not support this language
+ lang: 'mm-NN',
+ },
+ },
+
+ plugins: [
+ photoSwipePlugin({
+ locales: {
+ '/': {
+ // Override share label text
+ share: 'Share with friends',
+ },
+
+ '/xx/': {
+ // Complete locale config for `mm-NN` language here
+ },
+ },
+ }),
+ ],
+ })
+ ```
+
+::: details Built-in Supported Languages
+
+- **Simplified Chinese** (zh-CN)
+- **Traditional Chinese** (zh-TW)
+- **English (United States)** (en-US)
+- **German** (de-DE)
+- **German (Australia)** (de-AT)
+- **Russian** (ru-RU)
+- **Ukrainian** (uk-UA)
+- **Vietnamese** (vi-VN)
+- **Portuguese (Brazil)** (pt-BR)
+- **Polish** (pl-PL)
+- **French** (fr-FR)
+- **Spanish** (es-ES)
+- **Slovak** (sk-SK)
+- **Japanese** (ja-JP)
+- **Turkish** (tr-TR)
+- **Korean** (ko-KR)
+- **Finnish** (fi-FI)
+- **Indonesian** (id-ID)
+- **Dutch** (nl-NL)
+
+:::
+
+## Frontmatter
+
+- Type: `string | false`
+- Details:
+
+Image selector for the current page, or `false` to disable photo-swipe in current page.
+
+## Client Config
+
+### definePhotoSwipeConfig
+
+Options passed to [`photo-swipe`](http://photoswipe.com/)
+
+```ts title=".vuepress/client.ts"
+import { definePhotoSwipeConfig } from '@vuepress/plugin-photo-swipe/client'
+
+definePhotoSwipeConfig({
+ // set photoswipe options here
+})
+
+export default {}
+```
+
+## API
+
+You can also call photoswipe with apis.
+
+`createPhotoSwipe` allows you to programmatically view images links with PhotoSwipe:
+
+```vue
+
+
+
+
+
+```
+
+`registerPhotoSwipe` allows you to register photoswipe for the given image elements:
+
+```vue
+
+```
+
+## Styles
+
+You can customize the style via CSS variables:
+
+@[code css](@vuepress/plugin-photo-swipe/src/client/styles/vars.css)
diff --git a/docs/zh/plugins/photo-swipe.md b/docs/zh/plugins/photo-swipe.md
new file mode 100644
index 0000000000..b58169eecf
--- /dev/null
+++ b/docs/zh/plugins/photo-swipe.md
@@ -0,0 +1,273 @@
+# photo-swipe
+
+
+
+此插件会使页面正文内的图片在点击时进入浏览模式浏览。
+
+## 使用方法
+
+```bash
+npm i -D @vuepress/plugin-photo-swipe@next
+```
+
+```ts
+import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
+
+export default {
+ plugins: [
+ photoSwipePlugin({
+ // 选项
+ }),
+ ],
+}
+```
+
+在图片预览模式中,你可以:
+
+- 左右滑动按顺序浏览页面内其他的图片
+- 查看图片的描述
+- 对图片进行缩放
+- 全屏浏览图片
+- 下载图片
+- 分享图片
+
+::: tip
+
+- 除了点击右上角的 "×" 退出浏览模式外,在上下滚动超过一定距离后,会自动退出图片浏览模式。
+- 在移动端,或使用 PC 触控板,你可以使用平移、缩放手势在浏览模式中平移、缩放图片。
+
+:::
+
+## 插件选项
+
+### selector
+
+- 类型:`string | string[]`
+- 默认值:`".theme-default-content :not(a) > img:not([no-view])"`
+- 详情:图片选择器
+
+### scrollToClose
+
+- 类型:`boolean`
+- 默认值:`true`
+- 详情:是否在滚动时关闭当前图片。
+
+### delay
+
+- 类型:`number`
+- 默认值:`800`
+- 详情:
+
+ 操作页面 DOM 的延时,单位 ms。
+
+ ::: tip
+
+ 如果你使用的主题有切换动画,建议配置此选项为 `切换动画时长 + 200`。
+
+ :::
+
+### locales
+
+- 类型:`PhotoSwipeLocaleConfig`
+
+ ```ts
+ interface PhotoSwipeLocaleData {
+ /**
+ * 关闭按钮标签文字
+ */
+ close: string
+
+ /**
+ * 全屏按钮标签文字
+ */
+ fullscreen: string
+
+ /**
+ * 分享按钮标签文字
+ */
+ share: string
+
+ /**
+ * 缩放按钮标签文字
+ */
+ zoom: string
+
+ /**
+ * 上一张图片按钮标签文字
+ */
+ prev: string
+
+ /**
+ * 下一张图片按钮标签文字
+ */
+ next: string
+
+ /**
+ * 功能按钮配置
+ */
+ buttons: PhotoSwipeDefaultUI.ShareButtonData[]
+ }
+
+ interface PhotoSwipeLocaleConfig {
+ [localePath: string]: PhotoSwipeLocaleData
+ }
+ ```
+
+- 详情:Photo Swipe 插件的国际化配置。
+
+- 示例:
+
+ ```ts
+ import { defineUserConfig } from 'vuepress'
+ import { photoSwipePlugin } from '@vuepress/plugin-photo-swipe'
+
+ export default defineUserConfig({
+ locales: {
+ '/': {
+ // 这是一个支持的语言
+ lang: 'zh-CN',
+ },
+ '/xx/': {
+ // 这是一个没有收到插件支持的语言
+ lang: 'mm-NN',
+ },
+ },
+
+ plugins: [
+ photoSwipePlugin({
+ locales: {
+ '/': {
+ // 覆盖分享标签文字
+ share: '分享给伙伴',
+ },
+
+ '/xx/': {
+ // 在这里完整设置 `mm-NN` 的多语言配置
+ },
+ },
+ }),
+ ],
+ })
+ ```
+
+::: details 内置支持语言
+
+- **简体中文** (zh-CN)
+- **繁体中文** (zh-TW)
+- **英文(美国)** (en-US)
+- **德语** (de-DE)
+- **德语(澳大利亚)** (de-AT)
+- **俄语** (ru-RU)
+- **乌克兰语** (uk-UA)
+- **越南语** (vi-VN)
+- **葡萄牙语(巴西)** (pt-BR)
+- **波兰语** (pl-PL)
+- **法语** (fr-FR)
+- **西班牙语** (es-ES)
+- **斯洛伐克** (sk-SK)
+- **日语** (ja-JP)
+- **土耳其语** (tr-TR)
+- **韩语** (ko-KR)
+- **芬兰语** (fi-FI)
+- **印尼语** (id-ID)
+- **荷兰语** (nl-NL)
+
+:::
+
+## Frontmatter
+
+### photoswipe
+
+- 类型: `string | false`
+- 详情:
+
+ 当前页面的图片选择器或 `false` 以在当前页面中禁用 photo-swipe。
+
+## 客户端配置
+
+### definePhotoSwipeConfig
+
+传递给 [`photo-swipe`](http://photoswipe.com/) 的额外选项。
+
+```ts title=".vuepress/client.ts"
+import { definePhotoSwipeConfig } from '@vuepress/plugin-photo-swipe/client'
+
+definePhotoSwipeConfig({
+ // 在此设置 photoswipe 选项
+})
+
+export default {}
+```
+
+## API
+
+你可以通过 API 来调用 photoswipe。
+
+`createPhotoSwipe` 允许你以编程的方式查看图片链接:
+
+```vue
+
+
+
+
+
+```
+
+`registerPhotoSwipe` 允许你为给定的图片元素注册 photoswipe:
+
+```vue
+
+```
+
+## 样式
+
+你可以通过 CSS 变量来自定义部分样式:
+
+@[code css](@vuepress/plugin-photo-swipe/src/client/styles/vars.css)
diff --git a/plugins/plugin-photo-swipe/package.json b/plugins/plugin-photo-swipe/package.json
new file mode 100644
index 0000000000..51126d15f5
--- /dev/null
+++ b/plugins/plugin-photo-swipe/package.json
@@ -0,0 +1,57 @@
+{
+ "name": "@vuepress/plugin-photo-swipe",
+ "version": "2.0.0-rc.11",
+ "description": "VuePress plugin - photo-swipe",
+ "keywords": [
+ "vuepress-plugin",
+ "vuepress",
+ "photo-swipe",
+ "image",
+ "preview",
+ "zoom"
+ ],
+ "homepage": "https://ecosystem.vuejs.press/plugins/photo-swipe.html",
+ "bugs": {
+ "url": "https://github.com/vuepress/ecosystem/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/vuepress/ecosystem.git",
+ "directory": "plugins/plugin-photo-swipe"
+ },
+ "license": "MIT",
+ "author": {
+ "name": "Mr.Hope",
+ "email": "mister-hope@outlook.com",
+ "url": "https://mister-hope.com"
+ },
+ "type": "module",
+ "exports": {
+ ".": "./lib/node/index.js",
+ "./client": "./lib/client/index.js",
+ "./client/*": "./lib/client/*",
+ "./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",
+ "style": "sass src:lib --no-source-map"
+ },
+ "dependencies": {
+ "@vuepress/helper": "workspace:~2.0.0-rc.11",
+ "@vueuse/core": "^10.7.2",
+ "photoswipe": "^5.4.3",
+ "vue": "^3.4.15"
+ },
+ "peerDependencies": {
+ "vuepress": "2.0.0-rc.6"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/plugins/plugin-photo-swipe/src/client/composables/index.ts b/plugins/plugin-photo-swipe/src/client/composables/index.ts
new file mode 100644
index 0000000000..ed58be28a1
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/composables/index.ts
@@ -0,0 +1 @@
+export * from './usePhotoSwipe.js'
diff --git a/plugins/plugin-photo-swipe/src/client/composables/usePhotoSwipe.ts b/plugins/plugin-photo-swipe/src/client/composables/usePhotoSwipe.ts
new file mode 100644
index 0000000000..1c7f6e0ae3
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/composables/usePhotoSwipe.ts
@@ -0,0 +1,64 @@
+import { useLocaleConfig } from '@vuepress/helper/client'
+import { nextTick, onMounted, onUnmounted, watch } from 'vue'
+import { usePageData } from 'vuepress/client'
+import type { PhotoSwipePluginLocaleData } from '../../shared/index.js'
+import { usePhotoSwipeOptions } from '../helpers/index.js'
+import { getImages, registerPhotoSwipe } from '../utils/index.js'
+
+import 'photoswipe/dist/photoswipe.css'
+import '../styles/photo-swipe.scss'
+
+export interface UsePhotoSwipeOptions {
+ selector: string | string[]
+ locales: Record<
+ string,
+ Record<`${keyof PhotoSwipePluginLocaleData}Title`, string>
+ >
+ /** @default 500 */
+ delay?: number
+ /** @default true */
+ scrollToClose?: boolean
+}
+
+export const usePhotoSwipe = ({
+ selector,
+ locales,
+ delay = 500,
+ scrollToClose = true,
+}: UsePhotoSwipeOptions): void => {
+ const photoSwipeOptions = usePhotoSwipeOptions()
+ const locale = useLocaleConfig(locales)
+ const page = usePageData()
+
+ let destroy: (() => void) | null = null
+
+ const setupPhotoSwipe = (): Promise =>
+ new Promise((resolve) => setTimeout(resolve, delay))
+ .then(() => nextTick())
+ .then(async () => {
+ destroy = await registerPhotoSwipe(
+ getImages(selector),
+ {
+ ...photoSwipeOptions,
+ ...locale.value,
+ },
+ scrollToClose,
+ )
+ })
+
+ onMounted(() => {
+ setupPhotoSwipe()
+
+ watch(
+ () => page.value.path,
+ () => {
+ destroy?.()
+ setupPhotoSwipe()
+ },
+ )
+ })
+
+ onUnmounted(() => {
+ destroy?.()
+ })
+}
diff --git a/plugins/plugin-photo-swipe/src/client/config.ts b/plugins/plugin-photo-swipe/src/client/config.ts
new file mode 100644
index 0000000000..e57ba6a9e2
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/config.ts
@@ -0,0 +1,35 @@
+import type { ClientConfig } from 'vuepress/client'
+import { defineClientConfig } from 'vuepress/client'
+import type { PhotoSwipePluginLocaleData } from '../shared/index.js'
+import { usePhotoSwipe } from './composables/index.js'
+import { injectPhotoSwipeConfig } from './helpers/index.js'
+
+import './styles/vars.css'
+
+declare const __PS_SELECTOR__: string | string[]
+declare const __PS_DELAY__: number
+declare const __PS_LOCALES__: Record<
+ string,
+ Record<`${keyof PhotoSwipePluginLocaleData}Title`, string>
+>
+declare const __PS_SCROLL_TO_CLOSE__: boolean
+
+const selector = __PS_SELECTOR__
+const locales = __PS_LOCALES__
+const delay = __PS_DELAY__
+const scrollToClose = __PS_SCROLL_TO_CLOSE__
+
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
+export default defineClientConfig({
+ enhance: ({ app }) => {
+ injectPhotoSwipeConfig(app)
+ },
+ setup: () => {
+ usePhotoSwipe({
+ selector,
+ delay,
+ locales,
+ scrollToClose,
+ })
+ },
+}) as ClientConfig
diff --git a/plugins/plugin-photo-swipe/src/client/helpers/index.ts b/plugins/plugin-photo-swipe/src/client/helpers/index.ts
new file mode 100644
index 0000000000..db039e3f2b
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/helpers/index.ts
@@ -0,0 +1 @@
+export * from './photo-swipe.js'
diff --git a/plugins/plugin-photo-swipe/src/client/helpers/photo-swipe.ts b/plugins/plugin-photo-swipe/src/client/helpers/photo-swipe.ts
new file mode 100644
index 0000000000..e8968e24b3
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/helpers/photo-swipe.ts
@@ -0,0 +1,26 @@
+import type { PhotoSwipeOptions as OriginalPhotoSwipeOptions } from 'photoswipe'
+import type { App } from 'vue'
+import { inject } from 'vue'
+
+export type PhotoSwipeOptions = Omit<
+ OriginalPhotoSwipeOptions,
+ // These are handled internally
+ 'dataSource' | 'index'
+>
+
+declare const __VUEPRESS_DEV__: boolean
+
+let photoswipeOptions: PhotoSwipeOptions = {}
+
+const photoswipeSymbol = Symbol(__VUEPRESS_DEV__ ? 'photoswipe' : '')
+
+export const definePhotoSwipeConfig = (options: PhotoSwipeOptions): void => {
+ photoswipeOptions = options
+}
+
+export const usePhotoSwipeOptions = (): PhotoSwipeOptions =>
+ inject(photoswipeSymbol)!
+
+export const injectPhotoSwipeConfig = (app: App): void => {
+ app.provide(photoswipeSymbol, photoswipeOptions)
+}
diff --git a/plugins/plugin-photo-swipe/src/client/index.ts b/plugins/plugin-photo-swipe/src/client/index.ts
new file mode 100644
index 0000000000..b2f2024fd1
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/index.ts
@@ -0,0 +1,3 @@
+export * from './helpers/index.js'
+export * from './composables/index.js'
+export * from './utils/index.js'
diff --git a/plugins/plugin-photo-swipe/src/client/styles/photo-swipe.css b/plugins/plugin-photo-swipe/src/client/styles/photo-swipe.css
new file mode 100644
index 0000000000..113d8ac55d
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/styles/photo-swipe.css
@@ -0,0 +1,38 @@
+.photo-swipe-loading {
+ position: absolute;
+ inset: 0;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.photo-swipe-bullets-indicator {
+ position: absolute;
+ bottom: 30px;
+ left: 50%;
+
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ transform: translate(-50%, 0);
+}
+
+.photo-swipe-bullet {
+ width: 12px;
+ height: 6px;
+ margin: 0 5px;
+ border-radius: 3px;
+
+ background: var(--photo-swipe-bullet);
+
+ transition:
+ width 0.3s,
+ color 0.3s;
+}
+
+.photo-swipe-bullet.active {
+ width: 30px;
+ background: var(--photo-swipe-bullet-active);
+}
diff --git a/plugins/plugin-photo-swipe/src/client/styles/vars.css b/plugins/plugin-photo-swipe/src/client/styles/vars.css
new file mode 100644
index 0000000000..b721a131d3
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/styles/vars.css
@@ -0,0 +1,4 @@
+:root {
+ --photo-swipe-bullet: #fff;
+ --photo-swipe-bullet-active: #3eaf7c;
+}
diff --git a/plugins/plugin-photo-swipe/src/client/utils/createPhotoSwipe.ts b/plugins/plugin-photo-swipe/src/client/utils/createPhotoSwipe.ts
new file mode 100644
index 0000000000..44c244e3ba
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/utils/createPhotoSwipe.ts
@@ -0,0 +1,66 @@
+import { useEventListener } from '@vueuse/core'
+import type PhotoSwipe from 'photoswipe'
+import type { SlideData } from 'photoswipe'
+import type { PhotoSwipeOptions } from '../helpers/index.js'
+import { LOADING_ICON } from './icon.js'
+import { getImageUrlInfo } from './images.js'
+import { initPhotoSwipe } from './initPhotoSwipe.js'
+
+export interface PhotoSwipeState {
+ open: (index: number) => void
+ close: () => void
+ destroy: () => void
+}
+
+export const createPhotoSwipe = (
+ images: string[],
+ photoSwipeOptions: PhotoSwipeOptions,
+ scrollToClose = true,
+): Promise =>
+ import(/* webpackChunkName: "photo-swipe" */ 'photoswipe').then(
+ ({ default: PhotoSwipe }) => {
+ let currentPhotoSwipe: PhotoSwipe | null = null
+
+ const dataSource = images.map((image) => ({
+ html: LOADING_ICON,
+ msrc: image,
+ }))
+
+ images.forEach((image, index) => {
+ getImageUrlInfo(image).then((data) => {
+ dataSource.splice(index, 1, data)
+ currentPhotoSwipe?.refreshSlideContent(index)
+ })
+ })
+
+ const destroy = useEventListener('wheel', () => {
+ currentPhotoSwipe?.close()
+ })
+
+ return {
+ open: (index: number): void => {
+ currentPhotoSwipe?.close()
+
+ currentPhotoSwipe = new PhotoSwipe({
+ preloaderDelay: 0,
+ showHideAnimationType: 'zoom',
+ ...photoSwipeOptions,
+ dataSource,
+ index,
+ ...(scrollToClose
+ ? { closeOnVerticalDrag: true, wheelToZoom: false }
+ : {}),
+ })
+
+ initPhotoSwipe(currentPhotoSwipe)
+
+ currentPhotoSwipe.addFilter('placeholderSrc', () => images[index])
+ currentPhotoSwipe.init()
+ },
+ close: (): void => {
+ currentPhotoSwipe?.close()
+ },
+ destroy,
+ }
+ },
+ )
diff --git a/plugins/plugin-photo-swipe/src/client/utils/icon.ts b/plugins/plugin-photo-swipe/src/client/utils/icon.ts
new file mode 100644
index 0000000000..47faeb8fab
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/utils/icon.ts
@@ -0,0 +1,2 @@
+export const LOADING_ICON =
+ ''
diff --git a/plugins/plugin-photo-swipe/src/client/utils/images.ts b/plugins/plugin-photo-swipe/src/client/utils/images.ts
new file mode 100644
index 0000000000..88d089fbe7
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/utils/images.ts
@@ -0,0 +1,40 @@
+import { isString } from '@vuepress/helper/client'
+import type { SlideData } from 'photoswipe'
+
+export const getImages = (selector: string | string[]): HTMLImageElement[] =>
+ isString(selector)
+ ? Array.from(document.querySelectorAll(selector))
+ : selector
+ .map((item) =>
+ Array.from(document.querySelectorAll(item)),
+ )
+ .flat()
+
+export const getImageElementInfo = (
+ image: HTMLImageElement,
+): Promise =>
+ new Promise((resolve, reject) => {
+ if (image.complete) {
+ resolve({
+ type: 'image',
+ element: image,
+ src: image.src,
+ width: image.naturalWidth,
+ height: image.naturalHeight,
+ alt: image.alt,
+ msrc: image.src,
+ })
+ } else {
+ image.onload = (): void => resolve(getImageElementInfo(image))
+ image.onerror = (err): void => reject(err)
+ }
+ })
+
+export const getImageUrlInfo = (image: string): Promise =>
+ new Promise((resolve, reject) => {
+ const el = new Image()
+
+ el.src = image
+ el.onload = (): void => resolve(getImageElementInfo(el))
+ el.onerror = (err): void => reject(err)
+ })
diff --git a/plugins/plugin-photo-swipe/src/client/utils/index.ts b/plugins/plugin-photo-swipe/src/client/utils/index.ts
new file mode 100644
index 0000000000..c5d849d4d1
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/utils/index.ts
@@ -0,0 +1,3 @@
+export * from './createPhotoSwipe.js'
+export * from './images.js'
+export * from './usePhotoSwipe.js'
diff --git a/plugins/plugin-photo-swipe/src/client/utils/initPhotoSwipe.ts b/plugins/plugin-photo-swipe/src/client/utils/initPhotoSwipe.ts
new file mode 100644
index 0000000000..dcd3e335b2
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/utils/initPhotoSwipe.ts
@@ -0,0 +1,77 @@
+import { useFullscreen } from '@vueuse/core'
+import type PhotoSwipe from 'photoswipe'
+
+export const initPhotoSwipe = (photoSwipe: PhotoSwipe): void => {
+ const { isSupported, toggle } = useFullscreen()
+
+ photoSwipe.on('uiRegister', () => {
+ if (isSupported.value)
+ // add fullscreen button
+ photoSwipe.ui!.registerElement({
+ name: 'fullscreen',
+ order: 7,
+ isButton: true,
+
+ html: '',
+
+ onClick: () => {
+ toggle()
+ },
+ })
+
+ // add download button
+ photoSwipe.ui!.registerElement({
+ name: 'download',
+ order: 8,
+ isButton: true,
+ tagName: 'a',
+
+ // SVG with outline
+ html: {
+ isCustomSVG: true,
+ inner:
+ '',
+ outlineID: 'pswp__icn-download',
+ },
+
+ onInit: (el, photoSwipe) => {
+ el.setAttribute('download', '')
+ el.setAttribute('target', '_blank')
+ el.setAttribute('rel', 'noopener')
+
+ photoSwipe.on('change', () => {
+ el.setAttribute('href', photoSwipe.currSlide!.data.src!)
+ })
+ },
+ })
+
+ // add bullets indicator
+ photoSwipe.ui!.registerElement({
+ name: 'bulletsIndicator',
+ className: 'photo-swipe-bullets-indicator',
+ appendTo: 'wrapper',
+ onInit: (el, photoSwipe) => {
+ const bullets: HTMLElement[] = []
+ let prevIndex = -1
+
+ for (let i = 0; i < photoSwipe.getNumItems(); i++) {
+ const bullet = document.createElement('div')
+
+ bullet.className = 'photo-swipe-bullet'
+ bullet.onclick = (event: Event): void => {
+ photoSwipe.goTo(bullets.indexOf(event.target as HTMLElement))
+ }
+ bullets.push(bullet)
+ el.appendChild(bullet)
+ }
+
+ photoSwipe.on('change', () => {
+ if (prevIndex >= 0) bullets[prevIndex].classList.remove('active')
+
+ bullets[photoSwipe.currIndex].classList.add('active')
+ prevIndex = photoSwipe.currIndex
+ })
+ },
+ })
+ })
+}
diff --git a/plugins/plugin-photo-swipe/src/client/utils/usePhotoSwipe.ts b/plugins/plugin-photo-swipe/src/client/utils/usePhotoSwipe.ts
new file mode 100644
index 0000000000..dff5478dde
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/client/utils/usePhotoSwipe.ts
@@ -0,0 +1,71 @@
+import { useEventListener } from '@vueuse/core'
+import type PhotoSwipe from 'photoswipe'
+import type { SlideData } from 'photoswipe'
+import type { PhotoSwipeOptions } from '../helpers/index.js'
+import { LOADING_ICON } from './icon.js'
+import { getImageElementInfo } from './images.js'
+import { initPhotoSwipe } from './initPhotoSwipe.js'
+
+export const registerPhotoSwipe = (
+ images: HTMLImageElement[],
+ photoSwipeOptions: PhotoSwipeOptions,
+ scrollToClose = true,
+): Promise<() => void> =>
+ import(/* webpackChunkName: "photo-swipe" */ 'photoswipe').then(
+ ({ default: PhotoSwipe }) => {
+ let currentPhotoSwipe: PhotoSwipe | null = null
+
+ const dataSource = images.map((image) => ({
+ html: LOADING_ICON,
+ element: image,
+ msrc: image.src,
+ }))
+
+ images.forEach((image, index) => {
+ const handler = (): void => {
+ currentPhotoSwipe?.destroy()
+ currentPhotoSwipe = new PhotoSwipe({
+ preloaderDelay: 0,
+ showHideAnimationType: 'zoom',
+ ...photoSwipeOptions,
+ dataSource,
+ index,
+ ...(scrollToClose
+ ? { closeOnVerticalDrag: true, wheelToZoom: false }
+ : {}),
+ })
+
+ initPhotoSwipe(currentPhotoSwipe)
+
+ currentPhotoSwipe.addFilter('thumbEl', () => image)
+ currentPhotoSwipe.addFilter('placeholderSrc', () => image.src)
+ currentPhotoSwipe.init()
+ }
+
+ if (!image.getAttribute('photo-swipe')) {
+ image.style.cursor = 'zoom-in'
+ image.addEventListener('click', () => {
+ handler()
+ })
+ image.addEventListener('keypress', ({ key }) => {
+ if (key === 'Enter') handler()
+ })
+ // avoid registering multiple times
+ image.setAttribute('photo-swipe', '')
+ }
+
+ getImageElementInfo(image).then((data) => {
+ dataSource.splice(index, 1, data)
+ currentPhotoSwipe?.refreshSlideContent(index)
+ })
+ })
+
+ return scrollToClose
+ ? useEventListener('wheel', () => {
+ currentPhotoSwipe?.close()
+ })
+ : (): void => {
+ // do nothing
+ }
+ },
+ )
diff --git a/plugins/plugin-photo-swipe/src/node/index.ts b/plugins/plugin-photo-swipe/src/node/index.ts
new file mode 100644
index 0000000000..0958bd3e5b
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/node/index.ts
@@ -0,0 +1,5 @@
+export * from './locales.js'
+export * from './photoSwipePlugin.js'
+
+export type * from './options.js'
+export type * from '../shared/index.js'
diff --git a/plugins/plugin-photo-swipe/src/node/locales.ts b/plugins/plugin-photo-swipe/src/node/locales.ts
new file mode 100644
index 0000000000..7aa9fd1b61
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/node/locales.ts
@@ -0,0 +1,183 @@
+import type { PhotoSwipeLocaleConfig } from '../shared/index.js'
+
+export const photoSwipeLocales: PhotoSwipeLocaleConfig = {
+ '/en/': {
+ close: 'Close',
+ download: 'Download Image',
+ fullscreen: 'Switch to full screen',
+ zoom: 'Zoom in/out',
+ arrowPrev: 'Prev (Arrow Left)',
+ arrowNext: 'Next (Arrow Right)',
+ },
+
+ '/zh/': {
+ close: '关闭',
+ download: '下载图片',
+ fullscreen: '切换全屏',
+ zoom: '缩放',
+ arrowPrev: '上一个 (左箭头)',
+ arrowNext: '下一个 (右箭头)',
+ },
+
+ '/zh-tw/': {
+ close: '關閉',
+ download: '下載圖片',
+ fullscreen: '切換全屏',
+ zoom: '縮放',
+ arrowPrev: '上一個 (左箭頭)',
+ arrowNext: '下一個 (右箭頭)',
+ },
+
+ '/de/': {
+ close: 'Schließen',
+ download: 'Download',
+ fullscreen: 'Vollbild aktivieren',
+ zoom: 'Rein / rauszoomen',
+ arrowPrev: 'Zurück (Pfeil links)',
+ arrowNext: 'Weiter (Pfeil rechts)',
+ },
+
+ '/de-at/': {
+ close: 'Schließen',
+ download: 'Download',
+ fullscreen: 'Toggle fullscreen',
+ zoom: 'Rein / rauszoomen',
+ arrowPrev: 'Zurück (Pfeil links)',
+ arrowNext: 'Weiter (Pfeil rechts)',
+ },
+
+ '/vi/': {
+ close: 'Đóng',
+ download: 'download',
+ fullscreen: 'Bật chế độ toàn màn hình',
+ zoom: 'Phóng to / thu nhỏ',
+ arrowPrev: 'Trước (Mũi tên trái)',
+ arrowNext: 'Tiếp theo (Mũi tên Phải)',
+ },
+
+ '/uk/': {
+ close: 'Закрити',
+ download: 'Завантажити зображення',
+ fullscreen: 'Перейти на повний екран',
+ zoom: 'Збільшити/Зменшити',
+ arrowPrev: 'Попередня (Стрілка вліво)',
+ arrowNext: 'Далі (стрілка вправо)',
+ },
+
+ '/ru/': {
+ close: 'Закрыть',
+ download: 'Загрузить изображение',
+ fullscreen: 'Переключиться на полный экран',
+ zoom: 'Увеличить/Уменьшить',
+ arrowPrev: 'Предыдущая (Стрелка влево)',
+ arrowNext: 'Следующая (Стрелка вправо)',
+ },
+
+ '/br/': {
+ close: 'Fechar',
+ download: 'Baixar imagem',
+ fullscreen: 'Alternar para tela cheia',
+ zoom: 'Aproximar mais/menos',
+ arrowPrev: 'Anterior (Seta Esquerda)',
+ arrowNext: 'Próximo (Seta Direita)',
+ },
+
+ '/pl/': {
+ close: 'Zamknij',
+ download: 'Pobierz obraz',
+ fullscreen: 'Przełącz na pełny ekran',
+ zoom: 'Powiększ/pomniejsz',
+ arrowPrev: 'Poprzedni (strzałka w lewo)',
+ arrowNext: 'Następny (strzałka w prawo)',
+ },
+
+ '/sk/': {
+ close: 'Zatvor',
+ download: 'Stiahni obrázok',
+ fullscreen: 'Prepni na celú obrazovku',
+ zoom: 'Priblíž/Oddial',
+ arrowPrev: 'Predošlí (šípka doľava)',
+ arrowNext: 'Nasledujúci (šípka doprava)',
+ },
+
+ '/fr/': {
+ close: 'Fermer',
+ download: "Télécharger l'image",
+ fullscreen: 'Basculer en plein écran',
+ zoom: 'Zoom avant/arrière',
+ arrowPrev: 'Précédent (Flèche gauche)',
+ arrowNext: 'Suivant (Flèche droite)',
+ },
+
+ '/es/': {
+ close: 'Cerrar',
+ download: 'Descargar imagen',
+ fullscreen: 'Cambiar a pantalla completa',
+ zoom: 'Acercar/Alejar',
+ arrowPrev: 'Anterior (Flecha izquierda)',
+ arrowNext: 'Siguiente (Flecha derecha)',
+ },
+
+ '/ja/': {
+ close: '閉じる',
+ download: '画像ダウンロード',
+ fullscreen: '全画面表示への切り替え',
+ zoom: '拡大・縮小',
+ arrowPrev: '前へ(左矢印)',
+ arrowNext: '次へ(右矢印)',
+ },
+
+ '/tr/': {
+ close: 'Kapat',
+ download: 'Resmi indir',
+ fullscreen: 'Tam ekrana geç',
+ zoom: 'Yakınlaştır/Uzaklaştır',
+ arrowPrev: 'Önceki (Sol ok)',
+ arrowNext: 'Sonraki (Sağ ok)',
+ },
+
+ '/ko/': {
+ close: '닫기',
+ download: '이미지 다운로드',
+ fullscreen: '전체 화면 전환',
+ zoom: '확대/축소',
+ arrowPrev: '이전 (왼쪽 화살표)',
+ arrowNext: '다음 (오른쪽 화살표)',
+ },
+
+ '/fi/': {
+ close: 'Sulje',
+ download: 'Lataa kuva',
+ fullscreen: 'Vaihda kokoruututilaan',
+ zoom: 'Lähennä/Työnnä',
+ arrowPrev: 'Edellinen (Vasen nuoli)',
+ arrowNext: 'Seuraava (Oikea nuoli)',
+ },
+
+ '/hu/': {
+ close: 'Bezárás',
+ download: 'Kép letöltése',
+ fullscreen: 'Váltás teljes képernyőre',
+ zoom: 'Nagyítás/kicsinyítés',
+ arrowPrev: 'Előző (Balra nyíl)',
+ arrowNext: 'Következő (Jobbra nyíl)',
+ },
+
+ '/id/': {
+ close: 'Tutup',
+ download: 'Unduh gambar',
+ fullscreen: 'Beralih ke layar penuh',
+ zoom: 'Perbesar/Perkecil',
+ arrowPrev: 'Sebelumnya (Panah kiri)',
+ arrowNext: 'Selanjutnya (Panah kanan)',
+ },
+
+ '/nl/': {
+ close: 'Sluiten',
+ download: 'Download Image',
+ fullscreen: 'Verander naar fullscreen',
+ zoom: 'Zoom in/out',
+ arrowPrev: 'Vorige (Pijl Links)',
+ arrowNext: 'Volgende (Pijl Rechts)',
+ },
+}
diff --git a/plugins/plugin-photo-swipe/src/node/logger.ts b/plugins/plugin-photo-swipe/src/node/logger.ts
new file mode 100644
index 0000000000..5a4eafc9cb
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/node/logger.ts
@@ -0,0 +1,5 @@
+import { Logger } from '@vuepress/helper'
+
+export const PLUGIN_NAME = '@vuepress/plugin-photo-swipe'
+
+export const logger = new Logger(PLUGIN_NAME)
diff --git a/plugins/plugin-photo-swipe/src/node/options.ts b/plugins/plugin-photo-swipe/src/node/options.ts
new file mode 100644
index 0000000000..1752b5cf7f
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/node/options.ts
@@ -0,0 +1,42 @@
+import type { LocaleConfig } from 'vuepress/shared'
+import type { PhotoSwipePluginLocaleData } from '../shared/index.js'
+
+export interface PhotoSwipePluginOptions {
+ /**
+ * Image selector
+ *
+ * 图片选择器
+ *
+ * @default ".theme-default-content :not(a) > img:not([no-view])"
+ */
+ selector?: string | string[]
+
+ /**
+ * Whether close the current image when scrolling.
+ *
+ * 是否在滚动时关闭当前图片。
+ *
+ * @default true
+ */
+ scrollToClose?: boolean
+
+ /**
+ * The delay of photo-swipe fetching page images, in ms
+ *
+ * If the theme you are using has a switching animation, it is recommended to configure this option to `Switch animation duration + 200`
+ *
+ * photo-swipe 抓取页面图片的延时,单位 ms
+ *
+ * 如果你使用的主题有切换动画,建议配置此选项为 `切换动画时长 + 200`
+ *
+ * @default 800
+ */
+ delay?: number
+
+ /**
+ * Locale config
+ *
+ * 国际化配置
+ */
+ locales?: LocaleConfig
+}
diff --git a/plugins/plugin-photo-swipe/src/node/photoSwipePlugin.ts b/plugins/plugin-photo-swipe/src/node/photoSwipePlugin.ts
new file mode 100644
index 0000000000..bf2b753def
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/node/photoSwipePlugin.ts
@@ -0,0 +1,57 @@
+import {
+ addViteOptimizeDepsExclude,
+ addViteSsrNoExternal,
+ entries,
+ fromEntries,
+ getLocaleConfig,
+} from '@vuepress/helper'
+import type { PluginFunction } from 'vuepress/core'
+import { getDirname, path } from 'vuepress/utils'
+import { photoSwipeLocales } from './locales.js'
+import { logger, PLUGIN_NAME } from './logger.js'
+import type { PhotoSwipePluginOptions } from './options.js'
+
+const __dirname = getDirname(import.meta.url)
+
+export const photoSwipePlugin =
+ (options: PhotoSwipePluginOptions = {}): PluginFunction =>
+ (app) => {
+ if (app.env.isDebug) logger.info('Options:', options)
+
+ return {
+ name: PLUGIN_NAME,
+
+ define: (app): Record => ({
+ __PS_SELECTOR__:
+ options.selector ||
+ '.theme-default-content :not(a) > img:not([no-view])',
+ __PS_DELAY__: options.delay || 800,
+ __PS_SCROLL_TO_CLOSE__: options.scrollToClose ?? true,
+ __PS_LOCALES__: fromEntries(
+ entries(
+ getLocaleConfig({
+ app,
+ name: PLUGIN_NAME,
+ default: photoSwipeLocales,
+ config: options.locales,
+ }),
+ ).map(([localePath, localeOptions]) => [
+ localePath,
+ fromEntries(
+ entries(localeOptions).map(([key, value]) => [
+ `${key}Title`,
+ value,
+ ]),
+ ),
+ ]),
+ ),
+ }),
+
+ extendsBundlerOptions: (bundlerOptions: unknown, app): void => {
+ addViteOptimizeDepsExclude(bundlerOptions, app, 'photoswipe')
+ addViteSsrNoExternal(bundlerOptions, app, '@vuepress/helper')
+ },
+
+ clientConfigFile: path.resolve(__dirname, '../client/config.js'),
+ }
+ }
diff --git a/plugins/plugin-photo-swipe/src/shared/index.ts b/plugins/plugin-photo-swipe/src/shared/index.ts
new file mode 100644
index 0000000000..3b157fa5a9
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/shared/index.ts
@@ -0,0 +1 @@
+export * from './locales.js'
diff --git a/plugins/plugin-photo-swipe/src/shared/locales.ts b/plugins/plugin-photo-swipe/src/shared/locales.ts
new file mode 100644
index 0000000000..522ed07d9e
--- /dev/null
+++ b/plugins/plugin-photo-swipe/src/shared/locales.ts
@@ -0,0 +1,48 @@
+import type { ExactLocaleConfig } from '@vuepress/helper'
+
+export interface PhotoSwipePluginLocaleData {
+ /**
+ * Close button label text
+ *
+ * 关闭按钮标签文字
+ */
+ close: string
+
+ /**
+ * Download button label text
+ *
+ * 下载按钮标签文字
+ */
+ download: string
+
+ /**
+ * Full screen button label text
+ *
+ * 全屏按钮标签文字
+ */
+ fullscreen: string
+
+ /**
+ * Zoom button label text
+ *
+ * 缩放按钮标签文字
+ */
+ zoom: string
+
+ /**
+ * Previous image button label text
+ *
+ * 上一张图片按钮标签文字
+ */
+ arrowPrev: string
+
+ /**
+ * Next image button label text
+ *
+ * 下一张图片按钮标签文字
+ */
+ arrowNext: string
+}
+
+export type PhotoSwipeLocaleConfig =
+ ExactLocaleConfig
diff --git a/plugins/plugin-photo-swipe/tsconfig.build.json b/plugins/plugin-photo-swipe/tsconfig.build.json
new file mode 100644
index 0000000000..e0a82d8177
--- /dev/null
+++ b/plugins/plugin-photo-swipe/tsconfig.build.json
@@ -0,0 +1,10 @@
+{
+ "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 779e901b68..b5a12982e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -116,6 +116,9 @@ importers:
'@vuepress/plugin-nprogress':
specifier: workspace:*
version: link:../plugins/plugin-nprogress
+ '@vuepress/plugin-photo-swipe':
+ specifier: workspace:*
+ version: link:../plugins/plugin-photo-swipe
'@vuepress/plugin-pwa-popup':
specifier: workspace:*
version: link:../plugins/plugin-pwa-popup
@@ -410,6 +413,24 @@ importers:
specifier: 2.0.0-rc.6
version: 2.0.0-rc.6(@vuepress/bundler-vite@2.0.0-rc.6)(@vuepress/bundler-webpack@2.0.0-rc.6)(typescript@5.3.3)(vue@3.4.15)
+ plugins/plugin-photo-swipe:
+ dependencies:
+ '@vuepress/helper':
+ specifier: workspace:~2.0.0-rc.11
+ version: link:../../tools/helper
+ '@vueuse/core':
+ specifier: ^10.7.2
+ version: 10.7.2(vue@3.4.15)
+ photoswipe:
+ specifier: ^5.4.3
+ version: 5.4.3
+ vue:
+ specifier: ^3.4.15
+ version: 3.4.15(typescript@5.3.3)
+ vuepress:
+ specifier: 2.0.0-rc.6
+ version: 2.0.0-rc.6(@vuepress/bundler-vite@2.0.0-rc.6)(@vuepress/bundler-webpack@2.0.0-rc.6)(typescript@5.3.3)(vue@3.4.15)
+
plugins/plugin-prismjs:
dependencies:
prismjs:
@@ -9501,6 +9522,11 @@ packages:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
dev: true
+ /photoswipe@5.4.3:
+ resolution: {integrity: sha512-9UC6oJBK4oXFZ5HcdlcvGkfEHsVrmE4csUdCQhEjHYb3PvPLO3PG7UhnPuOgjxwmhq5s17Un5NUdum01LgBDng==}
+ engines: {node: '>= 0.12.0'}
+ dev: false
+
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}