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