You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-01-02 00:08:04 +02:00
Compare commits
2 Commits
dev
...
youtube_em
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a81cc7b60 | ||
|
|
1b7ae5b460 |
@@ -1803,6 +1803,7 @@ packages/renderer/MdToHtml/renderMedia.js
|
||||
packages/renderer/MdToHtml/rules/abc.js
|
||||
packages/renderer/MdToHtml/rules/checkbox.js
|
||||
packages/renderer/MdToHtml/rules/code_inline.js
|
||||
packages/renderer/MdToHtml/rules/external_embed.js
|
||||
packages/renderer/MdToHtml/rules/fence.js
|
||||
packages/renderer/MdToHtml/rules/fountain.js
|
||||
packages/renderer/MdToHtml/rules/highlight_keywords.js
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1776,6 +1776,7 @@ packages/renderer/MdToHtml/renderMedia.js
|
||||
packages/renderer/MdToHtml/rules/abc.js
|
||||
packages/renderer/MdToHtml/rules/checkbox.js
|
||||
packages/renderer/MdToHtml/rules/code_inline.js
|
||||
packages/renderer/MdToHtml/rules/external_embed.js
|
||||
packages/renderer/MdToHtml/rules/fence.js
|
||||
packages/renderer/MdToHtml/rules/fountain.js
|
||||
packages/renderer/MdToHtml/rules/highlight_keywords.js
|
||||
|
||||
@@ -260,6 +260,15 @@ export default class ElectronAppWrapper {
|
||||
|
||||
require('@electron/remote/main').enable(this.win_.webContents);
|
||||
|
||||
// Add Referer header for YouTube embeds to fix Error 153
|
||||
this.win_.webContents.session.webRequest.onBeforeSendHeaders(
|
||||
{ urls: ['*://*.youtube.com/*', '*://*.youtube-nocookie.com/*'] },
|
||||
(details, callback) => {
|
||||
details.requestHeaders['Referer'] = 'https://joplinapp.org/';
|
||||
callback({ requestHeaders: details.requestHeaders });
|
||||
},
|
||||
);
|
||||
|
||||
if (!screen.getDisplayMatching(this.win_.getBounds())) {
|
||||
const { width: windowWidth, height: windowHeight } = this.win_.getBounds();
|
||||
const { width: primaryDisplayWidth, height: primaryDisplayHeight } = screen.getPrimaryDisplay().workArea;
|
||||
|
||||
@@ -742,7 +742,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: Ref<NoteBodyEditorRef>) => {
|
||||
'media-src \'self\' blob: data: *', // Audio and video players
|
||||
|
||||
// Disallow certain unused features
|
||||
'child-src \'none\'', // Should not contain sub-frames
|
||||
'child-src https://*.youtube.com https://*.youtube-nocookie.com', // Allow YouTube embeds
|
||||
'object-src \'none\'', // Objects can be used for script injection
|
||||
'form-action \'none\'', // No submitting forms
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
default-src 'self' joplin-content://* ;
|
||||
connect-src 'self' * http://* https://* joplin-content://* blob: ;
|
||||
style-src 'unsafe-inline' 'self' blob: joplin-content://* https://* http://* ;
|
||||
child-src 'self' joplin-content://* ;
|
||||
child-src 'self' joplin-content://* https://*.youtube.com https://*.youtube-nocookie.com ;
|
||||
script-src 'self' 'unsafe-inline' joplin-content://* ;
|
||||
media-src 'self' * blob: data: https://* http://* joplin-content://* ;
|
||||
img-src 'self' blob: data: http://* https://* joplin-content://* ;
|
||||
|
||||
@@ -55,6 +55,7 @@ const rules: RendererRules = {
|
||||
fountain: require('./MdToHtml/rules/fountain').default,
|
||||
abc: require('./MdToHtml/rules/abc').default,
|
||||
mermaid: require('./MdToHtml/rules/mermaid').default,
|
||||
external_embed: require('./MdToHtml/rules/external_embed').default,
|
||||
source_map: require('./MdToHtml/rules/source_map').default,
|
||||
tableHorizontallyScrollable: require('./MdToHtml/rules/tableHorizontallyScrollable').default,
|
||||
};
|
||||
|
||||
106
packages/renderer/MdToHtml/rules/external_embed.ts
Normal file
106
packages/renderer/MdToHtml/rules/external_embed.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import type * as MarkdownIt from 'markdown-it';
|
||||
|
||||
const extractVideoId = (url: string) => {
|
||||
const pattern = /^https?:\/\/(?:www\.)?(?: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
|
||||
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
|
||||
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)
|
||||
if (href &&
|
||||
idx + 2 < tokens.length &&
|
||||
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/${videoId}`;
|
||||
const escapedUrl = markdownIt.utils.escapeHtml(originalUrl);
|
||||
|
||||
return `<div class="joplin-editable">
|
||||
<span class="joplin-source" 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" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" 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,
|
||||
};
|
||||
@@ -226,3 +226,6 @@ xhdpi
|
||||
xxhdpi
|
||||
xxxhdpi
|
||||
scrollend
|
||||
youtube
|
||||
youtu
|
||||
nocookie
|
||||
Reference in New Issue
Block a user