'use strict' const { app, BrowserWindow, deprecate } = require('electron') const binding = process.electronBinding('dialog') const v8Util = process.electronBinding('v8_util') const DialogType = { OPEN: 'OPEN', SAVE: 'SAVE' } const saveFileDialogProperties = { createDirectory: 1 << 0, showHiddenFiles: 1 << 1, treatPackageAsDirectory: 1 << 2, showOverwriteConfirmation: 1 << 3, dontAddToRecent: 1 << 4 } const openFileDialogProperties = { openFile: 1 << 0, openDirectory: 1 << 1, multiSelections: 1 << 2, createDirectory: 1 << 3, // macOS showHiddenFiles: 1 << 4, promptToCreate: 1 << 5, // Windows noResolveAliases: 1 << 6, // macOS treatPackageAsDirectory: 1 << 7, // macOS dontAddToRecent: 1 << 8 // Windows } const normalizeAccessKey = (text) => { if (typeof text !== 'string') return text // macOS does not have access keys so remove single ampersands // and replace double ampersands with a single ampersand if (process.platform === 'darwin') { return text.replace(/&(&?)/g, '$1') } // Linux uses a single underscore as an access key prefix so escape // existing single underscores with a second underscore, replace double // ampersands with a single ampersand, and replace a single ampersand with // a single underscore if (process.platform === 'linux') { return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => { if (after === '&') return after return `_${after}` }) } return text } const checkAppInitialized = function () { if (!app.isReady()) { throw new Error('dialog module can only be used after app is ready') } } const setupDialogProperties = (type, properties) => { const dialogPropertiesTypes = (type === DialogType.OPEN) ? openFileDialogProperties : saveFileDialogProperties let dialogProperties = 0 for (const prop in dialogPropertiesTypes) { if (properties.includes(prop)) { dialogProperties |= dialogPropertiesTypes[prop] } } return dialogProperties } const saveDialog = (sync, window, options) => { checkAppInitialized() if (window && window.constructor !== BrowserWindow) { options = window window = null } if (options == null) options = { title: 'Save' } const { buttonLabel = '', defaultPath = '', filters = [], properties = [], title = '', message = '', securityScopedBookmarks = false, nameFieldLabel = '', showsTagField = true } = options if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string') if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string') const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window } settings.properties = setupDialogProperties(DialogType.SAVE, properties) return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings) } const openDialog = (sync, window, options) => { checkAppInitialized() if (window && window.constructor !== BrowserWindow) { options = window window = null } if (options == null) { options = { title: 'Open', properties: ['openFile'] } } const { buttonLabel = '', defaultPath = '', filters = [], properties = ['openFile'], title = '', message = '', securityScopedBookmarks = false } = options if (!Array.isArray(properties)) throw new TypeError('Properties must be an array') if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string') if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string') if (typeof message !== 'string') throw new TypeError('Message must be a string') const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window } settings.properties = setupDialogProperties(DialogType.OPEN, properties) return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings) } const messageBox = (sync, window, options) => { checkAppInitialized() if (window && window.constructor !== BrowserWindow) { options = window window = null } if (options == null) options = { type: 'none' } const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'] const messageBoxOptions = { noLink: 1 << 0 } let { buttons = [], cancelId, checkboxLabel = '', checkboxChecked, defaultId = -1, detail = '', icon = null, noLink = false, message = '', title = '', type = 'none' } = options const messageBoxType = messageBoxTypes.indexOf(type) if (messageBoxType === -1) throw new TypeError('Invalid message box type') if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array') if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey) if (typeof title !== 'string') throw new TypeError('Title must be a string') if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean') if (typeof message !== 'string') throw new TypeError('Message must be a string') if (typeof detail !== 'string') throw new TypeError('Detail must be a string') if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string') checkboxChecked = !!checkboxChecked if (checkboxChecked && !checkboxLabel) { throw new Error('checkboxChecked requires that checkboxLabel also be passed') } // Choose a default button to get selected when dialog is cancelled. if (cancelId == null) { // If the defaultId is set to 0, ensure the cancel button is a different index (1) cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0 for (let i = 0; i < buttons.length; i++) { const text = buttons[i].toLowerCase() if (text === 'cancel' || text === 'no') { cancelId = i break } } } const settings = { window, messageBoxType, buttons, defaultId, cancelId, noLink, title, message, detail, checkboxLabel, checkboxChecked, icon } if (sync) { return binding.showMessageBoxSync(settings) } else { return binding.showMessageBox(settings) } } module.exports = { showOpenDialog: function (window, options) { return openDialog(false, window, options) }, showOpenDialogSync: function (window, options) { return openDialog(true, window, options) }, showSaveDialog: function (window, options) { return saveDialog(false, window, options) }, showSaveDialogSync: function (window, options) { return saveDialog(true, window, options) }, showMessageBox: function (window, options) { return messageBox(false, window, options) }, showMessageBoxSync: function (window, options) { return messageBox(true, window, options) }, showErrorBox: function (...args) { return binding.showErrorBox(...args) }, showCertificateTrustDialog: function (window, options) { if (window && window.constructor !== BrowserWindow) options = window if (options == null || typeof options !== 'object') { throw new TypeError('options must be an object') } const { certificate, message = '' } = options if (certificate == null || typeof certificate !== 'object') { throw new TypeError('certificate must be an object') } if (typeof message !== 'string') throw new TypeError('message must be a string') return binding.showCertificateTrustDialog(window, certificate, message) } }