-
Notifications
You must be signed in to change notification settings - Fork 15
feat: accept settings updates from Sourcegraph pages #146
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,44 @@ | ||
| import { connectAsClient } from '@sourcegraph/extensions-client-common/lib/messaging' | ||
| import storage from '../../browser/storage' | ||
| import { updateExtensionSettings } from '../../shared/backend/extensions' | ||
|
|
||
| export function injectSourcegraphApp(marker: HTMLElement): void { | ||
| if (document.getElementById(marker.id)) { | ||
| return | ||
| } | ||
|
|
||
| // Generate and insert DOM element, in case this code executes first. | ||
| document.body.appendChild(marker) | ||
|
|
||
| connectAsClient() | ||
| .then(connection => { | ||
| storage.observeSync('clientSettings').subscribe(settings => { | ||
| connection.sendSettings(settings) | ||
| }) | ||
|
|
||
| connection.onEditSetting(edit => updateExtensionSettings('Client', edit).toPromise()) | ||
|
|
||
| connection.onGetSettings( | ||
| () => | ||
| new Promise<string>(resolve => { | ||
| storage.getSync(storageItems => { | ||
| resolve(storageItems.clientSettings) | ||
| }) | ||
| }) | ||
| ) | ||
| }) | ||
| .catch(error => console.error(error)) | ||
|
|
||
| window.addEventListener('load', () => { | ||
| dispatchSourcegraphEvents(marker) | ||
| dispatchSourcegraphEvents() | ||
| }) | ||
|
|
||
| if (document.readyState === 'complete' || document.readyState === 'interactive') { | ||
| dispatchSourcegraphEvents(marker) | ||
| dispatchSourcegraphEvents() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't this fire the event twice portentially?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like it to me, yeah |
||
| } | ||
| } | ||
|
|
||
| function dispatchSourcegraphEvents(marker: HTMLElement): void { | ||
| // Generate and insert DOM element, in case this code executes first. | ||
| document.body.appendChild(marker) | ||
| function dispatchSourcegraphEvents(): void { | ||
| // Send custom webapp <-> extension registration event in case webapp listener is attached first. | ||
| document.dispatchEvent(new CustomEvent<{}>('sourcegraph:browser-extension-registration')) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import { UpdateExtensionSettingsArgs } from '@sourcegraph/extensions-client-common/lib/context' | ||
| import { Controller as ExtensionsContextController } from '@sourcegraph/extensions-client-common/lib/controller' | ||
| import { ConfiguredExtension } from '@sourcegraph/extensions-client-common/lib/extensions/extension' | ||
| import { gql, graphQLContent } from '@sourcegraph/extensions-client-common/lib/graphql' | ||
|
|
@@ -17,15 +18,14 @@ import { isEqual } from 'lodash' | |
| import Alert from 'mdi-react/AlertIcon' | ||
| import MenuDown from 'mdi-react/MenuDownIcon' | ||
| import Menu from 'mdi-react/MenuIcon' | ||
| import { combineLatest, Observable, Subject, throwError } from 'rxjs' | ||
| import { combineLatest, from, Observable, throwError } from 'rxjs' | ||
| import { distinctUntilChanged, map, mergeMap, switchMap, take } from 'rxjs/operators' | ||
| import { ClientOptions } from 'sourcegraph/module/client/client' | ||
| import { MessageTransports } from 'sourcegraph/module/jsonrpc2/connection' | ||
| import { TextDocumentDecoration } from 'sourcegraph/module/protocol' | ||
| import { ConfigurationUpdateParams } from 'sourcegraph/module/protocol' | ||
| import uuid from 'uuid' | ||
| import { Disposable } from 'vscode-languageserver' | ||
| import storage from '../../browser/storage' | ||
| import storage, { StorageItems } from '../../browser/storage' | ||
| import { ExtensionConnectionInfo } from '../../extension/scripts/background' | ||
| import { onFirstMessage } from '../../extension/scripts/background' | ||
| import { getContext } from './context' | ||
|
|
@@ -255,45 +255,7 @@ export function createExtensionsContextController( | |
| map(([gqlCascade, storageCascade]) => mergeCascades(gqlToCascade(gqlCascade), storageCascade)), | ||
| distinctUntilChanged((a, b) => isEqual(a, b)) | ||
| ), | ||
| updateExtensionSettings: ( | ||
| subjectID, | ||
| args: { extensionID: string; edit?: ConfigurationUpdateParams; enabled?: boolean; remove?: boolean } | ||
| ) => { | ||
| if (subjectID !== 'Client') { | ||
| return throwError('Cannot update settings for ' + subjectID + '.') | ||
| } | ||
| const update = new Subject<undefined>() | ||
| storage.getSync(storageItems => { | ||
| let clientSettings = storageItems.clientSettings | ||
|
|
||
| const format = { tabSize: 2, insertSpaces: true, eol: '\n' } | ||
|
|
||
| if (args.edit) { | ||
| clientSettings = applyEdits( | ||
| clientSettings, | ||
| // TODO(chris): remove `.slice()` (which guards against | ||
| // mutation) once | ||
| // https://github.com/Microsoft/node-jsonc-parser/pull/12 | ||
| // is merged in. | ||
| setProperty(clientSettings, args.edit.path.slice(), args.edit.value, format) | ||
| ) | ||
| } else if (typeof args.enabled === 'boolean') { | ||
| clientSettings = applyEdits( | ||
| clientSettings, | ||
| setProperty(clientSettings, ['extensions', args.extensionID], args.enabled, format) | ||
| ) | ||
| } else if (args.remove) { | ||
| clientSettings = applyEdits( | ||
| clientSettings, | ||
| removeProperty(clientSettings, ['extensions', args.extensionID], format) | ||
| ) | ||
| } | ||
| storage.setSync({ clientSettings }, () => { | ||
| update.next(undefined) | ||
| }) | ||
| }) | ||
| return update | ||
| }, | ||
| updateExtensionSettings, | ||
| queryGraphQL: (request, variables) => | ||
| storage.observeSync('sourcegraphURL').pipe( | ||
| take(1), | ||
|
|
@@ -315,3 +277,41 @@ export function createExtensionsContextController( | |
| }, | ||
| }) | ||
| } | ||
|
|
||
| export const updateExtensionSettings = (subjectID, args: UpdateExtensionSettingsArgs): Observable<undefined> => { | ||
| if (subjectID !== 'Client') { | ||
| return throwError('Cannot update settings for ' + subjectID + '.') | ||
| } | ||
| return from( | ||
| new Promise<StorageItems>(resolve => storage.getSync(storageItems => resolve(storageItems))).then( | ||
| storageItems => { | ||
| let clientSettings = storageItems.clientSettings | ||
|
|
||
| const format = { tabSize: 2, insertSpaces: true, eol: '\n' } | ||
|
|
||
| if ('edit' in args && args.edit) { | ||
| clientSettings = applyEdits( | ||
| clientSettings, | ||
| // TODO(chris): remove `.slice()` (which guards against | ||
| // mutation) once | ||
| // https://github.com/Microsoft/node-jsonc-parser/pull/12 | ||
| // is merged in. | ||
| setProperty(clientSettings, args.edit.path.slice(), args.edit.value, format) | ||
| ) | ||
| } else if ('extensionID' in args) { | ||
| clientSettings = applyEdits( | ||
| clientSettings, | ||
| typeof args.enabled === 'boolean' | ||
| ? setProperty(clientSettings, ['extensions', args.extensionID], args.enabled, format) | ||
| : removeProperty(clientSettings, ['extensions', args.extensionID], format) | ||
| ) | ||
| } | ||
| return new Promise<undefined>(resolve => | ||
| storage.setSync({ clientSettings }, () => { | ||
| resolve(undefined) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't you pass
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I generally avoid that because it makes me less confident that the return value of the Promise will be what I intend for it to be. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it, if that's a concern I would eliminate it with in a different way with
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. (Except if it's inside another .then() handler, in which case the |
||
| }) | ||
| ) | ||
| } | ||
| ) | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This marker used to only be added at DOM load, which created a window of time during which this inject function was being called twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it doesn't wait for DOM load, how do you ensure that
document.bodyhas already loaded? Afaik it's not safe to do DOM manipulation until the DOM is loadedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function only gets called when the DOM is loaded:
https://sourcegraph.sgdev.org/github.com/sourcegraph/browser-extensions@0c49ab1c4677356d6a680d65cd68bb81200eeecf/-/blob/src/extension/scripts/inject.tsx#L111-116
https://sourcegraph.sgdev.org/github.com/sourcegraph/browser-extensions/-/blob/src/extension/scripts/inject.tsx#L87:13&tab=references
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then I don't understand your above comment. Was the issue that this event handler would get dispatched before the one that adds the marker, and now both add the marker?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah, what I said was ambiguous. If you look at the red part of the diff where this statement used to be, you'll see that it's inside a callback to the
loadevent, which executes asynchronously with respect to this function call.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see. Not exactly sure why it uses the
loadevent but might have a reason.