1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-26 22:41:17 +02:00

Desktop: Rich Text Editor: Disallow inline event handlers (#12106)

This commit is contained in:
Henry Heino
2025-04-17 05:02:35 -07:00
committed by GitHub
parent 3ffcf065fc
commit 56e2d3da89
13 changed files with 123 additions and 89 deletions

View File

@@ -13,13 +13,6 @@ export default function(context) {
const token = tokens[idx];
if (token.info !== 'justtesting') return defaultRender(tokens, idx, options, env, self);
const postMessageWithResponseTest = `
webviewApi.postMessage('${contentScriptId}', 'justtesting').then(function(response) {
console.info('Got response in content script: ' + response);
});
return false;
`;
// Rich text editor support:
// The joplin-editable and joplin-source CSS classes mark the generated div
// as a region that needs special processing when converting back to markdown.
@@ -38,14 +31,23 @@ export default function(context) {
${richTextEditorMetadata}
<p>JUST TESTING: <pre>${markdownIt.utils.escapeHtml(leftPad(token.content.trim(), 10, 'x'))}</pre></p>
<p><a href="#" onclick="${postMessageWithResponseTest.replace(/\n/g, ' ')}">Click to post a message "justtesting" to plugin and check the response in the console</a></p>
<p>
<a
href="#"
data-content-script-id="${markdownIt.utils.escapeHtml(contentScriptId)}"
class="post-message-link"
>
Click to post a message "justtesting" to plugin and check the response in the console
</a>
</p>
</div>
`;
};
},
assets: function() {
return [
{ name: 'markdownItTestPlugin.css' }
{ name: 'markdownItTestPlugin.css' },
{ name: 'markdownItTestPluginRuntime.js' },
];
},
}

View File

@@ -0,0 +1,14 @@
const addClickHandlers = () => {
const postMessageLinks = document.querySelectorAll('.post-message-link');
for (const link of postMessageLinks) {
const contentScriptId = link.getAttribute('data-content-script-id');
link.onclick = async () => {
const response = await webviewApi.postMessage(contentScriptId, 'justtesting');
link.textContent = 'Got response in content script: ' + response;
};
}
};
document.addEventListener('joplin-noteDidUpdate', () => {
addClickHandlers();
});

View File

@@ -43,6 +43,7 @@ import useKeyboardRefocusHandler from './utils/useKeyboardRefocusHandler';
import useDocument from '../../../hooks/useDocument';
import useEditDialog from './utils/useEditDialog';
import useEditDialogEventListeners from './utils/useEditDialogEventListeners';
import Setting from '@joplin/lib/models/Setting';
import useTextPatternsLookup from './utils/useTextPatternsLookup';
const logger = Logger.create('TinyMCE');
@@ -728,6 +729,25 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
language_url: ['en_US', 'en_GB'].includes(language) ? undefined : `${bridge().vendorDir()}/lib/tinymce/langs/${language}`,
toolbar: toolbar.join(' '),
localization_function: _,
// See https://www.tiny.cloud/docs/tinymce/latest/tinymce-and-csp/#content_security_policy
content_security_policy: Setting.value('featureFlag.richText.useStrictContentSecurityPolicy') ? [
// Media: *: Allow users to include images and videos from the internet (e.g. ![](http://example.com/image.png)).
// Media: blob: Allow loading images/videos/audio from blob URLs. The Rich Text Editor
// replaces certain base64 URLs with blob URLs.
// Media: data: Allow loading images and other media from data: URLs
'default-src \'self\'',
'img-src \'self\' blob: data: *', // Images
'media-src \'self\' blob: data: *', // Audio and video players
// Disallow certain unused features
'child-src \'none\'', // Should not contain sub-frames
'object-src \'none\'', // Objects can be used for script injection
'form-action \'none\'', // No submitting forms
// Styles: unsafe-inline: TinyMCE uses inline style="" styles.
// Styles: *: Allow users to include styles from the internet (e.g. <style src="https://example.com/style.css">)
'style-src \'self\' \'unsafe-inline\' * data:',
].join(' ; ') : undefined,
contextmenu: false,
browser_spellcheck: true,

View File

@@ -2,72 +2,37 @@ import PluginService from '@joplin/lib/services/plugins/PluginService';
import { useEffect } from 'react';
import { Editor } from 'tinymce';
const useWebViewApi = (editor: Editor, window: Window) => {
interface WebViewApi {
postMessage: (contentScriptId: string, message: unknown)=> Promise<unknown>;
}
interface ExtendedWindow extends Window {
webviewApi: WebViewApi;
}
const useWebViewApi = (editor: Editor, containerWindow: Window) => {
useEffect(() => {
if (!editor) return ()=>{};
if (!window) return ()=>{};
if (!containerWindow) return ()=>{};
const scriptElement = window.document.createElement('script');
const channelId = `plugin-post-message-${Math.random()}`;
scriptElement.appendChild(window.document.createTextNode(`
window.webviewApi = {
postMessage: (contentScriptId, message) => {
const channelId = ${JSON.stringify(channelId)};
const messageId = Math.random();
window.parent.postMessage({
channelId,
messageId,
contentScriptId,
message,
}, '*');
const waitForResponse = async () => {
while (true) {
const messageEvent = await new Promise(resolve => {
window.addEventListener('message', event => {
resolve(event);
}, {once: true});
});
if (messageEvent.source !== window.parent || messageEvent.data.messageId !== messageId) {
continue;
}
const data = messageEvent.data;
return data.response;
}
};
return waitForResponse();
},
};
`));
const editorWindow = editor.getWin();
editorWindow.document.head.appendChild(scriptElement);
const onMessageHandler = async (event: MessageEvent) => {
if (event.source !== editorWindow || event.data.channelId !== channelId) {
return;
}
const contentScriptId = event.data.contentScriptId;
const editorWindow = editor.getWin() as ExtendedWindow;
const webviewApi: WebViewApi = {
postMessage: async (contentScriptId: string, message: unknown) => {
const pluginService = PluginService.instance();
const plugin = pluginService.pluginById(
pluginService.pluginIdByContentScriptId(contentScriptId),
);
const result = await plugin.emitContentScriptMessage(contentScriptId, event.data.message);
editorWindow.postMessage({
messageId: event.data.messageId,
response: result,
}, '*');
return await plugin.emitContentScriptMessage(contentScriptId, message);
},
};
window.addEventListener('message', onMessageHandler);
editorWindow.webviewApi = webviewApi;
return () => {
window.removeEventListener('message', onMessageHandler);
scriptElement.remove();
if (editorWindow.webviewApi === webviewApi) {
editorWindow.webviewApi = undefined;
}
};
}, [editor, window]);
}, [editor, containerWindow]);
};
export default useWebViewApi;

View File

@@ -163,8 +163,7 @@ function NoteEditorContent(props: NoteEditorProps) {
scrollbarSize: props.scrollbarSize,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const allAssets = useCallback(async (markupLanguage: number, options: AllAssetsOptions = null): Promise<any[]> => {
const allAssets = useCallback(async (markupLanguage: number, options: AllAssetsOptions = null) => {
options = {
contentMaxWidthTarget: '',
...options,
@@ -172,7 +171,7 @@ function NoteEditorContent(props: NoteEditorProps) {
const theme = themeStyle(options.themeId ? options.themeId : props.themeId);
const markupToHtml = markupLanguageUtils.newMarkupToHtml({}, {
const markupToHtml = markupLanguageUtils.newMarkupToHtml(props.plugins, {
resourceBaseUrl: `joplin-content://note-viewer/${Setting.value('resourceDir')}/`,
customCss: props.customCss,
});
@@ -183,7 +182,7 @@ function NoteEditorContent(props: NoteEditorProps) {
scrollbarSize: props.scrollbarSize,
whiteBackgroundNoteRendering: options.whiteBackgroundNoteRendering,
});
}, [props.themeId, props.scrollbarSize, props.customCss, props.contentMaxWidth]);
}, [props.plugins, props.themeId, props.scrollbarSize, props.customCss, props.contentMaxWidth]);
const handleProvisionalFlag = useCallback(() => {
if (props.isProvisional) {

View File

@@ -1,5 +1,5 @@
module.exports = {
hash:"cfa07333af79f4db4bc9ca008fb257f8", files: {
hash:"6950f3929f5177b0bb2a0c039669810d", files: {
'highlight.js/atom-one-dark-reasonable.css': { data: require('./highlight.js/atom-one-dark-reasonable.css.base64.js'), mime: 'text/css', encoding: 'base64' },
'highlight.js/atom-one-light.css': { data: require('./highlight.js/atom-one-light.css.base64.js'), mime: 'text/css', encoding: 'base64' },
'katex/fonts/KaTeX_AMS-Regular.woff2': { data: require('./katex/fonts/KaTeX_AMS-Regular.woff2.base64.js'), mime: 'application/octet-stream', encoding: 'base64' },

View File

@@ -1 +1 @@
module.exports = {"hash":"cfa07333af79f4db4bc9ca008fb257f8","files":["highlight.js/atom-one-dark-reasonable.css","highlight.js/atom-one-light.css","katex/fonts/KaTeX_AMS-Regular.woff2","katex/fonts/KaTeX_Caligraphic-Bold.woff2","katex/fonts/KaTeX_Caligraphic-Regular.woff2","katex/fonts/KaTeX_Fraktur-Bold.woff2","katex/fonts/KaTeX_Fraktur-Regular.woff2","katex/fonts/KaTeX_Main-Bold.woff2","katex/fonts/KaTeX_Main-BoldItalic.woff2","katex/fonts/KaTeX_Main-Italic.woff2","katex/fonts/KaTeX_Main-Regular.woff2","katex/fonts/KaTeX_Math-BoldItalic.woff2","katex/fonts/KaTeX_Math-Italic.woff2","katex/fonts/KaTeX_SansSerif-Bold.woff2","katex/fonts/KaTeX_SansSerif-Italic.woff2","katex/fonts/KaTeX_SansSerif-Regular.woff2","katex/fonts/KaTeX_Script-Regular.woff2","katex/fonts/KaTeX_Size1-Regular.woff2","katex/fonts/KaTeX_Size2-Regular.woff2","katex/fonts/KaTeX_Size3-Regular.woff2","katex/fonts/KaTeX_Size4-Regular.woff2","katex/fonts/KaTeX_Typewriter-Regular.woff2","katex/katex.css","mermaid/mermaid.min.js","mermaid/mermaid_render.js"]}
module.exports = {"hash":"6950f3929f5177b0bb2a0c039669810d","files":["highlight.js/atom-one-dark-reasonable.css","highlight.js/atom-one-light.css","katex/fonts/KaTeX_AMS-Regular.woff2","katex/fonts/KaTeX_Caligraphic-Bold.woff2","katex/fonts/KaTeX_Caligraphic-Regular.woff2","katex/fonts/KaTeX_Fraktur-Bold.woff2","katex/fonts/KaTeX_Fraktur-Regular.woff2","katex/fonts/KaTeX_Main-Bold.woff2","katex/fonts/KaTeX_Main-BoldItalic.woff2","katex/fonts/KaTeX_Main-Italic.woff2","katex/fonts/KaTeX_Main-Regular.woff2","katex/fonts/KaTeX_Math-BoldItalic.woff2","katex/fonts/KaTeX_Math-Italic.woff2","katex/fonts/KaTeX_SansSerif-Bold.woff2","katex/fonts/KaTeX_SansSerif-Italic.woff2","katex/fonts/KaTeX_SansSerif-Regular.woff2","katex/fonts/KaTeX_Script-Regular.woff2","katex/fonts/KaTeX_Size1-Regular.woff2","katex/fonts/KaTeX_Size2-Regular.woff2","katex/fonts/KaTeX_Size3-Regular.woff2","katex/fonts/KaTeX_Size4-Regular.woff2","katex/fonts/KaTeX_Typewriter-Regular.woff2","katex/katex.css","mermaid/mermaid.min.js","mermaid/mermaid_render.js"]}

View File

@@ -1 +1 @@
module.exports = `LyogZ2xvYmFsIG1lcm1haWQgKi8KCmZ1bmN0aW9uIG1lcm1haWRSZWFkeSgpIHsKCS8vIFRoZSBNZXJtYWlkIGluaXRpYWxpemF0aW9uIGNvZGUgcmVuZGVycyB0aGUgTWVybWFpZCBjb2RlIHdpdGhpbiBhbnkgZWxlbWVudCB3aXRoIGNsYXNzICJtZXJtYWlkIiBvcgoJLy8gSUQgIm1lcm1haWQiLiBIb3dldmVyIGluIHNvbWUgY2FzZXMgc29tZSBlbGVtZW50cyBtaWdodCBoYXZlIHRoaXMgSUQgYnV0IG5vdCBiZSBNZXJtYWlkIGNvZGUuCgkvLyBGb3IgZXhhbXBsZSwgTWFya2Rvd24gY29kZSBsaWtlIHRoaXM6CgkvLwoJLy8gICAgICMgTWVybWFpZAoJLy8KCS8vIFdpbGwgZ2VuZXJhdGUgdGhpcyBIVE1MOgoJLy8KCS8vICAgICA8aDEgaWQ9Im1lcm1haWQiPk1lcm1haWQ8L2gxPgoJLy8KCS8vIEFuZCB0aGF0J3MgZ29pbmcgdG8gbWFrZSB0aGUgbGliIHNldCB0aGUgYG1lcm1haWRgIG9iamVjdCB0byB0aGUgSDEgZWxlbWVudC4KCS8vIFNvIGJlbG93LCB3ZSBkb3VibGUtY2hlY2sgdGhhdCB3aGF0IHdlIGhhdmUgcmVhbGx5IGlzIGFuIGluc3RhbmNlIG9mIHRoZSBsaWJyYXJ5LgoJcmV0dXJuIHR5cGVvZiBtZXJtYWlkICE9PSAndW5kZWZpbmVkJyAmJiBtZXJtYWlkICE9PSBudWxsICYmIHR5cGVvZiBtZXJtYWlkID09PSAnb2JqZWN0JyAmJiAhIW1lcm1haWQuaW5pdGlhbGl6ZTsKfQoKY29uc3QgaXNEYXJrTW9kZSA9ICgpID0+IHsKCS8vIElmIGFueSBtZXJtYWlkIGVsZW1lbnRzIGFyZSBtYXJrZWQgYXMgcmVxdWlyaW5nIGRhcmsgbW9kZSwgcmVuZGVyICphbGwqCgkvLyBtZXJtYWlkIGVsZW1lbnRzIGluIGRhcmsgbW9kZS4KCXJldHVybiAhIWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJy5tZXJtYWlkLmpvcGxpbi0tbWVybWFpZC11c2UtZGFyay10aGVtZScpOwp9OwoKZnVuY3Rpb24gbWVybWFpZEluaXQoKSB7CglpZiAobWVybWFpZFJlYWR5KCkpIHsKCQljb25zdCBtZXJtYWlkVGFyZ2V0Tm9kZXMgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCdtZXJtYWlkJyk7CgoJCXRyeSB7CgkJCWNvbnN0IGRhcmtNb2RlID0gaXNEYXJrTW9kZSgpOwoJCQltZXJtYWlkLmluaXRpYWxpemUoewoJCQkJLy8gV2UgY2FsbCBtZXJtYWlkLnJ1biBvdXJzZWx2ZXMgd2hlbmV2ZXIgdGhlIG5vdGUgdXBkYXRlcy4gRG9uJ3QgYXV0by1zdGFydAoJCQkJc3RhcnRPbkxvYWQ6IGZhbHNlLAoKCQkJCWRhcmtNb2RlLAoJCQkJdGhlbWU6IGRhcmtNb2RlID8gJ2RhcmsnIDogJ2RlZmF1bHQnLAoJCQl9KTsKCQkJbWVybWFpZC5ydW4oewoJCQkJbm9kZXM6IG1lcm1haWRUYXJnZXROb2RlcywKCQkJfSk7CgkJfSBjYXRjaCAoZXJyb3IpIHsKCQkJY29uc29sZS5lcnJvcignTWVybWFpZCBlcnJvcicsIGVycm9yKTsKCQl9CgoJCS8vIFJlc2V0dGluZyBlbGVtZW50cyBzaXplIC0gc2VlIG1lcm1haWQudHMKCQlmb3IgKGNvbnN0IGVsZW1lbnQgb2YgbWVybWFpZFRhcmdldE5vZGVzKSB7CgkJCWVsZW1lbnQuc3R5bGUud2lkdGggPSAnMTAwJSc7CgkJfQoJfQp9Cgpkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdqb3BsaW4tbm90ZURpZFVwZGF0ZScsICgpID0+IHsKCW1lcm1haWRJbml0KCk7Cn0pOwoKY29uc3QgaW5pdElJRF8gPSBzZXRJbnRlcnZhbCgoKSA9PiB7Cgljb25zdCBpc1JlYWR5ID0gbWVybWFpZFJlYWR5KCk7CglpZiAoaXNSZWFkeSkgewoJCWNsZWFySW50ZXJ2YWwoaW5pdElJRF8pOwoJCW1lcm1haWRJbml0KCk7Cgl9Cn0sIDEwMCk7Cgpkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgKCkgPT4gewoJLy8gSW4gc29tZSBlbnZpcm9ubWVudHMsIHdlIGNhbiBsb2FkIE1lcm1haWQgaW1tZWRpYXRlbHkgKGUuZy4gbW9iaWxlKS4KCS8vIElmIHdlIGRvbid0IGxvYWQgaXQgaW1tZWRpYXRlbHkgaW4gdGhlc2UgZW52aXJvbm1lbnRzLCBNZXJtYWlkIHNlZW1zCgkvLyB0byBpbml0aWFsaXplIGFuZCBhdXRvLXJ1biwgYnV0IHdpdGhvdXQgb3VyIGNvbmZpZ3VyYXRpb24gY2hhbmdlcy4KCWlmIChtZXJtYWlkUmVhZHkoKSkgewoJCW1lcm1haWRJbml0KCk7Cgl9IGVsc2UgewoJCWNsZWFySW50ZXJ2YWwoaW5pdElJRF8pOwoJfQp9KTsK`;
module.exports = `LyogZ2xvYmFsIG1lcm1haWQgKi8KCmZ1bmN0aW9uIG1lcm1haWRSZWFkeSgpIHsKCS8vIFRoZSBNZXJtYWlkIGluaXRpYWxpemF0aW9uIGNvZGUgcmVuZGVycyB0aGUgTWVybWFpZCBjb2RlIHdpdGhpbiBhbnkgZWxlbWVudCB3aXRoIGNsYXNzICJtZXJtYWlkIiBvcgoJLy8gSUQgIm1lcm1haWQiLiBIb3dldmVyIGluIHNvbWUgY2FzZXMgc29tZSBlbGVtZW50cyBtaWdodCBoYXZlIHRoaXMgSUQgYnV0IG5vdCBiZSBNZXJtYWlkIGNvZGUuCgkvLyBGb3IgZXhhbXBsZSwgTWFya2Rvd24gY29kZSBsaWtlIHRoaXM6CgkvLwoJLy8gICAgICMgTWVybWFpZAoJLy8KCS8vIFdpbGwgZ2VuZXJhdGUgdGhpcyBIVE1MOgoJLy8KCS8vICAgICA8aDEgaWQ9Im1lcm1haWQiPk1lcm1haWQ8L2gxPgoJLy8KCS8vIEFuZCB0aGF0J3MgZ29pbmcgdG8gbWFrZSB0aGUgbGliIHNldCB0aGUgYG1lcm1haWRgIG9iamVjdCB0byB0aGUgSDEgZWxlbWVudC4KCS8vIFNvIGJlbG93LCB3ZSBkb3VibGUtY2hlY2sgdGhhdCB3aGF0IHdlIGhhdmUgcmVhbGx5IGlzIGFuIGluc3RhbmNlIG9mIHRoZSBsaWJyYXJ5LgoJcmV0dXJuIHR5cGVvZiBtZXJtYWlkICE9PSAndW5kZWZpbmVkJyAmJiBtZXJtYWlkICE9PSBudWxsICYmIHR5cGVvZiBtZXJtYWlkID09PSAnb2JqZWN0JyAmJiAhIW1lcm1haWQuaW5pdGlhbGl6ZTsKfQoKY29uc3QgaXNEYXJrTW9kZSA9ICgpID0+IHsKCS8vIElmIGFueSBtZXJtYWlkIGVsZW1lbnRzIGFyZSBtYXJrZWQgYXMgcmVxdWlyaW5nIGRhcmsgbW9kZSwgcmVuZGVyICphbGwqCgkvLyBtZXJtYWlkIGVsZW1lbnRzIGluIGRhcmsgbW9kZS4KCXJldHVybiAhIWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJy5tZXJtYWlkLmpvcGxpbi0tbWVybWFpZC11c2UtZGFyay10aGVtZScpOwp9OwoKY29uc3QgaW5pdEV4cG9ydEJ1dHRvbnMgPSAoKSA9PiB7Cgljb25zdCBleHBvcnRCdXR0b25zID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnLm1lcm1haWQtZXhwb3J0LWdyYXBoID4gYnV0dG9uJyk7Cglmb3IgKGNvbnN0IGJ1dHRvbiBvZiBleHBvcnRCdXR0b25zKSB7CgkJYnV0dG9uLm9uY2xpY2sgPSAoKSA9PiB7CgkJCWNvbnN0IGJ1dHRvbkNvbnRhaW5lciA9IGJ1dHRvbi5wYXJlbnRFbGVtZW50OwoJCQljb25zdCBtZXJtYWlkRWxlbSA9IGJ1dHRvbkNvbnRhaW5lci5uZXh0RWxlbWVudFNpYmxpbmc7CgoJCQljb25zdCByaWdodENsaWNrRXZlbnQgPSBuZXcgUG9pbnRlckV2ZW50KCdjb250ZXh0bWVudScsIHtidWJibGVzOiB0cnVlfSk7CgkJCXJpZ2h0Q2xpY2tFdmVudC50YXJnZXQgPSBtZXJtYWlkRWxlbTsKCQkJbWVybWFpZEVsZW0uZGlzcGF0Y2hFdmVudChyaWdodENsaWNrRXZlbnQpOwoJCX07Cgl9Cn07CgpmdW5jdGlvbiBtZXJtYWlkSW5pdCgpIHsKCWlmIChtZXJtYWlkUmVhZHkoKSkgewoJCWNvbnN0IG1lcm1haWRUYXJnZXROb2RlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ21lcm1haWQnKTsKCgkJdHJ5IHsKCQkJY29uc3QgZGFya01vZGUgPSBpc0RhcmtNb2RlKCk7CgkJCW1lcm1haWQuaW5pdGlhbGl6ZSh7CgkJCQkvLyBXZSBjYWxsIG1lcm1haWQucnVuIG91cnNlbHZlcyB3aGVuZXZlciB0aGUgbm90ZSB1cGRhdGVzLiBEb24ndCBhdXRvLXN0YXJ0CgkJCQlzdGFydE9uTG9hZDogZmFsc2UsCgoJCQkJZGFya01vZGUsCgkJCQl0aGVtZTogZGFya01vZGUgPyAnZGFyaycgOiAnZGVmYXVsdCcsCgkJCX0pOwoJCQltZXJtYWlkLnJ1bih7CgkJCQlub2RlczogbWVybWFpZFRhcmdldE5vZGVzLAoJCQl9KTsKCQl9IGNhdGNoIChlcnJvcikgewoJCQljb25zb2xlLmVycm9yKCdNZXJtYWlkIGVycm9yJywgZXJyb3IpOwoJCX0KCgkJLy8gUmVzZXR0aW5nIGVsZW1lbnRzIHNpemUgLSBzZWUgbWVybWFpZC50cwoJCWZvciAoY29uc3QgZWxlbWVudCBvZiBtZXJtYWlkVGFyZ2V0Tm9kZXMpIHsKCQkJZWxlbWVudC5zdHlsZS53aWR0aCA9ICcxMDAlJzsKCQl9CgoJCWluaXRFeHBvcnRCdXR0b25zKCk7Cgl9Cn0KCmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2pvcGxpbi1ub3RlRGlkVXBkYXRlJywgKCkgPT4gewoJbWVybWFpZEluaXQoKTsKfSk7Cgpjb25zdCBpbml0SUlEXyA9IHNldEludGVydmFsKCgpID0+IHsKCWNvbnN0IGlzUmVhZHkgPSBtZXJtYWlkUmVhZHkoKTsKCWlmIChpc1JlYWR5KSB7CgkJY2xlYXJJbnRlcnZhbChpbml0SUlEXyk7CgkJbWVybWFpZEluaXQoKTsKCX0KfSwgMTAwKTsKCmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7CgkvLyBJbiBzb21lIGVudmlyb25tZW50cywgd2UgY2FuIGxvYWQgTWVybWFpZCBpbW1lZGlhdGVseSAoZS5nLiBtb2JpbGUpLgoJLy8gSWYgd2UgZG9uJ3QgbG9hZCBpdCBpbW1lZGlhdGVseSBpbiB0aGVzZSBlbnZpcm9ubWVudHMsIE1lcm1haWQgc2VlbXMKCS8vIHRvIGluaXRpYWxpemUgYW5kIGF1dG8tcnVuLCBidXQgd2l0aG91dCBvdXIgY29uZmlndXJhdGlvbiBjaGFuZ2VzLgoJaWYgKG1lcm1haWRSZWFkeSgpKSB7CgkJbWVybWFpZEluaXQoKTsKCX0gZWxzZSB7CgkJY2xlYXJJbnRlcnZhbChpbml0SUlEXyk7Cgl9Cn0pOwo=`;

View File

@@ -1727,6 +1727,17 @@ const builtInMetadata = (Setting: typeof SettingType) => {
isGlobal: true,
},
'featureFlag.richText.useStrictContentSecurityPolicy': {
value: true,
type: SettingItemType.Bool,
public: true,
storage: SettingStorage.File,
label: () => 'Stronger security controls in the Rich Text Editor',
description: () => 'Improves Rich Text Editor security by applying a strict content security policy to the Rich Text Editor\'s content.',
section: 'note',
isGlobal: true,
},
'sync.allowUnsupportedProviders': {
value: -1,
type: SettingItemType.Int,

View File

@@ -399,9 +399,10 @@ export default class MdToHtml implements MarkupRenderer {
public async allAssets(theme: any, noteStyleOptions: NoteStyleOptions = null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const assets: any = {};
for (const key in rules) {
const allRules = { ...rules, ...this.extraRendererRules_ };
for (const key in allRules) {
if (!this.pluginEnabled(key)) continue;
const rule = rules[key];
const rule = allRules[key];
if (rule.assets) {
assets[key] = rule.assets(theme);

View File

@@ -98,17 +98,6 @@ export default {
const exportGraphButton = (ruleOptions: RuleOptions) => {
const theme = ruleOptions.theme;
// Clicking on export button manually triggers a right click context menu event
const onClickHandler = `
const target = arguments[0].target;
const button = target.closest("div.mermaid-export-graph");
if (!button) return false;
const $mermaid_elem = button.nextElementSibling;
const rightClickEvent = new PointerEvent("contextmenu", {bubbles: true});
rightClickEvent.target = $mermaid_elem;
$mermaid_elem.dispatchEvent(rightClickEvent);
return false;
`.trim();
const style = `
display: block;
margin-left: auto;
@@ -119,9 +108,10 @@ const exportGraphButton = (ruleOptions: RuleOptions) => {
border: ${theme.buttonStyle.border};
`.trim();
// OnClick is handled in the renderer script
return `
<div class="mermaid-export-graph">
<button onclick='${onClickHandler}' style="${style}" alt="Export mermaid graph">${downloadIcon()}</button>
<button style="${style}" alt="Export mermaid graph">${downloadIcon()}</button>
</div>
`;
};

View File

@@ -22,6 +22,20 @@ const isDarkMode = () => {
return !!document.querySelector('.mermaid.joplin--mermaid-use-dark-theme');
};
const initExportButtons = () => {
const exportButtons = document.querySelectorAll('.mermaid-export-graph > button');
for (const button of exportButtons) {
button.onclick = () => {
const buttonContainer = button.parentElement;
const mermaidElem = buttonContainer.nextElementSibling;
const rightClickEvent = new PointerEvent('contextmenu', { bubbles: true });
rightClickEvent.target = mermaidElem;
mermaidElem.dispatchEvent(rightClickEvent);
};
}
};
function mermaidInit() {
if (mermaidReady()) {
const mermaidTargetNodes = document.getElementsByClassName('mermaid');
@@ -46,6 +60,8 @@ function mermaidInit() {
for (const element of mermaidTargetNodes) {
element.style.width = '100%';
}
initExportButtons();
}
}

View File

@@ -22,6 +22,20 @@ const isDarkMode = () => {
return !!document.querySelector('.mermaid.joplin--mermaid-use-dark-theme');
};
const initExportButtons = () => {
const exportButtons = document.querySelectorAll('.mermaid-export-graph > button');
for (const button of exportButtons) {
button.onclick = () => {
const buttonContainer = button.parentElement;
const mermaidElem = buttonContainer.nextElementSibling;
const rightClickEvent = new PointerEvent('contextmenu', {bubbles: true});
rightClickEvent.target = mermaidElem;
mermaidElem.dispatchEvent(rightClickEvent);
};
}
};
function mermaidInit() {
if (mermaidReady()) {
const mermaidTargetNodes = document.getElementsByClassName('mermaid');
@@ -46,6 +60,8 @@ function mermaidInit() {
for (const element of mermaidTargetNodes) {
element.style.width = '100%';
}
initExportButtons();
}
}