diff options
author | 2020-03-02 02:31:15 +0100 | |
---|---|---|
committer | 2020-03-02 02:31:15 +0100 | |
commit | 339d961b3f2e0c386e68e650ee0f05e705f38c9e (patch) | |
tree | 8c1ba574f50b7e130c35402b983032fbcccf1529 | |
parent | Merge remote-tracking branch 'origin/review/5.4.4-beta.2-kytwb' into develop (diff) | |
download | ferdium-app-339d961b3f2e0c386e68e650ee0f05e705f38c9e.tar.gz ferdium-app-339d961b3f2e0c386e68e650ee0f05e705f38c9e.tar.zst ferdium-app-339d961b3f2e0c386e68e650ee0f05e705f38c9e.zip |
Remove conflicting context menu
-rw-r--r-- | src/webview/contextMenu.js | 323 | ||||
-rw-r--r-- | src/webview/recipe.js | 11 |
2 files changed, 1 insertions, 333 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 | |||
4 | import { | ||
5 | clipboard, remote, ipcRenderer, shell, | ||
6 | } from 'electron'; | ||
7 | |||
8 | import { isDevMode, isMac } from '../environment'; | ||
9 | import { SPELLCHECKER_LOCALES } from '../i18n/languages'; | ||
10 | |||
11 | const debug = require('debug')('Ferdi:contextMenu'); | ||
12 | |||
13 | const { Menu } = remote; | ||
14 | |||
15 | // const win = remote.getCurrentWindow(); | ||
16 | const webContents = remote.getCurrentWebContents(); | ||
17 | |||
18 | function 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 | |||
27 | const 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 | |||
299 | export 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 | } | ||
diff --git a/src/webview/recipe.js b/src/webview/recipe.js index 1a22542d8..07d29f477 100644 --- a/src/webview/recipe.js +++ b/src/webview/recipe.js | |||
@@ -23,7 +23,6 @@ import RecipeWebview from './lib/RecipeWebview'; | |||
23 | 23 | ||
24 | import spellchecker, { switchDict, disable as disableSpellchecker, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; | 24 | import spellchecker, { switchDict, disable as disableSpellchecker, getSpellcheckerLocaleByFuzzyIdentifier } from './spellchecker'; |
25 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; | 25 | import { injectDarkModeStyle, isDarkModeStyleInjected, removeDarkModeStyle } from './darkmode'; |
26 | import contextMenu from './contextMenu'; | ||
27 | import './notifications'; | 26 | import './notifications'; |
28 | 27 | ||
29 | import { DEFAULT_APP_SETTINGS } from '../config'; | 28 | import { DEFAULT_APP_SETTINGS } from '../config'; |
@@ -72,15 +71,7 @@ class RecipeController { | |||
72 | 71 | ||
73 | debug('Send "hello" to host'); | 72 | debug('Send "hello" to host'); |
74 | setTimeout(() => ipcRenderer.sendToHost('hello'), 100); | 73 | setTimeout(() => ipcRenderer.sendToHost('hello'), 100); |
75 | 74 | await spellchecker(); | |
76 | this.spellcheckingProvider = await spellchecker(); | ||
77 | contextMenu( | ||
78 | this.spellcheckingProvider, | ||
79 | () => this.settings.app.enableSpellchecking, | ||
80 | () => this.settings.app.spellcheckerLanguage, | ||
81 | () => this.spellcheckerLanguage, | ||
82 | ); | ||
83 | |||
84 | autorun(() => this.update()); | 75 | autorun(() => this.update()); |
85 | } | 76 | } |
86 | 77 | ||