aboutsummaryrefslogtreecommitdiffstats
path: root/src/webview/contextMenu.js
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2018-11-30 14:32:45 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2018-11-30 14:32:45 +0100
commit3d87c0e45cead95ddb6c11fc6540b82e375bdcf5 (patch)
treec91f425a39cb585242d6df5b4070de4a2141b3b4 /src/webview/contextMenu.js
parentMerge branch 'update/monetization' into develop (diff)
downloadferdium-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.js175
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
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 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
160export 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}