feat(media): add support for video and audio elements in markdown

This commit is contained in:
purocean
2025-01-02 16:16:39 +08:00
parent 5b16b97840
commit f823def342
5 changed files with 58 additions and 5 deletions
@@ -348,13 +348,29 @@ body.find-in-preview-highlight ::selection {
cursor: zoom-in;
}
p > img[auto-center] {
video {
max-width: 100%;
}
audio {
max-width: 100%;
width: 300px;
height: 42px;
}
p > img[auto-center],
p > video[auto-center],
p > audio[auto-center] {
display: block;
margin-left: auto;
margin-right: auto;
}
img {
p > audio[auto-center] {
width: 100%;
}
img, video {
&.inline,
&[src*=".inline"],
&[origin-src*=".inline"] {
+12 -1
View File
@@ -10,6 +10,8 @@ import type { Components, Doc, ParseLinkResult, PathItem, PositionState, Resourc
const RE_POS = /:([0-9]+),?([0-9]+)?$/
const RE_EXTERNAL_LINK = /^[a-zA-Z_+-]{1,}:/
const RE_VIDEO = /\.(mp4|webm|ogg)$/
const RE_AUDIO = /\.(mp3|wav|ogg)$/
export function isAnchorToken (token: Token) {
return token.tag === 'a'
@@ -116,7 +118,16 @@ export function convertResourceState (currentFile: PathItem, state: StateCore, b
name: fileName,
}
token.attrSet(DOM_ATTR_NAME.LOCAL_IMAGE, 'true')
if (RE_VIDEO.test(fileName)) {
token.tag = 'video'
token.type = 'media'
} else if (RE_AUDIO.test(fileName)) {
token.tag = 'audio'
token.type = 'media'
} else {
token.attrSet(DOM_ATTR_NAME.LOCAL_IMAGE, 'true')
}
token.attrSet(DOM_ATTR_NAME.TARGET_PATH, filePath)
token.attrSet(DOM_ATTR_NAME.TARGET_REPO, repo)
token.attrSet(originAttr, attrVal)
@@ -11,6 +11,15 @@ const attrNameReg = /^[a-zA-Z_:][a-zA-Z0-9:._-]*$/
const attrEventReg = /^on/i
const defaultRules = {} as any
function onLeavepictureinpicture (e: Event) {
const target = e.target as HTMLMediaElement
if (!target.isConnected) {
target.pause()
} else {
(target as any).scrollIntoViewIfNeeded()
}
}
function validateAttrName (name: string) {
return attrNameReg.test(name) && !attrEventReg.test(name)
}
@@ -169,6 +178,16 @@ defaultRules.image = function (tokens: Token[], idx: number, options: any, env:
}, [])
}
defaultRules.media = function (tokens: Token[], idx: number, _options: any, _env: any, slf: Renderer) {
const token = tokens[idx]
return createVNode(token.tag, {
controlsList: 'nodownload',
controls: true,
onLeavepictureinpicture,
...slf.renderAttrs(token) as any,
}, [])
}
defaultRules.hardbreak = function () {
return createVNode('br')
}
+8 -1
View File
@@ -56,11 +56,18 @@ const MediaPlayer = defineComponent({
}
})
function onLeavepictureinpicture (e: Event) {
const target = e.target as HTMLMediaElement
if (!target.isConnected) {
target.pause()
}
}
return () => src.value ? h(
'div',
{ style: 'width: 100%; height: 100%; display: flex; align-items: center; justify-content: center' },
type.value === 'video'
? h('video', { src: src.value, controls: true, disablePictureInPicture: true, style: 'width: 100%; height: 100%; object-fit: contain' })
? h('video', { src: src.value, controls: true, onLeavepictureinpicture, style: 'width: 100%; height: 100%; object-fit: contain' })
: type.value === 'audio'
? h('audio', { src: src.value, controls: true })
: ''
+1 -1
View File
@@ -10,7 +10,7 @@ export default {
tokens[idx + 1]?.type === 'inline' &&
tokens[idx + 2]?.type === 'paragraph_close' &&
tokens[idx + 1]?.children?.length === 1 &&
tokens[idx + 1]?.children?.[0]?.type === 'image'
(tokens[idx + 1]?.children?.[0]?.type === 'image' || tokens[idx + 1]?.children?.[0]?.type === 'media')
) {
tokens[idx + 1]?.children?.[0].attrSet(ctx.args.DOM_ATTR_NAME.ONLY_CHILD, 'true')
}