mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-23 23:59:32 -04:00
UI/UX and tweaks to Markdown Descriptions (#279)
* Styles and editor done for requests * Add description markdown to WOrkspace settings * Show file icon beside request with description * Add tracking
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import uuid from 'uuid';
|
||||
import {parse as urlParse, format as urlFormat} from 'url';
|
||||
import {DEBOUNCE_MILLIS} from './constants';
|
||||
import {DEBOUNCE_MILLIS, getAppVersion, isDevelopment} from './constants';
|
||||
import * as querystring from './querystring';
|
||||
import {shell} from 'electron';
|
||||
|
||||
const URL_PATH_CHARACTER_WHITELIST = '+,;@=:';
|
||||
|
||||
@@ -257,3 +258,15 @@ export function preventDefault (e) {
|
||||
export function stopPropagation (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
export function clickLink (href) {
|
||||
if (href.match(/^http/i)) {
|
||||
const appName = isDevelopment() ? 'Insomnia Dev' : 'Insomnia';
|
||||
const qs = `utm_source=${appName}&utm_medium=app&utm_campaign=v${getAppVersion()}`;
|
||||
const attributedHref = querystring.joinUrl(href, qs);
|
||||
shell.openExternal(attributedHref);
|
||||
} else {
|
||||
// Don't modify non-http urls
|
||||
shell.openExternal(href);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import React, {PureComponent, PropTypes} from 'react';
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import {shell} from 'electron';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import {getAppVersion, isDevelopment} from '../../../common/constants';
|
||||
import * as querystring from '../../../common/querystring';
|
||||
import * as misc from '../../../common/misc';
|
||||
|
||||
@autobind
|
||||
class Link extends PureComponent {
|
||||
@@ -13,17 +11,7 @@ class Link extends PureComponent {
|
||||
|
||||
// Also call onClick that was passed to us if there was one
|
||||
onClick && onClick(e);
|
||||
|
||||
if (href.match(/^http/i)) {
|
||||
const appName = isDevelopment() ? 'Insomnia Dev' : 'Insomnia';
|
||||
const qs = `utm_source=${appName}&utm_medium=app&utm_campaign=v${getAppVersion()}`;
|
||||
const attributedHref = querystring.joinUrl(href, qs);
|
||||
shell.openExternal(attributedHref);
|
||||
} else {
|
||||
// Don't modify non-http urls
|
||||
shell.openExternal(href);
|
||||
}
|
||||
|
||||
misc.clickLink(href);
|
||||
trackEvent('Link', 'Click', href);
|
||||
}
|
||||
|
||||
|
||||
@@ -339,7 +339,8 @@ class CodeEditor extends PureComponent {
|
||||
hideScrollbars,
|
||||
noStyleActiveLine,
|
||||
noLint,
|
||||
indentSize
|
||||
indentSize,
|
||||
dynamicHeight
|
||||
} = this.props;
|
||||
|
||||
let mode;
|
||||
@@ -410,6 +411,10 @@ class CodeEditor extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
if (dynamicHeight) {
|
||||
options.viewportMargin = Infinity;
|
||||
}
|
||||
|
||||
// Strip of charset if there is one
|
||||
const cm = this.codeMirror;
|
||||
Object.keys(options).map(key => {
|
||||
@@ -558,11 +563,13 @@ class CodeEditor extends PureComponent {
|
||||
onMouseLeave,
|
||||
onClick,
|
||||
className,
|
||||
dynamicHeight,
|
||||
style
|
||||
} = this.props;
|
||||
|
||||
const classes = classnames(className, {
|
||||
'editor': true,
|
||||
'editor--dynamic-height': dynamicHeight,
|
||||
'editor--readonly': readOnly
|
||||
});
|
||||
|
||||
@@ -673,7 +680,8 @@ CodeEditor.propTypes = {
|
||||
readOnly: PropTypes.bool,
|
||||
filter: PropTypes.string,
|
||||
singleLine: PropTypes.bool,
|
||||
debounceMillis: PropTypes.number
|
||||
debounceMillis: PropTypes.number,
|
||||
dynamicHeight: PropTypes.bool
|
||||
};
|
||||
|
||||
export default CodeEditor;
|
||||
|
||||
@@ -2,9 +2,7 @@ import React, {PropTypes, PureComponent} from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import PromptButton from '../base/prompt-button';
|
||||
import {Dropdown, DropdownButton, DropdownHint, DropdownItem} from '../base/dropdown';
|
||||
import RequestSettingsModal from '../modals/request-settings-modal';
|
||||
import * as models from '../../../models';
|
||||
import {showModal} from '../modals/index';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import {DropdownDivider} from '../base/dropdown/index';
|
||||
|
||||
@@ -25,10 +23,6 @@ class RequestActionsDropdown extends PureComponent {
|
||||
trackEvent('Request', 'Generate Code', 'Request Action');
|
||||
}
|
||||
|
||||
_handleShowRequestSettings () {
|
||||
showModal(RequestSettingsModal, this.props.request);
|
||||
}
|
||||
|
||||
_handleRemove () {
|
||||
const {request} = this.props;
|
||||
models.request.remove(request);
|
||||
@@ -42,6 +36,7 @@ class RequestActionsDropdown extends PureComponent {
|
||||
render () {
|
||||
const {
|
||||
request, // eslint-disable-line no-unused-vars
|
||||
handleShowSettings,
|
||||
...other
|
||||
} = this.props;
|
||||
|
||||
@@ -63,7 +58,7 @@ class RequestActionsDropdown extends PureComponent {
|
||||
|
||||
<DropdownDivider/>
|
||||
|
||||
<DropdownItem onClick={this._handleShowRequestSettings}>
|
||||
<DropdownItem onClick={handleShowSettings}>
|
||||
<i className="fa fa-wrench"/> Settings
|
||||
<DropdownHint alt shift char=","/>
|
||||
</DropdownItem>
|
||||
@@ -75,6 +70,7 @@ class RequestActionsDropdown extends PureComponent {
|
||||
RequestActionsDropdown.propTypes = {
|
||||
handleDuplicateRequest: PropTypes.func.isRequired,
|
||||
handleGenerateCode: PropTypes.func.isRequired,
|
||||
handleShowSettings: PropTypes.func.isRequired,
|
||||
request: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
|
||||
165
app/ui/components/markdown-editor.js
Normal file
165
app/ui/components/markdown-editor.js
Normal file
@@ -0,0 +1,165 @@
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import autobind from 'autobind-decorator';
|
||||
import classnames from 'classnames';
|
||||
import marked from 'marked';
|
||||
import highlight from 'highlight.js';
|
||||
import {Tab, TabList, TabPanel, Tabs} from 'react-tabs';
|
||||
import {trackEvent} from '../../analytics';
|
||||
import Button from './base/button';
|
||||
import CodeEditor from './codemirror/code-editor';
|
||||
import * as misc from '../../common/misc';
|
||||
|
||||
@autobind
|
||||
class MarkdownEditor extends PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
markdown: props.defaultValue,
|
||||
compiled: ''
|
||||
};
|
||||
}
|
||||
|
||||
_trackTab (name) {
|
||||
trackEvent('Request', 'Markdown Editor Tab', name);
|
||||
}
|
||||
|
||||
_handleChange (markdown) {
|
||||
this.props.onChange(markdown);
|
||||
this._compileMarkdown(markdown);
|
||||
}
|
||||
|
||||
async _compileMarkdown (markdown) {
|
||||
const compiled = marked(await this.props.handleRender(markdown));
|
||||
this.setState({markdown, compiled});
|
||||
}
|
||||
|
||||
_setPreviewRef (n) {
|
||||
this._preview = n;
|
||||
}
|
||||
|
||||
_highlightCodeBlocks () {
|
||||
if (!this._preview) {
|
||||
return;
|
||||
}
|
||||
|
||||
const el = ReactDOM.findDOMNode(this._preview);
|
||||
for (const block of el.querySelectorAll('pre > code')) {
|
||||
highlight.highlightBlock(block);
|
||||
}
|
||||
|
||||
for (const a of el.querySelectorAll('a')) {
|
||||
a.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
misc.clickLink(e.target.getAttribute('href'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this._compileMarkdown(this.state.markdown);
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._highlightCodeBlocks();
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: false,
|
||||
pedantic: false,
|
||||
smartLists: true,
|
||||
smartypants: false
|
||||
});
|
||||
|
||||
this._highlightCodeBlocks();
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
fontSize,
|
||||
lineWrapping,
|
||||
indentSize,
|
||||
keyMap,
|
||||
placeholder,
|
||||
defaultPreviewMode,
|
||||
className,
|
||||
handleRender,
|
||||
handleGetRenderContext
|
||||
} = this.props;
|
||||
|
||||
const {markdown, compiled} = this.state;
|
||||
|
||||
return (
|
||||
<Tabs className={classnames('markdown-editor', 'outlined', className)}
|
||||
forceRenderTabPanel
|
||||
selectedIndex={defaultPreviewMode ? 1 : 0}>
|
||||
<TabList>
|
||||
<Tab>
|
||||
<Button onClick={this._trackTab} value="Write">
|
||||
Write
|
||||
</Button>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<Button onClick={this._trackTab} value="Preview">
|
||||
Preview
|
||||
</Button>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel className="markdown-editor__edit">
|
||||
<div className="form-control form-control--outlined">
|
||||
<CodeEditor
|
||||
hideGutters
|
||||
hideLineNumbers
|
||||
dynamicHeight
|
||||
manualPrettify
|
||||
noStyleActiveLine
|
||||
mode="text/plain"
|
||||
placeholder={placeholder}
|
||||
debounceMillis={300}
|
||||
keyMap={keyMap}
|
||||
fontSize={fontSize}
|
||||
lineWrapping={lineWrapping}
|
||||
indentSize={indentSize}
|
||||
defaultValue={markdown}
|
||||
render={handleRender}
|
||||
getRenderContext={handleGetRenderContext}
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="txt-sm italic faint">
|
||||
Styling with Markdown is supported
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<div className="markdown-editor__preview" ref={this._setPreviewRef}
|
||||
dangerouslySetInnerHTML={{__html: compiled}}>
|
||||
{/* Set from above */}
|
||||
</div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MarkdownEditor.propTypes = {
|
||||
// Required
|
||||
onChange: PropTypes.func.isRequired,
|
||||
defaultValue: PropTypes.string.isRequired,
|
||||
fontSize: PropTypes.number.isRequired,
|
||||
indentSize: PropTypes.number.isRequired,
|
||||
keyMap: PropTypes.string.isRequired,
|
||||
lineWrapping: PropTypes.bool.isRequired,
|
||||
handleRender: PropTypes.func.isRequired,
|
||||
handleGetRenderContext: PropTypes.func.isRequired,
|
||||
|
||||
// Optional
|
||||
placeholder: PropTypes.string,
|
||||
defaultPreviewMode: PropTypes.bool,
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
export default MarkdownEditor;
|
||||
@@ -1,74 +0,0 @@
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import {Tab, TabList, TabPanel, Tabs} from 'react-tabs';
|
||||
import {trackEvent} from '../../analytics';
|
||||
import Button from './base/button';
|
||||
import CodeEditor from './codemirror/code-editor';
|
||||
import marked from 'marked';
|
||||
|
||||
marked.setOptions({
|
||||
renderer: new marked.Renderer(),
|
||||
gfm: true,
|
||||
tables: true,
|
||||
breaks: false,
|
||||
pedantic: false,
|
||||
smartLists: true,
|
||||
smartypants: false
|
||||
});
|
||||
|
||||
@autobind
|
||||
class Markdown extends PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this._defaultValue = props.defaultValue;
|
||||
this._compiled = marked(this._defaultValue);
|
||||
}
|
||||
|
||||
_trackTab (name) {
|
||||
trackEvent('Request', 'Markdown Editor Tab', name);
|
||||
}
|
||||
|
||||
_handleChange (value) {
|
||||
this._compiled = marked(value);
|
||||
this.props.onChange(value);
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab>
|
||||
<Button onClick={this._trackTab} value="Write">
|
||||
Write
|
||||
</Button>
|
||||
</Tab>
|
||||
<Tab>
|
||||
<Button onClick={this._trackTab} value="Preview">
|
||||
Preview
|
||||
</Button>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel>
|
||||
<CodeEditor
|
||||
manualPrettify
|
||||
mode="text/x-markdown"
|
||||
placeholder="..."
|
||||
defaultValue={this._defaultValue}
|
||||
onChange={this._handleChange}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<div className="pad" dangerouslySetInnerHTML={{__html: this._compiled}}></div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Markdown.propTypes = {
|
||||
// Required
|
||||
onChange: PropTypes.func.isRequired,
|
||||
defaultValue: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default Markdown;
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import autobind from 'autobind-decorator';
|
||||
import Link from '../base/link';
|
||||
import Modal from '../base/modal';
|
||||
import ModalBody from '../base/modal-body';
|
||||
import ModalHeader from '../base/modal-header';
|
||||
@@ -8,14 +7,16 @@ import HelpTooltip from '../help-tooltip';
|
||||
import * as models from '../../../models';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import DebouncedInput from '../base/debounced-input';
|
||||
import Markdown from '../markdown';
|
||||
import MarkdownEditor from '../markdown-editor';
|
||||
|
||||
@autobind
|
||||
class RequestSettingsModal extends PureComponent {
|
||||
constructor (props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
request: null
|
||||
request: null,
|
||||
showDescription: false,
|
||||
defaultPreviewMode: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,12 +39,22 @@ class RequestSettingsModal extends PureComponent {
|
||||
|
||||
async _handleDescriptionChange (description) {
|
||||
const request = await models.request.update(this.state.request, {description});
|
||||
this.setState({request});
|
||||
this.setState({request, defaultPreviewMode: false});
|
||||
}
|
||||
|
||||
_handleAddDescription () {
|
||||
trackEvent('Request', 'Add Description');
|
||||
this.setState({showDescription: true});
|
||||
}
|
||||
|
||||
show (request) {
|
||||
this.modal.show();
|
||||
this.setState({request});
|
||||
const hasDescription = !!request.description;
|
||||
this.setState({
|
||||
request,
|
||||
showDescription: hasDescription,
|
||||
defaultPreviewMode: hasDescription
|
||||
});
|
||||
}
|
||||
|
||||
hide () {
|
||||
@@ -62,10 +73,21 @@ class RequestSettingsModal extends PureComponent {
|
||||
}
|
||||
|
||||
renderModalBody (request) {
|
||||
const {
|
||||
editorLineWrapping,
|
||||
editorFontSize,
|
||||
editorIndentSize,
|
||||
editorKeyMap,
|
||||
handleRender,
|
||||
handleGetRenderContext
|
||||
} = this.props;
|
||||
|
||||
const {showDescription, defaultPreviewMode} = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Request Name
|
||||
<label>Name
|
||||
{' '}
|
||||
<span className="txt-sm faint italic">
|
||||
(also rename by double-clicking in sidebar)
|
||||
@@ -79,22 +101,27 @@ class RequestSettingsModal extends PureComponent {
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Description
|
||||
{' '}
|
||||
<span className="txt-sm faint italic">
|
||||
(supports <Link href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"> markdown </Link>)
|
||||
</span>
|
||||
<div className="input input--area">
|
||||
<Markdown className="input"
|
||||
defaultValue={request.description}
|
||||
onChange={this._handleDescriptionChange}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div className="pad-top-sm">
|
||||
<h2 className="txt-lg">Cookie Handling</h2>
|
||||
{showDescription ? (
|
||||
<MarkdownEditor
|
||||
className="margin-top"
|
||||
defaultPreviewMode={defaultPreviewMode}
|
||||
fontSize={editorFontSize}
|
||||
indentSize={editorIndentSize}
|
||||
keyMap={editorKeyMap}
|
||||
placeholder="Write a description"
|
||||
lineWrapping={editorLineWrapping}
|
||||
handleRender={handleRender}
|
||||
handleGetRenderContext={handleGetRenderContext}
|
||||
defaultValue={request.description}
|
||||
onChange={this._handleDescriptionChange}
|
||||
/>
|
||||
) : (
|
||||
<button onClick={this._handleAddDescription}
|
||||
className="btn btn--outlined btn--super-duper-compact">
|
||||
Add Description
|
||||
</button>
|
||||
)}
|
||||
<div className="pad-top">
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Send cookies automatically
|
||||
{this.renderCheckboxInput('settingSendCookies')}
|
||||
@@ -105,9 +132,6 @@ class RequestSettingsModal extends PureComponent {
|
||||
{this.renderCheckboxInput('settingStoreCookies')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pad-top-sm">
|
||||
<h2 className="txt-lg">Advanced Settings</h2>
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Automatically encode special characters in URL
|
||||
{this.renderCheckboxInput('settingEncodeUrl')}
|
||||
@@ -147,6 +171,13 @@ class RequestSettingsModal extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
RequestSettingsModal.propTypes = {};
|
||||
RequestSettingsModal.propTypes = {
|
||||
editorFontSize: PropTypes.number.isRequired,
|
||||
editorIndentSize: PropTypes.number.isRequired,
|
||||
editorKeyMap: PropTypes.string.isRequired,
|
||||
editorLineWrapping: PropTypes.bool.isRequired,
|
||||
handleRender: PropTypes.func.isRequired,
|
||||
handleGetRenderContext: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default RequestSettingsModal;
|
||||
|
||||
@@ -9,6 +9,7 @@ import ModalHeader from '../base/modal-header';
|
||||
import PromptButton from '../base/prompt-button';
|
||||
import * as models from '../../../models/index';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import MarkdownEditor from '../markdown-editor';
|
||||
|
||||
@autobind
|
||||
class WorkspaceSettingsModal extends PureComponent {
|
||||
@@ -21,7 +22,9 @@ class WorkspaceSettingsModal extends PureComponent {
|
||||
keyPath: '',
|
||||
pfxPath: '',
|
||||
host: '',
|
||||
passphrase: ''
|
||||
passphrase: '',
|
||||
showDescription: false,
|
||||
defaultPreviewMode: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,6 +32,11 @@ class WorkspaceSettingsModal extends PureComponent {
|
||||
models.workspace.update(this.props.workspace, patch);
|
||||
}
|
||||
|
||||
_handleAddDescription () {
|
||||
this.setState({showDescription: true});
|
||||
trackEvent('Workspace', 'Add Description');
|
||||
}
|
||||
|
||||
_handleSetModalRef (n) {
|
||||
this.modal = n;
|
||||
}
|
||||
@@ -48,6 +56,10 @@ class WorkspaceSettingsModal extends PureComponent {
|
||||
|
||||
_handleDescriptionChange (description) {
|
||||
this._workspaceUpdate({description});
|
||||
|
||||
if (this.state.defaultPreviewMode !== false) {
|
||||
this.setState({defaultPreviewMode: false});
|
||||
}
|
||||
}
|
||||
|
||||
_handleCreateHostChange (e) {
|
||||
@@ -126,13 +138,16 @@ class WorkspaceSettingsModal extends PureComponent {
|
||||
|
||||
show () {
|
||||
this.modal.show();
|
||||
const hasDescription = !!this.props.workspace.description;
|
||||
this.setState({
|
||||
showAddCertificateForm: false,
|
||||
crtPath: '',
|
||||
keyPath: '',
|
||||
pfxPath: '',
|
||||
host: '',
|
||||
passphrase: ''
|
||||
passphrase: '',
|
||||
showDescription: hasDescription,
|
||||
defaultPreviewMode: hasDescription
|
||||
});
|
||||
}
|
||||
|
||||
@@ -154,8 +169,25 @@ class WorkspaceSettingsModal extends PureComponent {
|
||||
}
|
||||
|
||||
renderModalBody () {
|
||||
const {workspace} = this.props;
|
||||
const {pfxPath, crtPath, keyPath, showAddCertificateForm} = this.state;
|
||||
const {
|
||||
workspace,
|
||||
editorLineWrapping,
|
||||
editorFontSize,
|
||||
editorIndentSize,
|
||||
editorKeyMap,
|
||||
handleRender,
|
||||
handleGetRenderContext
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
pfxPath,
|
||||
crtPath,
|
||||
keyPath,
|
||||
showAddCertificateForm,
|
||||
showDescription,
|
||||
defaultPreviewMode
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ModalBody key={`body::${workspace._id}`} noScroll>
|
||||
<Tabs forceRenderTabPanel>
|
||||
@@ -167,40 +199,47 @@ class WorkspaceSettingsModal extends PureComponent {
|
||||
<button>Client Certificates</button>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanel className="pad scrollable">
|
||||
<div className="row-fill">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Workspace Name
|
||||
<DebouncedInput
|
||||
type="text"
|
||||
delay={500}
|
||||
placeholder="Awesome API"
|
||||
defaultValue={workspace.name}
|
||||
onChange={this._handleRename}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<TabPanel className="pad scrollable pad-top-sm">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Description
|
||||
<label>Name
|
||||
<DebouncedInput
|
||||
textarea
|
||||
type="text"
|
||||
delay={500}
|
||||
rows="4"
|
||||
placeholder="This workspace is for testing the Awesome API!"
|
||||
defaultValue={workspace.description}
|
||||
onChange={this._handleDescriptionChange}
|
||||
placeholder="Awesome API"
|
||||
defaultValue={workspace.name}
|
||||
onChange={this._handleRename}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
{showDescription ? (
|
||||
<MarkdownEditor
|
||||
className="margin-top"
|
||||
defaultPreviewMode={defaultPreviewMode}
|
||||
fontSize={editorFontSize}
|
||||
indentSize={editorIndentSize}
|
||||
keyMap={editorKeyMap}
|
||||
placeholder="Write a description"
|
||||
lineWrapping={editorLineWrapping}
|
||||
handleRender={handleRender}
|
||||
handleGetRenderContext={handleGetRenderContext}
|
||||
defaultValue={workspace.description}
|
||||
onChange={this._handleDescriptionChange}
|
||||
/>
|
||||
) : (
|
||||
<button onClick={this._handleAddDescription}
|
||||
className="btn btn--outlined btn--super-duper-compact">
|
||||
Add Description
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="form-control form-control--padded">
|
||||
<label htmlFor="nothing">Danger Zone
|
||||
<PromptButton onClick={this._handleRemoveWorkspace}
|
||||
addIcon
|
||||
className="width-auto btn btn--clicky">
|
||||
<i className="fa fa-trash-o"/> Delete Workspace
|
||||
</PromptButton>
|
||||
</label>
|
||||
<h2>Danger Zone</h2>
|
||||
<PromptButton onClick={this._handleRemoveWorkspace}
|
||||
addIcon
|
||||
className="width-auto btn btn--clicky">
|
||||
<i className="fa fa-trash-o"/> Delete Workspace
|
||||
</PromptButton>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel className="pad scrollable">
|
||||
@@ -376,7 +415,13 @@ class WorkspaceSettingsModal extends PureComponent {
|
||||
|
||||
WorkspaceSettingsModal.propTypes = {
|
||||
handleRemoveWorkspace: PropTypes.func.isRequired,
|
||||
workspace: PropTypes.object.isRequired
|
||||
workspace: PropTypes.object.isRequired,
|
||||
editorFontSize: PropTypes.number.isRequired,
|
||||
editorIndentSize: PropTypes.number.isRequired,
|
||||
editorKeyMap: PropTypes.string.isRequired,
|
||||
editorLineWrapping: PropTypes.bool.isRequired,
|
||||
handleRender: PropTypes.func.isRequired,
|
||||
handleGetRenderContext: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default WorkspaceSettingsModal;
|
||||
|
||||
@@ -8,6 +8,8 @@ import Editable from '../base/editable';
|
||||
import MethodTag from '../tags/method-tag';
|
||||
import * as models from '../../../models';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import {showModal} from '../modals/index';
|
||||
import RequestSettingsModal from '../modals/request-settings-modal';
|
||||
|
||||
@autobind
|
||||
class SidebarRequestRow extends PureComponent {
|
||||
@@ -55,6 +57,15 @@ class SidebarRequestRow extends PureComponent {
|
||||
trackEvent('Request', 'Activate', 'Sidebar');
|
||||
}
|
||||
|
||||
_handleShowRequestSettings () {
|
||||
showModal(RequestSettingsModal, this.props.request);
|
||||
}
|
||||
|
||||
_handleClickDescription () {
|
||||
trackEvent('Request', 'Click Description Icon');
|
||||
this._handleShowRequestSettings();
|
||||
}
|
||||
|
||||
setDragDirection (dragDirection) {
|
||||
if (dragDirection !== this.state.dragDirection) {
|
||||
this.setState({dragDirection});
|
||||
@@ -106,17 +117,28 @@ class SidebarRequestRow extends PureComponent {
|
||||
onContextMenu={this._handleShowRequestActions}>
|
||||
<div className="sidebar__clickable">
|
||||
<MethodTag method={request.method}/>
|
||||
<Editable value={request.name}
|
||||
onEditStart={this._handleEditStart}
|
||||
onSubmit={this._handleRequestUpdateName}/>
|
||||
<div>
|
||||
<Editable value={request.name}
|
||||
className="inline-block"
|
||||
onEditStart={this._handleEditStart}
|
||||
onSubmit={this._handleRequestUpdateName}/>
|
||||
{request.description && (
|
||||
<button title="View description"
|
||||
onClick={this._handleClickDescription}
|
||||
className="icon space-left txt-sm super-faint">
|
||||
<i className="fa fa-file-text-o"/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div className="sidebar__actions">
|
||||
<RequestActionsDropdown
|
||||
right
|
||||
ref={this._setRequestActionsDropdownRef}
|
||||
handleDuplicateRequest={handleDuplicateRequest}
|
||||
handleGenerateCode={handleGenerateCode}
|
||||
right
|
||||
handleShowSettings={this._handleShowRequestSettings}
|
||||
request={request}
|
||||
requestGroup={requestGroup}
|
||||
/>
|
||||
|
||||
@@ -375,8 +375,18 @@ class Wrapper extends PureComponent {
|
||||
<RequestCreateModal ref={registerModal}/>
|
||||
<PaymentNotificationModal ref={registerModal}/>
|
||||
<FilterHelpModal ref={registerModal}/>
|
||||
<RequestSettingsModal ref={registerModal}/>
|
||||
<RequestRenderErrorModal ref={registerModal}/>
|
||||
|
||||
<RequestSettingsModal
|
||||
ref={registerModal}
|
||||
editorFontSize={settings.editorFontSize}
|
||||
editorIndentSize={settings.editorIndentSize}
|
||||
editorKeyMap={settings.editorKeyMap}
|
||||
editorLineWrapping={settings.editorLineWrapping}
|
||||
handleRender={handleRender}
|
||||
handleGetRenderContext={handleGetRenderContext}
|
||||
/>
|
||||
|
||||
<CookiesModal
|
||||
ref={registerModal}
|
||||
workspace={activeWorkspace}
|
||||
@@ -391,6 +401,12 @@ class Wrapper extends PureComponent {
|
||||
<WorkspaceSettingsModal
|
||||
ref={registerModal}
|
||||
workspace={activeWorkspace}
|
||||
editorFontSize={settings.editorFontSize}
|
||||
editorIndentSize={settings.editorIndentSize}
|
||||
editorKeyMap={settings.editorKeyMap}
|
||||
editorLineWrapping={settings.editorLineWrapping}
|
||||
handleRender={handleRender}
|
||||
handleGetRenderContext={handleGetRenderContext}
|
||||
handleRemoveWorkspace={this._handleRemoveActiveWorkspace}/>
|
||||
<WorkspaceShareSettingsModal
|
||||
ref={registerModal}
|
||||
|
||||
@@ -71,19 +71,12 @@
|
||||
padding: @padding-sm;
|
||||
border-radius: @radius-md;
|
||||
background-color: @hl-xxs;
|
||||
&.input--area {
|
||||
background-color: transparent;
|
||||
padding:0;
|
||||
}
|
||||
}
|
||||
|
||||
.input,
|
||||
input,
|
||||
select {
|
||||
height: @line-height-xs;
|
||||
&.input--area {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.input[data-focused="on"],
|
||||
@@ -164,13 +157,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
text-align: center;
|
||||
color: var(--hl);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-font);
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
text-align: center;
|
||||
padding: 0 (@padding-md * 1.5);
|
||||
height: @line-height-md;
|
||||
border: 1px solid transparent;
|
||||
background: var(--color-bg);
|
||||
|
||||
&:not(.btn--plain) {
|
||||
padding: 0 (@padding-md * 1.5);
|
||||
height: @line-height-md;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
&.btn--compact {
|
||||
padding: 0 @padding-md;
|
||||
height: @line-height-sm;
|
||||
|
||||
124
app/ui/css/components/markdown-editor.less
Normal file
124
app/ui/css/components/markdown-editor.less
Normal file
@@ -0,0 +1,124 @@
|
||||
@import '../constants/dimensions';
|
||||
@import '../constants/colors';
|
||||
|
||||
.markdown-editor {
|
||||
border: 1px solid var(--hl-md);
|
||||
border-radius: @radius-md;
|
||||
|
||||
.markdown-editor__edit {
|
||||
padding: @padding-xs @padding-sm;
|
||||
|
||||
// Not sure why this style doesn't work on .CodeMirror...
|
||||
.CodeMirror-scroll {
|
||||
max-height: 30vh;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-editor__preview {
|
||||
max-height: 30vh;
|
||||
padding: @padding-sm;
|
||||
overflow: auto;
|
||||
|
||||
h1 {
|
||||
font-size: @font-size-xxl;
|
||||
}
|
||||
|
||||
& > * {
|
||||
margin: @padding-sm 0 @padding-md 0;
|
||||
padding: 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
p > code {
|
||||
padding: 0 @padding-xs;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: circle;
|
||||
padding-left: @padding-md;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
.hljs-meta,
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: var(--hl);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-subst {
|
||||
color: var(--color-font);
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-literal,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-type,
|
||||
.hljs-class .hljs-title,
|
||||
.hljs-tag .hljs-attr {
|
||||
color: var(--color-surprise);
|
||||
}
|
||||
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-title,
|
||||
.hljs-section,
|
||||
.hljs-selector-id,
|
||||
.hljs-doctag {
|
||||
color: var(--color-danger);
|
||||
}
|
||||
|
||||
.hljs-string {
|
||||
color: var(--color-notice);
|
||||
}
|
||||
|
||||
.hljs-subst {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-attr,
|
||||
.hljs-regexp,
|
||||
.hljs-link,
|
||||
.hljs-attribute {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
background: #fdd;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
background: #dfd;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,6 @@
|
||||
padding-right: @padding-md;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ReactTabs__Tab--selected {
|
||||
@@ -89,6 +88,10 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ReactTabs__Tab > button > a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.ReactTabs__TabPanel {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -115,4 +115,14 @@
|
||||
&.editor--readonly .CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&.editor--dynamic-height {
|
||||
.CodeMirror {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
@import 'components/environment-editor';
|
||||
@import 'components/header-editor';
|
||||
@import 'components/key-value-editor';
|
||||
@import 'components/markdown-editor';
|
||||
@import 'components/method-dropdown';
|
||||
@import 'components/modal';
|
||||
@import 'components/pane';
|
||||
|
||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -3580,6 +3580,11 @@
|
||||
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
|
||||
"integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ="
|
||||
},
|
||||
"highlight.js": {
|
||||
"version": "9.12.0",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz",
|
||||
"integrity": "sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4="
|
||||
},
|
||||
"hkdf": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hkdf/-/hkdf-0.0.2.tgz",
|
||||
|
||||
@@ -114,6 +114,7 @@
|
||||
"codemirror": "5.24.2",
|
||||
"electron-context-menu": "0.9.0",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"highlight.js": "9.12.0",
|
||||
"hkdf": "0.0.2",
|
||||
"html-entities": "1.2.0",
|
||||
"httpsnippet": "1.16.5",
|
||||
@@ -121,7 +122,7 @@
|
||||
"insomnia-importers": "1.3.3",
|
||||
"jsonlint": "1.6.2",
|
||||
"jsonpath": "0.2.11",
|
||||
"marked": "^0.3.6",
|
||||
"marked": "0.3.6",
|
||||
"mime-types": "2.1.14",
|
||||
"mkdirp": "0.5.1",
|
||||
"moment": "2.18.1",
|
||||
|
||||
Reference in New Issue
Block a user