mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Clickable codemirror links (#81)
* Multiple recursive rendering * Click links in respnose view
This commit is contained in:
parent
61a27b5c5f
commit
873a59cdbc
@ -19,6 +19,13 @@ class RequestUrlBar extends Component {
|
||||
_urlChangeDebounceTimeout = null;
|
||||
_lastPastedText = null;
|
||||
|
||||
_setDropdownRef = n => this._dropdown = n;
|
||||
|
||||
_handleMetaClickSend = e => {
|
||||
e.preventDefault();
|
||||
this._dropdown.show();
|
||||
};
|
||||
|
||||
_handleFormSubmit = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -215,8 +222,9 @@ class RequestUrlBar extends Component {
|
||||
let sendButton;
|
||||
if (!cancelButton) {
|
||||
sendButton = (
|
||||
<Dropdown key="dropdown" className="tall" right={true}>
|
||||
<Dropdown key="dropdown" className="tall" right={true} ref={this._setDropdownRef}>
|
||||
<DropdownButton className="urlbar__send-btn"
|
||||
onContextMenu={this._handleMetaClickSend}
|
||||
onClick={this._handleClickSend}
|
||||
type="submit">
|
||||
{downloadPath ? "Download" : "Send"}
|
||||
|
@ -34,20 +34,19 @@ class Toast extends Component {
|
||||
|
||||
let notification;
|
||||
try {
|
||||
const queryParameters = [
|
||||
{name: 'lastLaunch', value: stats.lastLaunch},
|
||||
{name: 'firstLaunch', value: stats.created},
|
||||
{name: 'launches', value: stats.launches},
|
||||
{name: 'platform', value: constants.getAppPlatform()},
|
||||
{name: 'version', value: constants.getAppVersion()},
|
||||
{name: 'requests', value: (await db.count(models.request.type)) + ''},
|
||||
{name: 'requestGroups', value: (await db.count(models.requestGroup.type)) + ''},
|
||||
{name: 'environments', value: (await db.count(models.environment.type)) + ''},
|
||||
{name: 'workspaces', value: (await db.count(models.workspace.type)) + ''},
|
||||
];
|
||||
const data = {
|
||||
lastLaunch: stats.lastLaunch,
|
||||
firstLaunch: stats.created,
|
||||
launches: stats.launches,
|
||||
platform: constants.getAppPlatform(),
|
||||
version: constants.getAppVersion(),
|
||||
requests: await db.count(models.request.type),
|
||||
requestGroups: await db.count(models.requestGroup.type),
|
||||
environments: await db.count(models.environment.type),
|
||||
workspaces: await db.count(models.workspace.type),
|
||||
};
|
||||
|
||||
const qs = querystring.buildFromParams(queryParameters);
|
||||
notification = await fetch.get(`/notification?${qs}`);
|
||||
notification = await fetch.post(`/notification`, data);
|
||||
} catch (e) {
|
||||
console.warn('[toast] Failed to fetch notifications', e);
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import 'codemirror/addon/display/placeholder';
|
||||
import 'codemirror/addon/lint/lint';
|
||||
import 'codemirror/addon/lint/json-lint';
|
||||
import 'codemirror/addon/lint/lint.css';
|
||||
import 'codemirror/addon/mode/overlay';
|
||||
import 'codemirror/keymap/vim';
|
||||
import 'codemirror/keymap/emacs';
|
||||
import 'codemirror/keymap/sublime';
|
||||
@ -130,26 +131,61 @@ class Editor extends Component {
|
||||
|
||||
const {value} = this.props;
|
||||
|
||||
// Add overlay to editor to make all links clickable
|
||||
CodeMirror.defineMode('clickable', (config, parserConfig) => {
|
||||
const baseMode = CodeMirror.getMode(config, parserConfig.baseMode || 'text/plain');
|
||||
|
||||
// Only add the click mode if we have links to click
|
||||
if (!this.props.onClickLink) {
|
||||
return baseMode;
|
||||
}
|
||||
|
||||
const overlay = {
|
||||
token: function (stream, state) {
|
||||
// console.log('state', state);
|
||||
if (stream.match(/^(https?:\/\/)?([\da-z.\-]+)\.([a-z.]{2,6})([\/\w .\-]*)*\/?/, true)) {
|
||||
return 'clickable';
|
||||
}
|
||||
|
||||
while (stream.next() != null && !stream.match("http", false)) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return CodeMirror.overlayMode(baseMode, overlay, true);
|
||||
});
|
||||
|
||||
this.codeMirror = CodeMirror.fromTextArea(textarea, BASE_CODEMIRROR_OPTIONS);
|
||||
this.codeMirror.on('change', misc.debounce(this._codemirrorValueChanged.bind(this)));
|
||||
this.codeMirror.on('paste', misc.debounce(this._codemirrorValueChanged.bind(this)));
|
||||
if (!this.codeMirror.getOption('indentWithTabs')) {
|
||||
this.codeMirror.setOption('extraKeys', {
|
||||
Tab: cm => {
|
||||
var spaces = Array(this.codeMirror.getOption('indentUnit') + 1).join(' ');
|
||||
const spaces = Array(this.codeMirror.getOption('indentUnit') + 1).join(' ');
|
||||
cm.replaceSelection(spaces);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Do this a bit later so we don't block the render process
|
||||
setTimeout(() => {
|
||||
this._codemirrorSetValue(value || '');
|
||||
}, 50);
|
||||
setTimeout(() => this._codemirrorSetValue(value || ''), 50);
|
||||
|
||||
this._codemirrorSetOptions();
|
||||
};
|
||||
|
||||
_handleEditorClick = e => {
|
||||
if (!this.props.onClickLink) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.target.className.indexOf('cm-clickable') >= 0) {
|
||||
this.props.onClickLink(e.target.innerHTML);
|
||||
}
|
||||
};
|
||||
|
||||
_isJSON (mode) {
|
||||
if (!mode) {
|
||||
return false;
|
||||
@ -229,10 +265,15 @@ class Editor extends Component {
|
||||
// Clone first so we can modify it
|
||||
const readOnly = this.props.readOnly || false;
|
||||
|
||||
const normalizedMode = this.props.mode ? this.props.mode.split(';')[0] : 'text/plain';
|
||||
|
||||
let options = {
|
||||
readOnly,
|
||||
placeholder: this.props.placeholder || '',
|
||||
mode: this.props.mode || 'text/plain',
|
||||
mode: {
|
||||
name: 'clickable',
|
||||
baseMode: normalizedMode,
|
||||
},
|
||||
lineWrapping: this.props.lineWrapping,
|
||||
keyMap: this.props.keyMap || 'default',
|
||||
matchBrackets: !readOnly,
|
||||
@ -240,7 +281,6 @@ class Editor extends Component {
|
||||
};
|
||||
|
||||
// Strip of charset if there is one
|
||||
options.mode = options.mode ? options.mode.split(';')[0] : 'text/plain';
|
||||
Object.keys(options).map(key => {
|
||||
this.codeMirror.setOption(key, options[key]);
|
||||
});
|
||||
@ -432,7 +472,7 @@ class Editor extends Component {
|
||||
|
||||
return (
|
||||
<div className={classes} style={{fontSize: `${fontSize || 12}px`}}>
|
||||
<div className="editor__container">
|
||||
<div className="editor__container" onClick={this._handleEditorClick}>
|
||||
<textarea
|
||||
ref={this._handleInitTextarea}
|
||||
defaultValue=" "
|
||||
@ -449,6 +489,7 @@ class Editor extends Component {
|
||||
Editor.propTypes = {
|
||||
onChange: PropTypes.func,
|
||||
onFocusChange: PropTypes.func,
|
||||
onClickLink: PropTypes.func,
|
||||
keyMap: PropTypes.string,
|
||||
mode: PropTypes.string,
|
||||
placeholder: PropTypes.string,
|
||||
|
@ -10,6 +10,8 @@ import {MOD_SYM} from '../../../common/constants';
|
||||
|
||||
|
||||
class RequestActionsDropdown extends Component {
|
||||
_setDropdownRef = n => this._dropdown = n;
|
||||
|
||||
_handleDuplicate = () => {
|
||||
const {request, handleDuplicateRequest} = this.props;
|
||||
handleDuplicateRequest(request);
|
||||
@ -54,11 +56,15 @@ class RequestActionsDropdown extends Component {
|
||||
trackEvent('Request', 'Delete', 'Action');
|
||||
};
|
||||
|
||||
show () {
|
||||
this._dropdown.show();
|
||||
}
|
||||
|
||||
render () {
|
||||
const {request, ...other} = this.props;
|
||||
|
||||
return (
|
||||
<Dropdown {...other}>
|
||||
<Dropdown ref={this._setDropdownRef} {...other}>
|
||||
<DropdownButton>
|
||||
<i className="fa fa-caret-down"></i>
|
||||
</DropdownButton>
|
||||
|
@ -8,6 +8,8 @@ import {showModal} from '../modals';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
|
||||
class RequestGroupActionsDropdown extends Component {
|
||||
_setDropdownRef = n => this._dropdown = n;
|
||||
|
||||
_handleRename = async () => {
|
||||
const {requestGroup} = this.props;
|
||||
|
||||
@ -45,11 +47,15 @@ class RequestGroupActionsDropdown extends Component {
|
||||
showModal(EnvironmentEditModal, this.props.requestGroup);
|
||||
};
|
||||
|
||||
show () {
|
||||
this._dropdown.show();
|
||||
}
|
||||
|
||||
render () {
|
||||
const {requestGroup, ...other} = this.props;
|
||||
|
||||
return (
|
||||
<Dropdown {...other}>
|
||||
<Dropdown ref={this._setDropdownRef} {...other}>
|
||||
<DropdownButton>
|
||||
<i className="fa fa-caret-down"></i>
|
||||
</DropdownButton>
|
||||
|
@ -12,6 +12,7 @@ import SettingsTheme from '../settings/SettingsTheme';
|
||||
import * as models from '../../../models/index';
|
||||
import {getAppVersion, getAppName} from '../../../common/constants';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import * as session from '../../../sync/session';
|
||||
|
||||
export const TAB_INDEX_EXPORT = 1;
|
||||
|
||||
@ -64,13 +65,17 @@ class SettingsModal extends Component {
|
||||
} = this.props;
|
||||
|
||||
const {currentTabIndex} = this.state;
|
||||
const email = session.isLoggedIn() ? session.getEmail() : null;
|
||||
|
||||
return (
|
||||
<Modal ref={m => this.modal = m} tall={true} {...this.props}>
|
||||
<ModalHeader>
|
||||
{getAppName()} Preferences
|
||||
|
||||
<span className="faint txt-sm">v{getAppVersion()}</span>
|
||||
<span className="faint txt-sm">
|
||||
–
|
||||
v{getAppVersion()}
|
||||
{email ? ` – ${email}` : null}
|
||||
</span>
|
||||
</ModalHeader>
|
||||
<ModalBody noScroll={true}>
|
||||
<Tabs onSelect={i => this._handleTabSelect(i)} selectedIndex={currentTabIndex}>
|
||||
|
@ -9,12 +9,19 @@ import {trackEvent} from '../../../analytics/index';
|
||||
class SidebarRequestGroupRow extends PureComponent {
|
||||
state = {dragDirection: 0};
|
||||
|
||||
_setRequestGroupActionsDropdownRef = n => this._requestGroupActionsDropdown = n;
|
||||
|
||||
_handleCollapse = () => {
|
||||
const {requestGroup, handleSetRequestGroupCollapsed, isCollapsed} = this.props;
|
||||
handleSetRequestGroupCollapsed(requestGroup._id, !isCollapsed);
|
||||
trackEvent('Folder', 'Toggle Visible', !isCollapsed ? 'Close' : 'Open')
|
||||
};
|
||||
|
||||
_handleShowActions = e => {
|
||||
e.preventDefault();
|
||||
this._requestGroupActionsDropdown.show();
|
||||
};
|
||||
|
||||
_nullFunction = () => null;
|
||||
|
||||
setDragDirection (dragDirection) {
|
||||
@ -55,7 +62,7 @@ class SidebarRequestGroupRow extends PureComponent {
|
||||
|
||||
// NOTE: We only want the button draggable, not the whole container (ie. no children)
|
||||
const button = connectDragSource(connectDropTarget(
|
||||
<button onClick={this._handleCollapse}>
|
||||
<button onClick={this._handleCollapse} onContextMenu={this._handleShowActions}>
|
||||
<div className="sidebar__clickable">
|
||||
<i className={'sidebar__item__icon fa ' + folderIconClass}></i>
|
||||
<span>{requestGroup.name}</span>
|
||||
@ -72,6 +79,7 @@ class SidebarRequestGroupRow extends PureComponent {
|
||||
|
||||
<div className="sidebar__actions">
|
||||
<RequestGroupActionsDropdown
|
||||
ref={this._setRequestGroupActionsDropdownRef}
|
||||
handleCreateRequest={handleCreateRequest}
|
||||
handleCreateRequestGroup={handleCreateRequestGroup}
|
||||
handleDuplicateRequestGroup={handleDuplicateRequestGroup}
|
||||
|
@ -15,6 +15,13 @@ class SidebarRequestRow extends PureComponent {
|
||||
isEditing: false,
|
||||
};
|
||||
|
||||
_setRequestActionsDropdownRef = n => this._requestActionsDropdown = n;
|
||||
|
||||
_handleShowRequestActions = e => {
|
||||
e.preventDefault();
|
||||
this._requestActionsDropdown.show();
|
||||
};
|
||||
|
||||
_handleEditStart = () => {
|
||||
trackEvent('Request', 'Rename', 'In Place');
|
||||
this.setState({isEditing: true});
|
||||
@ -84,8 +91,9 @@ class SidebarRequestRow extends PureComponent {
|
||||
} else {
|
||||
node = (
|
||||
<li className={classes}>
|
||||
<div className={classnames('sidebar__item', 'sidebar__item--request', {'sidebar__item--active': isActive})}>
|
||||
<button className="wide" onClick={this._handleRequestActivate}>
|
||||
<div
|
||||
className={classnames('sidebar__item', 'sidebar__item--request', {'sidebar__item--active': isActive})}>
|
||||
<button className="wide" onClick={this._handleRequestActivate} onContextMenu={this._handleShowRequestActions}>
|
||||
<div className="sidebar__clickable">
|
||||
<MethodTag method={request.method}/>
|
||||
<Editable value={request.name}
|
||||
@ -95,6 +103,7 @@ class SidebarRequestRow extends PureComponent {
|
||||
</button>
|
||||
<div className="sidebar__actions">
|
||||
<RequestActionsDropdown
|
||||
ref={this._setRequestActionsDropdownRef}
|
||||
handleDuplicateRequest={handleDuplicateRequest}
|
||||
handleGenerateCode={handleGenerateCode}
|
||||
right={true}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {shell} from 'electron';
|
||||
import Editor from '../base/Editor';
|
||||
import ResponseWebView from './ResponseWebview';
|
||||
import ResponseRaw from './ResponseRaw';
|
||||
@ -12,6 +13,10 @@ class ResponseViewer extends Component {
|
||||
blockingBecauseTooLarge: false
|
||||
};
|
||||
|
||||
_handleOpenLink (link) {
|
||||
shell.openExternal(link);
|
||||
}
|
||||
|
||||
_handleDismissBlocker () {
|
||||
this.setState({blockingBecauseTooLarge: false});
|
||||
}
|
||||
@ -149,6 +154,7 @@ class ResponseViewer extends Component {
|
||||
|
||||
return (
|
||||
<Editor
|
||||
onClickLink={this._handleOpenLink}
|
||||
value={body}
|
||||
updateFilter={updateFilter}
|
||||
filter={filter}
|
||||
|
@ -243,6 +243,15 @@
|
||||
color: var(--color-font-danger);
|
||||
}
|
||||
|
||||
span.cm-clickable {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span.cm-clickable:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.CodeMirror-activeline-background {
|
||||
background: @hl-md;
|
||||
}
|
||||
|
@ -100,7 +100,7 @@
|
||||
"dependencies": {
|
||||
"analytics-node": "^2.1.0",
|
||||
"classnames": "^2.2.3",
|
||||
"codemirror": "^5.22.0",
|
||||
"codemirror": "^5.23.0",
|
||||
"electron-context-menu": "^0.4.0",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"hkdf": "0.0.2",
|
||||
|
Loading…
Reference in New Issue
Block a user