文件
mynode/packages/renderer/MdToHtml/rules/externalEmbed.ts
chen 301194963b
一些检查失败了
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
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

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,
};