diff --git a/package.json b/package.json index 14e19e69..9da554e9 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ "worker-loader": "^2.0.0" }, "dependencies": { + "@slimsag/react-shortcuts": "^1.2.1", "@sourcegraph/codeintellify": "^3.9.0", "@sourcegraph/extensions-client-common": "^10.0.0", "@sourcegraph/react-loading-spinner": "0.0.6", diff --git a/src/libs/code_intelligence/extensions.tsx b/src/libs/code_intelligence/extensions.tsx index dd63d9f8..b0f03a3e 100644 --- a/src/libs/code_intelligence/extensions.tsx +++ b/src/libs/code_intelligence/extensions.tsx @@ -25,6 +25,7 @@ import * as H from 'history' import { isErrorLike } from '../../shared/backend/errors' import { createExtensionsContextController, createMessageTransports } from '../../shared/backend/extensions' import { GlobalDebug } from '../../shared/components/GlobalDebug' +import { ShortcutProvider } from '../../shared/components/ShortcutProvider' import { sourcegraphUrl } from '../../shared/util/context' import { getGlobalDebugMount } from '../github/extensions' import { MountGetter } from './code_intelligence' @@ -111,11 +112,13 @@ export function initializeExtensions( const { extensionsContextController, extensionsController } = createControllers(documents) render( - , + + + , getCommandPaletteMount() ) diff --git a/src/libs/github/extensions.tsx b/src/libs/github/extensions.tsx index bc5f765b..54dff167 100644 --- a/src/libs/github/extensions.tsx +++ b/src/libs/github/extensions.tsx @@ -8,6 +8,7 @@ import { ContributableMenu } from 'sourcegraph/module/protocol' import * as React from 'react' import { render } from 'react-dom' import { GlobalDebug } from '../../shared/components/GlobalDebug' +import { ShortcutProvider } from '../../shared/components/ShortcutProvider' export function getCommandPaletteMount(): HTMLElement { const headerElem = document.querySelector('div.HeaderMenu>div:last-child') @@ -51,11 +52,13 @@ export function injectExtensionsGlobalComponents({ extensionsContextController: Controller }): void { render( - , + + + , getCommandPaletteMount() ) diff --git a/src/shared/components/GlobalDebug.tsx b/src/shared/components/GlobalDebug.tsx index 0d8f3a87..090606d5 100644 --- a/src/shared/components/GlobalDebug.tsx +++ b/src/shared/components/GlobalDebug.tsx @@ -6,6 +6,7 @@ import * as H from 'history' import MenuDownIcon from 'mdi-react/MenuDownIcon' import * as React from 'react' import { sourcegraphUrl } from '../util/context' +import { ShortcutProvider } from './ShortcutProvider' interface Props { location: H.Location @@ -28,13 +29,17 @@ export const GlobalDebug: React.SFC = props =>
  • - void }>} - caretIcon={MenuDownIcon as React.ComponentType<{ className: string; onClick?: () => void }>} - extensionsController={props.extensionsController} - link={ExtensionLink} - /> + + void }> + } + caretIcon={MenuDownIcon as React.ComponentType<{ className: string; onClick?: () => void }>} + extensionsController={props.extensionsController} + link={ExtensionLink} + /> +
diff --git a/src/shared/components/ShortcutProvider.tsx b/src/shared/components/ShortcutProvider.tsx new file mode 100644 index 00000000..2b2e41b7 --- /dev/null +++ b/src/shared/components/ShortcutProvider.tsx @@ -0,0 +1,42 @@ +import { Context, ContextProvider, ProviderProps, ShortcutManager } from '@slimsag/react-shortcuts' +import * as React from 'react' + +/** + * Describes the variable this file injects into the `global` object. It is + * heavily prefixed to avoid collisions. + */ +interface GlobalContext { + /** The singleton ShortcutManager object. */ + browserExtensionShortcutManager?: ShortcutManager +} + +// This ShortcutProvider is derived from the default @shopify/react-shortcuts +// implementation: +// +// https://github.com/Shopify/quilt/blob/master/packages/react-shortcuts/src/ShortcutProvider/ShortcutProvider.tsx +// +// We cannot use the default implementation above because it assumes the +// application is rendered via a single React component in order to create the +// ShortcutManager singleton. In our case, there are multiple React components +// on the page and they each need to use a single ShortcutManager, so we must +// manage it ourselves here. If we did not do this, we would have multiple +// ShortcutManagers and each would register their own conflicting document +// event handlers. +export class ShortcutProvider extends React.Component { + public componentDidMount(): void { + const globals = global as GlobalContext + if (!globals.browserExtensionShortcutManager) { + globals.browserExtensionShortcutManager = new ShortcutManager() + globals.browserExtensionShortcutManager.setup() + } + } + + public render(): JSX.Element | null { + const globals = global as GlobalContext + const context: Context = { + shortcutManager: globals.browserExtensionShortcutManager, + } + + return {this.props.children} + } +} diff --git a/yarn.lock b/yarn.lock index f6e16363..13382de7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1306,6 +1306,13 @@ dependencies: prop-types "^15.6.2" +"@slimsag/react-shortcuts@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@slimsag/react-shortcuts/-/react-shortcuts-1.2.1.tgz#a3ea054a057137de8636bc9fabac61369103e597" + integrity sha512-dUTQjBX1yjnHbqkTL5ditqtx5R6QhcXI3lCnjOy8e5XUOr5LOtVpol1mufU2lAb7DtEYrSVRQpUeNmZrTEgLcg== + dependencies: + prop-types "^15.6.2" + "@sourcegraph/codeintellify@^3.9.0": version "3.9.0" resolved "https://registry.yarnpkg.com/@sourcegraph/codeintellify/-/codeintellify-3.9.0.tgz#1e3f002558cc367e8c5ee9b0a54e0c47c6ee8761"