mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
Removed Request.ContentType, added urlencoded editor, and added Copy to headers
This commit is contained in:
parent
cf572d79a5
commit
3ed07c67ee
@ -3,16 +3,14 @@ import React, {PropTypes} from 'react';
|
||||
import Dropdown from '../components/base/Dropdown';
|
||||
import {CONTENT_TYPES, getContentTypeName} from '../lib/contentTypes';
|
||||
|
||||
const ContentTypeDropdown = ({updateRequestContentType, activeContentType}) => {
|
||||
const contentTypes = CONTENT_TYPES.filter(ct => ct !== activeContentType);
|
||||
|
||||
const ContentTypeDropdown = ({updateRequestContentType}) => {
|
||||
return (
|
||||
<Dropdown>
|
||||
<button className="tall">
|
||||
<i className="fa fa-caret-down"></i>
|
||||
</button>
|
||||
<ul>
|
||||
{contentTypes.map(contentType => (
|
||||
{CONTENT_TYPES.map(contentType => (
|
||||
<li key={contentType}>
|
||||
<button onClick={e => updateRequestContentType(contentType)}>
|
||||
{getContentTypeName(contentType)}
|
||||
@ -25,8 +23,7 @@ const ContentTypeDropdown = ({updateRequestContentType, activeContentType}) => {
|
||||
};
|
||||
|
||||
ContentTypeDropdown.propTypes = {
|
||||
updateRequestContentType: PropTypes.func.isRequired,
|
||||
activeContentType: PropTypes.string.isRequired
|
||||
updateRequestContentType: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ContentTypeDropdown;
|
||||
|
@ -1,25 +1,63 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import React, {PropTypes, Component} from 'react';
|
||||
import Editor from './base/Editor';
|
||||
import KeyValueEditor from './base/KeyValueEditor';
|
||||
import {CONTENT_TYPE_FORM_URLENCODED} from '../lib/contentTypes';
|
||||
import {getContentTypeFromHeaders} from '../lib/contentTypes';
|
||||
import * as querystring from '../lib/querystring';
|
||||
|
||||
const RequestBodyEditor = ({fontSize, lineWrapping, body, contentType, onChange, className}) => (
|
||||
class RequestBodyEditor extends Component {
|
||||
static _getBodyFromPairs (pairs) {
|
||||
const params = [];
|
||||
for (let {name, value} of pairs) {
|
||||
params.push({name, value});
|
||||
}
|
||||
|
||||
return querystring.buildFromParams(params, false);
|
||||
}
|
||||
|
||||
static _getPairsFromBody (body) {
|
||||
return querystring.deconstructToParams(body, false);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
fontSize,
|
||||
lineWrapping,
|
||||
request,
|
||||
onChange,
|
||||
className
|
||||
} = this.props;
|
||||
|
||||
const contentType = getContentTypeFromHeaders(request.headers);
|
||||
|
||||
if (contentType === CONTENT_TYPE_FORM_URLENCODED) {
|
||||
return (
|
||||
<KeyValueEditor
|
||||
onChange={pairs => onChange(RequestBodyEditor._getBodyFromPairs(pairs))}
|
||||
pairs={RequestBodyEditor._getPairsFromBody(request.body)}
|
||||
uniquenessKey={request._id}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Editor
|
||||
fontSize={fontSize}
|
||||
value={body}
|
||||
value={request.body}
|
||||
className={className}
|
||||
onChange={onChange}
|
||||
mode={contentType}
|
||||
lineWrapping={lineWrapping}
|
||||
placeholder="request body here..."
|
||||
/>
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RequestBodyEditor.propTypes = {
|
||||
// Functions
|
||||
onChange: PropTypes.func.isRequired,
|
||||
|
||||
// Other
|
||||
body: PropTypes.string.isRequired,
|
||||
contentType: PropTypes.string.isRequired,
|
||||
request: PropTypes.object.isRequired,
|
||||
|
||||
// Optional
|
||||
fontSize: PropTypes.number,
|
||||
|
@ -11,6 +11,7 @@ import RequestUrlBar from '../components/RequestUrlBar';
|
||||
|
||||
import {getContentTypeName} from '../lib/contentTypes';
|
||||
import {renderRequest} from '../lib/render';
|
||||
import {getContentTypeFromHeaders} from '../lib/contentTypes';
|
||||
|
||||
class RequestPane extends Component {
|
||||
render () {
|
||||
@ -54,11 +55,8 @@ class RequestPane extends Component {
|
||||
<Tabs className="pane__body">
|
||||
<TabList>
|
||||
<Tab>
|
||||
<button>{getContentTypeName(request.contentType)}</button>
|
||||
<ContentTypeDropdown
|
||||
activeContentType={request.contentType}
|
||||
updateRequestContentType={updateRequestContentType}
|
||||
/>
|
||||
<button>{getContentTypeName(getContentTypeFromHeaders(request.headers))}</button>
|
||||
<ContentTypeDropdown updateRequestContentType={updateRequestContentType}/>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<button>
|
||||
@ -80,12 +78,10 @@ class RequestPane extends Component {
|
||||
</TabList>
|
||||
<TabPanel className="editor-wrapper">
|
||||
<RequestBodyEditor
|
||||
request={request}
|
||||
onChange={updateRequestBody}
|
||||
requestId={request._id}
|
||||
contentType={request.contentType}
|
||||
fontSize={editorFontSize}
|
||||
lineWrapping={editorLineWrapping}
|
||||
body={request.body}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
|
41
app/components/ResponseHeadersViewer.js
Normal file
41
app/components/ResponseHeadersViewer.js
Normal file
@ -0,0 +1,41 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import CopyButton from './base/CopyButton';
|
||||
|
||||
const ResponseHeadersViewer = ({headers}) => {
|
||||
const headersString = headers.map(
|
||||
h => `${h.name}: ${h.value}`
|
||||
).join('\n');
|
||||
|
||||
return (
|
||||
<div>
|
||||
<table className="wide">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{headers.map((h, i) => (
|
||||
<tr className="selectable" key={i}>
|
||||
<td>{h.name}</td>
|
||||
<td>{h.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<p className="pad-top">
|
||||
<CopyButton
|
||||
className="pull-right btn btn--super-compact btn--outlined"
|
||||
content={headersString}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
ResponseHeadersViewer.propTypes = {
|
||||
headers: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
export default ResponseHeadersViewer;
|
@ -4,8 +4,9 @@ import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'
|
||||
import StatusTag from './StatusTag';
|
||||
import SizeTag from './SizeTag';
|
||||
import TimeTag from './TimeTag';
|
||||
import PreviewModeDropdown from '../components/PreviewModeDropdown';
|
||||
import ResponseViewer from '../components/ResponseViewer';
|
||||
import PreviewModeDropdown from './PreviewModeDropdown';
|
||||
import ResponseBodyViewer from './ResponseBodyViewer';
|
||||
import ResponseHeadersViewer from './ResponseHeadersViewer';
|
||||
import {getPreviewModeName} from '../lib/previewModes';
|
||||
import {PREVIEW_MODE_SOURCE} from '../lib/previewModes';
|
||||
import {REQUEST_TIME_TO_SHOW_COUNTER} from '../lib/constants';
|
||||
@ -108,7 +109,7 @@ class ResponsePane extends Component {
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
{response.error ? (
|
||||
<ResponseViewer
|
||||
<ResponseBodyViewer
|
||||
contentType={response.contentType}
|
||||
previewMode={PREVIEW_MODE_SOURCE}
|
||||
editorLineWrapping={editorLineWrapping}
|
||||
@ -117,7 +118,7 @@ class ResponsePane extends Component {
|
||||
url={response.url}
|
||||
/>
|
||||
) : (
|
||||
<ResponseViewer
|
||||
<ResponseBodyViewer
|
||||
contentType={response.contentType}
|
||||
previewMode={previewMode}
|
||||
editorLineWrapping={editorLineWrapping}
|
||||
@ -129,22 +130,7 @@ class ResponsePane extends Component {
|
||||
)}
|
||||
</TabPanel>
|
||||
<TabPanel className="scrollable pad">
|
||||
<table className="wide">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{response.headers.map((h, i) => (
|
||||
<tr className="selectable" key={i}>
|
||||
<td>{h.name}</td>
|
||||
<td>{h.value}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<ResponseHeadersViewer headers={response.headers}/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</section>
|
||||
|
43
app/components/base/CopyButton.js
Normal file
43
app/components/base/CopyButton.js
Normal file
@ -0,0 +1,43 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
const {clipboard} = require('electron');
|
||||
|
||||
class CopyButton extends Component {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showConfirmation: false
|
||||
}
|
||||
}
|
||||
_handleClick (e) {
|
||||
e.preventDefault();
|
||||
|
||||
clipboard.writeText(this.props.content);
|
||||
|
||||
this.setState({showConfirmation: true});
|
||||
|
||||
this._timeout = setTimeout(() => {
|
||||
this.setState({showConfirmation: false});
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this._timeout);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {content, ...other} = this.props;
|
||||
const {showConfirmation} = this.state;
|
||||
|
||||
return (
|
||||
<button onClick={this._handleClick.bind(this)} {...other}>
|
||||
{showConfirmation ? 'Copied' : 'Copy'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
CopyButton.propTypes = {
|
||||
content: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default CopyButton;
|
@ -479,7 +479,7 @@ class App extends Component {
|
||||
updateRequestParameters={parameters => db.requestUpdate(activeRequest, {parameters})}
|
||||
updateRequestAuthentication={authentication => db.requestUpdate(activeRequest, {authentication})}
|
||||
updateRequestHeaders={headers => db.requestUpdate(activeRequest, {headers})}
|
||||
updateRequestContentType={contentType => db.requestUpdate(activeRequest, {contentType})}
|
||||
updateRequestContentType={contentType => db.requestUpdateContentType(activeRequest, contentType)}
|
||||
updateSettingsShowPasswords={showPasswords => db.settingsUpdate(settings, {showPasswords})}
|
||||
/>
|
||||
|
||||
|
@ -5,8 +5,8 @@ import * as fs from 'fs';
|
||||
import * as methods from '../lib/constants';
|
||||
import {generateId} from './util';
|
||||
import {PREVIEW_MODE_SOURCE} from '../lib/previewModes';
|
||||
import {CONTENT_TYPE_TEXT} from '../lib/contentTypes';
|
||||
import {DB_PERSIST_INTERVAL, DEFAULT_SIDEBAR_WIDTH} from '../lib/constants';
|
||||
import {CONTENT_TYPE_JSON} from '../lib/contentTypes';
|
||||
|
||||
export const TYPE_SETTINGS = 'Settings';
|
||||
export const TYPE_WORKSPACE = 'Workspace';
|
||||
@ -40,10 +40,12 @@ const MODEL_DEFAULTS = {
|
||||
url: '',
|
||||
name: 'New Request',
|
||||
method: methods.METHOD_GET,
|
||||
contentType: CONTENT_TYPE_TEXT,
|
||||
body: '',
|
||||
parameters: [],
|
||||
headers: [],
|
||||
headers: [{
|
||||
name: 'Content-Type',
|
||||
value: CONTENT_TYPE_JSON
|
||||
}],
|
||||
authentication: {},
|
||||
metaPreviewMode: PREVIEW_MODE_SOURCE,
|
||||
metaSortKey: -1 * Date.now()
|
||||
@ -324,6 +326,24 @@ export function requestUpdate (request, patch) {
|
||||
return docUpdate(request, patch);
|
||||
}
|
||||
|
||||
export function requestUpdateContentType (request, contentType) {
|
||||
let headers = [...request.headers];
|
||||
const contentTypeHeader = headers.find(
|
||||
h => h.name.toLowerCase() === 'content-type'
|
||||
);
|
||||
|
||||
if (!contentType) {
|
||||
// Remove the contentType header if we are unsetting it
|
||||
headers = headers.filter(h => h !== contentTypeHeader);
|
||||
} else if (contentTypeHeader) {
|
||||
contentTypeHeader.value = contentType;
|
||||
} else {
|
||||
headers.push({name: 'Content-Type', value: contentType})
|
||||
}
|
||||
|
||||
return docUpdate(request, {headers});
|
||||
}
|
||||
|
||||
export function requestCopy (request) {
|
||||
const name = `${request.name} (Copy)`;
|
||||
return requestCreate(Object.assign({}, request, {name}));
|
||||
|
@ -1,13 +1,15 @@
|
||||
export const CONTENT_TYPE_JSON = 'application/json';
|
||||
export const CONTENT_TYPE_XML = 'application/xml';
|
||||
export const CONTENT_TYPE_TEXT = 'text/plain';
|
||||
export const CONTENT_TYPE_FORM = 'application/x-www-form-urlencoded';
|
||||
export const CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded';
|
||||
export const CONTENT_TYPE_OTHER = '';
|
||||
|
||||
const contentTypeMap = {
|
||||
[CONTENT_TYPE_JSON]: 'JSON',
|
||||
[CONTENT_TYPE_XML]: 'XML',
|
||||
[CONTENT_TYPE_TEXT]: 'Plain Text',
|
||||
[CONTENT_TYPE_FORM]: 'Form Data'
|
||||
[CONTENT_TYPE_TEXT]: 'Text',
|
||||
[CONTENT_TYPE_OTHER]: 'Other',
|
||||
[CONTENT_TYPE_FORM_URLENCODED]: 'Form URL Encoded'
|
||||
};
|
||||
|
||||
export const CONTENT_TYPES = Object.keys(contentTypeMap);
|
||||
@ -19,6 +21,10 @@ export const CONTENT_TYPES = Object.keys(contentTypeMap);
|
||||
* @returns {*|string}
|
||||
*/
|
||||
export function getContentTypeName (contentType) {
|
||||
// TODO: Make this more robust maybe...
|
||||
return contentTypeMap[contentType] || 'Unknown';
|
||||
return contentTypeMap[contentType] || contentTypeMap[CONTENT_TYPE_OTHER];
|
||||
}
|
||||
|
||||
export function getContentTypeFromHeaders (headers) {
|
||||
const header = headers.find(({name}) => name.toLowerCase() === 'content-type');
|
||||
return header ? header.value : null;
|
||||
}
|
||||
|
@ -186,13 +186,6 @@ export function exportCurl (requestId) {
|
||||
// Headers
|
||||
const hasContentTypeHeader = !!renderedRequest.headers.find(h => h.name.toUpperCase() === 'CONTENT-TYPE');
|
||||
|
||||
if (!hasContentTypeHeader && !IS_GET_REQUEST) {
|
||||
const value = renderedRequest.contentType;
|
||||
const name = 'Content-Type';
|
||||
|
||||
renderedRequest.headers.push({name, value})
|
||||
}
|
||||
|
||||
for (let i = 0; i < renderedRequest.headers.length; i++) {
|
||||
const {name, value} = renderedRequest.headers[i];
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as db from '../../database';
|
||||
import {getAppVersion} from '../appInfo';
|
||||
import {getContentTypeFromHeaders} from '../contentTypes';
|
||||
|
||||
const VERSION_CHROME_APP = 1;
|
||||
const VERSION_DESKTOP_APP = 2;
|
||||
@ -64,18 +65,33 @@ function importRequest (importedRequest, parentId, exportFormat) {
|
||||
}
|
||||
}
|
||||
|
||||
// Add the content type header
|
||||
const headers = importedRequest.headers || [];
|
||||
const contentType = getContentTypeFromHeaders(headers);
|
||||
if (!contentType) {
|
||||
const derivedContentType = FORMAT_MAP[importedRequest.__insomnia.format];
|
||||
|
||||
if (derivedContentType) {
|
||||
headers.push({
|
||||
name: 'Content-Type',
|
||||
value: FORMAT_MAP[importedRequest.__insomnia.format]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
db.requestCreate({
|
||||
parentId,
|
||||
name: importedRequest.name,
|
||||
url: importedRequest.url,
|
||||
method: importedRequest.method,
|
||||
body: importedRequest.body,
|
||||
headers: importedRequest.headers || [],
|
||||
headers: headers,
|
||||
parameters: importedRequest.params || [],
|
||||
contentType: FORMAT_MAP[importedRequest.__insomnia.format] || 'text/plain',
|
||||
authentication: auth
|
||||
});
|
||||
} else if (exportFormat === VERSION_DESKTOP_APP) {
|
||||
const headers =
|
||||
db.requestCreate({
|
||||
parentId,
|
||||
name: importedRequest.name,
|
||||
@ -84,7 +100,6 @@ function importRequest (importedRequest, parentId, exportFormat) {
|
||||
body: importedRequest.body,
|
||||
headers: importedRequest.headers,
|
||||
parameters: importedRequest.parameters,
|
||||
contentType: importedRequest.contentType,
|
||||
authentication: importedRequest.authentication
|
||||
});
|
||||
} else {
|
||||
@ -175,7 +190,6 @@ export function exportJSON () {
|
||||
url: r.url,
|
||||
name: r.name,
|
||||
method: r.method,
|
||||
contentType: r.contentType,
|
||||
body: r.body,
|
||||
parameters: r.parameters,
|
||||
headers: r.headers,
|
||||
|
@ -1,30 +1,39 @@
|
||||
|
||||
export function getJoiner (url) {
|
||||
url = url || '';
|
||||
return url.indexOf('?') === -1 ? '?' : '&';
|
||||
}
|
||||
|
||||
export function joinURL (url, qs) {
|
||||
if (!qs) { return url; }
|
||||
if (!qs) {
|
||||
return url;
|
||||
}
|
||||
url = url || '';
|
||||
return url + getJoiner(url) + qs;
|
||||
}
|
||||
|
||||
export function build (param) {
|
||||
// Skip non-name ones
|
||||
if (!param.name) { return ''; }
|
||||
export function build (param, strict = true) {
|
||||
// Skip non-name ones in strict mode
|
||||
if (strict && !param.name) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (param.value) {
|
||||
if (!strict || param.value) {
|
||||
return encodeURIComponent(param.name) + '=' + encodeURIComponent(param.value);
|
||||
} else {
|
||||
return encodeURIComponent(param.name);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildFromParams (parameters) {
|
||||
/**
|
||||
*
|
||||
* @param parameters
|
||||
* @param strict allow empty names and values
|
||||
* @returns {string}
|
||||
*/
|
||||
export function buildFromParams (parameters, strict = true) {
|
||||
let items = [];
|
||||
for (var i = 0; i < parameters.length; i++) {
|
||||
let built = build(parameters[i]);
|
||||
let built = build(parameters[i], strict);
|
||||
|
||||
if (!built) {
|
||||
continue;
|
||||
@ -35,3 +44,29 @@ export function buildFromParams (parameters) {
|
||||
|
||||
return items.join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param qs
|
||||
* @param strict allow empty names and values
|
||||
* @returns {Array}
|
||||
*/
|
||||
export function deconstructToParams (qs, strict = true) {
|
||||
const stringPairs = qs.split('&');
|
||||
const pairs = [];
|
||||
|
||||
for (let stringPair of stringPairs) {
|
||||
const tmp = stringPair.split('=');
|
||||
|
||||
const name = decodeURIComponent(tmp[0] || '');
|
||||
const value = decodeURIComponent(tmp[1] || '');
|
||||
|
||||
if (strict && !name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pairs.push({name, value});
|
||||
}
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user