-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(plugin-md-power): add
@[youtube](url)
syntax supported
- Loading branch information
1 parent
0715ee9
commit b8379e2
Showing
2 changed files
with
151 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<script setup lang="ts"> | ||
import { toRefs } from 'vue' | ||
import { useSize } from '../composables/size.js' | ||
const props = defineProps<{ | ||
src: string | ||
title: string | ||
width?: string | ||
height?: string | ||
ratio?: string | ||
}>() | ||
const IFRAME_ALLOW = 'accelerometer; autoplay; clipboard-write; encrypted-media; fullscreen; gyroscope; picture-in-picture' | ||
const options = toRefs(props) | ||
const { el, width, height, resize } = useSize(options) | ||
function onLoad() { | ||
resize() | ||
} | ||
</script> | ||
|
||
<template> | ||
<ClientOnly> | ||
<iframe | ||
ref="el" | ||
class="video_youtube_iframe" | ||
:src="src" | ||
:title="title || 'Youtube'" | ||
:style="{ width, height }" | ||
:allow="IFRAME_ALLOW" | ||
@load="onLoad" | ||
/> | ||
</ClientOnly> | ||
</template> | ||
|
||
<style> | ||
.video_youtube_iframe { | ||
width: 100%; | ||
margin: 16px auto; | ||
border: none; | ||
border-radius: 5px; | ||
} | ||
</style> |
106 changes: 106 additions & 0 deletions
106
plugins/plugin-md-power/src/node/features/video/youtube.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/** | ||
* @[youtube](id) | ||
*/ | ||
import { URLSearchParams } from 'node:url' | ||
import type { PluginWithOptions } from 'markdown-it' | ||
import type { RuleBlock } from 'markdown-it/lib/parser_block.js' | ||
import type { YoutubeTokenMeta } from '../../../shared/video.js' | ||
import { resolveAttrs } from '../../utils/resolveAttrs.js' | ||
import { parseRect } from '../../utils/parseRect.js' | ||
import { timeToSeconds } from '../../utils/timeToSeconds.js' | ||
|
||
const YOUTUBE_LINK = 'https://www.youtube.com/embed/' | ||
|
||
// @[youtube]() | ||
const MIN_LENGTH = 13 | ||
|
||
// char codes of '@[youtube' | ||
const START_CODES = [64, 91, 121, 111, 117, 116, 117, 98, 101] | ||
|
||
// regexp to match the import syntax | ||
const SYNTAX_RE = /^@\[youtube(?:\s+([^]*?))?\]\(([^)]*)\)/ | ||
|
||
function createYoutubeRuleBlock(): RuleBlock { | ||
return (state, startLine, endLine, silent) => { | ||
const pos = state.bMarks[startLine] + state.tShift[startLine] | ||
const max = state.eMarks[startLine] | ||
|
||
// return false if the length is shorter than min length | ||
if (pos + MIN_LENGTH > max) | ||
return false | ||
|
||
// check if it's matched the start | ||
for (let i = 0; i < START_CODES.length; i += 1) { | ||
if (state.src.charCodeAt(pos + i) !== START_CODES[i]) | ||
return false | ||
} | ||
|
||
// check if it's matched the syntax | ||
const match = state.src.slice(pos, max).match(SYNTAX_RE) | ||
if (!match) | ||
return false | ||
|
||
// return true as we have matched the syntax | ||
if (silent) | ||
return true | ||
|
||
const [, info = '', id = ''] = match | ||
|
||
const { attrs } = resolveAttrs(info) | ||
|
||
const meta: YoutubeTokenMeta = { | ||
id, | ||
autoplay: attrs.autoplay ?? false, | ||
loop: attrs.loop ?? false, | ||
start: timeToSeconds(attrs.start), | ||
end: timeToSeconds(attrs.end), | ||
title: attrs.title, | ||
width: attrs.width ? parseRect(attrs.width) : '100%', | ||
height: attrs.height ? parseRect(attrs.height) : '', | ||
ratio: attrs.ratio ? parseRect(attrs.ratio) : '', | ||
} | ||
|
||
const token = state.push('video_youtube', '', 0) | ||
|
||
token.meta = meta | ||
token.map = [startLine, startLine + 1] | ||
token.info = info | ||
|
||
state.line = startLine + 1 | ||
|
||
return true | ||
} | ||
} | ||
|
||
function resolveYoutube(meta: YoutubeTokenMeta): string { | ||
const params = new URLSearchParams() | ||
|
||
meta.autoplay && params.set('autoplay', '1') | ||
meta.loop && params.set('loop', '1') | ||
meta.start && params.set('start', meta.start.toString()) | ||
meta.end && params.set('end', meta.end.toString()) | ||
|
||
const source = `${YOUTUBE_LINK}/${meta.id}?${params.toString()}` | ||
|
||
return `<VideoYoutube src="${source}" width="${meta.width}" height="${meta.height}" ratio="${meta.ratio}" title="${meta.title}" />` | ||
} | ||
|
||
export const youtubePlugin: PluginWithOptions<never> = (md) => { | ||
md.block.ruler.before( | ||
'import_code', | ||
'video_youtube', | ||
createYoutubeRuleBlock(), | ||
{ | ||
alt: ['paragraph', 'reference', 'blockquote', 'list'], | ||
}, | ||
) | ||
|
||
md.renderer.rules.video_youtube = (tokens, index) => { | ||
const token = tokens[index] | ||
|
||
const content = resolveYoutube(token.meta) | ||
token.content = content | ||
|
||
return content | ||
} | ||
} |