From 8cb1cfc5fb294efc11ebdedd9ab5431afbe69342 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Wed, 11 Dec 2019 21:30:13 +0100 Subject: [PATCH 1/3] Fix load remote state --- .../editor/legacy/console_editor/editor.tsx | 56 ++++++++++++++++++- .../application/hooks/use_set_input_editor.ts | 13 +++-- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx index 442ed330e9b7a4..5c9b7e5a68dc9a 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx @@ -20,6 +20,11 @@ import React, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react'; import { EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { debounce } from 'lodash'; + +// Node v5 querystring for browser. +// @ts-ignore +import * as qs from 'querystring-browser'; import { EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useServicesContext, useEditorReadContext } from '../../../../contexts'; @@ -81,10 +86,54 @@ function EditorUI() { useEffect(() => { editorInstanceRef.current = senseEditor.create(editorRef.current!); - const { content: text } = history.getSavedEditorState() || { - content: DEFAULT_INPUT_VALUE, + const getQueryParams = () => { + const [, queryString] = window.location.hash.split('?'); + return qs.parse(queryString || ''); }; - editorInstanceRef.current.update(text); + + const onHashChange = debounce(() => { + const qParams = getQueryParams(); + if (!qParams || !qParams.load_from) { + return; + } + const sourceLocation: string = qParams.load_from; + if (/^https?:\/\//.test(sourceLocation)) { + const loadFrom: Record = { + url: sourceLocation, + // Having dataType here is required as it doesn't allow jQuery to `eval` content + // coming from the external source thereby preventing XSS attack. + dataType: 'text', + kbnXsrfToken: false, + }; + + if (/https?:\/\/api\.github\.com/.test(sourceLocation)) { + loadFrom.headers = { Accept: 'application/vnd.github.v3.raw' }; + } + + $.ajax(loadFrom).done(async data => { + await editorInstanceRef.current!.update(data); + editorInstanceRef.current!.moveToNextRequestEdge(false); + editorInstanceRef.current!.highlightCurrentRequestsAndUpdateActionBar(); + editorInstanceRef + .current!.getCoreEditor() + .getContainer() + .focus(); + }); + } + }, 200); + + window.addEventListener('hashchange', onHashChange); + + const firstParamsCheck = getQueryParams(); + if (firstParamsCheck && firstParamsCheck.load_from) { + // Process initial loading + onHashChange(); + } else { + const { content: text } = history.getSavedEditorState() || { + content: DEFAULT_INPUT_VALUE, + }; + editorInstanceRef.current.update(text); + } function setupAutosave() { let timer: number; @@ -121,6 +170,7 @@ function EditorUI() { return () => { unsubscribeResizer(); mappings.clearSubscriptions(); + window.removeEventListener('hashchange', onHashChange); }; }, [history, setInputEditor]); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_set_input_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_set_input_editor.ts index 672f3e269ead9a..fbd53762c27e6b 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_set_input_editor.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_set_input_editor.ts @@ -16,15 +16,18 @@ * specific language governing permissions and limitations * under the License. */ - +import { useCallback } from 'react'; import { useEditorActionContext } from '../contexts/editor_context'; import { instance as registry } from '../contexts/editor_context/editor_registry'; export const useSetInputEditor = () => { const dispatch = useEditorActionContext(); - return (editor: any) => { - dispatch({ type: 'setInputEditor', payload: editor }); - registry.setInputEditor(editor); - }; + return useCallback( + (editor: any) => { + dispatch({ type: 'setInputEditor', payload: editor }); + registry.setInputEditor(editor); + }, + [dispatch] + ); }; From c23b3e89e691fe0caf808c2ac88cfedc60789b86 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 12 Dec 2019 10:45:55 +0100 Subject: [PATCH 2/3] Clean up variable usage, add comment, move forceRetokenize to private method --- .../editor/legacy/console_editor/editor.tsx | 50 +++++++++---------- .../legacy_core_editor/legacy_core_editor.ts | 35 ++++++------- 2 files changed, 41 insertions(+), 44 deletions(-) diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx index 5c9b7e5a68dc9a..ca0e206b4627f7 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx @@ -85,18 +85,19 @@ function EditorUI() { useEffect(() => { editorInstanceRef.current = senseEditor.create(editorRef.current!); + const editor = editorInstanceRef.current; - const getQueryParams = () => { - const [, queryString] = window.location.hash.split('?'); + const readQueryParams = () => { + const [, queryString] = (window.location.hash || '').split('?'); return qs.parse(queryString || ''); }; + // Support for loading a console snippet from a remote source, like support docs. const onHashChange = debounce(() => { - const qParams = getQueryParams(); - if (!qParams || !qParams.load_from) { + const { load_from: sourceLocation } = readQueryParams(); + if (!sourceLocation) { return; } - const sourceLocation: string = qParams.load_from; if (/^https?:\/\//.test(sourceLocation)) { const loadFrom: Record = { url: sourceLocation, @@ -111,35 +112,32 @@ function EditorUI() { } $.ajax(loadFrom).done(async data => { - await editorInstanceRef.current!.update(data); - editorInstanceRef.current!.moveToNextRequestEdge(false); - editorInstanceRef.current!.highlightCurrentRequestsAndUpdateActionBar(); - editorInstanceRef - .current!.getCoreEditor() - .getContainer() - .focus(); + const coreEditor = editor.getCoreEditor(); + await editor.update(data, true); + editor.moveToNextRequestEdge(false); + coreEditor.clearSelection(); + editor.highlightCurrentRequestsAndUpdateActionBar(); + coreEditor.getContainer().focus(); }); } }, 200); - window.addEventListener('hashchange', onHashChange); - const firstParamsCheck = getQueryParams(); - if (firstParamsCheck && firstParamsCheck.load_from) { - // Process initial loading + if (readQueryParams().load_from) { + // Do an initial check against the current hash value. onHashChange(); } else { const { content: text } = history.getSavedEditorState() || { content: DEFAULT_INPUT_VALUE, }; - editorInstanceRef.current.update(text); + editor.update(text); } function setupAutosave() { let timer: number; const saveDelay = 500; - editorInstanceRef.current!.getCoreEditor().on('change', () => { + editor.getCoreEditor().on('change', () => { if (timer) { clearTimeout(timer); } @@ -149,22 +147,19 @@ function EditorUI() { function saveCurrentState() { try { - const content = editorInstanceRef.current!.getCoreEditor().getValue(); + const content = editor.getCoreEditor().getValue(); history.updateCurrentState(content); } catch (e) { // Ignoring saving error } } - setInputEditor(editorInstanceRef.current); + setInputEditor(editor); setTextArea(editorRef.current!.querySelector('textarea')); mappings.retrieveAutoCompleteInfo(); - const unsubscribeResizer = subscribeResizeChecker( - editorRef.current!, - editorInstanceRef.current.getCoreEditor() - ); + const unsubscribeResizer = subscribeResizeChecker(editorRef.current!, editor.getCoreEditor()); setupAutosave(); return () => { @@ -175,10 +170,11 @@ function EditorUI() { }, [history, setInputEditor]); useEffect(() => { - applyCurrentSettings(editorInstanceRef.current!.getCoreEditor(), settings); + const { current: editor } = editorInstanceRef; + applyCurrentSettings(editor!.getCoreEditor(), settings); // Preserve legacy focus behavior after settings have updated. - editorInstanceRef - .current!.getCoreEditor() + editor! + .getCoreEditor() .getContainer() .focus(); }, [settings]); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts index 621f4eeb0163e3..608c73335b3e53 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts @@ -104,25 +104,12 @@ export class LegacyCoreEditor implements CoreEditor { return this.editor.getValue(); } - setValue(text: string, forceRetokenize: boolean): Promise { + async setValue(text: string, forceRetokenize: boolean): Promise { const session = this.editor.getSession(); session.setValue(text); - return new Promise(resolve => { - if (!forceRetokenize) { - // resolve immediately - resolve(); - return; - } - - // force update of tokens, but not on this thread to allow for ace rendering. - setTimeout(function() { - let i; - for (i = 0; i < session.getLength(); i++) { - session.getTokens(i); - } - resolve(); - }); - }); + if (forceRetokenize) { + await this.forceRetokenize(); + } } getLineValue(lineNumber: number): string { @@ -241,6 +228,20 @@ export class LegacyCoreEditor implements CoreEditor { return Boolean((this.editor as any).completer && (this.editor as any).completer.activated); } + private forceRetokenize() { + const session = this.editor.getSession(); + return new Promise(resolve => { + // force update of tokens, but not on this thread to allow for ace rendering. + setTimeout(function() { + let i; + for (i = 0; i < session.getLength(); i++) { + session.getTokens(i); + } + resolve(); + }); + }); + } + // eslint-disable-next-line @typescript-eslint/camelcase private DO_NOT_USE_onPaste(text: string) { if (text && curl.detectCURL(text)) { From fddaad4914164e9fe938d913e947ffaf9ee8925d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Thu, 12 Dec 2019 11:18:00 +0100 Subject: [PATCH 3/3] Optimize sequence of checking hash on initial load --- .../editor/legacy/console_editor/editor.tsx | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx index ca0e206b4627f7..a52c15c20c902e 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx @@ -92,25 +92,21 @@ function EditorUI() { return qs.parse(queryString || ''); }; - // Support for loading a console snippet from a remote source, like support docs. - const onHashChange = debounce(() => { - const { load_from: sourceLocation } = readQueryParams(); - if (!sourceLocation) { - return; - } - if (/^https?:\/\//.test(sourceLocation)) { + const loadBufferFromRemote = (url: string) => { + if (/^https?:\/\//.test(url)) { const loadFrom: Record = { - url: sourceLocation, + url, // Having dataType here is required as it doesn't allow jQuery to `eval` content // coming from the external source thereby preventing XSS attack. dataType: 'text', kbnXsrfToken: false, }; - if (/https?:\/\/api\.github\.com/.test(sourceLocation)) { + if (/https?:\/\/api\.github\.com/.test(url)) { loadFrom.headers = { Accept: 'application/vnd.github.v3.raw' }; } + // Fire and forget. $.ajax(loadFrom).done(async data => { const coreEditor = editor.getCoreEditor(); await editor.update(data, true); @@ -120,12 +116,21 @@ function EditorUI() { coreEditor.getContainer().focus(); }); } + }; + + // Support for loading a console snippet from a remote source, like support docs. + const onHashChange = debounce(() => { + const { load_from: url } = readQueryParams(); + if (!url) { + return; + } + loadBufferFromRemote(url); }, 200); window.addEventListener('hashchange', onHashChange); - if (readQueryParams().load_from) { - // Do an initial check against the current hash value. - onHashChange(); + const initialQueryParams = readQueryParams(); + if (initialQueryParams.load_from) { + loadBufferFromRemote(initialQueryParams.load_from); } else { const { content: text } = history.getSavedEditorState() || { content: DEFAULT_INPUT_VALUE,