一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
111 行
3.7 KiB
TypeScript
111 行
3.7 KiB
TypeScript
import type * as MarkdownIt from 'markdown-it';
|
|
|
|
const extractVideoId = (url: string) => {
|
|
const pattern = /^https?:\/\/(?:www\.|m\.)?(?:youtube\.com\/watch\?(?:.*&)?v=|youtu\.be\/|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/;
|
|
const match = url.match(pattern);
|
|
return match ? match[1] : null;
|
|
};
|
|
|
|
const plugin = (markdownIt: MarkdownIt) => {
|
|
const defaultLinkOpenRender = markdownIt.renderer.rules.link_open || function(tokens, idx, options, env, self) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- markdown-it's renderToken signature is (tokens, idx, options) but this fallback passes (tokens, idx, options, env, self); the extra args are unused but kept for parity with rules that override renderToken
|
|
return (self.renderToken as any)(tokens, idx, options, env, self);
|
|
};
|
|
|
|
const defaultTextRender = markdownIt.renderer.rules.text || function(tokens, idx) {
|
|
return tokens[idx].content;
|
|
};
|
|
|
|
const defaultLinkCloseRender = markdownIt.renderer.rules.link_close || function(tokens, idx, options, env, self) {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- markdown-it's renderToken signature is (tokens, idx, options) but this fallback passes (tokens, idx, options, env, self); the extra args are unused but kept for parity with rules that override renderToken
|
|
return (self.renderToken as any)(tokens, idx, options, env, self);
|
|
};
|
|
|
|
// Track active embed state
|
|
let activeEmbedVideo: { videoId: string; originalUrl: string } | null = null;
|
|
|
|
markdownIt.renderer.rules.link_open = function(tokens, idx, options, env, self) {
|
|
const token = tokens[idx];
|
|
const href = token.attrGet('href');
|
|
|
|
// Check if this is a standalone YouTube link (next token is text matching href, then link_close)
|
|
// and that the link is the only content on the line (tokens array contains only link_open, text, link_close)
|
|
if (href &&
|
|
idx === 0 &&
|
|
tokens.length === 3 &&
|
|
tokens[idx + 1].type === 'text' &&
|
|
tokens[idx + 1].content === href &&
|
|
tokens[idx + 2].type === 'link_close') {
|
|
|
|
const videoId = extractVideoId(href);
|
|
|
|
if (videoId) {
|
|
activeEmbedVideo = { videoId, originalUrl: href };
|
|
return '';
|
|
}
|
|
}
|
|
|
|
return defaultLinkOpenRender(tokens, idx, options, env, self);
|
|
};
|
|
|
|
markdownIt.renderer.rules.text = function(tokens, idx, options, env, self) {
|
|
// Skip text content if we're in an active embed
|
|
if (activeEmbedVideo) {
|
|
return '';
|
|
}
|
|
return defaultTextRender(tokens, idx, options, env, self);
|
|
};
|
|
|
|
markdownIt.renderer.rules.link_close = function(tokens, idx, options, env, self) {
|
|
// Check if we have an active embed to close
|
|
if (activeEmbedVideo) {
|
|
const videoId = activeEmbedVideo.videoId;
|
|
const originalUrl = activeEmbedVideo.originalUrl;
|
|
activeEmbedVideo = null; // Clear state
|
|
|
|
const embedUrl = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(videoId)}`;
|
|
const escapedUrl = markdownIt.utils.escapeHtml(originalUrl);
|
|
|
|
return `
|
|
<div class="joplin-editable">
|
|
<span class="joplin-source" hidden data-joplin-source-open="" data-joplin-source-close="">${escapedUrl}</span>
|
|
<div class="joplin-youtube-player-rendered">
|
|
<iframe src="${embedUrl}" title="YouTube video player" frameborder="0" allowfullscreen></iframe>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return defaultLinkCloseRender(tokens, idx, options, env, self);
|
|
};
|
|
};
|
|
|
|
const assets = () => {
|
|
return [
|
|
{
|
|
inline: true,
|
|
mime: 'text/css',
|
|
text: `
|
|
.joplin-youtube-player-rendered {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
}
|
|
.joplin-youtube-player-rendered iframe {
|
|
width: 100%;
|
|
aspect-ratio: 16 / 9;
|
|
}
|
|
`,
|
|
},
|
|
].map(e => {
|
|
return {
|
|
source: 'youtube',
|
|
...e,
|
|
};
|
|
});
|
|
};
|
|
|
|
export default {
|
|
plugin,
|
|
assets,
|
|
};
|