From 873a59cdbc51fc40b02fbfffb1c9b12f892bdd66 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 7 Feb 2017 16:31:48 -0800 Subject: [PATCH] Clickable codemirror links (#81) * Multiple recursive rendering * Click links in respnose view --- app/ui/components/RequestUrlBar.js | 10 +++- app/ui/components/Toast.js | 25 ++++----- app/ui/components/base/Editor.js | 55 ++++++++++++++++--- .../dropdowns/RequestActionsDropdown.js | 8 ++- .../dropdowns/RequestGroupActionsDropdown.js | 8 ++- app/ui/components/modals/SettingsModal.js | 9 ++- .../sidebar/SidebarRequestGroupRow.js | 10 +++- .../components/sidebar/SidebarRequestRow.js | 13 ++++- app/ui/components/viewers/ResponseViewer.js | 6 ++ app/ui/css/components/editor.less | 9 +++ package.json | 2 +- 11 files changed, 126 insertions(+), 29 deletions(-) diff --git a/app/ui/components/RequestUrlBar.js b/app/ui/components/RequestUrlBar.js index 60ff61a9ed..cf43a6f2d4 100644 --- a/app/ui/components/RequestUrlBar.js +++ b/app/ui/components/RequestUrlBar.js @@ -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 = ( - + {downloadPath ? "Download" : "Send"} diff --git a/app/ui/components/Toast.js b/app/ui/components/Toast.js index 7b70f0b4b2..ccc2c0bf6f 100644 --- a/app/ui/components/Toast.js +++ b/app/ui/components/Toast.js @@ -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); } diff --git a/app/ui/components/base/Editor.js b/app/ui/components/base/Editor.js index 9f9ff6f65d..8fb6c72f59 100644 --- a/app/ui/components/base/Editor.js +++ b/app/ui/components/base/Editor.js @@ -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 (
-
+