mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-02-05 07:13:23 -08:00
Improve mailto and web link handling experience (#43)
This commit is contained in:
parent
4a18a40b49
commit
3a771faaab
43 changed files with 254 additions and 81 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
name: FastMail
|
||||
url: 'https://fastmail.com'
|
||||
category: Productivity
|
||||
mailtoHandler: 'http://www.fastmail.fm/action/compose/?mailto=%s'
|
||||
featured: true
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
name: Gmail
|
||||
url: 'https://gmail.com'
|
||||
category: Productivity
|
||||
mailtoHandler: 'https://mail.google.com/mail/?extsrc=mailto&url=%s'
|
||||
featured: true
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
name: 'Google Voice'
|
||||
category: 'Social Networking'
|
||||
url: 'https://voice.google.com'
|
||||
featured: true
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
name: Messages
|
||||
category: 'Social Networking'
|
||||
url: 'https://messages.android.com/'
|
||||
featured: true
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
name: Messenger
|
||||
category: 'Social Networking'
|
||||
url: 'https://messenger.com'
|
||||
featured: true
|
||||
|
|
@ -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
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
name: Twitter
|
||||
category: 'Social Networking'
|
||||
url: 'https://twitter.com/'
|
||||
featured: true
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
name: WhatsApp
|
||||
url: 'https://web.whatsapp.com'
|
||||
category: 'Social Networking'
|
||||
featured: true
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
name: 'Yahoo Mail'
|
||||
url: 'https://mail.yahoo.com'
|
||||
category: Productivity
|
||||
mailtoHandler: 'https://compose.mail.yahoo.com/?To=%s'
|
||||
category: Productivity
|
||||
|
|
@ -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
|
||||
34
public/constants/mailto-urls.js
Normal file
34
public/constants/mailto-urls.js
Normal 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;
|
||||
|
|
@ -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));
|
||||
});
|
||||
|
|
|
|||
27
public/libs/extract-hostname.js
Normal file
27
public/libs/extract-hostname.js
Normal 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;
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
18
src/helpers/get-mailto-url.js
Normal file
18
src/helpers/get-mailto-url.js
Normal 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;
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export const save = () => (dispatch, getState) => {
|
|||
id,
|
||||
{
|
||||
name: form.name,
|
||||
homeUrl: form.homeUrl,
|
||||
homeUrl: form.homeUrl.trim(),
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue