From b5ebecb6e15db2abb3d51237197290045dad3753 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Tue, 8 Apr 2025 22:12:32 +0800 Subject: [PATCH 1/2] tweaks --- .vscode/settings.json | 5 + plugins/features/plugin-media/package.json | 78 +++++ .../features/plugin-media/rollup.config.ts | 9 + .../src/client/components/ArtPlayer.ts | 301 ++++++++++++++++++ .../src/client/components/BiliBili.ts | 147 +++++++++ .../src/client/components/VidStack.ts | 139 ++++++++ .../src/client/components/index.ts | 3 + .../src/client/composables/index.ts | 1 + .../src/client/composables/useSize.ts | 68 ++++ .../features/plugin-media/src/client/index.ts | 1 + .../src/client/styles/art-player.scss | 3 + .../src/client/styles/bili-bili.scss | 15 + .../src/client/styles/vidstack.scss | 15 + .../plugin-media/src/client/utils/getLink.ts | 5 + .../src/client/utils/iframeAllow.ts | 2 + .../plugin-media/src/client/utils/index.ts | 3 + .../src/client/utils/registerMse.ts | 97 ++++++ .../features/plugin-media/src/node/index.ts | 2 + .../plugin-media/src/node/mediaPlugin.ts | 13 + .../features/plugin-media/src/node/options.ts | 6 + .../plugin-media/src/shared/artplayer.ts | 16 + .../features/plugin-media/src/shared/index.ts | 3 + .../plugin-media/src/shared/locales.ts | 27 ++ .../features/plugin-media/src/shared/share.ts | 122 +++++++ .../features/plugin-media/tsconfig.build.json | 10 + .../src/node/mediumZoomPlugin.ts | 2 +- pnpm-lock.yaml | 204 ++++++++++++ tsconfig.build.json | 1 + 28 files changed, 1297 insertions(+), 1 deletion(-) create mode 100644 plugins/features/plugin-media/package.json create mode 100644 plugins/features/plugin-media/rollup.config.ts create mode 100644 plugins/features/plugin-media/src/client/components/ArtPlayer.ts create mode 100644 plugins/features/plugin-media/src/client/components/BiliBili.ts create mode 100644 plugins/features/plugin-media/src/client/components/VidStack.ts create mode 100644 plugins/features/plugin-media/src/client/components/index.ts create mode 100644 plugins/features/plugin-media/src/client/composables/index.ts create mode 100644 plugins/features/plugin-media/src/client/composables/useSize.ts create mode 100644 plugins/features/plugin-media/src/client/index.ts create mode 100644 plugins/features/plugin-media/src/client/styles/art-player.scss create mode 100644 plugins/features/plugin-media/src/client/styles/bili-bili.scss create mode 100644 plugins/features/plugin-media/src/client/styles/vidstack.scss create mode 100644 plugins/features/plugin-media/src/client/utils/getLink.ts create mode 100644 plugins/features/plugin-media/src/client/utils/iframeAllow.ts create mode 100644 plugins/features/plugin-media/src/client/utils/index.ts create mode 100644 plugins/features/plugin-media/src/client/utils/registerMse.ts create mode 100644 plugins/features/plugin-media/src/node/index.ts create mode 100644 plugins/features/plugin-media/src/node/mediaPlugin.ts create mode 100644 plugins/features/plugin-media/src/node/options.ts create mode 100644 plugins/features/plugin-media/src/shared/artplayer.ts create mode 100644 plugins/features/plugin-media/src/shared/index.ts create mode 100644 plugins/features/plugin-media/src/shared/locales.ts create mode 100644 plugins/features/plugin-media/src/shared/share.ts create mode 100644 plugins/features/plugin-media/tsconfig.build.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 45ab58a9cf..62d9e1d6ca 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,10 +25,13 @@ ], "cSpell.words": [ "artalk", + "artplayer", + "bilibili", "bumpp", "commitlint", "composables", "darkmode", + "dashjs", "devtool", "docsearch", "domhandler", @@ -54,6 +57,7 @@ "mdit", "meteorlxy", "mhchem", + "mpegts", "nord", "npmmirror", "nprogress", @@ -82,6 +86,7 @@ "twoslash", "umami", "unmount", + "vidstack", "vuejs", "vuepress", "vueuse", diff --git a/plugins/features/plugin-media/package.json b/plugins/features/plugin-media/package.json new file mode 100644 index 0000000000..a67ee35c69 --- /dev/null +++ b/plugins/features/plugin-media/package.json @@ -0,0 +1,78 @@ +{ + "name": "@vuepress/plugin-media", + "version": "2.0.0-rc.91", + "description": "VuePress plugin - media", + "keywords": [ + "vuepress-plugin", + "vuepress", + "plugin", + "medium", + "zoom", + "image" + ], + "homepage": "https://ecosystem.vuejs.press/plugins/features/media.html", + "bugs": { + "url": "https://github.com/vuepress/ecosystem/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vuepress/ecosystem.git", + "directory": "plugins/features/plugin-media" + }, + "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", + "./package.json": "./package.json" + }, + "main": "./lib/node/index.js", + "types": "./lib/node/index.d.ts", + "files": [ + "lib" + ], + "scripts": { + "build": "tsc -b tsconfig.build.json", + "bundle": "rollup -c rollup.config.ts --configPlugin esbuild", + "clean": "rimraf --glob ./lib ./*.tsbuildinfo", + "copy": "cpx \"src/**/*.css\" lib" + }, + "dependencies": { + "@vuepress/helper": "workspace:*", + "@vueuse/core": "^13.0.0", + "vue": "catalog:" + }, + "peerDependencies": { + "artplayer": "^5.0.0", + "dashjs": "4.7.4", + "hls.js": "^1.4.12", + "mpegts.js": "^1.7.3", + "vidstack": "^1.12.9", + "vuepress": "catalog:" + }, + "peerDependenciesMeta": { + "artplayer": { + "optional": true + }, + "dashjs": { + "optional": true + }, + "hls.js": { + "optional": true + }, + "mpegts.js": { + "optional": true + }, + "vidstack": { + "optional": true + } + }, + "publishConfig": { + "access": "public" + } +} diff --git a/plugins/features/plugin-media/rollup.config.ts b/plugins/features/plugin-media/rollup.config.ts new file mode 100644 index 0000000000..86a1f5ac55 --- /dev/null +++ b/plugins/features/plugin-media/rollup.config.ts @@ -0,0 +1,9 @@ +import { rollupBundle } from '../../../scripts/rollup.js' + +export default [ + ...rollupBundle('node/index'), + ...rollupBundle({ + base: 'client', + files: ['config', 'index'], + }), +] diff --git a/plugins/features/plugin-media/src/client/components/ArtPlayer.ts b/plugins/features/plugin-media/src/client/components/ArtPlayer.ts new file mode 100644 index 0000000000..1fe13947f6 --- /dev/null +++ b/plugins/features/plugin-media/src/client/components/ArtPlayer.ts @@ -0,0 +1,301 @@ +import { LoadingIcon, keys } from '@vuepress/helper/client' +import type Artplayer from 'artplayer' +import type { Option as ArtPlayerInitOptions } from 'artplayer/types/option.js' +import type { PropType, VNode } from 'vue' +import { camelize, defineComponent, h, onMounted, onUnmounted, ref } from 'vue' +import { usePageLang } from 'vuepress/client' + +import type { ArtPlayerOptions } from '../../shared/index.js' +import { useSize } from '../composables/index.js' +import { getLink } from '../utils/getLink.js' +import { + SUPPORTED_VIDEO_TYPES, + getTypeByUrl, + registerMseDash, + registerMseFlv, + registerMseHls, +} from '../utils/registerMse.js' + +import '../styles/art-player.css' + +const BOOLEAN_TRUE_ATTRS = [ + 'no-fullscreen', + 'no-hotkey', + 'no-playback-rate', + 'no-setting', + 'no-mutex', + 'no-plays-inline', +] as const + +const BOOLEAN_FALSE_ATTRS = [ + 'airplay', + 'autoplay', + 'aspect-ratio', + 'auto-mini', + 'auto-size', + 'auto-orientation', + 'auto-playback', + 'fast-forward', + 'flip', + 'fullscreen-web', + 'lock', + 'loop', + 'is-live', + 'muted', + 'mini-progress-bar', + 'pip', + 'screenshot', + 'subtitle-offset', +] as const + +// Note: This should be updated with https://github.com/zhw2590582/ArtPlayer/blob/master/packages/artplayer/src/i18n/index.js +const SUPPORTED_LANG_NAME = [ + 'en', + 'pl', + 'cs', + 'es', + 'fa', + 'fr', + 'id', + 'ru', + 'tr', +] +const SUPPORTED_LANG_CODE = ['zh-cn', 'zh-tw'] + +type KebabCaseToCamelCase< + S extends string, + Cap extends boolean = false, +> = S extends `${infer Head}-${infer Tail}` + ? `${Cap extends true ? Capitalize : Head}${KebabCaseToCamelCase< + Tail, + true + >}` + : Cap extends true + ? Capitalize + : S + +type ArtPlayerBooleanOptionKey = + | (typeof BOOLEAN_FALSE_ATTRS extends readonly (infer T extends string)[] + ? KebabCaseToCamelCase + : never) + | (typeof BOOLEAN_TRUE_ATTRS extends readonly (infer T extends string)[] + ? T extends `no-${infer Key}` + ? KebabCaseToCamelCase + : never + : never) + +declare const ART_PLAYER_OPTIONS: ArtPlayerOptions + +const getLang = (lang: string): string => { + const langCode = lang.toLowerCase() + const [langName] = langCode.split('-') + + return SUPPORTED_LANG_CODE.includes(langCode) + ? langCode + : SUPPORTED_LANG_NAME.includes(langName) + ? langName + : langName === 'zh' + ? 'zh-cn' + : 'en' +} + +export const ArtPlayer = defineComponent({ + name: 'ArtPlayer', + + inheritAttrs: false, + + props: { + /** + * Video Source URL + * + * 视频源文件地址 + */ + src: { + type: String, + required: true, + }, + + /** + * Video Type + * + * 视频类型 + */ + type: String, + + /** + * Video poster + * + * 视频封面 + */ + poster: String, + + /** + * Video title + * + * 视频标题 + */ + title: String, + + /** + * Component width + * + * 组件宽度 + */ + width: { + type: [String, Number], + default: '100%', + }, + + /** + * Component height + * + * 组件高度 + */ + height: [String, Number], + + /** + * Component width / height ratio + * + * 组件长宽比 + */ + ratio: { + type: [String, Number], + default: 16 / 9, + }, + + /** + * ArtPlayer config + * + * ArtPlayer 配置 + */ + config: Object as PropType>, + + /** + * Customize Artplayer + * + * 对 Artplayer 进行自定义 + */ + customPlayer: Function as PropType< + ( + player: Artplayer, + ) => Artplayer | Promise | Promise | void + >, + }, + + setup(props, { attrs }) { + const lang = usePageLang() + const { el, width, height, resize } = useSize(props, 0) + + const loaded = ref(false) + let artPlayerInstance: Artplayer | null = null + + const getInitOptions = (): ArtPlayerInitOptions => { + const initOptions: ArtPlayerInitOptions = { + theme: '#3eaf7c', + ...ART_PLAYER_OPTIONS, + + container: el.value!, + poster: props.poster, + url: getLink(props.src), + type: props.type ?? getTypeByUrl(props.src), + lang: getLang(lang.value), + ...props.config, + // This option must be set false to avoid problems + useSSR: false, + } + + const attrsKeys = keys(attrs) + + BOOLEAN_TRUE_ATTRS.forEach((config) => { + if (attrsKeys.includes(config)) + initOptions[ + camelize(config.replace(/^no-/, '')) as ArtPlayerBooleanOptionKey + ] = false + }) + BOOLEAN_FALSE_ATTRS.forEach((config) => { + if (attrsKeys.includes(config)) + initOptions[camelize(config) as ArtPlayerBooleanOptionKey] = true + }) + + // Auto config mse + if (initOptions.type) { + const customType = (initOptions.customType ??= {}) + + if (SUPPORTED_VIDEO_TYPES.includes(initOptions.type.toLowerCase())) + switch (initOptions.type.toLowerCase()) { + case 'm3u8': + case 'hls': + customType[initOptions.type] ??= ( + video: HTMLVideoElement, + src: string, + player: Artplayer, + ): Promise => + registerMseHls(video, src, (destroy) => { + player.on('destroy', destroy) + }) + break + + case 'flv': + case 'ts': + customType[initOptions.type] ??= ( + video: HTMLVideoElement, + src: string, + player: Artplayer, + ): Promise => + registerMseFlv(video, src, (destroy) => { + player.on('destroy', destroy) + }) + break + + case 'mpd': + case 'dash': + customType[initOptions.type] ??= ( + video: HTMLVideoElement, + src: string, + player: Artplayer, + ): Promise => + registerMseDash(video, src, (destroy) => { + player.on('destroy', destroy) + }) + break + + default: + } + else + // eslint-disable-next-line no-console + console.warn( + `[components]: ArtPlayer does not support current file type ${initOptions.type}!`, + ) + } + + return initOptions + } + + onMounted(async () => { + const { default: Artplayer } = await import( + /* webpackChunkName: "artplayer" */ 'artplayer' + ) + const player = new Artplayer(getInitOptions()) + + artPlayerInstance = (await props.customPlayer?.(player)) ?? player + loaded.value = true + resize() + }) + + onUnmounted(() => { + artPlayerInstance?.destroy() + }) + + return (): (VNode | null)[] => [ + h('div', { + ref: el, + class: 'vp-artplayer', + style: { + width: width.value, + height: height.value, + }, + }), + loaded.value ? null : h(LoadingIcon), + ] + }, +}) diff --git a/plugins/features/plugin-media/src/client/components/BiliBili.ts b/plugins/features/plugin-media/src/client/components/BiliBili.ts new file mode 100644 index 0000000000..993da3bbd5 --- /dev/null +++ b/plugins/features/plugin-media/src/client/components/BiliBili.ts @@ -0,0 +1,147 @@ +import { LoadingIcon } from '@vuepress/helper/client' +import type { VNode } from 'vue' +import { computed, defineComponent, h, ref } from 'vue' + +import { useSize } from '../composables/index.js' +import { videoIframeAllow } from '../utils/index.js' + +import '../styles/bili-bili.css' + +const VIDEO_LINK = 'https://player.bilibili.com/player.html' + +export const BiliBili = defineComponent({ + name: 'BiliBili', + + props: { + /** + * BiliBili video id + * + * B 站视频 ID + */ + bvid: String, + + /** + * BiliBili video aid + * + * B 站视频 a ID + */ + aid: String, + + /** + * BiliBili video cid + * + * B 站视频 CID + */ + cid: String, + + /** + * BiliBili video title + * + * B 站视频标题 + */ + title: { + type: String, + default: 'A BiliBili video', + }, + + /** + * BiliBili video page + * + * B 站视频分页 + */ + page: { + type: [String, Number], + default: 1, + }, + + /** + * Component width + * + * 组件宽度 + */ + width: { + type: [String, Number], + default: '100%', + }, + + /** + * Component height + * + * 组件高度 + */ + height: [String, Number], + + /** + * Component width / height ratio + * + * 组件长宽比 + */ + ratio: { + type: [String, Number], + default: 16 / 9, + }, + + /** + * Start time in seconds + * + * 基于秒数的开始时间 + */ + time: { + type: [String, Number], + default: 0, + }, + + /** + * Whether autoplay + * + * 是否自动播放 + */ + autoplay: Boolean, + }, + + setup(props) { + const { el, width, height, resize } = useSize(props) + + const loaded = ref(false) + + const videoLink = computed(() => { + const { aid, bvid, cid, autoplay, time, page } = props + + return aid && cid + ? `${VIDEO_LINK}?aid=${aid}&cid=${cid}&t=${time}&autoplay=${ + autoplay ? 1 : 0 + }&p=${page}` + : bvid + ? `${VIDEO_LINK}?bvid=${bvid}&t=${time}&autoplay=${autoplay ? 1 : 0}` + : null + }) + + return (): (VNode | null)[] => + videoLink.value + ? [ + h( + 'div', + { class: 'bilibili-desc' }, + h('a', { class: 'sr-only', href: videoLink.value }, props.title), + ), + h('iframe', { + ref: el, + // Tip: `https://www.bilibili.com/blackboard/newplayer.html?bvid=${props.bvid}&as_wide=1&page=1` only support whitelist sites now + src: videoLink.value, + title: props.title, + class: 'bilibili-iframe', + allow: videoIframeAllow, + style: { + width: width.value, + height: loaded.value ? height.value : 0, + }, + onLoad: () => { + loaded.value = true + resize() + }, + }), + loaded.value ? null : h(LoadingIcon), + ] + : [] + }, +}) diff --git a/plugins/features/plugin-media/src/client/components/VidStack.ts b/plugins/features/plugin-media/src/client/components/VidStack.ts new file mode 100644 index 0000000000..12d1d14b16 --- /dev/null +++ b/plugins/features/plugin-media/src/client/components/VidStack.ts @@ -0,0 +1,139 @@ +import type { ExactLocaleConfig } from '@vuepress/helper/client' +import { isArray, isString, useLocaleConfig } from '@vuepress/helper/client' +import type { + DASHNamespaceLoader, + DefaultLayoutProps, + HLSConstructorLoader, + PlayerSrc, + TextTrackInit, +} from 'vidstack' +import type { MediaPlayerElement } from 'vidstack/elements' +import type { VidstackPlayerConfig } from 'vidstack/global/player' +import { VidstackPlayer, VidstackPlayerLayout } from 'vidstack/global/player' +import type { PropType, VNode } from 'vue' +import { defineComponent, h, onBeforeUnmount, onMounted, shallowRef } from 'vue' + +import type { VidstackLocaleData } from '../../shared/index.js' +import { getLink } from '../utils/getLink.js' + +import 'vidstack/player/styles/default/theme.css' +import 'vidstack/player/styles/default/layouts/audio.css' +import 'vidstack/player/styles/default/layouts/video.css' +import '../styles/vidstack.css' + +declare const DASHJS_INSTALLED: boolean +declare const HLS_JS_INSTALLED: boolean +declare const VIDSTACK_LOCALES: ExactLocaleConfig + +export const VidStack = defineComponent({ + name: 'VidStack', + + props: { + /** + * sources + */ + src: { + type: [String, Array, Object] as PropType, + required: true, + }, + + /** + * tracks + */ + tracks: { + type: Array as PropType, + default: () => [], + }, + + /** + * poster + */ + poster: String, + + /** + * thumbnails + */ + thumbnails: String, + + /** + * title + */ + title: String, + + /** + * VidStack player options + */ + player: { + type: Object as PropType< + Omit< + VidstackPlayerConfig, + 'poster' | 'sources' | 'src' | 'target' | 'title' | 'tracks' + > + >, + }, + + /** + * VidStack layout options + */ + layout: { + type: Object as PropType>, + }, + + /** + * Dark mode + */ + darkmode: Boolean, + }, + + setup(props) { + const vidstack = shallowRef() + const locale = useLocaleConfig(VIDSTACK_LOCALES) + + let player: MediaPlayerElement | null = null + + onMounted(async () => { + const options: VidstackPlayerConfig = { + target: vidstack.value!, + crossOrigin: true, + poster: props.poster, + title: props.title, + ...props.player, + layout: new VidstackPlayerLayout({ + colorScheme: props.darkmode ? 'dark' : 'light', + thumbnails: props.thumbnails, + translations: locale.value, + ...props.layout, + }), + } + + options.src = isString(props.src) + ? getLink(props.src) + : isArray(props.src) + ? props.src.map((src) => (isString(src) ? getLink(src) : src)) + : props.src + + if (props.tracks.length) options.tracks = props.tracks + + player = await VidstackPlayer.create(options) + + player.addEventListener('provider-change', () => { + if (player!.provider?.type === 'hls' && HLS_JS_INSTALLED) + player!.provider.library = (() => + import( + /* webpackChunkName: "hls" */ 'hls.js/dist/hls.min.js' + )) as HLSConstructorLoader + else if (player!.provider?.type === 'dash' && DASHJS_INSTALLED) + player!.provider.library = (() => + import( + /* webpackChunkName: "dashjs" */ 'dashjs' + )) as DASHNamespaceLoader + }) + }) + + onBeforeUnmount(() => { + player?.destroy() + }) + + return (): VNode => h('div', { ref: vidstack }) + }, +}) diff --git a/plugins/features/plugin-media/src/client/components/index.ts b/plugins/features/plugin-media/src/client/components/index.ts new file mode 100644 index 0000000000..f6ae2de74b --- /dev/null +++ b/plugins/features/plugin-media/src/client/components/index.ts @@ -0,0 +1,3 @@ +export * from './ArtPlayer.js' +export * from './BiliBili.js' +export * from './VidStack.js' diff --git a/plugins/features/plugin-media/src/client/composables/index.ts b/plugins/features/plugin-media/src/client/composables/index.ts new file mode 100644 index 0000000000..0fc6125277 --- /dev/null +++ b/plugins/features/plugin-media/src/client/composables/index.ts @@ -0,0 +1 @@ +export * from "./useSize.js"; diff --git a/plugins/features/plugin-media/src/client/composables/useSize.ts b/plugins/features/plugin-media/src/client/composables/useSize.ts new file mode 100644 index 0000000000..26993287b9 --- /dev/null +++ b/plugins/features/plugin-media/src/client/composables/useSize.ts @@ -0,0 +1,68 @@ +import { isString } from '@vuepress/helper/client' +import type { MaybeRef } from '@vueuse/core' +import { useEventListener } from '@vueuse/core' +import type { Ref, ShallowRef } from 'vue' +import { computed, isRef, onMounted, ref, shallowRef, unref, watch } from 'vue' + +const getValue = (value: number | string): string => + isString(value) ? value : `${value}px` + +export interface SizeOptions { + width: number | string | undefined + height: number | string | undefined + ratio: number | string | undefined +} + +export interface SizeInfo { + el: ShallowRef + width: Ref + height: Ref + resize: () => void +} + +export const useSize = ( + options: SizeOptions, + extraHeight: MaybeRef = 0, +): SizeInfo => { + const el = shallowRef() + const width = computed(() => getValue(unref(options.width) ?? '100%')) + const height = ref('auto') + + const getRadio = (ratio: number | string | undefined): number => { + if (isString(ratio)) { + const [width, height] = ratio.split(':') + const parsedRadio = Number(width) / Number(height) + + if (!Number.isNaN(parsedRadio)) return parsedRadio + } + + return typeof ratio === 'number' ? ratio : 16 / 9 + } + + const getHeight = (width: number): string => { + const height = unref(options.height) + const ratio = getRadio(unref(options.ratio)) + + return height + ? getValue(height) + : `${Number(width) / ratio + unref(extraHeight)}px` + } + + const updateHeight = (): void => { + if (el.value) height.value = getHeight(el.value.clientWidth) + } + + onMounted(() => { + updateHeight() + if (isRef(extraHeight)) watch(extraHeight, updateHeight) + useEventListener('orientationchange', updateHeight) + useEventListener('resize', updateHeight) + }) + + return { + el, + width, + height, + resize: updateHeight, + } +} diff --git a/plugins/features/plugin-media/src/client/index.ts b/plugins/features/plugin-media/src/client/index.ts new file mode 100644 index 0000000000..166b13ec0e --- /dev/null +++ b/plugins/features/plugin-media/src/client/index.ts @@ -0,0 +1 @@ +export * from './components/index.js' diff --git a/plugins/features/plugin-media/src/client/styles/art-player.scss b/plugins/features/plugin-media/src/client/styles/art-player.scss new file mode 100644 index 0000000000..77f1db9cb3 --- /dev/null +++ b/plugins/features/plugin-media/src/client/styles/art-player.scss @@ -0,0 +1,3 @@ +.vp-artplayer { + text-align: center; +} diff --git a/plugins/features/plugin-media/src/client/styles/bili-bili.scss b/plugins/features/plugin-media/src/client/styles/bili-bili.scss new file mode 100644 index 0000000000..c3cf8afac0 --- /dev/null +++ b/plugins/features/plugin-media/src/client/styles/bili-bili.scss @@ -0,0 +1,15 @@ +.bilibili-desc a { + @media print { + display: block; + } +} + +.bilibili-iframe { + margin: 8px 0; + border: none; + border-radius: 8px; + + @media print { + display: none; + } +} diff --git a/plugins/features/plugin-media/src/client/styles/vidstack.scss b/plugins/features/plugin-media/src/client/styles/vidstack.scss new file mode 100644 index 0000000000..cf13c8c5b5 --- /dev/null +++ b/plugins/features/plugin-media/src/client/styles/vidstack.scss @@ -0,0 +1,15 @@ +.vds-audio-layout, +.vds-video-layout { + box-shadow: 2px 2px 10px 0 var(--vp-c-shadow); + + &, + &.light, + &.dark { + --default-brand: var(--vp-c-accent-bg); + --audio-bg: var(--vp-c-bg); + --default-controls-color: var(--vp-c-text-mute); + --default-focus-ring-color: var(--vp-c-accent-bg); + --default-border-radius: 8px; + --audio-border: 1px solid var(--vp-c-border); + } +} diff --git a/plugins/features/plugin-media/src/client/utils/getLink.ts b/plugins/features/plugin-media/src/client/utils/getLink.ts new file mode 100644 index 0000000000..3e4915bbd9 --- /dev/null +++ b/plugins/features/plugin-media/src/client/utils/getLink.ts @@ -0,0 +1,5 @@ +import { isLinkHttp } from "@vuepress/helper/client"; +import { withBase } from "vuepress/client"; + +export const getLink = (url: string): string => + isLinkHttp(url) ? url : withBase(url); diff --git a/plugins/features/plugin-media/src/client/utils/iframeAllow.ts b/plugins/features/plugin-media/src/client/utils/iframeAllow.ts new file mode 100644 index 0000000000..88b9ca1b3d --- /dev/null +++ b/plugins/features/plugin-media/src/client/utils/iframeAllow.ts @@ -0,0 +1,2 @@ +export const videoIframeAllow = + "accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture"; diff --git a/plugins/features/plugin-media/src/client/utils/index.ts b/plugins/features/plugin-media/src/client/utils/index.ts new file mode 100644 index 0000000000..7cc76e50fc --- /dev/null +++ b/plugins/features/plugin-media/src/client/utils/index.ts @@ -0,0 +1,3 @@ +export * from './iframeAllow.js' +export * from './getLink.js' +export * from './registerMse.js' diff --git a/plugins/features/plugin-media/src/client/utils/registerMse.ts b/plugins/features/plugin-media/src/client/utils/registerMse.ts new file mode 100644 index 0000000000..f441d094d0 --- /dev/null +++ b/plugins/features/plugin-media/src/client/utils/registerMse.ts @@ -0,0 +1,97 @@ +declare const DASHJS_INSTALLED: boolean +declare const HLS_JS_INSTALLED: boolean +declare const MPEGTS_JS_INSTALLED: boolean + +export const SUPPORTED_VIDEO_TYPES = ['mp4', 'mp3', 'webm', 'ogg'] + +if (typeof DASHJS_INSTALLED !== 'undefined' && DASHJS_INSTALLED) + SUPPORTED_VIDEO_TYPES.push('mpd', 'dash') + +if (typeof HLS_JS_INSTALLED !== 'undefined' && HLS_JS_INSTALLED) + SUPPORTED_VIDEO_TYPES.push('m3u8', 'hls') + +if (typeof MPEGTS_JS_INSTALLED !== 'undefined' && MPEGTS_JS_INSTALLED) + SUPPORTED_VIDEO_TYPES.push('ts', 'flv') + +export const getTypeByUrl = (url: string): string => url.split('.').pop() ?? '' + +export const registerMseDash = async ( + mediaElement: HTMLMediaElement, + src: string, + onDestroy: (destroy: () => void) => void, + autoPlay = false, + startTime = 0, +): Promise => { + if (typeof DASHJS_INSTALLED !== 'undefined' && DASHJS_INSTALLED) { + const dashjs = (await import(/* webpackChunkName: "dashjs" */ 'dashjs')) + .default + + if (dashjs.supportsMediaSource()) { + const dashPlayer = dashjs.MediaPlayer().create() + + dashPlayer.initialize(mediaElement, src, autoPlay, startTime) + + onDestroy(() => { + dashPlayer.destroy() + }) + } + } +} + +export const registerMseFlv = async ( + mediaElement: HTMLMediaElement, + src: string, + onDestroy: (destroy: () => void) => void, +): Promise => { + if (typeof MPEGTS_JS_INSTALLED !== 'undefined' && MPEGTS_JS_INSTALLED) { + const mpegts = ( + await import( + /* webpackChunkName: "mpegts.js" */ 'mpegts.js/dist/mpegts.js' + ) + ).default + + if (mpegts.isSupported()) { + const flvPlayer = mpegts.createPlayer({ + type: 'flv', + url: src, + }) + + flvPlayer.attachMediaElement(mediaElement) + flvPlayer.load() + + onDestroy(() => { + flvPlayer.destroy() + }) + } + } +} + +export const registerMseHls = async ( + mediaElement: HTMLMediaElement, + src: string, + onDestroy: (destroy: () => void) => void, +): Promise => { + if ( + mediaElement.canPlayType('application/x-mpegURL') || + mediaElement.canPlayType('application/vnd.apple.mpegURL') + ) { + mediaElement.src = src + } else if (typeof HLS_JS_INSTALLED !== 'undefined' && HLS_JS_INSTALLED) { + const hls = ( + await import(/* webpackChunkName: "hls.js" */ 'hls.js/dist/hls.min.js') + ).default + + if (hls.isSupported()) { + const hlsInstance = new hls() + + hlsInstance.attachMedia(mediaElement) + hlsInstance.on(hls.Events.MEDIA_ATTACHED, () => { + hlsInstance.loadSource(src) + }) + + onDestroy(() => { + hlsInstance.destroy() + }) + } + } +} diff --git a/plugins/features/plugin-media/src/node/index.ts b/plugins/features/plugin-media/src/node/index.ts new file mode 100644 index 0000000000..334250e5a6 --- /dev/null +++ b/plugins/features/plugin-media/src/node/index.ts @@ -0,0 +1,2 @@ +export * from './mediaPlugin.js' +export type * from './options.js' diff --git a/plugins/features/plugin-media/src/node/mediaPlugin.ts b/plugins/features/plugin-media/src/node/mediaPlugin.ts new file mode 100644 index 0000000000..943516c63c --- /dev/null +++ b/plugins/features/plugin-media/src/node/mediaPlugin.ts @@ -0,0 +1,13 @@ +import type { Plugin } from 'vuepress/core' +import { getDirname, path } from 'vuepress/utils' +import type { MediaPluginOptions } from './options.js' + +const __dirname = import.meta.dirname || getDirname(import.meta.url) + +export const mediaPlugin = ({ bilibili }: MediaPluginOptions = {}): Plugin => ({ + name: '@vuepress/plugin-media', + + clientConfigFile: path.resolve(__dirname, '../client/config.js'), + + define: {}, +}) diff --git a/plugins/features/plugin-media/src/node/options.ts b/plugins/features/plugin-media/src/node/options.ts new file mode 100644 index 0000000000..3e4ff29040 --- /dev/null +++ b/plugins/features/plugin-media/src/node/options.ts @@ -0,0 +1,6 @@ +/** + * Options for @vuepress/plugin-media + */ +export interface MediaPluginOptions { + bilibili?: boolean +} diff --git a/plugins/features/plugin-media/src/shared/artplayer.ts b/plugins/features/plugin-media/src/shared/artplayer.ts new file mode 100644 index 0000000000..048283257a --- /dev/null +++ b/plugins/features/plugin-media/src/shared/artplayer.ts @@ -0,0 +1,16 @@ +import type { Option as ArtPlayerInitOptions } from 'artplayer/types/option.js' + +export type ArtPlayerOptions = Partial< + Omit< + ArtPlayerInitOptions, + | 'container' + | 'contextmenu' + | 'controls' + | 'customType' + | 'layers' + | 'plugins' + | 'settings' + | 'type' + | 'url' + > +> diff --git a/plugins/features/plugin-media/src/shared/index.ts b/plugins/features/plugin-media/src/shared/index.ts new file mode 100644 index 0000000000..003e1f0db5 --- /dev/null +++ b/plugins/features/plugin-media/src/shared/index.ts @@ -0,0 +1,3 @@ +export type * from './artplayer.js' +export type * from './locales.js' +export type * from './share.js' diff --git a/plugins/features/plugin-media/src/shared/locales.ts b/plugins/features/plugin-media/src/shared/locales.ts new file mode 100644 index 0000000000..014f764926 --- /dev/null +++ b/plugins/features/plugin-media/src/shared/locales.ts @@ -0,0 +1,27 @@ +import type { DefaultLayoutTranslations } from 'vidstack' + +export interface PDFLocaleData { + /** + * PDF hint text + * + * @description Only used if the browser does not support embedding PDF and no PDFJS URL is provided. + * [url] will be replaced by actual PDF link. + * + * PDF 提示文字 + * + * @description 只有在浏览器不支持嵌入 PDF 且没有提供 PDFJS URL 时才会使用 + * [url] 会被实际 PDF 链接替换 + */ + hint: string +} + +export interface SiteInfoLocaleData { + /** + * Source text + * + * 源代码文字 + */ + source: string +} + +export type VidstackLocaleData = Partial diff --git a/plugins/features/plugin-media/src/shared/share.ts b/plugins/features/plugin-media/src/shared/share.ts new file mode 100644 index 0000000000..155c52b9e2 --- /dev/null +++ b/plugins/features/plugin-media/src/shared/share.ts @@ -0,0 +1,122 @@ +export type ShareAction = 'navigate' | 'open' | 'popup' | 'qrcode' + +export type BuiltInShareService = + | 'buffer' + | 'douban' + | 'email' + | 'evernote' + | 'facebook' + | 'flipboard' + | 'line' + | 'linkedin' + | 'messenger' + | 'pinterest' + | 'qq' + | 'qrcode' + | 'qzone' + | 'reddit' + | 'skype' + | 'sms' + | 'snapchat' + | 'telegram' + | 'tumblr' + | 'twitter' + | 'vk' + | 'weibo' + | 'whatsapp' + | 'wordpress' + +export type ShareServiceVariableName = + | 'cover' + | 'description' + | 'excerpt' + | 'image' + | 'summary' + | 'tags' + | 'title' + | 'twitterUserName' + | 'url' + +export interface ShareServiceConfig { + /** + * Share link + * + * @description You can use `[` and `]` to wrap the variable name, and the variable will be replaced with the value of the page.: + * + * - `title` will be replaced with the title of the page + * - `description` will be replaced with the description of the page + * - `url` will be replaced with the url of the page + * - `summary` will be replaced with the summary of the page + * - `tags` will be replaced with the tags of the page + * - `cover` will be replaced with the cover/banner of the page + * - `image` will be replaced with the first image of the page + * + * 分享链接 + * + * @description 你可以使用 `[` 和 `]` 包裹变量名,变量将会被替换为页面的值: + * + * - `title` 将会被替换为页面的标题 + * - `description` 将会被替换为页面的描述 + * - `url` 将会被替换为页面的链接 + * - `summary` 将会被替换为页面的综述 + * - `tags` 将会被替换为页面的标签 + * - `cover` 将会被替换为页面的封面 + * - `image` 将会被替换为页面的第一张图片 + */ + link: string + + /** + * Action of share button + * + * @description + * - `open` will open the link in a new tab + * - `navigate` will navigate to the link + * - `popup` will open a popup window + * - `qrcode` will show a QR code with link + * + * 分享按钮的行为 + * + * @description + * - `open` 将会在新标签页打开链接 + * - `navigate` 将会跳转到链接 + * - `popup` 将会打开一个弹窗 + * - `qrcode` 将会显示一个二维码 + * + * @default "popup" + */ + action?: ShareAction + + /** + * Theme color of icon + * + * 图标的主题色 + * + * @default 'currentColor' + */ + color?: string + + /** + * Plain icon shape + * + * 纯色图标的形状 + */ + shape: string + + /** + * Colorful icon + * + * 彩色图标 + */ + icon?: string +} + +export interface ShareServiceOptions extends ShareServiceConfig { + /** + * Service name + * + * 服务名称 + */ + name: string +} + +export type ShareService = BuiltInShareService | ShareServiceOptions diff --git a/plugins/features/plugin-media/tsconfig.build.json b/plugins/features/plugin-media/tsconfig.build.json new file mode 100644 index 0000000000..f7f7fe795a --- /dev/null +++ b/plugins/features/plugin-media/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "../../../tsconfig.build.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./lib", + "types": ["vuepress/client-types", "vite/client", "webpack-env"] + }, + "include": ["./src"], + "references": [{ "path": "../../../tools/helper/tsconfig.build.json" }] +} diff --git a/plugins/features/plugin-medium-zoom/src/node/mediumZoomPlugin.ts b/plugins/features/plugin-medium-zoom/src/node/mediumZoomPlugin.ts index 55aa149bb2..d36188c368 100644 --- a/plugins/features/plugin-medium-zoom/src/node/mediumZoomPlugin.ts +++ b/plugins/features/plugin-medium-zoom/src/node/mediumZoomPlugin.ts @@ -1,6 +1,6 @@ import type { Plugin } from 'vuepress/core' import { getDirname, path } from 'vuepress/utils' -import type { MediumZoomPluginOptions } from './options' +import type { MediumZoomPluginOptions } from './options.js' const __dirname = import.meta.dirname || getDirname(import.meta.url) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index df0f7f1078..ff6ec43952 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -603,6 +603,36 @@ importers: specifier: 'catalog:' version: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.15)(jiti@2.4.2)(lightningcss@1.29.3)(sass-embedded@1.86.0)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.2)(yaml@2.7.0))(@vuepress/bundler-webpack@2.0.0-rc.20(esbuild@0.25.2)(typescript@5.8.2))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)) + plugins/features/plugin-media: + dependencies: + '@vuepress/helper': + specifier: workspace:* + version: link:../../../tools/helper + '@vueuse/core': + specifier: ^13.0.0 + version: 13.0.0(vue@3.5.13(typescript@5.8.2)) + artplayer: + specifier: ^5.0.0 + version: 5.2.2 + dashjs: + specifier: 4.7.4 + version: 4.7.4 + hls.js: + specifier: ^1.4.12 + version: 1.6.0 + mpegts.js: + specifier: ^1.7.3 + version: 1.8.0 + vidstack: + specifier: ^1.12.9 + version: 1.12.13 + vue: + specifier: 'catalog:' + version: 3.5.13(typescript@5.8.2) + vuepress: + specifier: 'catalog:' + version: 2.0.0-rc.20(@vuepress/bundler-vite@2.0.0-rc.20(@types/node@22.13.15)(jiti@2.4.2)(lightningcss@1.29.3)(sass-embedded@1.86.0)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)(typescript@5.8.2)(yaml@2.7.0))(@vuepress/bundler-webpack@2.0.0-rc.20(esbuild@0.25.2)(typescript@5.8.2))(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)) + plugins/features/plugin-medium-zoom: dependencies: '@vuepress/helper': @@ -2297,6 +2327,9 @@ packages: '@floating-ui/dom@1.1.1': resolution: {integrity: sha512-TpIO93+DIujg3g7SykEAGZMDtbJRrmnYRCNYSjJlvIbGhBjRSNTLVbNeDQBrzy9qDgUbiWdc7KA0uZHZ2tJmiw==} + '@floating-ui/dom@1.6.13': + resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==} + '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} @@ -3942,6 +3975,9 @@ packages: peerDependencies: marked: ^14.1.0 + artplayer@5.2.2: + resolution: {integrity: sha512-JsPRA9e+7KyWTh5KBeKLTcCigzGhpZulRFXvOeiFMbxnpPz9kZ/1STp1LZONqQXHkxX9wHTO6JhGOvzrduH2Lw==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -4006,6 +4042,15 @@ packages: batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + bcp-47-match@1.0.3: + resolution: {integrity: sha512-LggQ4YTdjWQSKELZF5JwchnBa1u0pIQSZf5lSdOHEdbVP55h0qICA/FUp3+W99q0xqxYa1ZQizTUH87gecII5w==} + + bcp-47-normalize@1.1.1: + resolution: {integrity: sha512-jWZ1Jdu3cs0EZdfCkS0UE9Gg01PtxnChjEBySeB+Zo6nkqtFfnvtoQQgP1qU1Oo4qgJgxhTI6Sf9y/pZIhPs0A==} + + bcp-47@1.0.8: + resolution: {integrity: sha512-Y9y1QNBBtYtv7hcmoX0tR+tUNSFZGZ6OL6vKPObq8BbOhkCoyayF6ogfLTgAli/KuAEbsYHYUNq2AQuY6IuLag==} + before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -4217,6 +4262,9 @@ packages: resolution: {integrity: sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw==} engines: {node: ^18.17.0 || >=20.5.0} + codem-isoboxer@0.3.9: + resolution: {integrity: sha512-4XOTqEzBWrGOZaMd+sTED2hLpzfBbiQCf1W6OBGkIHqk1D8uwy8WFLazVbdQwfDpQ+vf39lqTGPa9IhWW0roTA==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -4556,6 +4604,9 @@ packages: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} + dashjs@4.7.4: + resolution: {integrity: sha512-+hldo25QPP3H/NOwqUrvt4uKdMse60/Gsz9AUAnoYfhga8qHWq4nWiojUosOiigbigkDTCAn9ORcvUaKCvmfCA==} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -4845,6 +4896,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es6-promise@4.2.8: + resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} + esbuild-loader@4.3.0: resolution: {integrity: sha512-D7HeJNdkDKKMarPQO/3dlJT6RwN2YJO7ENU6RPlpOz5YxSHnUNi2yvW41Bckvi1EVwctIaLzlb0ni5ag2GINYA==} peerDependencies: @@ -5030,6 +5084,9 @@ packages: fast-content-type-parse@2.0.1: resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -5404,6 +5461,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + hls.js@1.6.0: + resolution: {integrity: sha512-AlW8ymcDKZuKtzXCUmEy4nOcHRkebnShH6t6hC2+QJQP0WXlTUSSO9Kp22uSEYdCgpwkXEJsfOhqxrgO2tDctQ==} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -5425,6 +5485,9 @@ packages: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} + html-entities@1.4.0: + resolution: {integrity: sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -5557,6 +5620,9 @@ packages: resolution: {integrity: sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + immutable@5.1.1: resolution: {integrity: sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==} @@ -5572,6 +5638,9 @@ packages: import-meta-resolve@4.1.0: resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==} + imsc@1.1.5: + resolution: {integrity: sha512-V8je+CGkcvGhgl2C1GlhqFFiUOIEdwXbXLiu1Fcubvvbo+g9inauqT3l0pNYXGoLPBj3jxtZz9t+wCopMkwadQ==} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -6013,6 +6082,9 @@ packages: resolution: {integrity: sha512-c+cBWLWXafHzmSEQwRVKjHP6KkWntvqvAAT83agwmWrOwRpEXWDtiIlkopwzPcLRau6BcS6BwOttTlAWboH3BQ==} engines: {node: ^20.17.0 || >=22.9.0} + lie@3.1.1: + resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + lightningcss-darwin-arm64@1.29.3: resolution: {integrity: sha512-fb7raKO3pXtlNbQbiMeEu8RbBVHnpyqAoxTyTRMEWFQWmscGC2wZxoHzZ+YKAepUuKT9uIW5vL2QbFivTgprZg==} engines: {node: '>= 12.0.0'} @@ -6094,6 +6166,9 @@ packages: lit-element@4.1.1: resolution: {integrity: sha512-HO9Tkkh34QkTeUmEdNYhMT8hzLid7YlMlATSi1q4q17HE5d9mrrEHJ/o8O2D0cMi182zK1F3v7x0PWFjrhXFew==} + lit-html@2.8.0: + resolution: {integrity: sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==} + lit-html@3.2.1: resolution: {integrity: sha512-qI/3lziaPMSKsrwlxH/xMgikhQ0EGOX2ICU73Bi/YHFvz2j/yMCIrw4+puF2IpQ4+upd3EWbvnHM9+PnJn48YA==} @@ -6112,6 +6187,9 @@ packages: resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} engines: {node: '>=8.9.0'} + localforage@1.10.0: + resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -6306,6 +6384,10 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} + media-captions@1.0.4: + resolution: {integrity: sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==} + engines: {node: '>=16'} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -6520,6 +6602,9 @@ packages: engines: {node: '>=10'} hasBin: true + mpegts.js@1.8.0: + resolution: {integrity: sha512-ZtujqtmTjWgcDDkoOnLvrOKUTO/MKgLHM432zGDI8oPaJ0S+ebPxg1nEpDpLw6I7KmV/GZgUIrfbWi3qqEircg==} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -6704,6 +6789,9 @@ packages: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true + option-validator@2.0.6: + resolution: {integrity: sha512-tmZDan2LRIRQyhUGvkff68/O0R8UmF+Btmiiz0SmSw2ng3CfPZB9wJlIjHpe/MKUZqyIZkVIXCrwr1tIN+0Dzg==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -7644,6 +7732,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + sax@1.2.1: + resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} + sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} @@ -8293,6 +8384,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + ua-parser-js@1.0.40: + resolution: {integrity: sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==} + hasBin: true + uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} @@ -8385,6 +8480,10 @@ packages: resolution: {integrity: sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==} engines: {node: '>=18.12.0'} + unplugin@1.16.1: + resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==} + engines: {node: '>=14.0.0'} + unrs-resolver@1.3.3: resolution: {integrity: sha512-PFLAGQzYlyjniXdbmQ3dnGMZJXX5yrl2YS4DLRfR3BhgUsE1zpRIrccp9XMOGRfIHpdFvCn/nr5N1KMVda4x3A==} @@ -8446,6 +8545,10 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vidstack@1.12.13: + resolution: {integrity: sha512-vuNeyRmWoH/7EoFVDYjp9nkgcqtCMmal518LDeb78dYKgWb+p6+vtY0AzDhrkBv5q1UiCn+xwmjMmwvSlPLuhQ==} + engines: {node: '>=18'} + vite-node@3.1.1: resolution: {integrity: sha512-V+IxPAE2FvXpTCHXyNem0M+gWm6J7eRyWPR6vYoG/Gl+IscNOjXzztUhimQgTxaAoUoj40Qqimaa0NLIOOAH4w==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -8626,6 +8729,9 @@ packages: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + webpack@5.98.0: resolution: {integrity: sha512-UFynvx+gM44Gv9qFgj0acCQK2VE1CtdfwFdimkapco3hlPCJ/zeq73n2yVKimVbtm+TnApIugGhLJnkU6gjYXA==} engines: {node: '>=10.13.0'} @@ -8644,6 +8750,10 @@ packages: resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} engines: {node: '>=0.8.0'} + webworkify-webpack@https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef: + resolution: {tarball: https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef} + version: 2.1.5 + whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} @@ -9929,6 +10039,11 @@ snapshots: dependencies: '@floating-ui/core': 1.6.9 + '@floating-ui/dom@1.6.13': + dependencies: + '@floating-ui/core': 1.6.9 + '@floating-ui/utils': 0.2.9 + '@floating-ui/utils@0.2.9': {} '@humanfs/core@0.19.1': {} @@ -11920,6 +12035,10 @@ snapshots: dependencies: marked: 15.0.7 + artplayer@5.2.2: + dependencies: + option-validator: 2.0.6 + assertion-error@2.0.1: {} astral-regex@2.0.0: {} @@ -11982,6 +12101,19 @@ snapshots: batch@0.6.1: {} + bcp-47-match@1.0.3: {} + + bcp-47-normalize@1.1.1: + dependencies: + bcp-47: 1.0.8 + bcp-47-match: 1.0.3 + + bcp-47@1.0.8: + dependencies: + is-alphabetical: 1.0.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + before-after-hook@3.0.2: {} big.js@5.2.2: {} @@ -12224,6 +12356,8 @@ snapshots: cmd-shim@7.0.0: {} + codem-isoboxer@0.3.9: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -12587,6 +12721,19 @@ snapshots: dargs@8.1.0: {} + dashjs@4.7.4: + dependencies: + bcp-47-match: 1.0.3 + bcp-47-normalize: 1.1.1 + codem-isoboxer: 0.3.9 + es6-promise: 4.2.8 + fast-deep-equal: 2.0.1 + html-entities: 1.4.0 + imsc: 1.1.5 + localforage: 1.10.0 + path-browserify: 1.0.1 + ua-parser-js: 1.0.40 + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -12884,6 +13031,8 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es6-promise@4.2.8: {} + esbuild-loader@4.3.0(webpack@5.98.0(esbuild@0.25.2)): dependencies: esbuild: 0.25.2 @@ -13189,6 +13338,8 @@ snapshots: fast-content-type-parse@2.0.1: {} + fast-deep-equal@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -13595,6 +13746,8 @@ snapshots: he@1.2.0: {} + hls.js@1.6.0: {} + hookable@5.5.3: {} hookified@1.8.1: {} @@ -13618,6 +13771,8 @@ snapshots: dependencies: whatwg-encoding: 2.0.0 + html-entities@1.4.0: {} + html-escaper@2.0.2: {} html-minifier-terser@6.1.0: @@ -13773,6 +13928,8 @@ snapshots: ignore@7.0.3: {} + immediate@3.0.6: {} + immutable@5.1.1: {} import-fresh@3.3.1: @@ -13787,6 +13944,10 @@ snapshots: import-meta-resolve@4.1.0: {} + imsc@1.1.5: + dependencies: + sax: 1.2.1 + imurmurhash@0.1.4: {} index-to-position@1.0.0: {} @@ -14192,6 +14353,10 @@ snapshots: transitivePeerDependencies: - supports-color + lie@3.1.1: + dependencies: + immediate: 3.0.6 + lightningcss-darwin-arm64@1.29.3: optional: true @@ -14251,6 +14416,10 @@ snapshots: '@lit/reactive-element': 2.0.4 lit-html: 3.2.1 + lit-html@2.8.0: + dependencies: + '@types/trusted-types': 2.0.7 + lit-html@3.2.1: dependencies: '@types/trusted-types': 2.0.7 @@ -14271,6 +14440,10 @@ snapshots: emojis-list: 3.0.0 json5: 2.2.3 + localforage@1.10.0: + dependencies: + lie: 3.1.1 + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -14541,6 +14714,8 @@ snapshots: mdurl@2.0.0: {} + media-captions@1.0.4: {} + media-typer@0.3.0: {} medium-zoom@1.1.0: {} @@ -14800,6 +14975,11 @@ snapshots: mkdirp@3.0.1: {} + mpegts.js@1.8.0: + dependencies: + es6-promise: 4.2.8 + webworkify-webpack: https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef + ms@2.0.0: {} ms@2.1.3: {} @@ -14993,6 +15173,10 @@ snapshots: opener@1.5.2: {} + option-validator@2.0.6: + dependencies: + kind-of: 6.0.3 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -15892,6 +16076,8 @@ snapshots: optionalDependencies: '@parcel/watcher': 2.5.1 + sax@1.2.1: {} + sax@1.4.1: {} schema-utils@4.3.0: @@ -16668,6 +16854,8 @@ snapshots: typescript@5.8.2: {} + ua-parser-js@1.0.40: {} + uc.micro@2.1.0: {} uglify-js@3.19.3: @@ -16753,6 +16941,11 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.2 + unplugin@1.16.1: + dependencies: + acorn: 8.14.1 + webpack-virtual-modules: 0.6.2 + unrs-resolver@1.3.3: optionalDependencies: '@unrs/resolver-binding-darwin-arm64': 1.3.3 @@ -16818,6 +17011,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.2 + vidstack@1.12.13: + dependencies: + '@floating-ui/dom': 1.6.13 + lit-html: 2.8.0 + media-captions: 1.0.4 + unplugin: 1.16.1 + vite-node@3.1.1(@types/node@22.13.15)(jiti@2.4.2)(lightningcss@1.29.3)(sass-embedded@1.86.0)(sass@1.86.0)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0): dependencies: cac: 6.7.14 @@ -17037,6 +17237,8 @@ snapshots: webpack-sources@3.2.3: {} + webpack-virtual-modules@0.6.2: {} + webpack@5.98.0(esbuild@0.25.2): dependencies: '@types/eslint-scope': 3.7.7 @@ -17075,6 +17277,8 @@ snapshots: websocket-extensions@0.1.4: {} + webworkify-webpack@https://codeload.github.com/xqq/webworkify-webpack/tar.gz/24d1e719b4a6cac37a518b2bb10fe124527ef4ef: {} + whatwg-encoding@2.0.0: dependencies: iconv-lite: 0.6.3 diff --git a/tsconfig.build.json b/tsconfig.build.json index 1726530867..50ea2caa33 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -71,6 +71,7 @@ { "path": "./plugins/features/plugin-copy-code/tsconfig.build.json" }, { "path": "./plugins/features/plugin-copyright/tsconfig.build.json" }, { "path": "./plugins/features/plugin-icon/tsconfig.build.json" }, + { "path": "./plugins/features/plugin-media/tsconfig.build.json" }, { "path": "./plugins/features/plugin-medium-zoom/tsconfig.build.json" }, { "path": "./plugins/features/plugin-notice/tsconfig.build.json" }, { "path": "./plugins/features/plugin-nprogress/tsconfig.build.json" }, From 575caf681367cdd77ccc78f478f44557fdc6f351 Mon Sep 17 00:00:00 2001 From: Mister-Hope Date: Thu, 5 Jun 2025 22:58:03 +0800 Subject: [PATCH 2/2] chore: tweaks --- .../features/plugin-media/src/client/composables/useSize.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/features/plugin-media/src/client/composables/useSize.ts b/plugins/features/plugin-media/src/client/composables/useSize.ts index 26993287b9..b3274946aa 100644 --- a/plugins/features/plugin-media/src/client/composables/useSize.ts +++ b/plugins/features/plugin-media/src/client/composables/useSize.ts @@ -43,9 +43,7 @@ export const useSize = ( const height = unref(options.height) const ratio = getRadio(unref(options.ratio)) - return height - ? getValue(height) - : `${Number(width) / ratio + unref(extraHeight)}px` + return height ? getValue(height) : `${width / ratio + unref(extraHeight)}px` } const updateHeight = (): void => {