From 3056921fdab478ae5cb6561acda42db07749fee8 Mon Sep 17 00:00:00 2001
From: Gregory Schier
Date: Fri, 9 Jun 2017 14:42:19 -0700
Subject: [PATCH] Support multiline text in form data editors (#299)
* Support multiline text in form data editors
* Some tweaks around rendering and syntax
* Embed markdown editor
---
app/common/misc.js | 12 +-
app/ui/components/base/file-input-button.js | 4 +-
.../components/codemirror/one-line-editor.js | 18 ++
.../dropdowns/content-type-dropdown.js | 5 +-
app/ui/components/editors/body/form-editor.js | 3 +-
.../editors/body/url-encoded-editor.js | 1 +
app/ui/components/key-value-editor/editor.js | 12 +-
app/ui/components/key-value-editor/row.js | 210 +++++++++++++-----
app/ui/components/markdown-editor.js | 19 +-
app/ui/components/modals/code-prompt-modal.js | 180 +++++++++++++++
app/ui/components/modals/prompt-modal.js | 30 ++-
.../components/modals/request-create-modal.js | 8 +-
app/ui/components/settings/account.js | 12 +-
app/ui/components/settings/general.js | 5 +-
app/ui/components/settings/plugins.js | 2 +-
app/ui/components/wrapper.js | 11 +
app/ui/containers/app.js | 14 +-
app/ui/css/components/forms.less | 10 +-
app/ui/css/components/key-value-editor.less | 5 +-
app/ui/css/components/markdown-editor.less | 14 +-
app/ui/css/editor/general.less | 4 +-
app/ui/css/layout/base.less | 4 +
22 files changed, 463 insertions(+), 120 deletions(-)
create mode 100644 app/ui/components/modals/code-prompt-modal.js
diff --git a/app/common/misc.js b/app/common/misc.js
index bdc7b447c..726f08163 100644
--- a/app/common/misc.js
+++ b/app/common/misc.js
@@ -221,26 +221,26 @@ export function debounce (callback, millis = DEBOUNCE_MILLIS) {
}, millis).bind(null, '__key__');
}
-export function describeByteSize (bytes) {
+export function describeByteSize (bytes, long) {
bytes = Math.round(bytes * 10) / 10;
let size;
// NOTE: We multiply these by 2 so we don't end up with
// values like 0 GB
- let unit = 'B';
+ let unit = long ? 'bytes' : 'B';
if (bytes < 1024 * 2) {
size = bytes;
- unit = 'B';
+ unit = long ? 'bytes' : 'B';
} else if (bytes < 1024 * 1024 * 2) {
size = bytes / 1024;
- unit = 'KB';
+ unit = long ? 'kilobytes' : 'KB';
} else if (bytes < 1024 * 1024 * 1024 * 2) {
size = bytes / 1024 / 1024;
- unit = 'MB';
+ unit = long ? 'megabytes' : 'MB';
} else {
size = bytes / 1024 / 1024 / 1024;
- unit = 'GB';
+ unit = long ? 'gigabytes' : 'GB';
}
const rounded = (Math.round(size * 10) / 10);
diff --git a/app/ui/components/base/file-input-button.js b/app/ui/components/base/file-input-button.js
index 175e3ad1d..becf22b7e 100644
--- a/app/ui/components/base/file-input-button.js
+++ b/app/ui/components/base/file-input-button.js
@@ -36,13 +36,14 @@ class FileInputButton extends PureComponent {
}
render () {
- const {showFileName, path, name, ...extraProps} = this.props;
+ const {showFileName, showFileIcon, path, name, ...extraProps} = this.props;
const fileName = pathBasename(path);
return (
);
@@ -56,6 +57,7 @@ FileInputButton.propTypes = {
// Optional
showFileName: PropTypes.bool,
+ showFileIcon: PropTypes.bool,
name: PropTypes.string
};
diff --git a/app/ui/components/codemirror/one-line-editor.js b/app/ui/components/codemirror/one-line-editor.js
index 0103d8785..438e5b7c6 100644
--- a/app/ui/components/codemirror/one-line-editor.js
+++ b/app/ui/components/codemirror/one-line-editor.js
@@ -63,6 +63,24 @@ class OneLineEditor extends PureComponent {
}
}
+ getSelectionStart () {
+ if (this._editor) {
+ return this._editor.getSelectionStart();
+ } else {
+ console.warn('Tried to get selection start of one-line-editor when ');
+ return this._input.value.length;
+ }
+ }
+
+ getSelectionEnd () {
+ if (this._editor) {
+ return this._editor.getSelectionEnd();
+ } else {
+ console.warn('Tried to get selection end of one-line-editor when ');
+ return this._input.value.length;
+ }
+ }
+
componentDidMount () {
document.body.addEventListener('click', this._handleDocumentClick);
}
diff --git a/app/ui/components/dropdowns/content-type-dropdown.js b/app/ui/components/dropdowns/content-type-dropdown.js
index 75c53e074..04041679f 100644
--- a/app/ui/components/dropdowns/content-type-dropdown.js
+++ b/app/ui/components/dropdowns/content-type-dropdown.js
@@ -23,9 +23,10 @@ class ContentTypeDropdown extends PureComponent {
const hasFile = body.fileName && body.fileName.length;
const isEmpty = !hasParams && !hasText && !hasFile;
const isFile = body.mimeType === CONTENT_TYPE_FILE;
- const isMultipart = body.mimeType === CONTENT_TYPE_FORM_DATA;
+ const isMultipartWithFiles = body.mimeType === CONTENT_TYPE_FORM_DATA &&
+ body.params.find(p => p.type === 'file');
const isFormUrlEncoded = body.mimeType === CONTENT_TYPE_FORM_URLENCODED;
- const isText = !isFile && !isMultipart;
+ const isText = !isFile && !isMultipartWithFiles;
const willBeFile = mimeType === CONTENT_TYPE_FILE;
const willBeMultipart = mimeType === CONTENT_TYPE_FORM_DATA;
diff --git a/app/ui/components/editors/body/form-editor.js b/app/ui/components/editors/body/form-editor.js
index 25c7c9992..c0c1544fe 100644
--- a/app/ui/components/editors/body/form-editor.js
+++ b/app/ui/components/editors/body/form-editor.js
@@ -42,6 +42,8 @@ class FormEditor extends PureComponent {
diff --git a/app/ui/components/editors/body/url-encoded-editor.js b/app/ui/components/editors/body/url-encoded-editor.js
index 876a906f4..f925757dd 100644
--- a/app/ui/components/editors/body/url-encoded-editor.js
+++ b/app/ui/components/editors/body/url-encoded-editor.js
@@ -34,6 +34,7 @@ class UrlEncodedEditor extends PureComponent {
))}
@@ -378,7 +380,8 @@ class Editor extends PureComponent {
valuePlaceholder={`New ${valuePlaceholder}`}
onFocusName={this._handleAddFromName}
onFocusValue={this._handleAddFromValue}
- multipart={multipart}
+ allowMultiline={allowMultiline}
+ allowFile={allowFile}
pair={{name: '', value: ''}}
/> : null
}
@@ -397,7 +400,8 @@ Editor.propTypes = {
handleGetRenderContext: PropTypes.func,
handleGetAutocompleteNameConstants: PropTypes.func,
handleGetAutocompleteValueConstants: PropTypes.func,
- multipart: PropTypes.bool,
+ allowFile: PropTypes.bool,
+ allowMultiline: PropTypes.bool,
sortable: PropTypes.bool,
maxPairs: PropTypes.number,
namePlaceholder: PropTypes.string,
diff --git a/app/ui/components/key-value-editor/row.js b/app/ui/components/key-value-editor/row.js
index 8db09249f..562d4feec 100644
--- a/app/ui/components/key-value-editor/row.js
+++ b/app/ui/components/key-value-editor/row.js
@@ -1,14 +1,16 @@
// eslint-disable-next-line filenames/match-exported
-import React, {PureComponent, PropTypes} from 'react';
+import React, {PropTypes, PureComponent} from 'react';
import ReactDOM from 'react-dom';
import autobind from 'autobind-decorator';
import {DragSource, DropTarget} from 'react-dnd';
import classnames from 'classnames';
import FileInputButton from '../base/file-input-button';
-import {Dropdown, DropdownItem, DropdownButton} from '../base/dropdown/index';
+import {Dropdown, DropdownButton, DropdownItem} from '../base/dropdown/index';
import PromptButton from '../base/prompt-button';
+import CodePromptModal from '../modals/code-prompt-modal';
import Button from '../base/button';
import OneLineEditor from '../codemirror/one-line-editor';
+import {showModal} from '../modals/index';
@autobind
class KeyValueEditorRow extends PureComponent {
@@ -25,16 +27,12 @@ class KeyValueEditorRow extends PureComponent {
focusNameEnd () {
if (this._nameInput) {
this._nameInput.focusEnd();
- } else {
- console.warn('Unable to focus non-existing nameInput');
}
}
focusValueEnd () {
if (this._valueInput) {
this._valueInput.focusEnd();
- } else {
- console.warn('Unable to focus non-existing valueInput');
}
}
@@ -61,6 +59,24 @@ class KeyValueEditorRow extends PureComponent {
this._sendChange({name});
}
+ _handleValuePaste (e) {
+ const value = e.clipboardData.getData('text/plain');
+ if (value && value.includes('\n')) {
+ e.preventDefault();
+
+ // Insert the pasted text into the current selection. Unfortunately, this
+ // is the easiest way to do this.
+ const currentValue = this._valueInput.getValue();
+ const prefix = currentValue.slice(0, this._valueInput.getSelectionStart());
+ const suffix = currentValue.slice(this._valueInput.getSelectionEnd());
+ const finalValue = `${prefix}${value}${suffix}`;
+
+ // Update type and value
+ this._handleTypeChange({type: 'text', multiline: 'text/plain'});
+ this._handleValueChange(finalValue);
+ }
+ }
+
_handleValueChange (value) {
this._sendChange({value});
}
@@ -69,8 +85,14 @@ class KeyValueEditorRow extends PureComponent {
this._sendChange({fileName});
}
- _handleTypeChange (type) {
- this._sendChange({type});
+ _handleTypeChange (def) {
+ // Remove newlines if converting to text
+ let value = this.props.pair.value || '';
+ if (def.type === 'text' && !def.multiline && value.includes('\n')) {
+ value = value.replace(/\n/g, '');
+ }
+
+ this._sendChange({type: def.type, multiline: def.multiline, value});
}
_handleDisableChange (disabled) {
@@ -123,15 +145,130 @@ class KeyValueEditorRow extends PureComponent {
}
}
+ _handleEditMultiline () {
+ const {pair, handleRender, handleGetRenderContext} = this.props;
+
+ showModal(CodePromptModal, {
+ submitName: 'Done',
+ title: `Edit ${pair.name}`,
+ defaultValue: pair.value,
+ onChange: this._handleValueChange,
+ enableRender: handleRender || handleGetRenderContext,
+ onModeChange: mode => {
+ this._handleTypeChange(Object.assign({}, pair, {multiline: mode}));
+ }
+ });
+ }
+
+ renderPairValue () {
+ const {
+ pair,
+ readOnly,
+ forceInput,
+ valueInputType,
+ valuePlaceholder,
+ handleRender,
+ handleGetRenderContext
+ } = this.props;
+
+ if (pair.type === 'file') {
+ return (
+
+ );
+ } else if (pair.type === 'text' && pair.multiline) {
+ const numWords = (pair.value || '').replace(/\s+/g, ' ').trim().split(' ').length;
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ }
+
+ renderPairSelector () {
+ const {
+ hideButtons,
+ allowMultiline,
+ allowFile
+ } = this.props;
+
+ const showDropdown = allowMultiline || allowFile;
+
+ // Put a spacer in for dropdown if needed
+ if (hideButtons && showDropdown) {
+ return (
+
+ );
+ }
+
+ if (hideButtons) {
+ return null;
+ }
+
+ if (showDropdown) {
+ return (
+
+
+
+
+
+ Text
+
+ {allowMultiline && (
+
+ Text (Multi-line)
+
+ )}
+ {allowFile && (
+
+ File
+
+ )}
+
+ );
+ } else {
+ return null;
+ }
+ }
+
render () {
const {
pair,
namePlaceholder,
- valuePlaceholder,
handleRender,
handleGetRenderContext,
- valueInputType,
- multipart,
sortable,
noDropZone,
hideButtons,
@@ -185,53 +322,11 @@ class KeyValueEditorRow extends PureComponent {
onKeyDown={this._handleKeyDown}
/>
-
- {pair.type === 'file' ? (
-
- ) : (
-
- )}
+
+ {this.renderPairValue()}
- {multipart && (
- !hideButtons ? (
-
-
-
-
-
- Text
-
-
- File
-
-
- ) : (
-
- )
- )}
+ {this.renderPairSelector()}
{!hideButtons ? (
- Or Login
+ Or Login
);
@@ -63,15 +64,12 @@ class Account extends PureComponent {
{session.getEmail()}
-
+
Manage Account
-
+
Sign Out
-
+
);
}
diff --git a/app/ui/components/settings/general.js b/app/ui/components/settings/general.js
index 72765be74..0c40dee71 100644
--- a/app/ui/components/settings/general.js
+++ b/app/ui/components/settings/general.js
@@ -90,7 +90,10 @@ class General extends PureComponent {
-