diff --git a/app/common/misc.js b/app/common/misc.js index 9de696f12..4d372c934 100644 --- a/app/common/misc.js +++ b/app/common/misc.js @@ -321,7 +321,19 @@ export function escapeRegex (str: string): string { } export function fuzzyMatch (searchString: string, text: string): boolean { - const regexSearchString = escapeRegex(searchString.toLowerCase()).split('').join('.*'); - const toMatch = new RegExp(regexSearchString); + const lowercase = searchString.toLowerCase(); + + // Split into individual chars, then escape the ones that need it. + const regexSearchString = lowercase.split('').map(v => escapeRegex(v)).join('.*'); + + let toMatch; + try { + toMatch = new RegExp(regexSearchString); + } catch (err) { + console.warn('Invalid regex', searchString, regexSearchString); + // Invalid regex somehow + return false; + } + return toMatch.test(text.toLowerCase()); } diff --git a/app/ui/components/base/link.js b/app/ui/components/base/link.js index 6898b26f6..78ba16041 100644 --- a/app/ui/components/base/link.js +++ b/app/ui/components/base/link.js @@ -1,17 +1,26 @@ -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; +// @flow +import * as React from 'react'; import autobind from 'autobind-decorator'; import {trackEvent} from '../../../analytics/index'; import * as misc from '../../../common/misc'; +type Props = {| + href: string, + title?: string, + button?: boolean, + onClick?: Function, + children?: React.Node +|}; + @autobind -class Link extends PureComponent { - _handleClick (e) { +class Link extends React.PureComponent { + _handleClick (e: SyntheticEvent) { e && e.preventDefault(); const {href, onClick} = this.props; // Also call onClick that was passed to us if there was one onClick && onClick(e); + misc.clickLink(href); trackEvent('Link', 'Click', href); } @@ -30,13 +39,4 @@ class Link extends PureComponent { } } -Link.propTypes = { - href: PropTypes.string.isRequired, - - // Optional - button: PropTypes.bool, - onClick: PropTypes.func, - children: PropTypes.node -}; - export default Link; diff --git a/app/ui/components/base/mailto.js b/app/ui/components/base/mailto.js new file mode 100644 index 000000000..7232071cb --- /dev/null +++ b/app/ui/components/base/mailto.js @@ -0,0 +1,36 @@ +// @flow +import * as React from 'react'; +import autobind from 'autobind-decorator'; +import * as querystring from '../../../common/querystring'; +import Link from './link'; + +type Props = {| + email: string, + children?: React.Node, + subject?: string, + body?: string, +|}; + +@autobind +class Mailto extends React.PureComponent { + render () { + const {email, body, subject, children} = this.props; + + const params = []; + if (subject) { + params.push({name: 'subject', value: subject}); + } + if (body) { + params.push({name: 'body', value: body}); + } + + const qs = querystring.buildFromParams(params); + const href = querystring.joinUrl(`mailto:${email}`, qs); + + return ( + {children || email} + ); + } +} + +export default Mailto; diff --git a/app/ui/components/error-boundary.js b/app/ui/components/error-boundary.js new file mode 100644 index 000000000..1dd5fe470 --- /dev/null +++ b/app/ui/components/error-boundary.js @@ -0,0 +1,94 @@ +// @flow +import * as React from 'react'; +import {showError} from './modals/index'; +import Mailto from './base/mailto'; + +type Props = { + children: React.Node, + errorClassName?: string, + showAlert?: boolean, + replaceWith?: React.Node +}; + +type State = { + error: Error | null, + info: {componentStack: string} | null +}; + +class SingleErrorBoundary extends React.PureComponent { + constructor (props: Props) { + super(props); + this.state = { + error: null, + info: null + }; + } + + componentDidCatch (error: Error, info: {componentStack: string}) { + const {children} = this.props; + const firstChild = Array.isArray(children) && children.length === 1 ? children[0] : children; + + this.setState({error, info}); + + let componentName = 'component'; + try { + componentName = (firstChild: any).type.name; + } catch (err) { + // It's okay + } + + if (this.props.showAlert) { + try { + showError({ + error, + title: 'Application Error', + message: ( +

+ Failed to render {componentName}. + Please send the following error to + {' '} + . +

+ ) + }); + } catch (err) { + // UI is so broken that we can't even show an alert + } + } + } + + render () { + const {error, info} = this.state; + const {errorClassName, children} = this.props; + + if (error && info) { + return ( +
+ Render Failure: {error.message} +
+ ); + } + + return children; + } +} + +class ErrorBoundary extends React.PureComponent { + render () { + const {children, ...extraProps} = this.props; + + if (!children) { + return null; + } + + // Unwrap multiple children into single children for better error isolation + const childArray = Array.isArray(children) ? children : [children]; + return childArray.map((child, i) => ( + + {child} + + )); + } +} + +export default ErrorBoundary; diff --git a/app/ui/components/modals/error-modal.js b/app/ui/components/modals/error-modal.js index 5205164a1..f68d7622a 100644 --- a/app/ui/components/modals/error-modal.js +++ b/app/ui/components/modals/error-modal.js @@ -35,8 +35,6 @@ class ErrorModal extends PureComponent { const {title, error, addCancel, message} = options; this.setState({title, error, addCancel, message}); - console.warn(`Error: ${message || ''}`, error.stack); - this.modal.show(); return new Promise(resolve => { @@ -51,9 +49,11 @@ class ErrorModal extends PureComponent { {title || 'Uh Oh!'} - {message && message} + {message ? ( +
{message}
+ ) : null} {error && ( -
+            
               {error.stack}
             
)} diff --git a/app/ui/components/modals/settings-modal.js b/app/ui/components/modals/settings-modal.js index 796593789..b050ca21b 100644 --- a/app/ui/components/modals/settings-modal.js +++ b/app/ui/components/modals/settings-modal.js @@ -161,7 +161,8 @@ class SettingsModal extends PureComponent { activeTheme={settings.theme} /> - + diff --git a/app/ui/components/request-pane.js b/app/ui/components/request-pane.js index da7af6bcb..e362645f0 100644 --- a/app/ui/components/request-pane.js +++ b/app/ui/components/request-pane.js @@ -25,6 +25,7 @@ import RequestSettingsModal from './modals/request-settings-modal'; import MarkdownPreview from './markdown-preview'; import type {Settings} from '../../models/settings'; import * as hotkeys from '../../common/hotkeys'; +import ErrorBoundary from './error-boundary'; type Props = { // Functions @@ -249,22 +250,24 @@ class RequestPane extends React.PureComponent { return (
- + + +
@@ -331,46 +334,54 @@ class RequestPane extends React.PureComponent {
- + + +
- + + +
- + + +
@@ -382,18 +393,20 @@ class RequestPane extends React.PureComponent {
- + + +
- + + +
) : ( diff --git a/app/ui/components/response-pane.js b/app/ui/components/response-pane.js index 3c716a10f..3977979d1 100644 --- a/app/ui/components/response-pane.js +++ b/app/ui/components/response-pane.js @@ -26,6 +26,7 @@ import {cancelCurrentRequest} from '../../network/network'; import {trackEvent} from '../../analytics'; import Hotkey from './hotkey'; import * as hotkeys from '../../common/hotkeys'; +import ErrorBoundary from './error-boundary'; type Props = { // Functions @@ -278,60 +279,70 @@ class ResponsePane extends React.PureComponent {
- + + +
- + + +
- + + +
- + + +
- + + +
); } diff --git a/app/ui/components/viewers/response-headers-viewer.js b/app/ui/components/viewers/response-headers-viewer.js index db4ce7fc9..d49a8f7f2 100644 --- a/app/ui/components/viewers/response-headers-viewer.js +++ b/app/ui/components/viewers/response-headers-viewer.js @@ -10,32 +10,30 @@ class ResponseHeadersViewer extends PureComponent { h => `${h.name}: ${h.value}` ).join('\n'); - return ( -
- - - - - + return [ +
NameValue
+ + + + + + + + {headers.map((h, i) => ( + + + - - - {headers.map((h, i) => ( - - - - - ))} - -
NameValue
{h.name}{h.value}
{h.name}{h.value}
-

- -

-
- ); + ))} + + , +

+ +

+ ]; } } diff --git a/app/ui/components/wrapper.js b/app/ui/components/wrapper.js index eb51b31d1..944441f36 100644 --- a/app/ui/components/wrapper.js +++ b/app/ui/components/wrapper.js @@ -41,6 +41,7 @@ import {trackEvent} from '../../analytics/index'; import * as importers from 'insomnia-importers'; import type {CookieJar} from '../../models/cookie-jar'; import type {Environment} from '../../models/environment'; +import ErrorBoundary from './error-boundary'; type Props = { // Helper Functions @@ -389,124 +390,15 @@ class Wrapper extends React.PureComponent { const columns = `${realSidebarWidth}rem 0 minmax(0, ${paneWidth}fr) 0 minmax(0, ${1 - paneWidth}fr)`; const rows = `minmax(0, ${paneHeight}fr) 0 minmax(0, ${1 - paneHeight}fr)`; - return ( -
- -