feat(bext): add ability to remove saved sg url from suggestion list (#52555)

This commit is contained in:
Erzhan Torokulov 2023-05-31 13:55:04 +06:00 committed by GitHub
parent 123e752481
commit 6ee50e0ce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 195 additions and 147 deletions

View File

@ -13,6 +13,8 @@ All notable changes to Sourcegraph [Browser Extensions](./README.md) are documen
## Unreleased
- add ability to remove saved sg url from suggestion list [pull/52555](https://github.com/sourcegraph/sourcegraph/pull/52555)
- Fix code-intel tooltips for pull request pages on Bitbucket: https://github.com/sourcegraph/sourcegraph/pull/52609
## Chrome & Firefox 23.4.14.1343, Safari 1.24

View File

@ -35,46 +35,10 @@
margin-top: 0.75rem;
}
.icon {
display: inline-flex;
place-items: center;
padding: 0.5rem;
border-radius: 100%;
}
.popover[data-reach-combobox-popover] {
margin: 0;
padding: 0.25rem 0;
background: var(--color-bg-1);
border: 1px solid var(--border-color);
border-radius: 3px;
box-shadow: var(--dropdown-shadow);
:global(.theme-dark) & {
background-color: var(--input-bg);
}
&,
[data-suggested-value] {
font-size: 0.875rem;
font-weight: normal;
}
[data-reach-combobox-list] {
list-style: none;
margin: 0;
padding: 0;
}
[data-reach-combobox-option] {
list-style: none;
margin: 0;
padding: 0.5rem 0.75rem;
cursor: pointer;
&:hover,
&[aria-selected='true'] {
background: var(--color-bg-3);
}
.suggestion-remove-button {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
&:hover {
background: var(--color-bg-3);
}
}

View File

@ -5,7 +5,7 @@ import { DecoratorFn, Meta, Story } from '@storybook/react'
import GithubIcon from 'mdi-react/GithubIcon'
import { Observable, of } from 'rxjs'
import { H1, H2, H3 } from '@sourcegraph/wildcard'
import { Grid, H1, H2, H3 } from '@sourcegraph/wildcard'
import { BrandedStory } from '@sourcegraph/wildcard/src/stories'
import { OptionsPage, OptionsPageProps } from './OptionsPage'
@ -26,21 +26,30 @@ const config: Meta = {
export default config
const OptionsPageWrapper: React.FunctionComponent<React.PropsWithChildren<Partial<OptionsPageProps>>> = props => (
<OptionsPage
isFullPage={false}
isActivated={true}
onToggleActivated={action('onToggleActivated')}
optionFlags={[{ key: 'allowErrorReporting', label: 'Allow error reporting', value: false }]}
onChangeOptionFlag={action('onChangeOptionFlag')}
version=""
sourcegraphUrl=""
validateSourcegraphUrl={validateSourcegraphUrl}
onChangeSourcegraphUrl={action('onChangeSourcegraphUrl')}
suggestedSourcegraphUrls={['https://k8s.sgdev.org', 'https://sourcegraph.com']}
{...props}
/>
)
const OptionsPageWrapper: React.FunctionComponent<React.PropsWithChildren<Partial<OptionsPageProps>>> = props => {
const [urls, setUrls] = useState([
'https://sourcegraph.com',
'https://k8s.sgdev.org',
'https://sourcegraph.sourcegraph.com',
])
return (
<OptionsPage
isFullPage={false}
isActivated={true}
onToggleActivated={action('onToggleActivated')}
optionFlags={[{ key: 'allowErrorReporting', label: 'Allow error reporting', value: false }]}
onChangeOptionFlag={action('onChangeOptionFlag')}
version=""
sourcegraphUrl=""
validateSourcegraphUrl={validateSourcegraphUrl}
onChangeSourcegraphUrl={action('onChangeSourcegraphUrl')}
suggestedSourcegraphUrls={urls}
onSuggestedSourcegraphUrlDelete={url => setUrls(urls.filter(item => item !== url))}
{...props}
/>
)
}
const Interactive: Story = args => {
const [isActivated, setIsActivated] = useState(false)
@ -68,75 +77,73 @@ const WithAdvancedSettings: Story = args => {
export const AllOptionsPages: Story = (args = {}) => (
<div>
<H1 className="text-center mb-3">All Options Pages</H1>
<div>
<div className="d-flex justify-content-center">
<div className="mx-4">
<H3 className="text-center">Interactive</H3>
<Interactive {...args} />
</div>
<div className="mx-4">
<H3 className="text-center">URL validation error</H3>
<OptionsPageWrapper validateSourcegraphUrl={invalidSourcegraphUrl} {...args} />
</div>
<div className="mx-4">
<H3 className="text-center">With advanced settings</H3>
<WithAdvancedSettings {...args} />
</div>
<Grid columnCount={3}>
<div>
<H3 className="text-center">Interactive</H3>
<Interactive {...args} />
</div>
<div className="d-flex justify-content-center mt-5">
<div className="mx-4">
<H3 className="text-center">On Sourcegraph.com</H3>
<OptionsPageWrapper
requestPermissionsHandler={requestPermissionsHandler}
showSourcegraphComAlert={true}
sourcegraphUrl={args.sourcegraphUrl}
version={args.version}
/>
</div>
<div className="mx-4">
<H3 className="text-center">Asking for permission</H3>
<OptionsPageWrapper
permissionAlert={{ name: 'GitHub', icon: GithubIcon }}
requestPermissionsHandler={requestPermissionsHandler}
{...args}
/>
</div>
<div>
<H3 className="text-center">URL validation error</H3>
<OptionsPageWrapper validateSourcegraphUrl={invalidSourcegraphUrl} {...args} />
</div>
<H2 className="mt-5 text-center">Not synced repository</H2>
<div className="d-flex justify-content-center mb-3">
<div className="mx-4">
<H3 className="text-center">Sourcegraph.com</H3>
<OptionsPageWrapper
sourcegraphUrl="https://sourcegraph.com"
currentUser={{ settingsURL: '/users/john-doe/settings', siteAdmin: false }}
hasRepoSyncError={true}
requestPermissionsHandler={requestPermissionsHandler}
showSourcegraphComAlert={args.showSourcegraphComAlert}
version={args.version}
/>
</div>
<div className="mx-4">
<H3 className="text-center">Self-hosted</H3>
<OptionsPageWrapper
currentUser={{ settingsURL: '/users/john-doe/settings', siteAdmin: false }}
hasRepoSyncError={true}
requestPermissionsHandler={requestPermissionsHandler}
{...args}
/>
</div>
<div className="mx-4">
<H3 className="text-center">Self-hosted instance, user is admin</H3>
<OptionsPageWrapper
currentUser={{ settingsURL: '/users/john-doe/settings', siteAdmin: true }}
hasRepoSyncError={true}
requestPermissionsHandler={requestPermissionsHandler}
{...args}
/>
</div>
<div>
<H3 className="text-center">With advanced settings</H3>
<WithAdvancedSettings {...args} />
</div>
</div>
<div>
<H3 className="text-center">No previous url suggestion</H3>
<OptionsPageWrapper suggestedSourcegraphUrls={[]} {...args} />
</div>
<div>
<H3 className="text-center">On Sourcegraph.com</H3>
<OptionsPageWrapper
requestPermissionsHandler={requestPermissionsHandler}
showSourcegraphComAlert={true}
sourcegraphUrl={args.sourcegraphUrl}
version={args.version}
/>
</div>
<div>
<H3 className="text-center">Asking for permission</H3>
<OptionsPageWrapper
permissionAlert={{ name: 'GitHub', icon: GithubIcon }}
requestPermissionsHandler={requestPermissionsHandler}
{...args}
/>
</div>
</Grid>
<H2 className="mt-5 text-center">Not synced repository</H2>
<Grid columnCount={3}>
<div>
<H3 className="text-center">Sourcegraph.com</H3>
<OptionsPageWrapper
sourcegraphUrl="https://sourcegraph.com"
currentUser={{ settingsURL: '/users/john-doe/settings', siteAdmin: false }}
hasRepoSyncError={true}
requestPermissionsHandler={requestPermissionsHandler}
showSourcegraphComAlert={args.showSourcegraphComAlert}
version={args.version}
/>
</div>
<div>
<H3 className="text-center">Self-hosted</H3>
<OptionsPageWrapper
currentUser={{ settingsURL: '/users/john-doe/settings', siteAdmin: false }}
hasRepoSyncError={true}
requestPermissionsHandler={requestPermissionsHandler}
{...args}
/>
</div>
<div>
<H3 className="text-center">Self-hosted instance, user is admin</H3>
<OptionsPageWrapper
currentUser={{ settingsURL: '/users/john-doe/settings', siteAdmin: true }}
hasRepoSyncError={true}
requestPermissionsHandler={requestPermissionsHandler}
{...args}
/>
</div>
</Grid>
</div>
)
AllOptionsPages.argTypes = {

View File

@ -1,15 +1,35 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { mdiEarth, mdiBookOpenPageVariant, mdiCheckCircleOutline, mdiLock, mdiBlockHelper, mdiOpenInNew } from '@mdi/js'
import { Combobox, ComboboxInput, ComboboxOption, ComboboxPopover, ComboboxList } from '@reach/combobox'
import {
mdiEarth,
mdiBookOpenPageVariant,
mdiCheckCircleOutline,
mdiLock,
mdiBlockHelper,
mdiOpenInNew,
mdiClose,
} from '@mdi/js'
import classNames from 'classnames'
import { Observable } from 'rxjs'
import { SourcegraphLogo } from '@sourcegraph/branded/src/components/SourcegraphLogo'
import { Toggle } from '@sourcegraph/branded/src/components/Toggle'
import { createURLWithUTM } from '@sourcegraph/shared/src/tracking/utm'
import { useInputValidation, deriveInputClassName } from '@sourcegraph/shared/src/util/useInputValidation'
import { Button, Link, Icon, Label, H4, Text, LoaderInput } from '@sourcegraph/wildcard'
import { InputValidationState, useInputValidation } from '@sourcegraph/shared/src/util/useInputValidation'
import {
Combobox,
ComboboxInput,
ComboboxOption,
ComboboxPopover,
ComboboxList,
Button,
Link,
Icon,
Label,
H4,
Text,
InputStatus,
} from '@sourcegraph/wildcard'
import { CurrentUserResult } from '../../graphql-operations'
import { getPlatformName, isDefaultSourcegraphUrl } from '../../shared/util/context'
@ -31,6 +51,7 @@ export interface OptionsPageProps {
// Suggested Sourcegraph URLs
suggestedSourcegraphUrls: string[]
onSuggestedSourcegraphUrlDelete: (url: string) => void
// Option flags
optionFlags: { key: string; label: string; value: boolean }[]
@ -75,6 +96,7 @@ export const OptionsPage: React.FunctionComponent<React.PropsWithChildren<Option
suggestedSourcegraphUrls,
hasRepoSyncError,
currentUser,
onSuggestedSourcegraphUrlDelete,
}) => {
const [showAdvancedSettings, setShowAdvancedSettings] = useState(initialShowAdvancedSettings)
@ -110,6 +132,7 @@ export const OptionsPage: React.FunctionComponent<React.PropsWithChildren<Option
<SourcegraphURLForm
value={sourcegraphUrl}
suggestions={suggestedSourcegraphUrls}
onSuggestionDelete={onSuggestedSourcegraphUrlDelete}
onChange={onChangeSourcegraphUrl}
validate={validateSourcegraphUrl}
/>
@ -276,13 +299,28 @@ interface SourcegraphURLFormProps {
value: OptionsPageProps['sourcegraphUrl']
validate: OptionsPageProps['validateSourcegraphUrl']
onChange: OptionsPageProps['onChangeSourcegraphUrl']
suggestions: OptionsPageProps['sourcegraphUrl'][]
suggestions: OptionsPageProps['suggestedSourcegraphUrls']
onSuggestionDelete: OptionsPageProps['onSuggestedSourcegraphUrlDelete']
}
const getInputStatusFromKind = (kind: InputValidationState['kind']): InputStatus => {
switch (kind) {
case 'INVALID':
return InputStatus.error
case 'VALID':
return InputStatus.valid
case 'LOADING':
return InputStatus.loading
default:
return InputStatus.initial
}
}
export const SourcegraphURLForm: React.FunctionComponent<React.PropsWithChildren<SourcegraphURLFormProps>> = ({
value,
validate,
suggestions,
onSuggestionDelete,
onChange,
}) => {
const urlInputReference = useRef<HTMLInputElement | null>(null)
@ -332,29 +370,51 @@ export const SourcegraphURLForm: React.FunctionComponent<React.PropsWithChildren
<form onSubmit={preventDefault} noValidate={true}>
<Label htmlFor="sourcegraph-url">Sourcegraph URL</Label>
<Combobox openOnFocus={true} onSelect={nextUrlFieldChange}>
<LoaderInput loading={urlState.kind === 'LOADING'} className={deriveInputClassName(urlState)}>
<ComboboxInput
type="url"
required={true}
spellCheck={false}
autoComplete="off"
autocomplete={false}
pattern="^https://.*"
placeholder="https://"
onFocus={onFocus}
id="sourcegraph-url"
ref={urlInputElements}
value={urlState.value}
onChange={nextUrlFieldChange}
className={classNames('form-control', 'test-sourcegraph-url', deriveInputClassName(urlState))}
/>
</LoaderInput>
<ComboboxInput
type="url"
required={true}
spellCheck={false}
autoComplete="off"
autocomplete={false}
status={getInputStatusFromKind(urlState.kind)}
pattern="^https://.*"
placeholder="https://"
onFocus={onFocus}
id="sourcegraph-url"
ref={urlInputElements}
value={urlState.value}
onChange={nextUrlFieldChange}
className="test-sourcegraph-url"
/>
{suggestions.length > 1 && hasInteracted && (
<ComboboxPopover className={styles.popover}>
<ComboboxPopover>
<ComboboxList>
{suggestions.map(suggestion => (
<ComboboxOption key={suggestion} value={suggestion} />
<ComboboxOption
key={suggestion}
value={suggestion}
className="d-flex justify-content-between p-0"
>
<Text className="py-2 pl-3 m-0">{suggestion}</Text>
<Button
className={classNames('m-0 py-0 px-2', styles.suggestionRemoveButton)}
onClick={event => {
// prevent click from becoming option selection
event.preventDefault()
event.stopPropagation()
if (
confirm(
`Are you sure you want to remove ${suggestion} from auto suggestion list?`
)
) {
onSuggestionDelete(suggestion)
}
}}
>
<Icon svgPath={mdiClose} aria-label="Remove suggestion" />
</Button>
</ComboboxOption>
))}
</ComboboxList>
</ComboboxPopover>

View File

@ -248,6 +248,20 @@ const Options: React.FunctionComponent<React.PropsWithChildren<unknown>> = () =>
[telemetryService]
)
const handleRemovePreviousSourcegraphUrl = useCallback(
(url: string): void => {
if (!url || previouslyUsedUrls?.length === 0) {
return
}
storage.sync
.set({
previouslyUsedURLs: previouslyUsedUrls?.filter(previouslyUsedUrl => previouslyUsedUrl !== url),
})
.catch(console.error)
},
[previouslyUsedUrls]
)
return (
<ThemeWrapper>
<WildcardThemeProvider isBranded={true}>
@ -255,6 +269,7 @@ const Options: React.FunctionComponent<React.PropsWithChildren<unknown>> = () =>
isFullPage={isFullPage}
sourcegraphUrl={sourcegraphUrl || ''}
suggestedSourcegraphUrls={uniqURLs(previouslyUsedUrls || [])}
onSuggestedSourcegraphUrlDelete={handleRemovePreviousSourcegraphUrl}
onChangeSourcegraphUrl={handleChangeSourcegraphUrl}
version={version}
validateSourcegraphUrl={validateSourcegraphUrl}