git ssb

1+

Daan Patchwork / patchwork



Tree: d1627bb325d9d1acbb1ba98a75ceb37a6b537c85

Files: d1627bb325d9d1acbb1ba98a75ceb37a6b537c85 / lib / context-menu.js

7155 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 clipboard.writeText(msgKey)
102 }
103 }
104}
105
106function copyMsgText(window) {
107 const msgKey = lastClickInfo?.msg?.key
108 return {
109 label: "Copy Message Text",
110 visible: !!msgKey,
111 click: function () {
112 window.webContents.send('copy-message-text', msgKey)
113 },
114 };
115}
116
117function openOnExternalViewer(config) {
118 const msgKey = lastClickInfo?.msg?.key
119 return {
120 label: 'Open In Online Viewer',
121 visible: !!msgKey,
122 click: function () {
123 const key = msgKey
124 const gateway = config.gateway ||
125 'https://viewer.scuttlebot.io'
126 const url = `${gateway}/${encodeURIComponent(key)}`
127 shell.openExternal(url);
128 }
129 }
130}
131
132function copyExternalLink(config) {
133 const msgKey = lastClickInfo?.msg?.key
134 return {
135 label: 'Copy External Link',
136 visible: !!msgKey,
137 click: function () {
138 const key = msgKey
139 const gateway = config.gateway ||
140 'https://viewer.scuttlebot.io'
141 const url = `${gateway}/${encodeURIComponent(key)}`
142 clipboard.writeText(url)
143 }
144 }
145}
146
147function findRefs(parameters, navigate) {
148 const extractedRef =
149 parameters.mediaType === "none"
150 ? ref.extract(parameters.linkURL)
151 : ref.extract(parameters.srcURL);
152 const usageOrRef = extractedRef && parameters.mediaType === "none"
153 ? 'References To'
154 : 'Usages Of'
155 const label = !!extractedRef
156 ? `Find ${usageOrRef} ${extractedRef.slice(0, 10).replaceAll("&", "&&&")}...`
157 : "";
158 return {
159 label,
160 visible: !!extractedRef,
161 click: () => {
162 navigate(`?${extractedRef}`);
163 },
164 };
165}
166
167function copyRef(parameters) {
168 const extractedRef =
169 parameters.mediaType === "none"
170 ? ref.extract(parameters.linkURL)
171 : ref.extract(parameters.srcURL);
172 const label = !!extractedRef
173 ? `Copy Reference ${extractedRef.slice(0, 10).replaceAll("&", "&&&")}...`
174 : "";
175 return {
176 label,
177 visible: !!extractedRef,
178 click: () => {
179 clipboard.writeText(extractedRef);
180 },
181 };
182}
183
184function copyEmail(parameters) {
185 return {
186 label: "Copy Email Address",
187 // FIXME: this fails for "mailto:" links that actually are hand-coded in markdown
188 // example: Mail me at my [work address](mailto:daan@business.corp)
189 visible: parameters.linkURL.startsWith("mailto:"),
190 click: () => {
191 // Omit the mailto: portion of the link; we just want the address
192 clipboard.writeText(parameters.linkText);
193 },
194 };
195}
196
197function copyEmbedMd(parameters) {
198 return {
199 label: "Copy Embed Markdown",
200 visible: parameters.mediaType !== "none",
201 click: () => {
202 const extractedRef = ref.extract(parameters.srcURL);
203 clipboard.writeText(`![${parameters.titleText}](${extractedRef})`);
204 },
205 };
206}
207
208function openMediaInBrowser(parameters) {
209 return {
210 label: "Open With Browser",
211 visible: parameters.mediaType !== "none",
212 click: () => {
213 shell.openExternal(parameters.srcURL);
214 },
215 };
216}
217
218function searchwithDDG(parameters) {
219 return {
220 label: "Search With DuckDuckGo",
221 // Only show it when right-clicking text
222 visible: parameters.selectionText.trim().length > 0,
223 click: () => {
224 const url = `https://duckduckgo.com/?q=${encodeURIComponent(
225 parameters.selectionText
226 )}`;
227 shell.openExternal(url);
228 },
229 };
230}
231
232function reloadWindow() {
233 return {
234 label: "Reload",
235 click: function (item, focusedWindow) {
236 if (focusedWindow) {
237 focusedWindow.reload();
238 }
239 },
240 };
241}
242
243function openServerDevTools(serverDevToolsCallback) {
244 return {
245 label: "Inspect Server Process",
246 click: serverDevToolsCallback,
247 };
248}

Built with git-ssb-web