diff --git a/app/common/render.js b/app/common/render.js index f0ea601aa..b0823fcc8 100644 --- a/app/common/render.js +++ b/app/common/render.js @@ -122,20 +122,29 @@ export function recursiveRender (obj, context) { return newObj; } -export async function getRenderedRequest (request, environmentId) { - const ancestors = await db.withAncestors(request); - const workspace = ancestors.find(doc => doc.type === models.workspace.type); +export async function getRenderContext (request, environmentId, ancestors = null) { + if (!ancestors) { + ancestors = await db.withAncestors(request); + } + const workspace = ancestors.find(doc => doc.type === models.workspace.type); const rootEnvironment = await models.environment.getOrCreateForWorkspace(workspace); const subEnvironment = await models.environment.getById(environmentId); - const cookieJar = await models.cookieJar.getOrCreateForWorkspace(workspace); // Generate the context we need to render - const renderContext = buildRenderContext( + return buildRenderContext( ancestors, rootEnvironment, subEnvironment ); +} + +export async function getRenderedRequest (request, environmentId) { + const ancestors = await db.withAncestors(request); + const workspace = ancestors.find(doc => doc.type === models.workspace.type); + const cookieJar = await models.cookieJar.getOrCreateForWorkspace(workspace); + + const renderContext = await getRenderContext(request, environmentId, ancestors); // Render all request properties const renderedRequest = recursiveRender(request, renderContext); diff --git a/app/package.json b/app/package.json index d2325816b..aa6d00f65 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "insomnia", - "version": "4.2.12", + "version": "4.2.13", "productName": "Insomnia", "longName": "Insomnia REST Client", "description": "A simple and beautiful REST API client", diff --git a/app/ui/components/RequestPane.js b/app/ui/components/RequestPane.js index 225238916..d9cf6be04 100644 --- a/app/ui/components/RequestPane.js +++ b/app/ui/components/RequestPane.js @@ -72,6 +72,7 @@ class RequestPane extends PureComponent { editorLineWrapping, handleSend, handleSendAndDownload, + handleRender, forceRefreshCounter, useBulkHeaderEditor, handleGenerateCode, @@ -190,6 +191,7 @@ class RequestPane extends PureComponent { { + CodeMirror.defineMode('master', (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 highlightLinks = !!this.props.onClickLink; + const highlightNunjucks = !this.props.readOnly; + + const regexUrl = /^(https?:\/\/)?([\da-z.\-]+)\.([a-z.]{2,6})([\/\w .\-]*)*\/?/; + const regexVariable = /^{{[ |a-zA-Z0-9_\-+,'"\\()\[\]]+}}/; + const regexTag = /^{%[ |a-zA-Z0-9_\-+,'"\\()\[\]]+%}/; + const regexComment = /^{#[^#]+#}/; const overlay = { token: function (stream, state) { - // console.log('state', state); - if (stream.match(/^(https?:\/\/)?([\da-z.\-]+)\.([a-z.]{2,6})([\/\w .\-]*)*\/?/, true)) { + if (highlightLinks && stream.match(regexUrl, true)) { return 'clickable'; } - while (stream.next() != null && !stream.match("http", false)) { - // Do nothing + if (highlightNunjucks && stream.match(regexVariable, true)) { + return 'variable-3'; + } + + if (highlightNunjucks && stream.match(regexTag, true)) { + return 'variable-3'; + } + + if (highlightNunjucks && stream.match(regexComment, true)) { + return 'comment'; + } + + while (stream.next() != null) { + if (stream.match(regexUrl, false)) break; + if (stream.match(regexVariable, false)) break; + if (stream.match(regexTag, false)) break; + if (stream.match(regexComment, false)) break; } return null; @@ -271,7 +289,7 @@ class Editor extends Component { readOnly, placeholder: this.props.placeholder || '', mode: { - name: 'clickable', + name: 'master', baseMode: normalizedMode, }, lineWrapping: this.props.lineWrapping, @@ -424,16 +442,12 @@ class Editor extends Component { } render () { - const {readOnly, fontSize, lightTheme, mode, filter} = this.props; + const {readOnly, fontSize, mode, filter} = this.props; const classes = classnames( 'editor', this.props.className, - { - 'editor--readonly': readOnly, - 'editor--light-theme': !!lightTheme, - 'editor--dark-theme': !lightTheme - } + {'editor--readonly': readOnly} ); const toolbarChildren = []; @@ -500,6 +514,7 @@ Editor.propTypes = { onChange: PropTypes.func, onFocusChange: PropTypes.func, onClickLink: PropTypes.func, + render: PropTypes.func, keyMap: PropTypes.string, mode: PropTypes.string, placeholder: PropTypes.string, @@ -509,8 +524,8 @@ Editor.propTypes = { autoPrettify: PropTypes.bool, manualPrettify: PropTypes.bool, className: PropTypes.any, - lightTheme: PropTypes.bool, updateFilter: PropTypes.func, + readOnly: PropTypes.bool, filter: PropTypes.string }; diff --git a/app/ui/components/editors/body/BodyEditor.js b/app/ui/components/editors/body/BodyEditor.js index 363525229..79538dcac 100644 --- a/app/ui/components/editors/body/BodyEditor.js +++ b/app/ui/components/editors/body/BodyEditor.js @@ -7,7 +7,7 @@ import {getContentTypeFromHeaders, CONTENT_TYPE_FORM_URLENCODED, CONTENT_TYPE_FO import {newBodyRaw, newBodyFormUrlEncoded, newBodyForm, newBodyFile} from '../../../../models/request'; class BodyEditor extends PureComponent { - _handleRawChange = (rawValue) => { + _handleRawChange = rawValue => { const {onChange, request} = this.props; const contentType = getContentTypeFromHeaders(request.headers); @@ -16,26 +16,26 @@ class BodyEditor extends PureComponent { onChange(newBody); }; - _handleFormUrlEncodedChange = (parameters) => { + _handleFormUrlEncodedChange = parameters => { const {onChange} = this.props; const newBody = newBodyFormUrlEncoded(parameters); onChange(newBody); }; - _handleFormChange = (parameters) => { + _handleFormChange = parameters => { const {onChange} = this.props; const newBody = newBodyForm(parameters); onChange(newBody); }; - _handleFileChange = (path) => { + _handleFileChange = path => { const {onChange} = this.props; const newBody = newBodyFile(path); onChange(newBody); }; render () { - const {keyMap, fontSize, lineWrapping, request} = this.props; + const {keyMap, fontSize, lineWrapping, request, handleRender} = this.props; const fileName = request.body.fileName; const mimeType = request.body.mimeType; const isBodyEmpty = typeof mimeType !== 'string' && !request.body.text; @@ -74,6 +74,7 @@ class BodyEditor extends PureComponent { lineWrapping={lineWrapping} contentType={contentType || 'text/plain'} content={request.body.text || ''} + render={handleRender} onChange={this._handleRawChange} /> ) @@ -81,7 +82,7 @@ class BodyEditor extends PureComponent { return (

- +

Select a body type from above

@@ -95,6 +96,7 @@ BodyEditor.propTypes = { // Required onChange: PropTypes.func.isRequired, handleUpdateRequestMimeType: PropTypes.func.isRequired, + handleRender: PropTypes.func.isRequired, request: PropTypes.object.isRequired, // Optional diff --git a/app/ui/components/editors/body/RawEditor.js b/app/ui/components/editors/body/RawEditor.js index b063f8b9c..5d2a87ed3 100644 --- a/app/ui/components/editors/body/RawEditor.js +++ b/app/ui/components/editors/body/RawEditor.js @@ -8,6 +8,7 @@ class RawEditor extends Component { content, fontSize, keyMap, + render, lineWrapping, onChange, className @@ -20,6 +21,7 @@ class RawEditor extends Component { keyMap={keyMap} value={content} className={className} + render={render} onChange={onChange} mode={contentType} lineWrapping={lineWrapping} @@ -34,11 +36,12 @@ RawEditor.propTypes = { onChange: PropTypes.func.isRequired, content: PropTypes.string.isRequired, contentType: PropTypes.string.isRequired, + fontSize: PropTypes.number.isRequired, + keyMap: PropTypes.string.isRequired, + lineWrapping: PropTypes.bool.isRequired, // Optional - fontSize: PropTypes.number, - keyMap: PropTypes.string, - lineWrapping: PropTypes.bool + render: PropTypes.func, }; export default RawEditor; diff --git a/app/ui/components/modals/SettingsModal.js b/app/ui/components/modals/SettingsModal.js index 5cfb1858a..5f22d18f9 100644 --- a/app/ui/components/modals/SettingsModal.js +++ b/app/ui/components/modals/SettingsModal.js @@ -2,6 +2,7 @@ import React, {Component, PropTypes} from 'react'; import {Tab, Tabs, TabList, TabPanel} from 'react-tabs'; import {shell} from 'electron'; import Modal from '../base/Modal'; +import Button from '../base/Button'; import ModalBody from '../base/ModalBody'; import ModalHeader from '../base/ModalHeader'; import SettingsShortcuts from '../settings/SettingsShortcuts'; @@ -23,6 +24,29 @@ class SettingsModal extends Component { this.state = {} } + _setModalRef = m => this.modal = m; + _trackTab = name => trackEvent('Setting', `Tab ${name}`); + _handleTabSelect = currentTabIndex => this.setState({currentTabIndex}); + _handleUpdateSetting = (key, value) => { + models.settings.update(this.props.settings, {[key]: value}); + trackEvent('Setting', 'Change', key) + }; + + _handleExportAllToFile = () => { + this.props.handleExportAllToFile(); + this.modal.hide() + }; + + _handleExportWorkspace = () => { + this.props.handleExportWorkspaceToFile(); + this.modal.hide() + }; + + _handleImport = () => { + this.props.handleImportFile(); + this.modal.hide() + }; + _handleChangeTheme = (theme, track = true) => { document.body.setAttribute('theme', theme); models.settings.update(this.props.settings, {theme}); @@ -52,83 +76,61 @@ class SettingsModal extends Component { this.modal.toggle(); } - _handleTabSelect (currentTabIndex) { - this.setState({currentTabIndex}); - } - render () { - const { - settings, - handleExportAllToFile, - handleExportWorkspaceToFile, - handleImportFile, - } = this.props; - + const {settings} = this.props; const {currentTabIndex} = this.state; const email = session.isLoggedIn() ? session.getEmail() : null; return ( - this.modal = m} tall={true} {...this.props}> + {getAppName()} Preferences   –  - v{getAppVersion()} - {email ? ` – ${email}` : null} + v{getAppVersion()} + {email ? ` – ${email}` : null} - this._handleTabSelect(i)} selectedIndex={currentTabIndex}> + - + - + - + - + - - + { - models.settings.update(settings, {[key]: value}); - trackEvent('Setting', 'Change', key) - }} + updateSetting={this._handleUpdateSetting} /> { - handleExportAllToFile(); - this.modal.hide() - }} - handleExportWorkspace={() => { - handleExportWorkspaceToFile(); - this.modal.hide() - }} - handleImport={() => { - handleImportFile(); - this.modal.hide() - }} + handleExportAll={this._handleExportAllToFile} + handleExportWorkspace={this._handleExportWorkspace} + handleImport={this._handleImport} /> diff --git a/app/ui/components/settings/SettingsGeneral.js b/app/ui/components/settings/SettingsGeneral.js index 38f01ba83..0cbe22eb0 100644 --- a/app/ui/components/settings/SettingsGeneral.js +++ b/app/ui/components/settings/SettingsGeneral.js @@ -1,109 +1,135 @@ -import React, {PropTypes} from 'react'; +import React, {Component, PropTypes} from 'react'; -const SettingsGeneral = ({settings, updateSetting}) => ( -
-
- -
+class SettingsGeneral extends Component { + _handleUpdateSetting = e => { + let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; -
- -
+ if (e.target.type === 'number') { + value = parseInt(value, 10); + } -
- -
+ this.props.updateSetting(e.target.name, value); + }; -
- -
+ render () { + const {settings} = this.props; + return ( +
+
+ +
-
- -
+
+ +
-
- -
+
+ +
-
-
- +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+
- -
- -
-
- -
- -
-
-
- -
-
- -
-
-
-
-); + ) + } +} SettingsGeneral.propTypes = { settings: PropTypes.object.isRequired, diff --git a/app/ui/containers/App.js b/app/ui/containers/App.js index c9eb54eba..c7638f636 100644 --- a/app/ui/containers/App.js +++ b/app/ui/containers/App.js @@ -28,6 +28,7 @@ import * as network from '../../common/network'; import {debounce} from '../../common/misc'; import * as mime from 'mime-types'; import * as path from 'path'; +import * as render from '../../common/render'; const KEY_ENTER = 13; const KEY_COMMA = 188; @@ -143,7 +144,7 @@ class App extends Component { models.requestGroup.create({parentId, name}) }; - _requestCreate = async (parentId) => { + _requestCreate = async parentId => { const request = await showModal(RequestCreateModal, {parentId}); this._handleSetActiveRequest(request._id) }; @@ -158,7 +159,14 @@ class App extends Component { } const newRequest = await models.request.duplicate(request); - this._handleSetActiveRequest(newRequest._id) + await this._handleSetActiveRequest(newRequest._id) + }; + + _handleRenderText = async text => { + const {activeEnvironment, activeRequest} = this.props; + const environmentId = activeEnvironment ? activeEnvironment._id : null; + const context = await render.getRenderContext(activeRequest, environmentId); + return render.render(text, context); }; _handleGenerateCodeForActiveRequest = () => { @@ -206,12 +214,13 @@ class App extends Component { this._savePaneWidth(paneWidth); }; - _handleSetActiveRequest = activeRequestId => { - this._updateActiveWorkspaceMeta({activeRequestId}); + _handleSetActiveRequest = async activeRequestId => { + await this._updateActiveWorkspaceMeta({activeRequestId}); }; - _handleSetActiveEnvironment = activeEnvironmentId => { - this._updateActiveWorkspaceMeta({activeEnvironmentId}); + _handleSetActiveEnvironment = async activeEnvironmentId => { + await this._updateActiveWorkspaceMeta({activeEnvironmentId}); + this._wrapper.forceRequestPaneRefresh(); }; _saveSidebarWidth = debounce(sidebarWidth => this._updateActiveWorkspaceMeta({sidebarWidth})); @@ -220,12 +229,12 @@ class App extends Component { this._saveSidebarWidth(sidebarWidth); }; - _handleSetSidebarHidden = sidebarHidden => { - this._updateActiveWorkspaceMeta({sidebarHidden}); + _handleSetSidebarHidden = async sidebarHidden => { + await this._updateActiveWorkspaceMeta({sidebarHidden}); }; - _handleSetSidebarFilter = sidebarFilter => { - this._updateActiveWorkspaceMeta({sidebarFilter}); + _handleSetSidebarFilter = async sidebarFilter => { + await this._updateActiveWorkspaceMeta({sidebarFilter}); }; _handleSetRequestGroupCollapsed = (requestGroupId, collapsed) => { @@ -431,7 +440,7 @@ class App extends Component { trackEvent('General', 'Launched', getAppVersion(), {nonInteraction: true}); } - db.onChange(changes => { + db.onChange(async changes => { for (const change of changes) { const [event, doc, fromSync] = change; const {activeRequest} = this.props; @@ -499,6 +508,7 @@ class App extends Component { handleStartDragPane={this._startDragPane} handleResetDragPane={this._resetDragPane} handleCreateRequest={this._requestCreate} + handleRender={this._handleRenderText} handleDuplicateRequest={this._requestDuplicate} handleDuplicateRequestGroup={this._requestGroupDuplicate} handleCreateRequestGroup={this._requestGroupCreate} diff --git a/app/ui/css/components/editor.less b/app/ui/css/components/editor.less index 81c8d5cfa..5726bd00d 100644 --- a/app/ui/css/components/editor.less +++ b/app/ui/css/components/editor.less @@ -176,7 +176,6 @@ box-sizing: border-box; } - span.cm-comment, span.cm-meta, span.cm-qualifier { color: var(--color-font); @@ -215,7 +214,7 @@ } span.cm-variable-3 { - color: var(--color-info); + color: var(--color-surprise); } span.cm-def { @@ -238,6 +237,10 @@ color: var(--color-surprise); } + span.cm-comment { + color: var(--hl); + } + span.cm-error { background: var(--color-danger); color: var(--color-font-danger); diff --git a/app/ui/css/constants/colors.less b/app/ui/css/constants/colors.less index 5780db581..10a657a81 100644 --- a/app/ui/css/constants/colors.less +++ b/app/ui/css/constants/colors.less @@ -45,7 +45,7 @@ body { --color-notice: #ead950; --color-warning: #ff9a1f; --color-danger: #ff5d4b; - --color-surprise: #a590ff; + --color-surprise: #a896ff; --color-info: #22c2f0; .tag {