Improve mailto and web link handling experience (#43)

This commit is contained in:
Quang Lam 2019-11-26 00:05:27 -06:00 committed by GitHub
parent 4a18a40b49
commit 3a771faaab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 254 additions and 81 deletions

View file

@ -25,7 +25,6 @@ The yml file requires just a few fields:
name: Gmail
url: 'https://gmail.com'
category: Productivity
mailtoHandler: 'https://mail.google.com/mail/?extsrc=mailto&url=%s'
```
The human then opens a PR. Tests pass, the PR gets merged. Yay!
@ -69,7 +68,6 @@ apps
- Social Networking
- Utilities
- Video
- `mailtoHandler` is not required, specifies the URL pattern to handle `mailto` links. See [Navigator.registerProtocolHandler() Web API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler). Example: `https://mail.google.com/mail/?extsrc=mailto&url=%s`.
### Icons

View file

@ -1,5 +1,3 @@
name: FastMail
url: 'https://fastmail.com'
category: Productivity
mailtoHandler: 'http://www.fastmail.fm/action/compose/?mailto=%s'
featured: true

View file

@ -1,5 +1,3 @@
name: Gmail
url: 'https://gmail.com'
category: Productivity
mailtoHandler: 'https://mail.google.com/mail/?extsrc=mailto&url=%s'
featured: true

View file

@ -1,4 +1,3 @@
name: 'Google Voice'
category: 'Social Networking'
url: 'https://voice.google.com'
featured: true

View file

@ -1,4 +1,3 @@
name: Messages
category: 'Social Networking'
url: 'https://messages.android.com/'
featured: true

View file

@ -1,4 +1,3 @@
name: Messenger
category: 'Social Networking'
url: 'https://messenger.com'
featured: true

View file

@ -1,5 +1,3 @@
name: 'Microsoft Outlook'
url: 'https://outlook.live.com/owa/'
category: Productivity
mailtoHandler: 'https://outlook.live.com/owa/?path=/mail/action/compose&to=%s'
featured: true

View file

@ -1,4 +1,3 @@
name: Twitter
category: 'Social Networking'
url: 'https://twitter.com/'
featured: true

View file

@ -1,4 +1,3 @@
name: WhatsApp
url: 'https://web.whatsapp.com'
category: 'Social Networking'
featured: true

View file

@ -1,4 +1,3 @@
name: 'Yahoo Mail'
url: 'https://mail.yahoo.com'
category: Productivity
mailtoHandler: 'https://compose.mail.yahoo.com/?To=%s'
category: Productivity

View file

@ -1,4 +1,3 @@
name: 'Zoho Mail'
url: 'https://mail.zoho.com'
category: Productivity
mailtoHandler: 'https://mail.zoho.com/mail/compose.do?extsrc=mailto&mode=compose&tp=zb&ct=%s'
category: Productivity

View file

@ -0,0 +1,34 @@
// specifies the URL pattern to handle `mailto` links.
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/registerProtocolHandler
const rawMailtoUrls = [
{
hostnames: ['fastmail.com'],
mailtoUrl: 'http://www.fastmail.fm/action/compose/?mailto=%s',
},
{
hostnames: ['gmail.com', 'mail.google.com', 'googlemail.com'],
mailtoUrl: 'https://mail.google.com/mail/?extsrc=mailto&url=%s',
},
{
hostnames: ['outlook.live.com', 'outlook.com', 'hotmail.com'],
mailtoUrl: 'https://outlook.live.com/owa/?path=/mail/action/compose&to=%s',
},
{
hostnames: ['mail.yahoo.com', 'yahoomail.com'],
mailtoUrl: 'https://compose.mail.yahoo.com/?To=%s',
},
{
hostnames: ['mail.zoho.com'],
mailtoUrl: 'https://mail.zoho.com/mail/compose.do?extsrc=mailto&mode=compose&tp=zb&ct=%s',
},
];
const MAILTO_URLS = {};
rawMailtoUrls.forEach((item) => {
item.hostnames.forEach((hostname) => {
MAILTO_URLS[hostname] = item.mailtoUrl;
});
});
module.exports = MAILTO_URLS;

View file

@ -11,6 +11,9 @@ const createMenu = require('./libs/create-menu');
const { addView } = require('./libs/views');
const { getPreference } = require('./libs/preferences');
const { getWorkspaces } = require('./libs/workspaces');
const extractHostname = require('./libs/extract-hostname');
const MAILTO_URLS = require('./constants/mailto-urls');
require('./libs/updater');
@ -54,6 +57,7 @@ if (!gotTheLock) {
app.on('ready', () => {
global.attachToMenubar = getPreference('attachToMenubar');
global.showNavigationBar = getPreference('navigationBar');
global.MAILTO_URLS = MAILTO_URLS;
commonInit();
});
@ -86,6 +90,36 @@ if (!gotTheLock) {
app.on('open-url', (e, url) => {
e.preventDefault();
const workspaces = Object.values(getWorkspaces());
if (workspaces.length < 1) return;
// handle mailto:
if (url.startsWith('mailto:')) {
const mailtoWorkspaces = workspaces
.filter((workspace) => extractHostname(workspace.homeUrl) in MAILTO_URLS);
// pick automically if there's only one choice
if (mailtoWorkspaces.length === 0) {
ipcMain.emit(
'request-show-message-box', null,
'None of your workspaces supports composing email messages.',
'error',
);
} else if (mailtoWorkspaces.length === 1) {
const mailtoUrl = MAILTO_URLS[extractHostname(mailtoWorkspaces[0].homeUrl)];
const u = mailtoUrl.replace('%s', url);
ipcMain.emit('request-load-url', null, u, mailtoWorkspaces[0].id);
} else {
app.whenReady()
.then(() => openUrlWithWindow.show(url));
}
return;
}
// handle https/http
// pick automically if there's only one choice
if (workspaces.length === 1) {
ipcMain.emit('request-load-url', null, url, workspaces[0].id);
}
app.whenReady()
.then(() => openUrlWithWindow.show(url));
});

View file

@ -0,0 +1,27 @@
/* eslint-disable prefer-destructuring */
const extractHostname = (url) => {
try {
let hostname;
// find & remove protocol (http, ftp, etc.) and get hostname
if (url.indexOf('://') > -1) {
hostname = url.split('/')[2];
} else {
hostname = url.split('/')[0];
}
// find & remove port number
hostname = hostname.split(':')[0];
// find & remove "?"
hostname = hostname.split('?')[0];
// find & remove "www"
hostname = hostname.replace('www.', '');
return hostname.trim();
} catch (_) {
return null;
}
};
module.exports = extractHostname;

View file

@ -19,8 +19,8 @@ const {
const mainWindow = require('../windows/main');
const createWorkspaceView = (name, homeUrl, picture, mailtoHandler) => {
const newWorkspace = createWorkspace(name, homeUrl, picture, mailtoHandler);
const createWorkspaceView = (name, homeUrl, picture) => {
const newWorkspace = createWorkspace(name, homeUrl, picture);
setActiveWorkspace(newWorkspace.id);
addView(mainWindow.get(), getWorkspace(newWorkspace.id));

View file

@ -157,7 +157,7 @@ const removeWorkspace = (id) => {
settings.delete(`workspaces.${v}.${id}`);
};
const createWorkspace = (name, homeUrl, picture, mailtoHandler) => {
const createWorkspace = (name, homeUrl) => {
const newId = uuidv1();
// find largest order
@ -173,7 +173,6 @@ const createWorkspace = (name, homeUrl, picture, mailtoHandler) => {
id: newId,
name,
homeUrl,
mailtoHandler,
order: max + 1,
active: false,
};

View file

@ -173,8 +173,8 @@ const loadListeners = () => {
e.returnValue = workspaces;
});
ipcMain.on('request-create-workspace', (e, name, homeUrl, picture, mailtoHandler) => {
createWorkspaceView(name, homeUrl, picture, mailtoHandler);
ipcMain.on('request-create-workspace', (e, name, homeUrl, picture) => {
createWorkspaceView(name, homeUrl, picture);
createMenu();
});

View file

@ -16,9 +16,20 @@ window.global = {};
window.ipcRenderer = ipcRenderer;
window.onload = () => {
window.close = () => {
ipcRenderer.send('request-go-home');
};
// overwrite gmail email discard button
if (window.location.href.startsWith('https://mail.google.com') && window.location.href.includes('source=mailto')) {
const checkExist = setInterval(() => {
if (document.getElementById(':qz')) {
const discardButton = document.getElementById(':qz');
// https://stackoverflow.com/a/46986927
discardButton.addEventListener('click', (e) => {
e.stopPropagation();
ipcRenderer.send('request-go-home');
}, true);
clearInterval(checkExist);
}
}, 100); // check every 100ms
}
const jsCodeInjection = ipcRenderer.sendSync('get-preference', 'jsCodeInjection');
const cssCodeInjection = ipcRenderer.sendSync('get-preference', 'cssCodeInjection');

View file

@ -1,7 +1,7 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');

View file

@ -1,7 +1,7 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');

View file

@ -1,7 +1,7 @@
const { BrowserWindow, ipcMain } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');

View file

@ -1,7 +1,7 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');

View file

@ -1,7 +1,7 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');

View file

@ -1,7 +1,7 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');

View file

@ -9,7 +9,7 @@ const windowStateKeeper = require('electron-window-state');
const { menubar } = require('menubar');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
let win;

View file

@ -1,7 +1,7 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const { getPreference } = require('../libs/preferences');
const mainWindow = require('./main');

View file

@ -1,7 +1,7 @@
const { BrowserWindow } = require('electron');
const path = require('path');
const { REACT_PATH } = require('../constants');
const { REACT_PATH } = require('../constants/paths');
const mainWindow = require('./main');
const { getPreference } = require('../libs/preferences');

View file

@ -63,7 +63,7 @@ const AddCustomAppCard = (props) => {
</div>
<div className={classes.infoContainer}>
<Typography variant="subtitle1" className={classes.appName}>
Add Custom App
Add Custom Workspace
</Typography>
<Typography variant="body1" color="textSecondary" className={classes.appUrl}>
Make it your own!

View file

@ -73,7 +73,6 @@ const AppCard = (props) => {
classes,
icon,
icon128,
mailtoHandler,
name,
onUpdateForm,
onUpdateMode,
@ -110,7 +109,7 @@ const AppCard = (props) => {
<MenuItem
onClick={() => {
onUpdateForm({
name, homeUrl: url, picturePath: icon, mailtoHandler,
name, homeUrl: url, picturePath: icon,
});
onUpdateMode('custom');
}}
@ -125,7 +124,7 @@ const AppCard = (props) => {
size="medium"
variant="contained"
onClick={() => {
requestCreateWorkspace(name, url, icon128, mailtoHandler);
requestCreateWorkspace(name, url, icon128);
remote.getCurrentWindow().close();
}}
>
@ -138,7 +137,6 @@ const AppCard = (props) => {
};
AppCard.defaultProps = {
mailtoHandler: null,
icon128: null,
};
@ -146,7 +144,6 @@ AppCard.propTypes = {
classes: PropTypes.object.isRequired,
icon128: PropTypes.string,
icon: PropTypes.string.isRequired,
mailtoHandler: PropTypes.string,
name: PropTypes.string.isRequired,
onUpdateForm: PropTypes.func.isRequired,
onUpdateMode: PropTypes.func.isRequired,

View file

@ -5,6 +5,8 @@ import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import connectComponent from '../../helpers/connect-component';
import isUrl from '../../helpers/is-url';
import getMailtoUrl from '../../helpers/get-mailto-url';
import { updateForm, save } from '../../state/add-workspace/actions';
@ -12,8 +14,6 @@ import defaultIcon from '../../images/default-icon.png';
import EnhancedDialogTitle from './enhanced-dialog-title';
import isUrl from '../../helpers/is-url';
const styles = (theme) => ({
root: {
background: theme.palette.background.paper,
@ -79,6 +79,7 @@ const AddWorkspaceCustom = ({
classes,
homeUrl,
homeUrlError,
isMailApp,
name,
nameError,
onSave,
@ -87,7 +88,7 @@ const AddWorkspaceCustom = ({
}) => (
<div className={classes.root}>
<EnhancedDialogTitle>
Add Custom App
Add Custom Workspace
</EnhancedDialogTitle>
<div>
<TextField
@ -119,6 +120,7 @@ const AddWorkspaceCustom = ({
}}
value={homeUrl}
onChange={(e) => onUpdateForm({ homeUrl: e.target.value })}
helperText={!homeUrlError && isMailApp && 'Email app detected.'}
/>
<div className={classes.avatarFlex}>
<div className={classes.avatarLeft}>
@ -178,6 +180,7 @@ AddWorkspaceCustom.propTypes = {
classes: PropTypes.object.isRequired,
homeUrl: PropTypes.string,
homeUrlError: PropTypes.string,
isMailApp: PropTypes.bool.isRequired,
name: PropTypes.string,
nameError: PropTypes.string,
onSave: PropTypes.func.isRequired,
@ -188,6 +191,7 @@ AddWorkspaceCustom.propTypes = {
const mapStateToProps = (state) => ({
homeUrl: state.addWorkspace.form.homeUrl,
homeUrlError: state.addWorkspace.form.homeUrlError,
isMailApp: Boolean(getMailtoUrl(state.addWorkspace.form.homeUrl)),
name: state.addWorkspace.form.name,
nameError: state.addWorkspace.form.nameError,
picturePath: state.addWorkspace.form.picturePath,

View file

@ -126,7 +126,6 @@ class AddWorkspace extends React.Component {
url={app.url}
icon={app.icon}
icon128={app.icon128}
mailtoHandler={app.mailtoHandler}
/>
))}
{!isGetting && <SubmitAppCard />}
@ -183,7 +182,7 @@ class AddWorkspace extends React.Component {
className={classes.bottomNavigation}
>
<BottomNavigationAction label="Catalog" value="catalog" icon={<ViewListIcon />} />
<BottomNavigationAction label="Add Custom App" value="custom" icon={<CreateIcon />} />
<BottomNavigationAction label="Custom Workspace" value="custom" icon={<CreateIcon />} />
</BottomNavigation>
</div>
);

View file

@ -5,11 +5,13 @@ import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import connectComponent from '../../helpers/connect-component';
import getMailtoUrl from '../../helpers/get-mailto-url';
import defaultIcon from '../../images/default-icon.png';
import { updateForm, save } from '../../state/edit-workspace/actions';
const styles = (theme) => ({
root: {
background: theme.palette.background.paper,
@ -67,6 +69,7 @@ const EditWorkspace = ({
classes,
homeUrl,
homeUrlError,
isMailApp,
name,
nameError,
onSave,
@ -104,6 +107,7 @@ const EditWorkspace = ({
}}
value={homeUrl}
onChange={(e) => onUpdateForm({ homeUrl: e.target.value })}
helperText={!homeUrlError && isMailApp && 'Email app detected.'}
/>
<div className={classes.avatarFlex}>
<div className={classes.avatarLeft}>
@ -161,6 +165,7 @@ EditWorkspace.propTypes = {
classes: PropTypes.object.isRequired,
homeUrl: PropTypes.string.isRequired,
homeUrlError: PropTypes.string,
isMailApp: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
nameError: PropTypes.string,
onSave: PropTypes.func.isRequired,
@ -171,6 +176,7 @@ EditWorkspace.propTypes = {
const mapStateToProps = (state) => ({
homeUrl: state.editWorkspace.form.homeUrl,
homeUrlError: state.editWorkspace.form.homeUrlError,
isMailApp: Boolean(getMailtoUrl(state.editWorkspace.form.homeUrl)),
id: state.editWorkspace.form.id,
name: state.editWorkspace.form.name,
nameError: state.editWorkspace.form.nameError,

View file

@ -9,34 +9,43 @@ import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import connectComponent from '../../helpers/connect-component';
import getWorkspacesAsList from '../../helpers/get-workspaces-as-list';
import getMailtoUrl from '../../helpers/get-mailto-url';
import { requestLoadURL } from '../../senders';
const { remote } = window.require('electron');
const OpenUrlWith = ({ workspaces }) => (
<List dense>
{getWorkspacesAsList(workspaces).map((workspace) => workspace.mailtoHandler && (
const OpenUrlWith = ({ workspaces }) => {
const incomingUrl = remote.getGlobal('incomingUrl');
const isMailtoUrl = incomingUrl.startsWith('mailto:');
const renderWorkspace = (workspace, i) => {
if (isMailtoUrl && !getMailtoUrl(workspace.homeUrl)) return null;
return (
<ListItem
button
onClick={() => {
const incomingUrl = remote.getGlobal('incomingUrl');
const u = incomingUrl.startsWith('mailto:') ? workspace.mailtoHandler.replace('%s', incomingUrl) : incomingUrl;
const u = isMailtoUrl ? getMailtoUrl(workspace.homeUrl).replace('%s', incomingUrl) : incomingUrl;
requestLoadURL(u, workspace.id);
remote.getCurrentWindow().close();
}}
>
<ListItemText
primary={workspace.name || `Workspace ${workspace.order + 1}`}
secondary={`#${workspace.order + 1}`}
primary={workspace.name || `Workspace ${i + 1}`}
secondary={`#${i + 1}`}
/>
<ChevronRightIcon color="action" />
</ListItem>
))}
</List>
);
);
};
return (
<List dense>
{getWorkspacesAsList(workspaces).map(renderWorkspace)}
</List>
);
};
OpenUrlWith.propTypes = {
workspaces: PropTypes.object.isRequired,

View file

@ -17,7 +17,7 @@ import connectComponent from '../../helpers/connect-component';
import StatedMenu from '../shared/stated-menu';
import { updateIsDefaultMailClient } from '../../state/general/actions';
import { updateIsDefaultMailClient, updateIsDefaultWebBrowser } from '../../state/general/actions';
import {
requestOpenInBrowser,
@ -51,7 +51,7 @@ const styles = (theme) => ({
const getThemeString = (theme) => {
if (theme === 'light') return 'Light';
if (theme === 'dark') return 'Dark';
return 'Automatic';
return 'System default';
};
const getOpenAtLoginString = (openAtLogin) => {
@ -67,9 +67,11 @@ const Preferences = ({
cssCodeInjection,
downloadPath,
isDefaultMailClient,
isDefaultWebBrowser,
jsCodeInjection,
navigationBar,
onUpdateIsDefaultMailClient,
onUpdateIsDefaultWebBrowser,
openAtLogin,
rememberLastPageVisited,
shareWorkspaceBrowsingData,
@ -93,7 +95,7 @@ const Preferences = ({
</ListItem>
)}
>
<MenuItem onClick={() => requestSetPreference('theme', 'automatic')}>Automatic</MenuItem>
<MenuItem onClick={() => requestSetPreference('theme', 'automatic')}>System default</MenuItem>
<MenuItem onClick={() => requestSetPreference('theme', 'light')}>Light</MenuItem>
<MenuItem onClick={() => requestSetPreference('theme', 'dark')}>Dark</MenuItem>
</StatedMenu>
@ -288,7 +290,7 @@ const Preferences = ({
</Paper>
<Typography variant="subtitle2" className={classes.sectionTitle}>
Default Email Client
Default App
</Typography>
<Paper className={classes.paper}>
<List dense>
@ -313,6 +315,29 @@ const Preferences = ({
</Button>
</ListItem>
)}
<Divider />
{isDefaultWebBrowser ? (
<ListItem>
<ListItemText secondary="Singlebox is your default web browser." />
</ListItem>
) : (
<ListItem>
<ListItemText primary="Default web browser" secondary="Make Singlebox the default web browser." />
<Button
variant="outlined"
size="small"
color="default"
className={classes.button}
onClick={() => {
remote.app.setAsDefaultProtocolClient('http');
remote.app.setAsDefaultProtocolClient('https');
onUpdateIsDefaultWebBrowser(remote.app.isDefaultProtocolClient('http'));
}}
>
Make default
</Button>
</ListItem>
)}
</List>
</Paper>
@ -379,9 +404,11 @@ Preferences.propTypes = {
cssCodeInjection: PropTypes.string,
downloadPath: PropTypes.string.isRequired,
isDefaultMailClient: PropTypes.bool.isRequired,
isDefaultWebBrowser: PropTypes.bool.isRequired,
jsCodeInjection: PropTypes.string,
navigationBar: PropTypes.bool.isRequired,
onUpdateIsDefaultMailClient: PropTypes.func.isRequired,
onUpdateIsDefaultWebBrowser: PropTypes.func.isRequired,
openAtLogin: PropTypes.oneOf(['yes', 'yes-hidden', 'no']).isRequired,
rememberLastPageVisited: PropTypes.bool.isRequired,
shareWorkspaceBrowsingData: PropTypes.bool.isRequired,
@ -410,6 +437,7 @@ const mapStateToProps = (state) => ({
const actionCreators = {
updateIsDefaultMailClient,
updateIsDefaultWebBrowser,
};
export default connectComponent(

View file

@ -17,6 +17,7 @@ export const UPDATE_DID_FAIL_LOAD = 'UPDATE_DID_FAIL_LOAD';
export const UPDATE_IS_DARK_MODE = 'UPDATE_IS_DARK_MODE';
export const UPDATE_IS_FULL_SCREEN = 'UPDATE_IS_FULL_SCREEN';
export const UPDATE_IS_DEFAULT_MAIL_CLIENT = 'UPDATE_IS_DEFAULT_MAIL_CLIENT';
export const UPDATE_IS_DEFAULT_WEB_BROWSER = 'UPDATE_IS_DEFAULT_WEB_BROWSER';
export const UPDATE_IS_LOADING = 'UPDATE_IS_LOADING';
// Find In Page

View file

@ -1,23 +1,27 @@
/* eslint-disable prefer-destructuring */
const extractHostname = (url) => {
let hostname;
try {
let hostname;
// find & remove protocol (http, ftp, etc.) and get hostname
if (url.indexOf('://') > -1) {
hostname = url.split('/')[2];
} else {
hostname = url.split('/')[0];
// find & remove protocol (http, ftp, etc.) and get hostname
if (url.indexOf('://') > -1) {
hostname = url.split('/')[2];
} else {
hostname = url.split('/')[0];
}
// find & remove port number
hostname = hostname.split(':')[0];
// find & remove "?"
hostname = hostname.split('?')[0];
// find & remove "www"
hostname = hostname.replace('www.', '');
return hostname.trim();
} catch (_) {
return null;
}
// find & remove port number
hostname = hostname.split(':')[0];
// find & remove "?"
hostname = hostname.split('?')[0];
// find & remove "www"
hostname = hostname.replace('www.', '');
return hostname;
};
export default extractHostname;

View file

@ -0,0 +1,18 @@
import extractHostname from './extract-hostname';
let MAILTO_URLS;
const getMailtoUrl = (url) => {
if (!MAILTO_URLS) {
MAILTO_URLS = window.require('electron').remote.getGlobal('MAILTO_URLS');
}
const extractedHostname = extractHostname(url);
if (extractedHostname in MAILTO_URLS) {
return MAILTO_URLS[extractedHostname];
}
return null;
};
export default getMailtoUrl;

View file

@ -16,7 +16,6 @@ export const requestShowAddWorkspaceWindow = () => ipcRenderer.send('request-sho
export const requestShowCodeInjectionWindow = (type) => ipcRenderer.send('request-show-code-injection-window', type);
export const requestShowLicenseRegistrationWindow = (type) => ipcRenderer.send('request-show-license-registration-window', type);
// Preferences
export const getPreference = (name) => ipcRenderer.sendSync('get-preference', name);
export const getPreferences = () => ipcRenderer.sendSync('get-preferences');
@ -32,7 +31,7 @@ export const requestSetSystemPreference = (name, value) => ipcRenderer.send('req
// Workspace
export const getWorkspace = (id) => ipcRenderer.sendSync('get-workspace', id);
export const getWorkspaces = () => ipcRenderer.sendSync('get-workspaces');
export const requestCreateWorkspace = (name, homeUrl, picture, mailtoHandler) => ipcRenderer.send('request-create-workspace', name, homeUrl, picture, mailtoHandler);
export const requestCreateWorkspace = (name, homeUrl, picture) => ipcRenderer.send('request-create-workspace', name, homeUrl, picture);
export const requestSetWorkspace = (id, opts) => ipcRenderer.send('request-set-workspace', id, opts);
export const requestSetWorkspacePicture = (id, picturePath) => ipcRenderer.send('request-set-workspace-picture', id, picturePath);
export const requestRemoveWorkspacePicture = (id) => ipcRenderer.send('request-remove-workspace-picture', id);

View file

@ -108,7 +108,7 @@ export const save = () => (dispatch, getState) => {
return dispatch(updateForm(validatedChanges));
}
requestCreateWorkspace(form.name, form.homeUrl, form.picturePath, form.mailtoHandler);
requestCreateWorkspace(form.name, form.homeUrl.trim(), form.picturePath);
remote.getCurrentWindow().close();
return null;
};

View file

@ -43,7 +43,7 @@ export const save = () => (dispatch, getState) => {
id,
{
name: form.name,
homeUrl: form.homeUrl,
homeUrl: form.homeUrl.trim(),
},
);

View file

@ -4,6 +4,7 @@ import {
UPDATE_DID_FAIL_LOAD,
UPDATE_IS_DARK_MODE,
UPDATE_IS_DEFAULT_MAIL_CLIENT,
UPDATE_IS_DEFAULT_WEB_BROWSER,
UPDATE_IS_FULL_SCREEN,
UPDATE_IS_LOADING,
} from '../../constants/actions';
@ -44,6 +45,13 @@ export const updateIsDefaultMailClient = (isDefaultMailClient) => (dispatch) =>
});
};
export const updateIsDefaultWebBrowser = (isDefaultWebBrowser) => (dispatch) => {
dispatch({
type: UPDATE_IS_DEFAULT_WEB_BROWSER,
isDefaultWebBrowser,
});
};
export const updateIsDarkMode = (isDarkMode) => (dispatch) => {
dispatch({
type: UPDATE_IS_DARK_MODE,

View file

@ -6,6 +6,7 @@ import {
UPDATE_DID_FAIL_LOAD,
UPDATE_IS_DARK_MODE,
UPDATE_IS_DEFAULT_MAIL_CLIENT,
UPDATE_IS_DEFAULT_WEB_BROWSER,
UPDATE_IS_FULL_SCREEN,
UPDATE_IS_LOADING,
} from '../../constants/actions';
@ -40,6 +41,14 @@ const isDefaultMailClient = (state = remote.app.isDefaultProtocolClient('mailto'
}
};
const isDefaultWebBrowser = (state = remote.app.isDefaultProtocolClient('http'), action) => {
switch (action.type) {
case UPDATE_IS_DEFAULT_WEB_BROWSER: return action.isDefaultWebBrowser;
default: return state;
}
};
const isDarkMode = (state = remote.systemPreferences.isDarkMode(), action) => {
switch (action.type) {
case UPDATE_IS_DARK_MODE: return action.isDarkMode;
@ -67,6 +76,7 @@ export default combineReducers({
didFailLoad,
isDarkMode,
isDefaultMailClient,
isDefaultWebBrowser,
isFullScreen,
isLoading,
});