1
0
mirror of https://github.com/laurent22/joplin.git synced 2026-01-02 00:08:04 +02:00

Compare commits

...

2 Commits

Author SHA1 Message Date
Laurent Cozic
5a81cc7b60 updarte 2025-12-31 16:37:22 +00:00
Laurent Cozic
1b7ae5b460 update 2025-12-31 16:29:49 +00:00
8 changed files with 123 additions and 2 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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://* ;

View File

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

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

View File

@@ -226,3 +226,6 @@ xhdpi
xxhdpi
xxxhdpi
scrollend
youtube
youtu
nocookie