aboutsummaryrefslogtreecommitdiffstats
path: root/src/webview/contextMenu.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/webview/contextMenu.js')
-rw-r--r--src/webview/contextMenu.js178
1 files changed, 178 insertions, 0 deletions
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
new file mode 100644
index 000000000..195306fda
--- /dev/null
+++ b/src/webview/contextMenu.js
@@ -0,0 +1,178 @@
1// This is heavily based on https://github.com/sindresorhus/electron-context-menu
2// ❤ @sindresorhus
3
4import { clipboard, remote, ipcRenderer, shell } from 'electron';
5
6import { isDevMode } from '../environment';
7
8const debug = require('debug')('Franz:contextMenu');
9
10const { Menu } = remote;
11
12// const win = remote.getCurrentWindow();
13const webContents = remote.getCurrentWebContents();
14
15function delUnusedElements(menuTpl) {
16 let notDeletedPrevEl;
17 return menuTpl.filter(el => el.visible !== false).filter((el, i, array) => {
18 const toDelete = el.type === 'separator' && (!notDeletedPrevEl || i === array.length - 1 || array[i + 1].type === 'separator');
19 notDeletedPrevEl = toDelete ? notDeletedPrevEl : el;
20 return !toDelete;
21 });
22}
23
24const buildMenuTpl = (props, suggestions) => {
25 const { editFlags } = props;
26 const hasText = props.selectionText.trim().length > 0;
27 const can = type => editFlags[`can${type}`] && hasText;
28
29 console.log(props);
30
31 let menuTpl = [
32 {
33 type: 'separator',
34 }, {
35 id: 'cut',
36 role: can('Cut') ? 'cut' : '',
37 enabled: can('Cut'),
38 visible: !!props.selectionText.trim(),
39 }, {
40 id: 'copy',
41 label: 'Copy',
42 role: can('Copy') ? 'copy' : '',
43 enabled: can('Copy'),
44 visible: props.isEditable || hasText,
45 }, {
46 id: 'paste',
47 label: 'Paste',
48 role: editFlags.canPaste ? 'paste' : '',
49 enabled: editFlags.canPaste,
50 visible: props.isEditable,
51 }, {
52 type: 'separator',
53 },
54 ];
55
56 if (props.linkURL && props.mediaType === 'none') {
57 menuTpl = [{
58 type: 'separator',
59 }, {
60 id: 'openLink',
61 label: 'Open Link in Browser',
62 click() {
63 shell.openExternal(props.linkURL);
64 },
65 }, {
66 id: 'copyLink',
67 label: 'Copy Link',
68 click() {
69 clipboard.write({
70 bookmark: props.linkText,
71 text: props.linkURL,
72 });
73 },
74 }, {
75 type: 'separator',
76 }];
77 }
78
79 if (props.mediaType === 'image') {
80 menuTpl.push({
81 type: 'separator',
82 }, {
83 id: 'openImage',
84 label: 'Open Image in Browser',
85 click() {
86 shell.openExternal(props.srcURL);
87 },
88 }, {
89 id: 'copyImageAddress',
90 label: 'Copy Image Address',
91 click() {
92 clipboard.write({
93 bookmark: props.srcURL,
94 text: props.srcURL,
95 });
96 },
97 }, {
98 type: 'separator',
99 });
100 }
101
102 if (props.mediaType === 'image') {
103 menuTpl.push({
104 id: 'saveImageAs',
105 label: 'Save Image As…',
106 async click() {
107 if (props.srcURL.startsWith('blob:')) {
108 const url = new window.URL(props.srcURL.substr(5));
109 const fileName = url.pathname.substr(1);
110 const resp = await window.fetch(props.srcURL);
111 const blob = await resp.blob();
112 const reader = new window.FileReader();
113 reader.readAsDataURL(blob);
114 reader.onloadend = () => {
115 const base64data = reader.result;
116
117 ipcRenderer.send('download-file', {
118 content: base64data,
119 fileOptions: {
120 name: fileName,
121 mime: blob.type,
122 },
123 });
124 };
125 debug('binary string', blob);
126 } else {
127 ipcRenderer.send('download-file', { url: props.srcURL });
128 }
129 },
130 }, {
131 type: 'separator',
132 });
133 }
134
135 console.log('suggestions', suggestions.length, suggestions);
136 if (suggestions.length > 0) {
137 suggestions.reverse().map(suggestion => menuTpl.unshift({
138 id: `suggestion-${suggestion}`,
139 label: suggestion,
140 click() {
141 webContents.replaceMisspelling(suggestion);
142 },
143 }));
144 }
145
146 if (isDevMode) {
147 menuTpl.push({
148 type: 'separator',
149 }, {
150 id: 'inspect',
151 label: 'Inspect Element',
152 click() {
153 webContents.inspectElement(props.x, props.y);
154 },
155 }, {
156 type: 'separator',
157 });
158 }
159
160 return delUnusedElements(menuTpl);
161};
162
163export default function contextMenu(spellcheckProvider) {
164 webContents.on('context-menu', (e, props) => {
165 e.preventDefault();
166
167 let suggestions = [];
168 if (spellcheckProvider && props.misspelledWord) {
169 suggestions = spellcheckProvider.getSuggestion(props.misspelledWord);
170
171 debug('Suggestions', suggestions);
172 }
173
174 const menu = Menu.buildFromTemplate(buildMenuTpl(props, suggestions.slice(0, 5)));
175
176 menu.popup(remote.getCurrentWindow());
177 });
178}