38c295427a
These won't work outside of the electron app so there is zero point in trying to open them in an external browser or offerring to copy them to the clipboard
131 lines
3.9 KiB
JavaScript
131 lines
3.9 KiB
JavaScript
const {clipboard, nativeImage, Menu, MenuItem, shell} = require('electron');
|
|
const url = require('url');
|
|
|
|
const PERMITTED_URL_SCHEMES = [
|
|
'http:',
|
|
'https:',
|
|
'mailto:',
|
|
];
|
|
|
|
function safeOpenURL(target) {
|
|
// openExternal passes the target to open/start/xdg-open,
|
|
// so put fairly stringent limits on what can be opened
|
|
// (for instance, open /bin/sh does indeed open a terminal
|
|
// with a shell, albeit with no arguments)
|
|
const parsedUrl = url.parse(target);
|
|
if (PERMITTED_URL_SCHEMES.indexOf(parsedUrl.protocol) > -1) {
|
|
// explicitly use the URL re-assembled by the url library,
|
|
// so we know the url parser has understood all the parts
|
|
// of the input string
|
|
const newTarget = url.format(parsedUrl);
|
|
shell.openExternal(newTarget);
|
|
}
|
|
}
|
|
|
|
function onWindowOrNavigate(ev, target) {
|
|
// always prevent the default: if something goes wrong,
|
|
// we don't want to end up opening it in the electron
|
|
// app, as we could end up opening any sort of random
|
|
// url in a window that has node scripting access.
|
|
ev.preventDefault();
|
|
safeOpenURL(target);
|
|
}
|
|
|
|
function onLinkContextMenu(ev, params) {
|
|
const url = params.linkURL || params.srcURL;
|
|
|
|
const popupMenu = new Menu();
|
|
// No point trying to open blob: URLs in an external browser: it ain't gonna work.
|
|
if (!url.startsWith('blob:')) {
|
|
popupMenu.append(new MenuItem({
|
|
label: url,
|
|
click() {
|
|
safeOpenURL(url);
|
|
},
|
|
}));
|
|
}
|
|
|
|
if (params.mediaType && params.mediaType === 'image' && !url.startsWith('file://')) {
|
|
popupMenu.append(new MenuItem({
|
|
label: 'Copy Image',
|
|
click() {
|
|
if (url.startsWith('data:')) {
|
|
clipboard.writeImage(nativeImage.createFromDataURL(url));
|
|
} else {
|
|
ev.sender.copyImageAt(params.x, params.y);
|
|
}
|
|
},
|
|
}));
|
|
}
|
|
|
|
// No point offerring to copy a blob: URL either
|
|
if (!url.startsWith('blob:')) {
|
|
popupMenu.append(new MenuItem({
|
|
label: 'Copy Link Address',
|
|
click() {
|
|
clipboard.writeText(url);
|
|
},
|
|
}));
|
|
}
|
|
// popup() requires an options object even for no options
|
|
popupMenu.popup({});
|
|
ev.preventDefault();
|
|
}
|
|
|
|
function _CutCopyPasteSelectContextMenus(params) {
|
|
return [{
|
|
role: 'cut',
|
|
enabled: params.editFlags.canCut,
|
|
}, {
|
|
role: 'copy',
|
|
enabled: params.editFlags.canCopy,
|
|
}, {
|
|
role: 'paste',
|
|
enabled: params.editFlags.canPaste,
|
|
}, {
|
|
role: 'pasteandmatchstyle',
|
|
enabled: params.editFlags.canPaste,
|
|
}, {
|
|
role: 'selectall',
|
|
enabled: params.editFlags.canSelectAll,
|
|
}];
|
|
}
|
|
|
|
function onSelectedContextMenu(ev, params) {
|
|
const items = _CutCopyPasteSelectContextMenus(params);
|
|
const popupMenu = Menu.buildFromTemplate(items);
|
|
|
|
// popup() requires an options object even for no options
|
|
popupMenu.popup({});
|
|
ev.preventDefault();
|
|
}
|
|
|
|
function onEditableContextMenu(ev, params) {
|
|
const items = [
|
|
{ role: 'undo' },
|
|
{ role: 'redo', enabled: params.editFlags.canRedo },
|
|
{ type: 'separator' },
|
|
].concat(_CutCopyPasteSelectContextMenus(params));
|
|
|
|
const popupMenu = Menu.buildFromTemplate(items);
|
|
|
|
// popup() requires an options object even for no options
|
|
popupMenu.popup({});
|
|
ev.preventDefault();
|
|
}
|
|
|
|
|
|
module.exports = (webContents) => {
|
|
webContents.on('new-window', onWindowOrNavigate);
|
|
webContents.on('will-navigate', onWindowOrNavigate);
|
|
|
|
webContents.on('context-menu', function(ev, params) {
|
|
if (params.linkURL || params.srcURL) {
|
|
onLinkContextMenu(ev, params);
|
|
} else if (params.selectionText) {
|
|
onSelectedContextMenu(ev, params);
|
|
} else if (params.isEditable) {
|
|
onEditableContextMenu(ev, params);
|
|
}
|
|
});
|
|
};
|