aboutsummaryrefslogtreecommitdiffstats
path: root/src/webview/contextMenu.js
diff options
context:
space:
mode:
authorLibravatar Amine Mouafik <amine@mouafik.fr>2020-03-02 02:31:15 +0100
committerLibravatar Amine Mouafik <amine@mouafik.fr>2020-03-02 02:31:15 +0100
commit339d961b3f2e0c386e68e650ee0f05e705f38c9e (patch)
tree8c1ba574f50b7e130c35402b983032fbcccf1529 /src/webview/contextMenu.js
parentMerge remote-tracking branch 'origin/review/5.4.4-beta.2-kytwb' into develop (diff)
downloadferdium-app-339d961b3f2e0c386e68e650ee0f05e705f38c9e.tar.gz
ferdium-app-339d961b3f2e0c386e68e650ee0f05e705f38c9e.tar.zst
ferdium-app-339d961b3f2e0c386e68e650ee0f05e705f38c9e.zip
Remove conflicting context menu
Diffstat (limited to 'src/webview/contextMenu.js')
-rw-r--r--src/webview/contextMenu.js323
1 files changed, 0 insertions, 323 deletions
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
deleted file mode 100644
index acd62d675..000000000
--- a/src/webview/contextMenu.js
+++ /dev/null
@@ -1,323 +0,0 @@
1// This is heavily based on https://github.com/sindresorhus/electron-context-menu
2// ❤ @sindresorhus
3
4import {
5 clipboard, remote, ipcRenderer, shell,
6} from 'electron';
7
8import { isDevMode, isMac } from '../environment';
9import { SPELLCHECKER_LOCALES } from '../i18n/languages';
10
11const debug = require('debug')('Ferdi:contextMenu');
12
13const { Menu } = remote;
14
15// const win = remote.getCurrentWindow();
16const webContents = remote.getCurrentWebContents();
17
18function delUnusedElements(menuTpl) {
19 let notDeletedPrevEl;
20 return menuTpl.filter(el => el.visible !== false).filter((el, i, array) => {
21 const toDelete = el.type === 'separator' && (!notDeletedPrevEl || i === array.length - 1 || array[i + 1].type === 'separator');
22 notDeletedPrevEl = toDelete ? notDeletedPrevEl : el;
23 return !toDelete;
24 });
25}
26
27const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheckerLanguage, spellcheckerLanguage) => {
28 const { editFlags } = props;
29 const textSelection = props.selectionText.trim();
30 const hasText = textSelection.length > 0;
31 const can = type => editFlags[`can${type}`] && hasText;
32
33 const canGoBack = webContents.canGoBack();
34 const canGoForward = webContents.canGoForward();
35
36 // @adlk: we can't use roles here due to a bug with electron where electron.remote.webContents.getFocusedWebContents() returns the first webview in DOM instead of the focused one
37 // Github issue creation is pending
38 let menuTpl = [
39 {
40 type: 'separator',
41 }, {
42 id: 'createTodo',
43 label: `Create todo: "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
44 visible: hasText,
45 click() {
46 debug('Create todo from selected text', textSelection);
47 ipcRenderer.sendToHost('feature:todos', {
48 action: 'todos:create',
49 data: {
50 title: textSelection,
51 url: window.location.href,
52 },
53 });
54 },
55 },
56 {
57 type: 'separator',
58 }, {
59 id: 'lookup',
60 label: `Look Up "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
61 visible: isMac && props.mediaType === 'none' && hasText,
62 click() {
63 debug('Show definition for selection', textSelection);
64 webContents.showDefinitionForSelection();
65 },
66 }, {
67 type: 'separator',
68 }, {
69 id: 'cut',
70 label: 'Cut',
71 click() {
72 if (can('Cut')) {
73 webContents.cut();
74 }
75 },
76 enabled: can('Cut'),
77 visible: hasText && props.isEditable,
78 }, {
79 id: 'copy',
80 label: 'Copy',
81 click() {
82 if (can('Copy')) {
83 webContents.copy();
84 }
85 },
86 enabled: can('Copy'),
87 visible: props.isEditable || hasText,
88 }, {
89 id: 'paste',
90 label: 'Paste',
91 click() {
92 if (editFlags.canPaste) {
93 webContents.paste();
94 }
95 },
96 enabled: editFlags.canPaste,
97 visible: props.isEditable,
98 }, {
99 type: 'separator',
100 visible: props.isEditable && hasText,
101 }, {
102 id: 'searchTextSelection',
103 label: `Search Google for "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
104 visible: hasText,
105 click() {
106 const url = `https://www.google.com/search?q=${textSelection}`;
107 debug('Search on Google', url);
108 shell.openExternal(url);
109 },
110 }, {
111 type: 'separator',
112 },
113 ];
114
115 if (props.linkURL && props.mediaType === 'none') {
116 menuTpl = [{
117 type: 'separator',
118 }, {
119 id: 'openLink',
120 label: 'Open Link in Browser',
121 click() {
122 debug('Open link in Browser', props.linkURL);
123 shell.openExternal(props.linkURL);
124 },
125 }, {
126 id: 'copyLink',
127 label: 'Copy Link',
128 click() {
129 clipboard.write({
130 bookmark: props.linkText,
131 text: props.linkURL,
132 });
133 },
134 }, {
135 type: 'separator',
136 }];
137 }
138
139 if (props.mediaType === 'image') {
140 menuTpl.push({
141 type: 'separator',
142 }, {
143 id: 'openImage',
144 label: 'Open Image in Browser',
145 click() {
146 debug('Open image in Browser', props.srcURL);
147 shell.openExternal(props.srcURL);
148 },
149 }, {
150 id: 'copyImageAddress',
151 label: 'Copy Image Address',
152 click() {
153 clipboard.write({
154 bookmark: props.srcURL,
155 text: props.srcURL,
156 });
157 },
158 }, {
159 type: 'separator',
160 });
161 }
162
163 if (props.mediaType === 'image') {
164 menuTpl.push({
165 id: 'saveImageAs',
166 label: 'Save Image As…',
167 async click() {
168 if (props.srcURL.startsWith('blob:')) {
169 const url = new window.URL(props.srcURL.substr(5));
170 const fileName = url.pathname.substr(1);
171 const resp = await window.fetch(props.srcURL);
172 const blob = await resp.blob();
173 const reader = new window.FileReader();
174 reader.readAsDataURL(blob);
175 reader.onloadend = () => {
176 const base64data = reader.result;
177
178 ipcRenderer.send('download-file', {
179 content: base64data,
180 fileOptions: {
181 name: fileName,
182 mime: blob.type,
183 },
184 });
185 };
186 debug('binary string', blob);
187 } else {
188 ipcRenderer.send('download-file', { url: props.srcURL });
189 }
190 },
191 }, {
192 type: 'separator',
193 });
194 }
195
196 if (suggestions.length > 0) {
197 suggestions.reverse().map(suggestion => menuTpl.unshift({
198 id: `suggestion-${suggestion}`,
199 label: suggestion,
200 click() {
201 webContents.replaceMisspelling(suggestion);
202 },
203 }));
204 }
205
206 if (canGoBack || canGoForward) {
207 menuTpl.push({
208 type: 'separator',
209 }, {
210 id: 'goBack',
211 label: 'Go Back',
212 enabled: canGoBack,
213 click() {
214 webContents.goBack();
215 },
216 }, {
217 id: 'goForward',
218 label: 'Go Forward',
219 enabled: canGoForward,
220 click() {
221 webContents.goForward();
222 },
223 }, {
224 type: 'separator',
225 });
226 }
227
228 const spellcheckingLanguages = [];
229 Object.keys(SPELLCHECKER_LOCALES).sort(Intl.Collator().compare).forEach((key) => {
230 spellcheckingLanguages.push({
231 id: `lang-${key}`,
232 label: SPELLCHECKER_LOCALES[key],
233 type: 'radio',
234 checked: spellcheckerLanguage === key,
235 click() {
236 debug('Setting service spellchecker to', key);
237 ipcRenderer.sendToHost('set-service-spellchecker-language', key);
238 },
239 });
240 });
241
242 menuTpl.push({
243 type: 'separator',
244 }, {
245 id: 'spellchecker',
246 label: 'Spell Checking',
247 visible: isSpellcheckEnabled,
248 submenu: [
249 {
250 id: 'spellchecker',
251 label: 'Available Languages',
252 enabled: false,
253 }, {
254 type: 'separator',
255 },
256 {
257 id: 'resetToDefault',
258 label: `Reset to system default (${defaultSpellcheckerLanguage === 'automatic' ? 'Automatic' : SPELLCHECKER_LOCALES[defaultSpellcheckerLanguage]})`,
259 type: 'radio',
260 visible: defaultSpellcheckerLanguage !== spellcheckerLanguage || (defaultSpellcheckerLanguage !== 'automatic' && spellcheckerLanguage === 'automatic'),
261 click() {
262 debug('Resetting service spellchecker to system default');
263 ipcRenderer.sendToHost('set-service-spellchecker-language', 'reset');
264 },
265 },
266 {
267 id: 'automaticDetection',
268 label: 'Automatic language detection',
269 type: 'radio',
270 checked: spellcheckerLanguage === 'automatic',
271 click() {
272 debug('Detect language automatically');
273 ipcRenderer.sendToHost('set-service-spellchecker-language', 'automatic');
274 },
275 },
276 {
277 type: 'separator',
278 visible: defaultSpellcheckerLanguage !== spellcheckerLanguage,
279 },
280 ...spellcheckingLanguages],
281 });
282
283
284 if (isDevMode) {
285 menuTpl.push({
286 type: 'separator',
287 }, {
288 id: 'inspect',
289 label: 'Inspect Element',
290 click() {
291 webContents.inspectElement(props.x, props.y);
292 },
293 });
294 }
295
296 return delUnusedElements(menuTpl);
297};
298
299export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) {
300 webContents.on('context-menu', async (e, props) => {
301 e.preventDefault();
302
303 let suggestions = [];
304 if (spellcheckProvider && props.misspelledWord) {
305 debug('Mispelled word', props.misspelledWord);
306 suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord);
307
308 debug('Suggestions', suggestions);
309 }
310
311 const menu = Menu.buildFromTemplate(
312 buildMenuTpl(
313 props,
314 suggestions.slice(0, 5),
315 isSpellcheckEnabled(),
316 getDefaultSpellcheckerLanguage(),
317 getSpellcheckerLanguage(),
318 ),
319 );
320
321 menu.popup();
322 });
323}