1- const { clipboard, nativeImage, Menu, MenuItem, shell} = require ( 'electron' ) ;
1+ const { clipboard, nativeImage, Menu, MenuItem, shell, dialog } = require ( 'electron' ) ;
22const url = require ( 'url' ) ;
3+ const fs = require ( 'fs' ) ;
4+ const request = require ( 'request' ) ;
35
46const MAILTO_PREFIX = "mailto:" ;
57
@@ -47,6 +49,7 @@ function onLinkContextMenu(ev, params) {
4749 } ) ) ;
4850 }
4951
52+ let addSaveAs = false ;
5053 if ( params . mediaType && params . mediaType === 'image' && ! url . startsWith ( 'file://' ) ) {
5154 popupMenu . append ( new MenuItem ( {
5255 label : 'Copy image' ,
@@ -58,6 +61,10 @@ function onLinkContextMenu(ev, params) {
5861 }
5962 } ,
6063 } ) ) ;
64+
65+ // We want the link to be ordered below the copy stuff, but don't want to duplicate
66+ // the `if` statement, so use a flag.
67+ addSaveAs = true ;
6168 }
6269
6370 // No point offering to copy a blob: URL either
@@ -79,6 +86,35 @@ function onLinkContextMenu(ev, params) {
7986 } ) ) ;
8087 }
8188 }
89+
90+ if ( addSaveAs ) {
91+ popupMenu . append ( new MenuItem ( {
92+ label : 'Save image as...' ,
93+ click ( ) {
94+ const targetFileName = params . titleText || "image.png" ;
95+ const filePath = dialog . showSaveDialog ( {
96+ defaultPath : targetFileName ,
97+ } ) ;
98+
99+ try {
100+ if ( url . startsWith ( "data:" ) ) {
101+ fs . writeFileSync ( filePath , nativeImage . createFromDataURL ( url ) ) ;
102+ } else {
103+ request . get ( url ) . pipe ( fs . createWriteStream ( filePath ) ) ;
104+ }
105+
106+ } catch ( err ) {
107+ console . error ( err ) ;
108+ dialog . showMessageBox ( {
109+ type : "error" ,
110+ title : "Failed to save image" ,
111+ message : "The image failed to save" ,
112+ } ) ;
113+ }
114+ } ,
115+ } ) ) ;
116+ }
117+
82118 // popup() requires an options object even for no options
83119 popupMenu . popup ( { } ) ;
84120 ev . preventDefault ( ) ;
0 commit comments