git ssb

1+

Daan Patchwork / patchwork



Tree: 4d9f238ff73f7136cd292da88f06c17fe1a8c445

Files: 4d9f238ff73f7136cd292da88f06c17fe1a8c445 / lib / context-menu.js

7121 bytesRaw
1const { shell, clipboard } = require('electron')
2const { BrowserWindow, ContextMenuParams, ipcMain, MenuItemConstructorOptions, WebContents } = require('electron')
3const contextMenu = require('electron-context-menu')
4const ref = require('ssb-ref')
5
6// used to receive out-of-band information about context-menu events
7// see below, Ctrl-F "context-menu-info"
8var lastClickInfo;
9
10module.exports = function (
11 config,
12 serverDevToolsCallback,
13 navigateHandler,
14 window
15) {
16 ipcMain.handle("context-menu-info", (event, info) => {
17 lastClickInfo = info;
18 return true;
19 });
20 contextMenu({
21 window,
22 menu: (defaultActions, parameters, _, dictionarySuggestions) => {
23 // elementAtPosition(window, parameters.x, parameters.y)
24 const isFileProtocol = parameters.linkURL.startsWith("file:");
25
26 // This is very similar to the boilerplate from electron-context-menu even
27 // though we don't use all the options. Some of the options are disabled
28 // via "condition && " guards just to clarify where we differ from the
29 // boilerplate.
30 // See the original menu structure here:
31 // https://github.com/sindresorhus/electron-context-menu/blob/621c29a8a133925ac25529e4bea2a738394e8609/index.js#L230
32 // We could probably get away with heavy, heavy modification instead of
33 // menu but this seems more understandable, all things considered
34 let menuTemplate = [
35 dictionarySuggestions.length > 0 && defaultActions.separator(),
36 ...dictionarySuggestions,
37 defaultActions.separator(),
38
39 defaultActions.learnSpelling(),
40 defaultActions.separator(),
41
42 defaultActions.lookUpSelection(),
43 defaultActions.separator(),
44
45 searchwithDDG(parameters), // instead of defaultActions.searchWithGoogle()
46 defaultActions.separator(),
47
48 defaultActions.cut(),
49 defaultActions.copy(),
50 defaultActions.paste(),
51 defaultActions.separator(),
52
53 // We typically don't want to copy links, only external ones
54 copyEmbedMd(parameters),
55 copyMsgText(window),
56 // this and the next one might return the same id. Bit redundant but
57 // only if we right-click on the message timestamp or such
58 copyMsgKey(),
59 copyRef(parameters),
60 copyEmail(parameters),
61 // We could make our own copyLink() instead which sets
62 // visible: !isFileProtocol but this is easier
63 !isFileProtocol && defaultActions.copyLink(),
64 copyExternalLink(config),
65 openOnExternalViewer(config),
66 findRefs(parameters, navigateHandler),
67 defaultActions.separator(),
68
69 openMediaInBrowser(parameters),
70 defaultActions.saveImage(),
71 defaultActions.saveImageAs(),
72 defaultActions.copyImage(),
73 defaultActions.copyImageAddress(),
74 defaultActions.separator(),
75
76 // this could trigger a web request from within patchwork and we don't want that
77 false && defaultActions.saveLinkAs(),
78 defaultActions.separator(),
79
80 defaultActions.inspect(),
81 openServerDevTools(serverDevToolsCallback),
82 defaultActions.services(),
83 defaultActions.separator(),
84
85 reloadWindow(),
86 ];
87
88 return menuTemplate;
89 },
90 });
91};
92
93// Every function below here will produce one MenuItemConstructorOptions object
94
95function copyMsgKey() {
96 const msgKey = lastClickInfo?.msg?.key
97 return {
98 label: "Copy Message Reference",
99 visible: !!msgKey,
100 click: function () {
101 }
102 }
103}
104
105function copyMsgText(window) {
106 const msgKey = lastClickInfo?.msg?.key
107 return {
108 label: "Copy Message Text",
109 visible: !!msgKey,
110 click: function () {
111 window.webContents.send('copy-message-text', msgKey)
112 },
113 };
114}
115
116function openOnExternalViewer(config) {
117 const msgKey = lastClickInfo?.msg?.key
118 return {
119 label: 'Open In Online Viewer',
120 visible: !!msgKey,
121 click: function () {
122 const key = msgKey
123 const gateway = config.gateway ||
124 'https://viewer.scuttlebot.io'
125 const url = `${gateway}/${encodeURIComponent(key)}`
126 shell.openExternal(url);
127 }
128 }
129}
130
131function copyExternalLink(config) {
132 const msgKey = lastClickInfo?.msg?.key
133 return {
134 label: 'Copy External Link',
135 visible: !!msgKey,
136 click: function () {
137 const key = msgKey
138 const gateway = config.gateway ||
139 'https://viewer.scuttlebot.io'
140 const url = `${gateway}/${encodeURIComponent(key)}`
141 clipboard.writeText(url)
142 }
143 }
144}
145
146function findRefs(parameters, navigate) {
147 const extractedRef =
148 parameters.mediaType === "none"
149 ? ref.extract(parameters.linkURL)
150 : ref.extract(parameters.srcURL);
151 const usageOrRef = extractedRef && parameters.mediaType === "none"
152 ? 'References To'
153 : 'Usages Of'
154 const label = !!extractedRef
155 ? `Find ${usageOrRef} ${extractedRef.slice(0, 10).replaceAll("&", "&&&")}...`
156 : "";
157 return {
158 label,
159 visible: !!extractedRef,
160 click: () => {
161 navigate(`?${extractedRef}`);
162 },
163 };
164}
165
166function copyRef(parameters) {
167 const extractedRef =
168 parameters.mediaType === "none"
169 ? ref.extract(parameters.linkURL)
170 : ref.extract(parameters.srcURL);
171 const label = !!extractedRef
172 ? `Copy Reference ${extractedRef.slice(0, 10).replaceAll("&", "&&&")}...`
173 : "";
174 return {
175 label,
176 visible: !!extractedRef,
177 click: () => {
178 clipboard.writeText(extractedRef);
179 },
180 };
181}
182
183function copyEmail(parameters) {
184 return {
185 label: "Copy Email Address",
186 // FIXME: this fails for "mailto:" links that actually are hand-coded in markdown
187 // example: Mail me at my [work address](mailto:daan@business.corp)
188 visible: parameters.linkURL.startsWith("mailto:"),
189 click: () => {
190 // Omit the mailto: portion of the link; we just want the address
191 clipboard.writeText(parameters.linkText);
192 },
193 };
194}
195
196function copyEmbedMd(parameters) {
197 return {
198 label: "Copy Embed Markdown",
199 visible: parameters.mediaType !== "none",
200 click: () => {
201 const extractedRef = ref.extract(parameters.srcURL);
202 clipboard.writeText(`![${parameters.titleText}](${extractedRef})`);
203 },
204 };
205}
206
207function openMediaInBrowser(parameters) {
208 return {
209 label: "Open With Browser",
210 visible: parameters.mediaType !== "none",
211 click: () => {
212 shell.openExternal(parameters.srcURL);
213 },
214 };
215}
216
217function searchwithDDG(parameters) {
218 return {
219 label: "Search With DuckDuckGo",
220 // Only show it when right-clicking text
221 visible: parameters.selectionText.trim().length > 0,
222 click: () => {
223 const url = `https://duckduckgo.com/?q=${encodeURIComponent(
224 parameters.selectionText
225 )}`;
226 shell.openExternal(url);
227 },
228 };
229}
230
231function reloadWindow() {
232 return {
233 label: "Reload",
234 click: function (item, focusedWindow) {
235 if (focusedWindow) {
236 focusedWindow.reload();
237 }
238 },
239 };
240}
241
242function openServerDevTools(serverDevToolsCallback) {
243 return {
244 label: "Inspect Server Process",
245 click: serverDevToolsCallback,
246 };
247}

Built with git-ssb-web