mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
264 lines
8.2 KiB
JavaScript
264 lines
8.2 KiB
JavaScript
'use strict';
|
|
|
|
// This is a fork of electron-context-menu@0.15.0. We need to fork it because
|
|
// the latest version only runs from the main process and we need it in the
|
|
// renderer process. It also has a dependency to electron-is-dev which also only
|
|
// runs in the main process.
|
|
//
|
|
// In fact we almost don't use any features of electron-context-menu, which is
|
|
// just a wrapper over Electron's own native context menu but with more bugs, so
|
|
// we should get rid of it, but for now this is good enough as a quick fix.
|
|
|
|
const electron = require('electron');
|
|
const electronRemote = require('@electron/remote');
|
|
|
|
const webContents = win => win.webContents || (win.getWebContents && win.getWebContents());
|
|
|
|
const decorateMenuItem = menuItem => {
|
|
return (options = {}) => {
|
|
if (options.transform && !options.click) {
|
|
menuItem.transform = options.transform;
|
|
}
|
|
|
|
return menuItem;
|
|
};
|
|
};
|
|
|
|
const removeUnusedMenuItems = menuTemplate => {
|
|
let notDeletedPreviousElement;
|
|
|
|
return menuTemplate
|
|
.filter(menuItem => menuItem !== undefined && menuItem !== false && menuItem.visible !== false)
|
|
.filter((menuItem, index, array) => {
|
|
const toDelete = menuItem.type === 'separator' && (!notDeletedPreviousElement || index === array.length - 1 || array[index + 1].type === 'separator');
|
|
notDeletedPreviousElement = toDelete ? notDeletedPreviousElement : menuItem;
|
|
return !toDelete;
|
|
});
|
|
};
|
|
|
|
const create = (win, options) => {
|
|
webContents(win).on('context-menu', (event, props) => {
|
|
if (typeof options.shouldShowMenu === 'function' && options.shouldShowMenu(event, props) === false) {
|
|
return;
|
|
}
|
|
|
|
const { editFlags } = props;
|
|
const hasText = props.selectionText.trim().length > 0;
|
|
const isLink = Boolean(props.linkURL);
|
|
const can = type => editFlags[`can${type}`] && hasText;
|
|
|
|
const defaultActions = {
|
|
separator: () => ({ type: 'separator' }),
|
|
lookUpSelection: decorateMenuItem({
|
|
id: 'lookUpSelection',
|
|
label: 'Look Up “{selection}”',
|
|
visible: process.platform === 'darwin' && hasText && !isLink,
|
|
click() {
|
|
if (process.platform === 'darwin') {
|
|
webContents(win).showDefinitionForSelection();
|
|
}
|
|
},
|
|
}),
|
|
cut: decorateMenuItem({
|
|
id: 'cut',
|
|
label: 'Cut',
|
|
enabled: can('Cut'),
|
|
visible: props.isEditable,
|
|
click(menuItem) {
|
|
props.selectionText = menuItem.transform ? menuItem.transform(props.selectionText) : props.selectionText;
|
|
electron.clipboard.writeText(props.selectionText);
|
|
webContents(win).delete();
|
|
},
|
|
}),
|
|
copy: decorateMenuItem({
|
|
id: 'copy',
|
|
label: 'Copy',
|
|
enabled: can('Copy'),
|
|
visible: props.isEditable || hasText,
|
|
click(menuItem) {
|
|
props.selectionText = menuItem.transform ? menuItem.transform(props.selectionText) : props.selectionText;
|
|
electron.clipboard.writeText(props.selectionText);
|
|
},
|
|
}),
|
|
paste: decorateMenuItem({
|
|
id: 'paste',
|
|
label: 'Paste',
|
|
enabled: editFlags.canPaste,
|
|
visible: props.isEditable,
|
|
click(menuItem) {
|
|
let clipboardContent = electron.clipboard.readText(props.selectionText);
|
|
clipboardContent = menuItem.transform ? menuItem.transform(clipboardContent) : clipboardContent;
|
|
webContents(win).insertText(clipboardContent);
|
|
},
|
|
}),
|
|
saveImage: decorateMenuItem({
|
|
id: 'saveImage',
|
|
label: 'Save Image',
|
|
visible: props.mediaType === 'image',
|
|
click(menuItem) {
|
|
props.srcURL = menuItem.transform ? menuItem.transform(props.srcURL) : props.srcURL;
|
|
// download(win, props.srcURL);
|
|
},
|
|
}),
|
|
saveImageAs: decorateMenuItem({
|
|
id: 'saveImageAs',
|
|
label: 'Save Image As…',
|
|
visible: props.mediaType === 'image',
|
|
click(menuItem) {
|
|
props.srcURL = menuItem.transform ? menuItem.transform(props.srcURL) : props.srcURL;
|
|
// download(win, props.srcURL, {saveAs: true});
|
|
},
|
|
}),
|
|
copyLink: decorateMenuItem({
|
|
id: 'copyLink',
|
|
label: 'Copy Link',
|
|
visible: props.linkURL.length !== 0 && props.mediaType === 'none',
|
|
click(menuItem) {
|
|
props.linkURL = menuItem.transform ? menuItem.transform(props.linkURL) : props.linkURL;
|
|
|
|
electron.clipboard.write({
|
|
bookmark: props.linkText,
|
|
text: props.linkURL,
|
|
});
|
|
},
|
|
}),
|
|
copyImage: decorateMenuItem({
|
|
id: 'copyImage',
|
|
label: 'Copy Image',
|
|
visible: props.mediaType === 'image',
|
|
click() {
|
|
webContents(win).copyImageAt(props.x, props.y);
|
|
},
|
|
}),
|
|
copyImageAddress: decorateMenuItem({
|
|
id: 'copyImageAddress',
|
|
label: 'Copy Image Address',
|
|
visible: props.mediaType === 'image',
|
|
click(menuItem) {
|
|
props.srcURL = menuItem.transform ? menuItem.transform(props.srcURL) : props.srcURL;
|
|
|
|
electron.clipboard.write({
|
|
bookmark: props.srcURL,
|
|
text: props.srcURL,
|
|
});
|
|
},
|
|
}),
|
|
inspect: () => ({
|
|
id: 'inspect',
|
|
label: 'Inspect Element',
|
|
click() {
|
|
win.inspectElement(props.x, props.y);
|
|
|
|
if (webContents(win).isDevToolsOpened()) {
|
|
webContents(win).devToolsWebContents.focus();
|
|
}
|
|
},
|
|
}),
|
|
services: () => ({
|
|
id: 'services',
|
|
label: 'Services',
|
|
role: 'services',
|
|
visible: process.platform === 'darwin' && (props.isEditable || hasText),
|
|
}),
|
|
};
|
|
|
|
const shouldShowInspectElement = typeof options.showInspectElement === 'boolean' ? options.showInspectElement : false;
|
|
|
|
let menuTemplate = [
|
|
defaultActions.separator(),
|
|
options.showLookUpSelection !== false && defaultActions.lookUpSelection(),
|
|
defaultActions.separator(),
|
|
defaultActions.cut(),
|
|
defaultActions.copy(),
|
|
defaultActions.paste(),
|
|
defaultActions.separator(),
|
|
defaultActions.saveImage(),
|
|
options.showSaveImageAs && defaultActions.saveImageAs(),
|
|
options.showCopyImage !== false && defaultActions.copyImage(),
|
|
options.showCopyImageAddress && defaultActions.copyImageAddress(),
|
|
defaultActions.separator(),
|
|
defaultActions.copyLink(),
|
|
defaultActions.separator(),
|
|
shouldShowInspectElement && defaultActions.inspect(),
|
|
options.showServices && defaultActions.services(),
|
|
defaultActions.separator(),
|
|
];
|
|
|
|
if (options.menu) {
|
|
menuTemplate = options.menu(defaultActions, props, win);
|
|
}
|
|
|
|
if (options.prepend) {
|
|
const result = options.prepend(defaultActions, props, win);
|
|
|
|
if (Array.isArray(result)) {
|
|
menuTemplate.unshift(...result);
|
|
}
|
|
}
|
|
|
|
if (options.append) {
|
|
const result = options.append(defaultActions, props, win);
|
|
|
|
if (Array.isArray(result)) {
|
|
menuTemplate.push(...result);
|
|
}
|
|
}
|
|
|
|
// Filter out leading/trailing separators
|
|
// TODO: https://github.com/electron/electron/issues/5869
|
|
menuTemplate = removeUnusedMenuItems(menuTemplate);
|
|
|
|
for (const menuItem of menuTemplate) {
|
|
// Apply custom labels for default menu items
|
|
if (options.labels && options.labels[menuItem.id]) {
|
|
menuItem.label = options.labels[menuItem.id];
|
|
}
|
|
|
|
// Replace placeholders in menu item labels
|
|
// if (typeof menuItem.label === 'string' && menuItem.label.includes('{selection}')) {
|
|
// const selectionString = typeof props.selectionText === 'string' ? props.selectionText.trim() : '';
|
|
// menuItem.label = menuItem.label.replace('{selection}', cliTruncate(selectionString, 25));
|
|
// }
|
|
}
|
|
|
|
if (menuTemplate.length > 0) {
|
|
const menu = (electronRemote ? electronRemote.Menu : electron.Menu).buildFromTemplate(menuTemplate);
|
|
|
|
//
|
|
// When `electronRemote` is not available, this runs in the browser process.
|
|
//
|
|
// We can safely use `win` in this case as it refers to the window the
|
|
// context-menu should open in.
|
|
//
|
|
// When this is being called from a web view, we can't use `win` as this
|
|
// would refer to the web view which is not allowed to render a popup menu.
|
|
//
|
|
menu.popup(electronRemote ? electronRemote.getCurrentWindow() : win);
|
|
}
|
|
});
|
|
};
|
|
|
|
module.exports = (options = {}) => {
|
|
if (options.window) {
|
|
const win = options.window;
|
|
|
|
// When window is a webview that has not yet finished loading webContents is not available
|
|
if (webContents(win) === undefined) {
|
|
win.addEventListener('dom-ready', () => {
|
|
create(win, options);
|
|
}, { once: true });
|
|
return;
|
|
}
|
|
|
|
return create(win, options);
|
|
}
|
|
|
|
for (const win of (electron.BrowserWindow || electronRemote.BrowserWindow).getAllWindows()) {
|
|
create(win, options);
|
|
}
|
|
|
|
(electron.app || electronRemote.app).on('browser-window-created', (event, win) => {
|
|
create(win, options);
|
|
});
|
|
};
|