mirror of https://github.com/laurent22/joplin.git
Merge branch 'laurent22:dev' into dev
commit
2c016264a6
|
@ -62,6 +62,7 @@ packages/app-mobile/locales
|
|||
packages/app-mobile/node_modules
|
||||
packages/app-mobile/pluginAssets/
|
||||
packages/fork-*
|
||||
!packages/fork-uslug
|
||||
packages/default-plugins/plugin-base-repo/
|
||||
packages/default-plugins/plugin-sources/
|
||||
packages/htmlpack/dist/
|
||||
|
@ -924,6 +925,8 @@ packages/editor/CodeMirror/editorCommands/duplicateLine.js
|
|||
packages/editor/CodeMirror/editorCommands/editorCommands.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.js
|
||||
packages/editor/CodeMirror/editorCommands/jumpToHash.test.js
|
||||
packages/editor/CodeMirror/editorCommands/jumpToHash.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
|
@ -1002,6 +1005,8 @@ packages/fork-htmlparser2/src/__tests__/events.js
|
|||
packages/fork-htmlparser2/src/__tests__/stream.js
|
||||
packages/fork-htmlparser2/src/index.spec.js
|
||||
packages/fork-htmlparser2/src/index.js
|
||||
packages/fork-uslug/lib/uslug.test.js
|
||||
packages/fork-uslug/lib/uslug.js
|
||||
packages/generator-joplin/generators/app/templates/api/index.js
|
||||
packages/generator-joplin/generators/app/templates/api/noteListType.js
|
||||
packages/generator-joplin/generators/app/templates/api/types.js
|
||||
|
|
|
@ -899,6 +899,8 @@ packages/editor/CodeMirror/editorCommands/duplicateLine.js
|
|||
packages/editor/CodeMirror/editorCommands/editorCommands.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.test.js
|
||||
packages/editor/CodeMirror/editorCommands/insertLineAfter.js
|
||||
packages/editor/CodeMirror/editorCommands/jumpToHash.test.js
|
||||
packages/editor/CodeMirror/editorCommands/jumpToHash.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.test.js
|
||||
packages/editor/CodeMirror/editorCommands/sortSelectedLines.js
|
||||
packages/editor/CodeMirror/editorCommands/supportsCommand.js
|
||||
|
@ -977,6 +979,8 @@ packages/fork-htmlparser2/src/__tests__/events.js
|
|||
packages/fork-htmlparser2/src/__tests__/stream.js
|
||||
packages/fork-htmlparser2/src/index.spec.js
|
||||
packages/fork-htmlparser2/src/index.js
|
||||
packages/fork-uslug/lib/uslug.test.js
|
||||
packages/fork-uslug/lib/uslug.js
|
||||
packages/generator-joplin/generators/app/templates/api/index.js
|
||||
packages/generator-joplin/generators/app/templates/api/noteListType.js
|
||||
packages/generator-joplin/generators/app/templates/api/types.js
|
||||
|
|
|
@ -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' },
|
||||
];
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -167,7 +167,9 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||
scrollTo: (options: ScrollOptions) => {
|
||||
if (options.type === ScrollOptionTypes.Hash) {
|
||||
if (!webviewRef.current) return;
|
||||
webviewRef.current.send('scrollToHash', options.value as string);
|
||||
const hash: string = options.value;
|
||||
webviewRef.current.send('scrollToHash', hash);
|
||||
editorRef.current.jumpToHash(hash);
|
||||
} else if (options.type === ScrollOptionTypes.Percent) {
|
||||
const percent = options.value as number;
|
||||
setEditorPercentScroll(percent);
|
||||
|
|
|
@ -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. ).
|
||||
// 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,
|
||||
|
||||
|
|
|
@ -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 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,
|
||||
}, '*');
|
||||
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),
|
||||
);
|
||||
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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -139,8 +139,11 @@
|
|||
const viewerPercent = scrollmap.translateL2V(percent);
|
||||
const newScrollTop = viewerPercent * maxScrollTop();
|
||||
|
||||
// The next scroll event cannot be skipped in order to correctly
|
||||
// scroll to the target section in a different note when follwing a link
|
||||
// Even if the scroll position hasn't changed (percent is the same),
|
||||
// we still ignore the next scroll event, so that it doesn't create
|
||||
// undesired side effects.
|
||||
// https://github.com/laurent22/joplin/issues/7617
|
||||
ignoreNextScrollEvent();
|
||||
|
||||
if (Math.floor(contentElement.scrollTop) !== Math.floor(newScrollTop)) {
|
||||
percentScroll_ = percent;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "3.3.4",
|
||||
"version": "3.3.5",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
|
|
|
@ -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' },
|
||||
|
|
|
@ -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"]}
|
|
@ -1 +1 @@
|
|||
module.exports = `LyogZ2xvYmFsIG1lcm1haWQgKi8KCmZ1bmN0aW9uIG1lcm1haWRSZWFkeSgpIHsKCS8vIFRoZSBNZXJtYWlkIGluaXRpYWxpemF0aW9uIGNvZGUgcmVuZGVycyB0aGUgTWVybWFpZCBjb2RlIHdpdGhpbiBhbnkgZWxlbWVudCB3aXRoIGNsYXNzICJtZXJtYWlkIiBvcgoJLy8gSUQgIm1lcm1haWQiLiBIb3dldmVyIGluIHNvbWUgY2FzZXMgc29tZSBlbGVtZW50cyBtaWdodCBoYXZlIHRoaXMgSUQgYnV0IG5vdCBiZSBNZXJtYWlkIGNvZGUuCgkvLyBGb3IgZXhhbXBsZSwgTWFya2Rvd24gY29kZSBsaWtlIHRoaXM6CgkvLwoJLy8gICAgICMgTWVybWFpZAoJLy8KCS8vIFdpbGwgZ2VuZXJhdGUgdGhpcyBIVE1MOgoJLy8KCS8vICAgICA8aDEgaWQ9Im1lcm1haWQiPk1lcm1haWQ8L2gxPgoJLy8KCS8vIEFuZCB0aGF0J3MgZ29pbmcgdG8gbWFrZSB0aGUgbGliIHNldCB0aGUgYG1lcm1haWRgIG9iamVjdCB0byB0aGUgSDEgZWxlbWVudC4KCS8vIFNvIGJlbG93LCB3ZSBkb3VibGUtY2hlY2sgdGhhdCB3aGF0IHdlIGhhdmUgcmVhbGx5IGlzIGFuIGluc3RhbmNlIG9mIHRoZSBsaWJyYXJ5LgoJcmV0dXJuIHR5cGVvZiBtZXJtYWlkICE9PSAndW5kZWZpbmVkJyAmJiBtZXJtYWlkICE9PSBudWxsICYmIHR5cGVvZiBtZXJtYWlkID09PSAnb2JqZWN0JyAmJiAhIW1lcm1haWQuaW5pdGlhbGl6ZTsKfQoKY29uc3QgaXNEYXJrTW9kZSA9ICgpID0+IHsKCS8vIElmIGFueSBtZXJtYWlkIGVsZW1lbnRzIGFyZSBtYXJrZWQgYXMgcmVxdWlyaW5nIGRhcmsgbW9kZSwgcmVuZGVyICphbGwqCgkvLyBtZXJtYWlkIGVsZW1lbnRzIGluIGRhcmsgbW9kZS4KCXJldHVybiAhIWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJy5tZXJtYWlkLmpvcGxpbi0tbWVybWFpZC11c2UtZGFyay10aGVtZScpOwp9OwoKZnVuY3Rpb24gbWVybWFpZEluaXQoKSB7CglpZiAobWVybWFpZFJlYWR5KCkpIHsKCQljb25zdCBtZXJtYWlkVGFyZ2V0Tm9kZXMgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCdtZXJtYWlkJyk7CgoJCXRyeSB7CgkJCWNvbnN0IGRhcmtNb2RlID0gaXNEYXJrTW9kZSgpOwoJCQltZXJtYWlkLmluaXRpYWxpemUoewoJCQkJLy8gV2UgY2FsbCBtZXJtYWlkLnJ1biBvdXJzZWx2ZXMgd2hlbmV2ZXIgdGhlIG5vdGUgdXBkYXRlcy4gRG9uJ3QgYXV0by1zdGFydAoJCQkJc3RhcnRPbkxvYWQ6IGZhbHNlLAoKCQkJCWRhcmtNb2RlLAoJCQkJdGhlbWU6IGRhcmtNb2RlID8gJ2RhcmsnIDogJ2RlZmF1bHQnLAoJCQl9KTsKCQkJbWVybWFpZC5ydW4oewoJCQkJbm9kZXM6IG1lcm1haWRUYXJnZXROb2RlcywKCQkJfSk7CgkJfSBjYXRjaCAoZXJyb3IpIHsKCQkJY29uc29sZS5lcnJvcignTWVybWFpZCBlcnJvcicsIGVycm9yKTsKCQl9CgoJCS8vIFJlc2V0dGluZyBlbGVtZW50cyBzaXplIC0gc2VlIG1lcm1haWQudHMKCQlmb3IgKGNvbnN0IGVsZW1lbnQgb2YgbWVybWFpZFRhcmdldE5vZGVzKSB7CgkJCWVsZW1lbnQuc3R5bGUud2lkdGggPSAnMTAwJSc7CgkJfQoJfQp9Cgpkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdqb3BsaW4tbm90ZURpZFVwZGF0ZScsICgpID0+IHsKCW1lcm1haWRJbml0KCk7Cn0pOwoKY29uc3QgaW5pdElJRF8gPSBzZXRJbnRlcnZhbCgoKSA9PiB7Cgljb25zdCBpc1JlYWR5ID0gbWVybWFpZFJlYWR5KCk7CglpZiAoaXNSZWFkeSkgewoJCWNsZWFySW50ZXJ2YWwoaW5pdElJRF8pOwoJCW1lcm1haWRJbml0KCk7Cgl9Cn0sIDEwMCk7Cgpkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdET01Db250ZW50TG9hZGVkJywgKCkgPT4gewoJLy8gSW4gc29tZSBlbnZpcm9ubWVudHMsIHdlIGNhbiBsb2FkIE1lcm1haWQgaW1tZWRpYXRlbHkgKGUuZy4gbW9iaWxlKS4KCS8vIElmIHdlIGRvbid0IGxvYWQgaXQgaW1tZWRpYXRlbHkgaW4gdGhlc2UgZW52aXJvbm1lbnRzLCBNZXJtYWlkIHNlZW1zCgkvLyB0byBpbml0aWFsaXplIGFuZCBhdXRvLXJ1biwgYnV0IHdpdGhvdXQgb3VyIGNvbmZpZ3VyYXRpb24gY2hhbmdlcy4KCWlmIChtZXJtYWlkUmVhZHkoKSkgewoJCW1lcm1haWRJbml0KCk7Cgl9IGVsc2UgewoJCWNsZWFySW50ZXJ2YWwoaW5pdElJRF8pOwoJfQp9KTsK`;
|
||||
module.exports = `LyogZ2xvYmFsIG1lcm1haWQgKi8KCmZ1bmN0aW9uIG1lcm1haWRSZWFkeSgpIHsKCS8vIFRoZSBNZXJtYWlkIGluaXRpYWxpemF0aW9uIGNvZGUgcmVuZGVycyB0aGUgTWVybWFpZCBjb2RlIHdpdGhpbiBhbnkgZWxlbWVudCB3aXRoIGNsYXNzICJtZXJtYWlkIiBvcgoJLy8gSUQgIm1lcm1haWQiLiBIb3dldmVyIGluIHNvbWUgY2FzZXMgc29tZSBlbGVtZW50cyBtaWdodCBoYXZlIHRoaXMgSUQgYnV0IG5vdCBiZSBNZXJtYWlkIGNvZGUuCgkvLyBGb3IgZXhhbXBsZSwgTWFya2Rvd24gY29kZSBsaWtlIHRoaXM6CgkvLwoJLy8gICAgICMgTWVybWFpZAoJLy8KCS8vIFdpbGwgZ2VuZXJhdGUgdGhpcyBIVE1MOgoJLy8KCS8vICAgICA8aDEgaWQ9Im1lcm1haWQiPk1lcm1haWQ8L2gxPgoJLy8KCS8vIEFuZCB0aGF0J3MgZ29pbmcgdG8gbWFrZSB0aGUgbGliIHNldCB0aGUgYG1lcm1haWRgIG9iamVjdCB0byB0aGUgSDEgZWxlbWVudC4KCS8vIFNvIGJlbG93LCB3ZSBkb3VibGUtY2hlY2sgdGhhdCB3aGF0IHdlIGhhdmUgcmVhbGx5IGlzIGFuIGluc3RhbmNlIG9mIHRoZSBsaWJyYXJ5LgoJcmV0dXJuIHR5cGVvZiBtZXJtYWlkICE9PSAndW5kZWZpbmVkJyAmJiBtZXJtYWlkICE9PSBudWxsICYmIHR5cGVvZiBtZXJtYWlkID09PSAnb2JqZWN0JyAmJiAhIW1lcm1haWQuaW5pdGlhbGl6ZTsKfQoKY29uc3QgaXNEYXJrTW9kZSA9ICgpID0+IHsKCS8vIElmIGFueSBtZXJtYWlkIGVsZW1lbnRzIGFyZSBtYXJrZWQgYXMgcmVxdWlyaW5nIGRhcmsgbW9kZSwgcmVuZGVyICphbGwqCgkvLyBtZXJtYWlkIGVsZW1lbnRzIGluIGRhcmsgbW9kZS4KCXJldHVybiAhIWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJy5tZXJtYWlkLmpvcGxpbi0tbWVybWFpZC11c2UtZGFyay10aGVtZScpOwp9OwoKY29uc3QgaW5pdEV4cG9ydEJ1dHRvbnMgPSAoKSA9PiB7Cgljb25zdCBleHBvcnRCdXR0b25zID0gZG9jdW1lbnQucXVlcnlTZWxlY3RvckFsbCgnLm1lcm1haWQtZXhwb3J0LWdyYXBoID4gYnV0dG9uJyk7Cglmb3IgKGNvbnN0IGJ1dHRvbiBvZiBleHBvcnRCdXR0b25zKSB7CgkJYnV0dG9uLm9uY2xpY2sgPSAoKSA9PiB7CgkJCWNvbnN0IGJ1dHRvbkNvbnRhaW5lciA9IGJ1dHRvbi5wYXJlbnRFbGVtZW50OwoJCQljb25zdCBtZXJtYWlkRWxlbSA9IGJ1dHRvbkNvbnRhaW5lci5uZXh0RWxlbWVudFNpYmxpbmc7CgoJCQljb25zdCByaWdodENsaWNrRXZlbnQgPSBuZXcgUG9pbnRlckV2ZW50KCdjb250ZXh0bWVudScsIHtidWJibGVzOiB0cnVlfSk7CgkJCXJpZ2h0Q2xpY2tFdmVudC50YXJnZXQgPSBtZXJtYWlkRWxlbTsKCQkJbWVybWFpZEVsZW0uZGlzcGF0Y2hFdmVudChyaWdodENsaWNrRXZlbnQpOwoJCX07Cgl9Cn07CgpmdW5jdGlvbiBtZXJtYWlkSW5pdCgpIHsKCWlmIChtZXJtYWlkUmVhZHkoKSkgewoJCWNvbnN0IG1lcm1haWRUYXJnZXROb2RlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlDbGFzc05hbWUoJ21lcm1haWQnKTsKCgkJdHJ5IHsKCQkJY29uc3QgZGFya01vZGUgPSBpc0RhcmtNb2RlKCk7CgkJCW1lcm1haWQuaW5pdGlhbGl6ZSh7CgkJCQkvLyBXZSBjYWxsIG1lcm1haWQucnVuIG91cnNlbHZlcyB3aGVuZXZlciB0aGUgbm90ZSB1cGRhdGVzLiBEb24ndCBhdXRvLXN0YXJ0CgkJCQlzdGFydE9uTG9hZDogZmFsc2UsCgoJCQkJZGFya01vZGUsCgkJCQl0aGVtZTogZGFya01vZGUgPyAnZGFyaycgOiAnZGVmYXVsdCcsCgkJCX0pOwoJCQltZXJtYWlkLnJ1bih7CgkJCQlub2RlczogbWVybWFpZFRhcmdldE5vZGVzLAoJCQl9KTsKCQl9IGNhdGNoIChlcnJvcikgewoJCQljb25zb2xlLmVycm9yKCdNZXJtYWlkIGVycm9yJywgZXJyb3IpOwoJCX0KCgkJLy8gUmVzZXR0aW5nIGVsZW1lbnRzIHNpemUgLSBzZWUgbWVybWFpZC50cwoJCWZvciAoY29uc3QgZWxlbWVudCBvZiBtZXJtYWlkVGFyZ2V0Tm9kZXMpIHsKCQkJZWxlbWVudC5zdHlsZS53aWR0aCA9ICcxMDAlJzsKCQl9CgoJCWluaXRFeHBvcnRCdXR0b25zKCk7Cgl9Cn0KCmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2pvcGxpbi1ub3RlRGlkVXBkYXRlJywgKCkgPT4gewoJbWVybWFpZEluaXQoKTsKfSk7Cgpjb25zdCBpbml0SUlEXyA9IHNldEludGVydmFsKCgpID0+IHsKCWNvbnN0IGlzUmVhZHkgPSBtZXJtYWlkUmVhZHkoKTsKCWlmIChpc1JlYWR5KSB7CgkJY2xlYXJJbnRlcnZhbChpbml0SUlEXyk7CgkJbWVybWFpZEluaXQoKTsKCX0KfSwgMTAwKTsKCmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7CgkvLyBJbiBzb21lIGVudmlyb25tZW50cywgd2UgY2FuIGxvYWQgTWVybWFpZCBpbW1lZGlhdGVseSAoZS5nLiBtb2JpbGUpLgoJLy8gSWYgd2UgZG9uJ3QgbG9hZCBpdCBpbW1lZGlhdGVseSBpbiB0aGVzZSBlbnZpcm9ubWVudHMsIE1lcm1haWQgc2VlbXMKCS8vIHRvIGluaXRpYWxpemUgYW5kIGF1dG8tcnVuLCBidXQgd2l0aG91dCBvdXIgY29uZmlndXJhdGlvbiBjaGFuZ2VzLgoJaWYgKG1lcm1haWRSZWFkeSgpKSB7CgkJbWVybWFpZEluaXQoKTsKCX0gZWxzZSB7CgkJY2xlYXJJbnRlcnZhbChpbml0SUlEXyk7Cgl9Cn0pOwo=`;
|
|
@ -12,6 +12,7 @@ import { RegionSpec } from './utils/formatting/RegionSpec';
|
|||
import toggleInlineSelectionFormat from './utils/formatting/toggleInlineSelectionFormat';
|
||||
import getSearchState from './utils/getSearchState';
|
||||
import { noteIdFacet, setNoteIdEffect } from './utils/selectedNoteIdExtension';
|
||||
import jumpToHash from './editorCommands/jumpToHash';
|
||||
|
||||
interface Callbacks {
|
||||
onUndoRedo(): void;
|
||||
|
@ -207,6 +208,10 @@ export default class CodeMirrorControl extends CodeMirror5Emulation implements E
|
|||
return textFound;
|
||||
}
|
||||
|
||||
public jumpToHash(hash: string) {
|
||||
return jumpToHash(this.editor, hash);
|
||||
}
|
||||
|
||||
public addStyles(...styles: Parameters<typeof EditorView.theme>) {
|
||||
const compartment = new Compartment();
|
||||
this.editor.dispatch({
|
||||
|
|
|
@ -13,6 +13,7 @@ import sortSelectedLines from './sortSelectedLines';
|
|||
import { closeSearchPanel, findNext, findPrevious, openSearchPanel, replaceAll, replaceNext, searchPanelOpen } from '@codemirror/search';
|
||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||
import { showLinkEditor } from '../utils/handleLinkEditRequests';
|
||||
import jumpToHash from './jumpToHash';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||
export type EditorCommandFunction = (editor: EditorView, ...args: any[])=> void|any;
|
||||
|
@ -107,6 +108,10 @@ const editorCommands: Record<EditorCommandType, EditorCommandFunction> = {
|
|||
}],
|
||||
});
|
||||
},
|
||||
|
||||
[EditorCommandType.JumpToHash]: (editor, hash: string) => {
|
||||
return jumpToHash(editor, hash);
|
||||
},
|
||||
};
|
||||
export default editorCommands;
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { EditorSelection } from '@codemirror/state';
|
||||
import createTestEditor from '../testUtil/createTestEditor';
|
||||
import jumpToHash from './jumpToHash';
|
||||
|
||||
describe('jumpToHash', () => {
|
||||
test.each([
|
||||
{
|
||||
doc: 'This is an anchor: <a id="test">Test</a>',
|
||||
expectedCursorLocation: 'This is an anchor: <a id="test">'.length,
|
||||
waitForTags: ['HTMLTag'],
|
||||
},
|
||||
{
|
||||
doc: '<div>HTML block: This is an anchor: <a id="test">Test</a></div>',
|
||||
expectedCursorLocation: '<div>HTML block: This is an anchor: <a id="test">'.length,
|
||||
waitForTags: ['HTMLBlock'],
|
||||
},
|
||||
])('should support jumping to elements with ID set to "test" (case %#)', async ({ doc: docText, expectedCursorLocation, waitForTags }) => {
|
||||
const editor = await createTestEditor(
|
||||
docText,
|
||||
EditorSelection.cursor(1),
|
||||
waitForTags,
|
||||
);
|
||||
expect(jumpToHash(editor, 'test')).toBe(true);
|
||||
const cursorPosition = editor.state.selection.main.anchor;
|
||||
expect(
|
||||
editor.state.sliceDoc(0, cursorPosition),
|
||||
).toBe(
|
||||
editor.state.sliceDoc(0, expectedCursorLocation),
|
||||
);
|
||||
});
|
||||
|
||||
test('should jump to Markdown headers', async () => {
|
||||
const editor = await createTestEditor(
|
||||
'Line 1\n## Line 2',
|
||||
EditorSelection.cursor(0),
|
||||
['ATXHeading2'],
|
||||
);
|
||||
expect(jumpToHash(editor, 'line-2')).toBe(true);
|
||||
expect(editor.state.selection.main.anchor).toBe(editor.state.doc.length);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,82 @@
|
|||
import { ensureSyntaxTree } from '@codemirror/language';
|
||||
import { EditorSelection } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import uslug from '@joplin/fork-uslug/lib/uslug';
|
||||
import { SyntaxNodeRef } from '@lezer/common';
|
||||
|
||||
const jumpToHash = (view: EditorView, hash: string) => {
|
||||
const state = view.state;
|
||||
const timeout = 1_000; // Maximum time to spend parsing the syntax tree
|
||||
let targetLocation: number|undefined = undefined;
|
||||
|
||||
const removeQuotes = (quoted: string) => quoted.replace(/^["'](.*)["']$/, '$1');
|
||||
|
||||
const makeEnterNode = (offset: number) => (node: SyntaxNodeRef) => {
|
||||
const nodeToText = (node: SyntaxNodeRef) => {
|
||||
return state.sliceDoc(node.from + offset, node.to + offset);
|
||||
};
|
||||
// Returns the attribute with the given name for [node]
|
||||
const getHtmlNodeAttr = (node: SyntaxNodeRef, attrName: string) => {
|
||||
if (node.from === node.to) return null; // Empty
|
||||
const content = node.node.resolveInner(node.from + 1);
|
||||
|
||||
// Search for the "id" attribute
|
||||
const attributes = content.getChildren('Attribute');
|
||||
for (const attribute of attributes) {
|
||||
const nameNode = attribute.getChild('AttributeName');
|
||||
const valueNode = attribute.getChild('AttributeValue');
|
||||
|
||||
if (nameNode && valueNode) {
|
||||
const name = nodeToText(nameNode).toLowerCase().replace(/^"(.*)"$/, '$1');
|
||||
if (name === attrName) {
|
||||
return removeQuotes(nodeToText(valueNode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const found = targetLocation !== undefined;
|
||||
if (found) return false; // Skip this node
|
||||
|
||||
let matches = false;
|
||||
if (node.name.startsWith('SetextHeading') || node.name.startsWith('ATXHeading')) {
|
||||
const nodeText = nodeToText(node)
|
||||
.replace(/^#+\s/, '') // Leading #s in headers
|
||||
.replace(/\n-+$/, ''); // Trailing --s in headers
|
||||
matches = hash === uslug(nodeText);
|
||||
} else if (node.name === 'HTMLTag' || node.name === 'HTMLBlock') {
|
||||
// CodeMirror adds HTML information to Markdown documents using overlays attached
|
||||
// to HTMLTag and HTMLBlock nodes.
|
||||
// Use .enter to enter the overlay and visit the HTML nodes:
|
||||
node.node.enter(node.from, 1).toTree().iterate({ enter: makeEnterNode(node.from) });
|
||||
} else if (node.name === 'OpenTag') {
|
||||
matches = getHtmlNodeAttr(node, 'id') === hash || getHtmlNodeAttr(node, 'name') === hash;
|
||||
}
|
||||
|
||||
if (matches) {
|
||||
targetLocation = node.to + offset;
|
||||
return false;
|
||||
}
|
||||
|
||||
const keepIterating = !matches;
|
||||
return keepIterating;
|
||||
};
|
||||
|
||||
// Iterate over the entire syntax tree.
|
||||
ensureSyntaxTree(state, state.doc.length, timeout).iterate({
|
||||
enter: makeEnterNode(0),
|
||||
});
|
||||
|
||||
if (targetLocation !== undefined) {
|
||||
view.dispatch({
|
||||
selection: EditorSelection.cursor(targetLocation),
|
||||
scrollIntoView: true,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export default jumpToHash;
|
|
@ -37,6 +37,7 @@
|
|||
"@codemirror/search": "6.5.8",
|
||||
"@codemirror/state": "6.4.1",
|
||||
"@codemirror/view": "6.35.0",
|
||||
"@joplin/fork-uslug": "^2.0.0",
|
||||
"@lezer/common": "1.2.3",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lezer/markdown": "1.3.2",
|
||||
|
|
|
@ -74,8 +74,9 @@ export enum EditorCommandType {
|
|||
SelectedText = 'selectedText',
|
||||
InsertText = 'insertText',
|
||||
ReplaceSelection = 'replaceSelection',
|
||||
|
||||
SetText = 'setText',
|
||||
|
||||
JumpToHash = 'jumpToHash',
|
||||
}
|
||||
|
||||
// Because the editor package can run in a WebView, plugin content scripts
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
Modified for Joplin:
|
||||
|
||||
- Added support for emojis - "🐶🐶🐶🐱" => "dogdogdogcat"
|
||||
- Smaller package size: Removed dependencies on functionality that's now built-in to JavaScript (Unicode normalization, Unicode character class regular expressions).
|
||||
- Types: Migrated to TypeScript.
|
||||
|
||||
* * *
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
module.exports = require('./lib/uslug');
|
||||
module.exports = require('./lib/uslug').default;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
testMatch: [
|
||||
'**/*.test.js',
|
||||
],
|
||||
};
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* List of Unicode code that are flagged as separator.
|
||||
*
|
||||
* Contains Unicode code of:
|
||||
* - Zs = Separator, space
|
||||
* - Zl = Separator, line
|
||||
* - Zp = Separator, paragraph
|
||||
*
|
||||
* This list has been computed from http://unicode.org/Public/UNIDATA/UnicodeData.txt
|
||||
* curl -s http://unicode.org/Public/UNIDATA/UnicodeData.txt | grep -E ';Zs;|;Zl;|;Zp;' | cut -d \; -f 1 | xargs -I{} printf '%d, ' 0x{}
|
||||
*
|
||||
*/
|
||||
exports.Z = [32, 160, 5760, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8232, 8233, 8239, 8287, 12288];
|
|
@ -1,61 +0,0 @@
|
|||
(function() {
|
||||
var L = require('./L').L,
|
||||
N = require('./N').N,
|
||||
Z = require('./Z').Z,
|
||||
M = require('./M').M,
|
||||
unorm = require('unorm');
|
||||
|
||||
var nodeEmoji = require('node-emoji')
|
||||
|
||||
var _unicodeCategory = function(code) {
|
||||
if (~L.indexOf(code)) return 'L';
|
||||
if (~N.indexOf(code)) return 'N';
|
||||
if (~Z.indexOf(code)) return 'Z';
|
||||
if (~M.indexOf(code)) return 'M';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
module.exports = function(string, options) {
|
||||
string = string || '';
|
||||
options = options || {};
|
||||
var allowedChars = options.allowedChars || '-_~';
|
||||
var lower = typeof options.lower === 'boolean' ? options.lower : true;
|
||||
var spaces = typeof options.spaces === 'boolean' ? options.spaces : false;
|
||||
var rv = [];
|
||||
var noEmojiString = nodeEmoji.unemojify(string);
|
||||
var chars = unorm.nfkc(noEmojiString);
|
||||
for(var i = 0; i < chars.length; i++) {
|
||||
var c = chars[i];
|
||||
var code = c.charCodeAt(0);
|
||||
// Allow Common CJK Unified Ideographs
|
||||
// See: http://www.unicode.org/versions/Unicode6.0.0/ch12.pdf - Table 12-2
|
||||
if (0x4E00 <= code && code <= 0x9FFF) {
|
||||
rv.push(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow Hangul
|
||||
if (0xAC00 <= code && code <= 0xD7A3) {
|
||||
rv.push(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Japanese ideographic punctuation
|
||||
if ((0x3000 <= code && code <= 0x3002) || (0xFF01 <= code && code <= 0xFF02)) {
|
||||
rv.push(' ');
|
||||
}
|
||||
|
||||
if (allowedChars.indexOf(c) != -1) {
|
||||
rv.push(c);
|
||||
continue;
|
||||
}
|
||||
var val = _unicodeCategory(code);
|
||||
if (val && ~'LNM'.indexOf(val)) rv.push(c);
|
||||
if (val && ~'Z'.indexOf(val)) rv.push(' ');
|
||||
}
|
||||
var slug = rv.join('').replace(/^\s+|\s+$/g, '').replace(/\s+/g,' ');
|
||||
if (!spaces) slug = slug.replace(/[\s\-]+/g,'-');
|
||||
if (lower) slug = slug.toLowerCase();
|
||||
return slug;
|
||||
};
|
||||
}());
|
|
@ -0,0 +1,51 @@
|
|||
// Based on @joplin/fork-uslug
|
||||
//
|
||||
// The original is Copyright (c) 2012 Jeremy Selier
|
||||
//
|
||||
// MIT Licensed
|
||||
//
|
||||
// You may find a copy of this license in the LICENSE file that should have been provided
|
||||
// to you with a copy of this software.
|
||||
import uslug from './uslug';
|
||||
|
||||
const word0 = 'Ελληνικά';
|
||||
const word1 = [word0, word0].join('-');
|
||||
const word2 = [word0, word0].join(' - ');
|
||||
|
||||
const tests: [string, string][] = [
|
||||
['', ''],
|
||||
['The \u212B symbol invented by A. J. \u00C5ngstr\u00F6m (1814, L\u00F6gd\u00F6, \u2013 1874) denotes the length 10\u207B\u00B9\u2070 m.', 'the-å-symbol-invented-by-a-j-ångström-1814-lögdö-1874-denotes-the-length-1010-m'],
|
||||
['Быстрее и лучше!', 'быстрее-и-лучше'],
|
||||
['xx x - "#$@ x', 'xx-x-x'],
|
||||
['Bän...g (bang)', 'bäng-bang'],
|
||||
[word0, word0.toLowerCase()],
|
||||
[word1, word1.toLowerCase()],
|
||||
[word2, word1.toLowerCase()],
|
||||
[' a ', 'a'],
|
||||
['tags/', 'tags'],
|
||||
['y_u_no', 'y_u_no'],
|
||||
['el-ni\xf1o', 'el-ni\xf1o'],
|
||||
['x荿', 'x荿'],
|
||||
['ϧ蒬蓣', '\u03e7蒬蓣'],
|
||||
['¿x', 'x'],
|
||||
['汉语/漢語', '汉语漢語'],
|
||||
['فار,سي', 'فارسي'],
|
||||
['เแโ|ใไ', 'เแโใไ'],
|
||||
['日本語ドキュメンテ(ーション)', '日本語ドキュメンテーション'],
|
||||
['一二三四五六七八九十!。。。', '一二三四五六七八九十'],
|
||||
['संसद में काम नहीं तो वेतन क्यों?', 'संसद-में-काम-नहीं-तो-वेतन-क्यों'],
|
||||
['เร่งรัด \'ปรับเงินเดือนท้องถิ่น 1 ขั้น\' ตามมติ ครม.', 'เร่งรัด-ปรับเงินเดือนท้องถิ่น-1-ขั้น-ตามมติ-ครม'],
|
||||
['オバマ大統領が病院爆撃の調査へ同意するように、協力してください!', 'オバマ大統領が病院爆撃の調査へ同意するように-協力してください'],
|
||||
['일본정부 법무대신(法務大臣): 우리는 일본 입관법의 재검토를 요구한다!', '일본정부-법무대신法務大臣-우리는-일본-입관법의-재검토를-요구한다'],
|
||||
['😁', 'grin'],
|
||||
['😁a', 'grina'],
|
||||
['🐶🐶🐶🐱', 'dogdogdogcat'],
|
||||
];
|
||||
describe('uslug', () => {
|
||||
it.each(tests)('should convert %s to %s', (input, expected) => {
|
||||
expect(uslug(input)).toBe(expected);
|
||||
});
|
||||
it('should support "allowedChars"', () => {
|
||||
expect(uslug('qbc,fe', { allowedChars: 'q' })).toBe('qbcfe');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
// Based on @joplin/fork-uslug
|
||||
//
|
||||
// The original is Copyright (c) 2012 Jeremy Selier
|
||||
//
|
||||
// MIT Licensed
|
||||
//
|
||||
// You may find a copy of this license in the LICENSE file that should have been provided
|
||||
// to you with a copy of this software.
|
||||
|
||||
const nodeEmoji = require('node-emoji');
|
||||
|
||||
// Very old browsers (e.g. Chrome < 64, which is from 2018) may not support
|
||||
// \p{} regexes.
|
||||
let regexes_;
|
||||
try {
|
||||
regexes_ = {
|
||||
// eslint-disable-next-line prefer-regex-literals -- Needed to prevent syntax errors
|
||||
L: new RegExp('\\p{L}', 'u'), N: new RegExp('\\p{N}', 'u'), Z: new RegExp('\\p{Z}', 'u'), M: new RegExp('\\p{M}', 'u'),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
regexes_ = undefined;
|
||||
}
|
||||
|
||||
const _unicodeCategory = function(c: string) {
|
||||
if (!regexes_) {
|
||||
console.warn('Unicode RegExps not loaded. Skipping category check.');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const [key, val] of Object.entries(regexes_)) {
|
||||
if (c.match(val)) return key;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
interface Options {
|
||||
lower?: boolean;
|
||||
spaces?: boolean;
|
||||
allowedChars?: string;
|
||||
}
|
||||
|
||||
export default function(string: string, options: Options = {}) {
|
||||
string = string || '';
|
||||
|
||||
options = options || {};
|
||||
|
||||
const allowedChars = options.allowedChars || '-_~';
|
||||
const lower = typeof options.lower === 'boolean' ? options.lower : true;
|
||||
const spaces = typeof options.spaces === 'boolean' ? options.spaces : false;
|
||||
|
||||
const rv = [];
|
||||
|
||||
const noEmojiString: string = nodeEmoji.unemojify(string);
|
||||
const chars = noEmojiString.normalize('NFKC').split('');
|
||||
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
const c = chars[i];
|
||||
const code = c.charCodeAt(0);
|
||||
|
||||
// Allow Common CJK Unified Ideographs
|
||||
// See: http://www.unicode.org/versions/Unicode6.0.0/ch12.pdf - Table 12-2
|
||||
|
||||
if (0x4E00 <= code && code <= 0x9FFF) {
|
||||
rv.push(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allow Hangul
|
||||
if (0xAC00 <= code && code <= 0xD7A3) {
|
||||
rv.push(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Japanese ideographic punctuation
|
||||
if ((0x3000 <= code && code <= 0x3002) || (0xFF01 <= code && code <= 0xFF02)) {
|
||||
rv.push(' ');
|
||||
}
|
||||
|
||||
if (allowedChars.indexOf(c) !== -1) {
|
||||
rv.push(c);
|
||||
continue;
|
||||
}
|
||||
|
||||
const val = _unicodeCategory(c);
|
||||
if (val && ~'LNM'.indexOf(val)) rv.push(c);
|
||||
if (val && ~'Z'.indexOf(val)) rv.push(' ');
|
||||
}
|
||||
let slug = rv.join('').replace(/^\s+|\s+$/g, '').replace(/\s+/g, ' ');
|
||||
|
||||
if (!spaces) slug = slug.replace(/[\s-]+/g, '-');
|
||||
if (lower) slug = slug.toLowerCase();
|
||||
|
||||
return slug;
|
||||
}
|
|
@ -1,25 +1,40 @@
|
|||
{
|
||||
"name": "@joplin/fork-uslug",
|
||||
"version": "1.0.22",
|
||||
"version": "2.0.0",
|
||||
"description": "A permissive slug generator that works with unicode.",
|
||||
"author": "Jeremy Selier <jerem.selier@gmail.com>",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "jest",
|
||||
"build": "yarn tsc",
|
||||
"tsc": "tsc --project tsconfig.json",
|
||||
"watch": "tsc --watch --preserveWatchOutput --project tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-emoji": "1.11.0",
|
||||
"unorm": "1.6.0"
|
||||
"node-emoji": "1.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"should": "13.2.3"
|
||||
"@types/jest": "29.5.12",
|
||||
"@types/node": "18.19.67",
|
||||
"jest": "29.7.0",
|
||||
"typescript": "5.4.5"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "http://github.com/jeremys/uslug.git"
|
||||
},
|
||||
"main": "./index",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./lib/uslug": {
|
||||
"require": "./lib/uslug.js",
|
||||
"types": "./lib/uslug.ts"
|
||||
}
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "http://github.com/jeremys/uslug/issues"
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
var should = require('should'),
|
||||
uslug = require('../lib/uslug');
|
||||
|
||||
|
||||
var word0 = 'Ελληνικά';
|
||||
var word1 = [word0, word0].join('-');
|
||||
var word2 = [word0, word0].join(' - ');
|
||||
|
||||
var tests = [
|
||||
['', ''],
|
||||
['The \u212B symbol invented by A. J. \u00C5ngstr\u00F6m (1814, L\u00F6gd\u00F6, \u2013 1874) denotes the length 10\u207B\u00B9\u2070 m.', 'the-å-symbol-invented-by-a-j-ångström-1814-lögdö-1874-denotes-the-length-1010-m'],
|
||||
['Быстрее и лучше!', 'быстрее-и-лучше'],
|
||||
['xx x - "#$@ x', 'xx-x-x'],
|
||||
['Bän...g (bang)', 'bäng-bang'],
|
||||
[word0, word0.toLowerCase()],
|
||||
[word1, word1.toLowerCase()],
|
||||
[word2, word1.toLowerCase()],
|
||||
[' a ', 'a'],
|
||||
['tags/', 'tags'],
|
||||
['y_u_no', 'y_u_no'],
|
||||
['el-ni\xf1o', 'el-ni\xf1o'],
|
||||
['x荿', 'x荿'],
|
||||
['ϧ蒬蓣', '\u03e7蒬蓣'],
|
||||
['¿x', 'x'],
|
||||
['汉语/漢語', '汉语漢語'],
|
||||
['فار,سي', 'فارسي'],
|
||||
['เแโ|ใไ', 'เแโใไ'],
|
||||
['日本語ドキュメンテ(ーション)', '日本語ドキュメンテーション'],
|
||||
['一二三四五六七八九十!。。。', '一二三四五六七八九十'],
|
||||
['संसद में काम नहीं तो वेतन क्यों?', 'संसद-में-काम-नहीं-तो-वेतन-क्यों'],
|
||||
['เร่งรัด \'ปรับเงินเดือนท้องถิ่น 1 ขั้น\' ตามมติ ครม.', 'เร่งรัด-ปรับเงินเดือนท้องถิ่น-1-ขั้น-ตามมติ-ครม'],
|
||||
['オバマ大統領が病院爆撃の調査へ同意するように、協力してください!', 'オバマ大統領が病院爆撃の調査へ同意するように-協力してください'],
|
||||
['일본정부 법무대신(法務大臣): 우리는 일본 입관법의 재검토를 요구한다!', '일본정부-법무대신法務大臣-우리는-일본-입관법의-재검토를-요구한다'],
|
||||
['😁', 'grin'],
|
||||
['😁a', 'grina'],
|
||||
['🐶🐶🐶🐱', 'dogdogdogcat'],
|
||||
];
|
||||
|
||||
for (var t in tests) {
|
||||
var test = tests[t];
|
||||
uslug(test[0]).should.equal(test[1]);
|
||||
}
|
||||
|
||||
uslug('qbc,fe', { allowedChars: 'q' }).should.equal('qbcfe');
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "node"]
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
"@aws-sdk/s3-request-presigner": "3.296.0",
|
||||
"@joplin/fork-htmlparser2": "^4.1.57",
|
||||
"@joplin/fork-sax": "^1.2.61",
|
||||
"@joplin/fork-uslug": "^1.0.22",
|
||||
"@joplin/fork-uslug": "^2.0.0",
|
||||
"@joplin/htmlpack": "~3.3",
|
||||
"@joplin/onenote-converter": "~3.3",
|
||||
"@joplin/renderer": "~3.3",
|
||||
|
|
|
@ -80,65 +80,71 @@ export default class InteropService_Importer_Raw extends InteropService_Importer
|
|||
|
||||
for (let i = 0; i < stats.length; i++) {
|
||||
const stat = stats[i];
|
||||
if (stat.isDirectory()) continue;
|
||||
if (fileExtension(stat.path).toLowerCase() !== 'md') continue;
|
||||
|
||||
const content = await shim.fsDriver().readFile(`${this.sourcePath_}/${stat.path}`);
|
||||
const item = await BaseItem.unserialize(content);
|
||||
const itemType = item.type_;
|
||||
const ItemClass = BaseItem.itemClass(item);
|
||||
try {
|
||||
if (stat.isDirectory()) continue;
|
||||
if (fileExtension(stat.path).toLowerCase() !== 'md') continue;
|
||||
|
||||
delete item.type_;
|
||||
const content = await shim.fsDriver().readFile(`${this.sourcePath_}/${stat.path}`);
|
||||
const item = await BaseItem.unserialize(content);
|
||||
const itemType = item.type_;
|
||||
const ItemClass = BaseItem.itemClass(item);
|
||||
|
||||
if (itemType === BaseModel.TYPE_NOTE) {
|
||||
await setFolderToImportTo(item.parent_id);
|
||||
delete item.type_;
|
||||
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
item.parent_id = itemIdMap[item.parent_id];
|
||||
item.body = await replaceLinkedItemIds(item.body);
|
||||
} else if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
if (destinationFolderId) continue;
|
||||
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
|
||||
if (item.parent_id) {
|
||||
if (itemType === BaseModel.TYPE_NOTE) {
|
||||
await setFolderToImportTo(item.parent_id);
|
||||
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
item.parent_id = itemIdMap[item.parent_id];
|
||||
}
|
||||
item.body = await replaceLinkedItemIds(item.body);
|
||||
} else if (itemType === BaseModel.TYPE_FOLDER) {
|
||||
if (destinationFolderId) continue;
|
||||
|
||||
item.title = await Folder.findUniqueItemTitle(item.title, item.parent_id);
|
||||
} else if (itemType === BaseModel.TYPE_RESOURCE) {
|
||||
const sourceId = item.id;
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
createdResources[item.id] = item;
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
|
||||
const sourceResourcePath = `${this.sourcePath_}/resources/${Resource.filename({ ...item, id: sourceId })}`;
|
||||
const destPath = Resource.fullPath(item);
|
||||
if (item.parent_id) {
|
||||
await setFolderToImportTo(item.parent_id);
|
||||
item.parent_id = itemIdMap[item.parent_id];
|
||||
}
|
||||
|
||||
if (await shim.fsDriver().exists(sourceResourcePath)) {
|
||||
await shim.fsDriver().copy(sourceResourcePath, destPath);
|
||||
} else {
|
||||
result.warnings.push(sprintf('Could not find resource file: %s', sourceResourcePath));
|
||||
}
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
const tag = await Tag.loadByTitle(item.title);
|
||||
if (tag) {
|
||||
itemIdMap[item.id] = tag.id;
|
||||
item.title = await Folder.findUniqueItemTitle(item.title, item.parent_id);
|
||||
} else if (itemType === BaseModel.TYPE_RESOURCE) {
|
||||
const sourceId = item.id;
|
||||
if (!itemIdMap[item.id]) itemIdMap[item.id] = uuid.create();
|
||||
item.id = itemIdMap[item.id];
|
||||
createdResources[item.id] = item;
|
||||
|
||||
const sourceResourcePath = `${this.sourcePath_}/resources/${Resource.filename({ ...item, id: sourceId })}`;
|
||||
const destPath = Resource.fullPath(item);
|
||||
|
||||
if (await shim.fsDriver().exists(sourceResourcePath)) {
|
||||
await shim.fsDriver().copy(sourceResourcePath, destPath);
|
||||
} else {
|
||||
result.warnings.push(sprintf('Could not find resource file: %s', sourceResourcePath));
|
||||
}
|
||||
} else if (itemType === BaseModel.TYPE_TAG) {
|
||||
const tag = await Tag.loadByTitle(item.title);
|
||||
if (tag) {
|
||||
itemIdMap[item.id] = tag.id;
|
||||
continue;
|
||||
}
|
||||
|
||||
const tagId = uuid.create();
|
||||
itemIdMap[item.id] = tagId;
|
||||
item.id = tagId;
|
||||
} else if (itemType === BaseModel.TYPE_NOTE_TAG) {
|
||||
noteTagsToCreate.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tagId = uuid.create();
|
||||
itemIdMap[item.id] = tagId;
|
||||
item.id = tagId;
|
||||
} else if (itemType === BaseModel.TYPE_NOTE_TAG) {
|
||||
noteTagsToCreate.push(item);
|
||||
continue;
|
||||
await ItemClass.save(item, { isNew: true, autoTimestamp: false });
|
||||
} catch (error) {
|
||||
error.message = `Could not import: ${stat.path}: ${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
|
||||
await ItemClass.save(item, { isNew: true, autoTimestamp: false });
|
||||
}
|
||||
|
||||
for (let i = 0; i < noteTagsToCreate.length; i++) {
|
||||
|
|
|
@ -18,7 +18,7 @@ import Plugin from '../Plugin';
|
|||
* now, are not well documented. You can find the list directly on GitHub
|
||||
* though at the following locations:
|
||||
*
|
||||
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/MainScreen/commands)
|
||||
* * [Main screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/WindowCommandsAndDialogs/commands)
|
||||
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/commands)
|
||||
* * [Editor commands](https://github.com/laurent22/joplin/tree/dev/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts)
|
||||
*
|
||||
|
@ -29,8 +29,13 @@ import Plugin from '../Plugin';
|
|||
* commands can be found in these places:
|
||||
*
|
||||
* * [Global commands](https://github.com/laurent22/joplin/tree/dev/packages/app-mobile/commands)
|
||||
* * [Note screen commands](https://github.com/laurent22/joplin/tree/dev/packages/app-mobile/components/screens/Note/commands)
|
||||
* * [Editor commands](https://github.com/laurent22/joplin/blob/dev/packages/app-mobile/components/NoteEditor/commandDeclarations.ts)
|
||||
*
|
||||
* Additionally, certain global commands have the same implementation on both platforms:
|
||||
*
|
||||
* * [Shared global commands](https://github.com/laurent22/joplin/tree/dev/packages/lib/commands)
|
||||
*
|
||||
* ## Executing editor commands
|
||||
*
|
||||
* There might be a situation where you want to invoke editor commands
|
||||
|
|
|
@ -152,14 +152,25 @@ const processStartFlags = async (argv: string[], setDefaults = true) => {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--enable-wayland-ime') === 0) {
|
||||
if (
|
||||
arg === '--enable-wayland-ime'
|
||||
|| arg === '--disable-gtk-ime'
|
||||
|| arg.startsWith('--wayland-text-input-version=')
|
||||
) {
|
||||
// Electron-specific flag - ignore it
|
||||
// Enables input method support on Linux/Wayland
|
||||
// Enables/configures input method support on Linux/Wayland
|
||||
// See https://github.com/laurent22/joplin/issues/10345
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.startsWith('--gtk-version=')) {
|
||||
// Electron-specific flag. Allows forcing a different GTK version.
|
||||
// See https://wiki.archlinux.org/title/Chromium#Native_Wayland_support
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--ozone-platform=') === 0) {
|
||||
// Electron-specific flag - ignore it
|
||||
// Allows users to run the app on native wayland
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@joplin/fork-htmlparser2": "^4.1.57",
|
||||
"@joplin/fork-uslug": "^1.0.22",
|
||||
"@joplin/fork-uslug": "^2.0.0",
|
||||
"@joplin/utils": "~3.3",
|
||||
"font-awesome-filetypes": "2.1.0",
|
||||
"fs-extra": "11.2.0",
|
||||
|
|
76
yarn.lock
76
yarn.lock
|
@ -8516,6 +8516,7 @@ __metadata:
|
|||
"@codemirror/search": 6.5.8
|
||||
"@codemirror/state": 6.4.1
|
||||
"@codemirror/view": 6.35.0
|
||||
"@joplin/fork-uslug": ^2.0.0
|
||||
"@joplin/lib": ~3.3
|
||||
"@lezer/common": 1.2.3
|
||||
"@lezer/highlight": 1.2.1
|
||||
|
@ -8563,13 +8564,15 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@joplin/fork-uslug@^1.0.22, @joplin/fork-uslug@workspace:packages/fork-uslug":
|
||||
"@joplin/fork-uslug@^2.0.0, @joplin/fork-uslug@workspace:packages/fork-uslug":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@joplin/fork-uslug@workspace:packages/fork-uslug"
|
||||
dependencies:
|
||||
"@types/jest": 29.5.12
|
||||
"@types/node": 18.19.67
|
||||
jest: 29.7.0
|
||||
node-emoji: 1.11.0
|
||||
should: 13.2.3
|
||||
unorm: 1.6.0
|
||||
typescript: 5.4.5
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
|
@ -8595,7 +8598,7 @@ __metadata:
|
|||
"@aws-sdk/s3-request-presigner": 3.296.0
|
||||
"@joplin/fork-htmlparser2": ^4.1.57
|
||||
"@joplin/fork-sax": ^1.2.61
|
||||
"@joplin/fork-uslug": ^1.0.22
|
||||
"@joplin/fork-uslug": ^2.0.0
|
||||
"@joplin/htmlpack": ~3.3
|
||||
"@joplin/onenote-converter": ~3.3
|
||||
"@joplin/renderer": ~3.3
|
||||
|
@ -8778,7 +8781,7 @@ __metadata:
|
|||
resolution: "@joplin/renderer@workspace:packages/renderer"
|
||||
dependencies:
|
||||
"@joplin/fork-htmlparser2": ^4.1.57
|
||||
"@joplin/fork-uslug": ^1.0.22
|
||||
"@joplin/fork-uslug": ^2.0.0
|
||||
"@joplin/utils": ~3.3
|
||||
"@types/jest": 29.5.12
|
||||
"@types/markdown-it": 13.0.9
|
||||
|
@ -43071,62 +43074,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"should-equal@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "should-equal@npm:2.0.0"
|
||||
dependencies:
|
||||
should-type: ^1.4.0
|
||||
checksum: 3f3580a223bf76f9309a4d957d2dcbd6059bda816f2e6656e822b7518218ef653c25e9271b2f5765ca6f5a72a217105ad343a8ceea831d15aff44dd691cc1dcd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"should-format@npm:^3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "should-format@npm:3.0.3"
|
||||
dependencies:
|
||||
should-type: ^1.3.0
|
||||
should-type-adaptors: ^1.0.1
|
||||
checksum: 5304e89b4d4c42078c7f66232d13cca1d6a1c00c173f500f64160f57d4ecd7522a25106b313fe8f8694547e8a1ce4d975f1f09a3d1618f1dc054db48c0683d87
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"should-type-adaptors@npm:^1.0.1":
|
||||
version: 1.1.0
|
||||
resolution: "should-type-adaptors@npm:1.1.0"
|
||||
dependencies:
|
||||
should-type: ^1.3.0
|
||||
should-util: ^1.0.0
|
||||
checksum: 94dd1d225c8f2590278f46689258a1df684ca1f26262459c4e2d64a09d06935ec1410a24fe7b5f98b9429093e48afef2ed1b370634e0444b930547df4943f70d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"should-type@npm:^1.3.0, should-type@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "should-type@npm:1.4.0"
|
||||
checksum: 88d9324c6c0c2f94e71d2f8b11c84e44de81f16eeb6fafcba47f4af430c65e46bad18eb472827526cad22b4fe693aba8b022739d1c453672faf28860df223491
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"should-util@npm:^1.0.0":
|
||||
version: 1.0.1
|
||||
resolution: "should-util@npm:1.0.1"
|
||||
checksum: c3be15e0fdc851f8338676b3f8b590d330bbea94ec41c1343cc9983dea295915073f69a215795454b6adda6579ec8927c7c0ab178b83f9f11a0247ccdba53381
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"should@npm:13.2.3":
|
||||
version: 13.2.3
|
||||
resolution: "should@npm:13.2.3"
|
||||
dependencies:
|
||||
should-equal: ^2.0.0
|
||||
should-format: ^3.0.3
|
||||
should-type: ^1.4.0
|
||||
should-type-adaptors: ^1.0.1
|
||||
should-util: ^1.0.0
|
||||
checksum: 74bcc0eb85e0a63a88e501ff9ca3b53dbc6d1ee47823c029a18a4b14b3ef4e2561733e161033df720599d2153283470e9647fdcb1bbc78903960ffb0363239c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"side-channel@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "side-channel@npm:1.0.4"
|
||||
|
@ -47493,13 +47440,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unorm@npm:1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "unorm@npm:1.6.0"
|
||||
checksum: 9a86546256a45f855b6cfe719086785d6aada94f63778cecdecece8d814ac26af76cb6da70130da0a08b8803bbf0986e56c7ec4249038198f3de02607fffd811
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unpack-string@npm:0.0.2":
|
||||
version: 0.0.2
|
||||
resolution: "unpack-string@npm:0.0.2"
|
||||
|
|
Loading…
Reference in New Issue