mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
Overhaul tooltip close button & badge styles (#10956)
This commit is contained in:
parent
9b97ad3752
commit
72469d8ef9
@ -35,6 +35,8 @@ const config = {
|
||||
|
||||
config.resolve.extensions.push('.ts', '.tsx')
|
||||
|
||||
const storybookDirectory = path.resolve(__dirname, '../node_modules/@storybook')
|
||||
|
||||
// Put our style rules at the beginning so they're processed by the time it
|
||||
// gets to storybook's style rules.
|
||||
config.module.rules.unshift({
|
||||
@ -52,9 +54,12 @@ const config = {
|
||||
},
|
||||
],
|
||||
// Make sure Storybook styles get handled by the Storybook config
|
||||
exclude: /node_modules\/@storybook\//,
|
||||
exclude: storybookDirectory,
|
||||
})
|
||||
|
||||
// Make sure Storybook style loaders are only evaluated for Storybook styles.
|
||||
config.module.rules.find(rule => rule.test?.toString() === /\.css$/.toString()).include = storybookDirectory
|
||||
|
||||
return config
|
||||
},
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ All notable changes to Sourcegraph are documented in this file.
|
||||
|
||||
- Repository search within a version context will link to the revision in the version context. [#10860](https://github.com/sourcegraph/sourcegraph/pull/10860)
|
||||
- Background permissions syncing becomes the default method to sync permissions from code hosts. Please [read our documentation for things to keep in mind before upgrading](https://docs.sourcegraph.com/admin/repo/permissions#background-permissions-syncing). [#10972](https://github.com/sourcegraph/sourcegraph/pull/10972)
|
||||
- The styling of the hover overlay was overhauled to never have badges or the close button overlap content while also always indicating whether the overlay is currently pinned. The styling on code hosts was also improved. [#10956](https://github.com/sourcegraph/sourcegraph/pull/10956)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@ -232,7 +232,7 @@ export const bitbucketServerCodeHost: CodeHost = {
|
||||
hoverOverlayClassProps: {
|
||||
className: 'aui-dialog',
|
||||
actionItemClassName: 'aui-button hover-action-item--bitbucket-server',
|
||||
closeButtonClassName: 'aui-button',
|
||||
iconButtonClassName: 'aui-button btn-icon--bitbucket-server',
|
||||
infoAlertClassName: notificationClassNames[NotificationType.Info],
|
||||
errorAlertClassName: notificationClassNames[NotificationType.Error],
|
||||
iconClassName,
|
||||
|
||||
@ -77,6 +77,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-icon--bitbucket-server {
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
height: 16px !important;
|
||||
line-height: 16px !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// Bitbucket's style is copied here because adding the aui-dropdown2-trigger class
|
||||
// to the command palette causes exceptions in Atlassian's JS.
|
||||
.command-list-popover-button--bitbucket-server {
|
||||
|
||||
@ -273,7 +273,7 @@ const nativeTooltipResolver: ViewResolver<NativeTooltip> = {
|
||||
resolveView: element => ({ element }),
|
||||
}
|
||||
|
||||
const iconClassName = 'action-item__icon--github v-align-text-bottom'
|
||||
const iconClassName = 'icon--github v-align-text-bottom'
|
||||
|
||||
export const githubCodeHost: CodeHost = {
|
||||
type: 'github',
|
||||
@ -324,7 +324,7 @@ export const githubCodeHost: CodeHost = {
|
||||
listItemClass: 'code-view-toolbar__item--github BtnGroup',
|
||||
actionItemClass: 'btn btn-sm tooltipped tooltipped-s BtnGroup-item action-item--github',
|
||||
actionItemPressedClass: 'selected',
|
||||
actionItemIconClass: 'action-item__icon--github v-align-text-bottom',
|
||||
actionItemIconClass: 'icon--github v-align-text-bottom',
|
||||
},
|
||||
completionWidgetClassProps: {
|
||||
widgetClassName: 'suggester-container',
|
||||
@ -337,7 +337,7 @@ export const githubCodeHost: CodeHost = {
|
||||
className: 'Box',
|
||||
actionItemClassName: 'btn btn-secondary',
|
||||
actionItemPressedClassName: 'active',
|
||||
closeButtonClassName: 'btn',
|
||||
iconButtonClassName: 'btn-octicon p-0',
|
||||
infoAlertClassName: 'flash flash-full',
|
||||
errorAlertClassName: 'flash flash-full flash-error',
|
||||
iconClassName,
|
||||
|
||||
@ -6,12 +6,12 @@
|
||||
.action-item--github {
|
||||
// Match GitHub's button height even if button only contains icon
|
||||
// (no text that would push the height)
|
||||
// stylelint-disable-next-line declaration-property-unit-whitelist
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.action-item__icon--github {
|
||||
.icon--github {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.code-view-toolbar--github {
|
||||
|
||||
@ -197,14 +197,15 @@ export const gitlabCodeHost = subTypeOf<CodeHost>()({
|
||||
},
|
||||
codeViewToolbarClassProps: {
|
||||
className: 'code-view-toolbar--gitlab',
|
||||
actionItemClass: 'btn btn-sm btn-secondary action-item--gitlab',
|
||||
actionItemClass: 'btn btn-sm btn-secondary ml-2 action-item--gitlab',
|
||||
actionItemPressedClass: 'active',
|
||||
},
|
||||
hoverOverlayClassProps: {
|
||||
className: 'card',
|
||||
className: 'card hover-overlay--gitlab',
|
||||
actionItemClassName: 'btn btn-secondary action-item--gitlab',
|
||||
actionItemPressedClassName: 'active',
|
||||
closeButtonClassName: 'btn',
|
||||
iconButtonClassName: 'btn btn-transparent p-0 btn-icon--gitlab',
|
||||
iconClassName: 'square s16',
|
||||
infoAlertClassName: notificationClassNames[NotificationType.Info],
|
||||
errorAlertClassName: notificationClassNames[NotificationType.Error],
|
||||
},
|
||||
|
||||
@ -3,15 +3,33 @@
|
||||
z-index: 1001 !important;
|
||||
}
|
||||
|
||||
.hover-overlay-mount__gitlab {
|
||||
.hover-overlay {
|
||||
code {
|
||||
background: none;
|
||||
color: unset;
|
||||
.btn-icon--gitlab {
|
||||
img {
|
||||
// Gitlab applies this to svgs,
|
||||
// need to apply it to imgs for consistency too
|
||||
position: relative;
|
||||
top: 2px;
|
||||
vertical-align: baseline;
|
||||
|
||||
// Replicate svg color hover effect
|
||||
opacity: 0.6;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stylelint-disable
|
||||
.hover-overlay--gitlab {
|
||||
border-color: #e5e5e5;
|
||||
|
||||
// Override Gitlab's default styles
|
||||
pre,
|
||||
code {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// highlight.js styles
|
||||
|
||||
|
||||
@ -195,7 +195,8 @@ export const phabricatorCodeHost: CodeHost = {
|
||||
hoverOverlayClassProps: {
|
||||
className: 'aphront-dialog-view hover-overlay--phabricator',
|
||||
actionItemClassName: 'button grey hover-overlay-action-item--phabricator',
|
||||
closeButtonClassName: 'button grey hover-overlay__close-button--phabricator',
|
||||
iconButtonClassName: 'button grey btn-icon--phabricator',
|
||||
iconClassName: 'icon--phabricator',
|
||||
infoAlertClassName: 'phui-info-view phui-info-severity-notice',
|
||||
errorAlertClassName: 'phui-info-view phui-info-severity-error',
|
||||
},
|
||||
|
||||
@ -2,18 +2,21 @@
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hover-overlay__close-button--phabricator {
|
||||
.btn-icon--phabricator {
|
||||
// Fight Phabricator selector specificity
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
// Mimics Phabricator's font icon style
|
||||
.action-item__icon--phabricator {
|
||||
.icon--phabricator {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
vertical-align: middle;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
.action-item--phabricator.action-item--pressed {
|
||||
@ -26,6 +29,9 @@
|
||||
border-bottom: none !important;
|
||||
border-top: none !important;
|
||||
border-right: none !important;
|
||||
.icon--phabricator {
|
||||
vertical-align: middle;
|
||||
}
|
||||
&:first-child {
|
||||
border-left: none !important;
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ $body-bg-color-light: #ffffff;
|
||||
@import '../../shared/src/extensions/ExtensionStatus';
|
||||
@import '../../shared/src/notifications/NotificationItem';
|
||||
@import '../../shared/src/notifications/Notifications';
|
||||
@import '../../shared/src/components/BadgeAttachment';
|
||||
|
||||
$body-color-light: #2b3750;
|
||||
$body-color-dark: #f2f4f8;
|
||||
|
||||
@ -66,6 +66,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@atlassian/aui": "^7.10.1",
|
||||
"@babel/core": "^7.9.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-decorators": "^7.8.3",
|
||||
|
||||
@ -1,45 +0,0 @@
|
||||
@import '../../src/global-styles/colors.scss';
|
||||
|
||||
.badge-decoration-attachment {
|
||||
&__icon-svg,
|
||||
&__contents {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
background: transparent;
|
||||
z-index: 1;
|
||||
border: none;
|
||||
border-radius: 100%;
|
||||
background-color: rgba($body-bg-color-dark, 0.8);
|
||||
}
|
||||
&__icon-svg {
|
||||
// TODO is there a better way to style this? padding vs margin (see __contents)
|
||||
margin: 0.25rem;
|
||||
}
|
||||
&__contents {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
color: #ffffff;
|
||||
|
||||
.theme-light & {
|
||||
color: $color-light-text-2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
.badge-decoration-attachment {
|
||||
&__contents {
|
||||
background-color: rgba($body-bg-color-light, 0.8);
|
||||
}
|
||||
|
||||
&__icon-svg {
|
||||
background-color: rgba($body-bg-color-light, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import { BadgeAttachment } from './BadgeAttachment'
|
||||
import badgeStyles from './BadgeAttachment.scss'
|
||||
import webStyles from '../../../web/src/SourcegraphWebApp.scss'
|
||||
import { BadgeAttachmentRenderOptions } from 'sourcegraph'
|
||||
|
||||
import { radios } from '@storybook/addon-knobs'
|
||||
|
||||
const label = 'Theme'
|
||||
const options = {
|
||||
Light: 'light',
|
||||
Dark: 'dark',
|
||||
}
|
||||
const defaultValue = 'light'
|
||||
const groupId = 'GROUP-ID1'
|
||||
|
||||
const isLightTheme = () => radios(label, options, defaultValue, groupId) === 'light'
|
||||
|
||||
const { add } = storiesOf('BadgeAttachment', module).addDecorator(story => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<style>{badgeStyles}</style>
|
||||
<div style={{ color: 'var(--body-color)' }} className={isLightTheme() ? 'theme-light' : 'theme-dark'}>
|
||||
<div>{story()}</div>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('info', () => (
|
||||
<BadgeAttachment
|
||||
attachment={{ kind: 'info', hoverMessage: ' this is hover tooltip(info)' }}
|
||||
isLightTheme={isLightTheme()}
|
||||
/>
|
||||
))
|
||||
|
||||
add('warning', () => (
|
||||
<BadgeAttachment
|
||||
attachment={{ kind: 'warning', hoverMessage: 'this is hover tooltip(warning)' }}
|
||||
isLightTheme={isLightTheme()}
|
||||
/>
|
||||
))
|
||||
|
||||
add('error', () => (
|
||||
<BadgeAttachment
|
||||
attachment={{ kind: 'error', hoverMessage: 'this is hover tooltip(error)' }}
|
||||
isLightTheme={isLightTheme()}
|
||||
/>
|
||||
))
|
||||
|
||||
const oldFormatBadge: Omit<BadgeAttachmentRenderOptions, 'kind'> = {
|
||||
icon: makeInfoIcon('#ffffff'),
|
||||
light: { icon: makeInfoIcon('#000000') },
|
||||
}
|
||||
|
||||
add('old format icon', () => (
|
||||
<BadgeAttachment attachment={oldFormatBadge as BadgeAttachmentRenderOptions} isLightTheme={isLightTheme()} />
|
||||
))
|
||||
|
||||
function makeIcon(svg: string): string {
|
||||
return `data:image/svg+xml;base64,${btoa(
|
||||
svg
|
||||
.split('\n')
|
||||
.map(r => r.trimStart())
|
||||
.join(' ')
|
||||
)}`
|
||||
}
|
||||
|
||||
function makeInfoIcon(color: string): string {
|
||||
return makeIcon(`
|
||||
<svg xmlns='http://www.w3.org/2000/svg' style="width:24px;height:24px" viewBox="0 0 24 24" fill="${color}">
|
||||
<path d="
|
||||
M11,
|
||||
9H13V7H11M12,
|
||||
20C7.59,
|
||||
20 4,
|
||||
16.41 4,
|
||||
12C4,
|
||||
7.59 7.59,
|
||||
4 12,
|
||||
4C16.41,
|
||||
4 20,
|
||||
7.59 20,
|
||||
12C20,
|
||||
16.41 16.41,
|
||||
20 12,
|
||||
20M12,
|
||||
2A10,
|
||||
10 0 0,
|
||||
0 2,
|
||||
12A10,
|
||||
10 0 0,
|
||||
0 12,
|
||||
22A10,
|
||||
10 0 0,
|
||||
0 22,
|
||||
12A10,
|
||||
10 0 0,
|
||||
0 12,
|
||||
2M11,
|
||||
17H13V11H11V17Z"
|
||||
/>
|
||||
</svg>
|
||||
`)
|
||||
}
|
||||
@ -6,7 +6,7 @@ import { BadgeAttachmentRenderOptions } from 'sourcegraph'
|
||||
const base64icon =
|
||||
'data:image/svg+xml;base64,IDxzdmcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBzdHlsZT0id2lkdGg6MjRweDtoZWlnaHQ6MjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjZmZmZmZmIj4gPHBhdGggZD0iIE0xMSwgOUgxM1Y3SDExTTEyLCAyMEM3LjU5LCAyMCA0LCAxNi40MSA0LCAxMkM0LCA3LjU5IDcuNTksIDQgMTIsIDRDMTYuNDEsIDQgMjAsIDcuNTkgMjAsIDEyQzIwLCAxNi40MSAxNi40MSwgMjAgMTIsIDIwTTEyLCAyQTEwLCAxMCAwIDAsIDAgMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMjJBMTAsIDEwIDAgMCwgMCAyMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMk0xMSwgMTdIMTNWMTFIMTFWMTdaIiAvPiA8L3N2Zz4g'
|
||||
|
||||
export const oldFormatBadge: Omit<BadgeAttachmentRenderOptions, 'kind'> = {
|
||||
export const base64ImageBadge: Omit<BadgeAttachmentRenderOptions, 'kind'> = {
|
||||
icon: base64icon,
|
||||
light: { icon: base64icon },
|
||||
hoverMessage:
|
||||
@ -14,8 +14,8 @@ export const oldFormatBadge: Omit<BadgeAttachmentRenderOptions, 'kind'> = {
|
||||
linkURL: 'https://docs.sourcegraph.com/user/code_intelligence/basic_code_intelligence',
|
||||
}
|
||||
|
||||
export const newFormatBadge: BadgeAttachmentRenderOptions = {
|
||||
...oldFormatBadge,
|
||||
export const badgeWithKind: BadgeAttachmentRenderOptions = {
|
||||
...base64ImageBadge,
|
||||
kind: 'info',
|
||||
}
|
||||
|
||||
@ -23,21 +23,14 @@ describe('BadgeAttachment', () => {
|
||||
afterAll(cleanup)
|
||||
|
||||
it('renders an img element with a base64 icon', () => {
|
||||
// note '"kind" is missing'
|
||||
const { container } = render(
|
||||
<BadgeAttachment attachment={oldFormatBadge as BadgeAttachmentRenderOptions} isLightTheme={true} />
|
||||
<BadgeAttachment attachment={base64ImageBadge as BadgeAttachmentRenderOptions} isLightTheme={true} />
|
||||
)
|
||||
const item = container.querySelector('.badge-decoration-attachment__contents')
|
||||
expect(item).toBeTruthy()
|
||||
// we used to render an image with base64 content as a source
|
||||
expect(item?.nodeName.toLowerCase()).toBe('img')
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders an svg element with a predefined icon', () => {
|
||||
const { container } = render(<BadgeAttachment attachment={newFormatBadge} isLightTheme={true} />)
|
||||
const item = container.querySelector('.badge-decoration-attachment__icon-svg')
|
||||
expect(item).toBeTruthy()
|
||||
// now we are using a proper svg for icons
|
||||
expect(item?.nodeName.toLowerCase()).toBe('svg')
|
||||
const { container } = render(<BadgeAttachment attachment={badgeWithKind} isLightTheme={true} />)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import * as React from 'react'
|
||||
import isAbsoluteUrl from 'is-absolute-url'
|
||||
import { isExternalLink } from '../util/url'
|
||||
import InformationIcon from 'mdi-react/InfoCircleOutlineIcon'
|
||||
import WarningIcon from 'mdi-react/AlertCircleOutlineIcon'
|
||||
import ErrorIcon from 'mdi-react/AlertDecagramOutlineIcon'
|
||||
@ -7,7 +7,8 @@ import { BadgeAttachmentRenderOptions } from 'sourcegraph'
|
||||
import { badgeAttachmentStyleForTheme } from '../api/client/services/decoration'
|
||||
import { LinkOrSpan } from './LinkOrSpan'
|
||||
import { isEncodedImage } from '../util/icon'
|
||||
import { MdiReactIconComponentType } from 'mdi-react'
|
||||
import { MdiReactIconComponentType, MdiReactIconProps } from 'mdi-react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const iconComponents: Record<BadgeAttachmentRenderOptions['kind'], MdiReactIconComponentType> = {
|
||||
info: InformationIcon,
|
||||
@ -15,46 +16,44 @@ const iconComponents: Record<BadgeAttachmentRenderOptions['kind'], MdiReactIconC
|
||||
error: ErrorIcon,
|
||||
}
|
||||
|
||||
const renderIcon = (badge: BadgeAttachmentRenderOptions, isLightTheme: boolean): JSX.Element | null => {
|
||||
if ('kind' in badge) {
|
||||
// means that we are using predefined icons
|
||||
const Icon = iconComponents[badge.kind]
|
||||
return <Icon className="icon-inline badge-decoration-attachment__icon-svg" />
|
||||
}
|
||||
|
||||
const style = badgeAttachmentStyleForTheme(badge, isLightTheme)
|
||||
|
||||
if (!style.icon || !isEncodedImage(style.icon)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<img
|
||||
className="badge-decoration-attachment__contents"
|
||||
// eslint-disable-next-line react/forbid-dom-props
|
||||
style={{
|
||||
color: style.color,
|
||||
backgroundColor: style.backgroundColor,
|
||||
}}
|
||||
src={style.icon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export const BadgeAttachment: React.FunctionComponent<{
|
||||
attachment: BadgeAttachmentRenderOptions
|
||||
isLightTheme: boolean
|
||||
}> = ({ attachment, isLightTheme }) => (
|
||||
<LinkOrSpan
|
||||
className="badge-decoration-attachment"
|
||||
to={attachment.linkURL}
|
||||
data-tooltip={attachment.hoverMessage}
|
||||
data-placement="left"
|
||||
// Use target to open external URLs
|
||||
target={attachment.linkURL && isAbsoluteUrl(attachment.linkURL) ? '_blank' : undefined}
|
||||
// Avoid leaking referrer URLs (which contain repository and path names, etc.) to external sites.
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{renderIcon(attachment, isLightTheme)}
|
||||
</LinkOrSpan>
|
||||
)
|
||||
className?: string
|
||||
iconClassName?: string
|
||||
iconButtonClassName?: string
|
||||
}> = ({ attachment, isLightTheme, className, iconButtonClassName, iconClassName }) => {
|
||||
const style = badgeAttachmentStyleForTheme(attachment, isLightTheme)
|
||||
const PredefinedIcon: React.ComponentType<MdiReactIconProps> | undefined =
|
||||
attachment.kind && iconComponents[attachment.kind]
|
||||
|
||||
return (
|
||||
<LinkOrSpan
|
||||
className={classNames(className, attachment.linkURL && iconButtonClassName)}
|
||||
to={attachment.linkURL}
|
||||
data-tooltip={attachment.hoverMessage}
|
||||
data-placement="left"
|
||||
// Use target to open external URLs
|
||||
target={attachment.linkURL && isExternalLink(attachment.linkURL) ? '_blank' : undefined}
|
||||
// Avoid leaking referrer URLs (which contain repository and path names, etc.) to external sites.
|
||||
rel="noreferrer noopener"
|
||||
>
|
||||
{PredefinedIcon ? (
|
||||
<PredefinedIcon className={iconClassName} />
|
||||
) : (
|
||||
style.icon &&
|
||||
isEncodedImage(style.icon) && (
|
||||
<img
|
||||
className={iconClassName}
|
||||
// eslint-disable-next-line react/forbid-dom-props
|
||||
style={{
|
||||
color: style.color,
|
||||
backgroundColor: style.backgroundColor,
|
||||
}}
|
||||
src={style.icon}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</LinkOrSpan>
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`BadgeAttachment renders an img element with a base64 icon 1`] = `
|
||||
<div>
|
||||
<a
|
||||
class=""
|
||||
data-placement="left"
|
||||
data-tooltip="Search-based results - click to see how these results are calculated and how to get precise intelligence with LSIF."
|
||||
href="https://docs.sourcegraph.com/user/code_intelligence/basic_code_intelligence"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
src="data:image/svg+xml;base64,IDxzdmcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBzdHlsZT0id2lkdGg6MjRweDtoZWlnaHQ6MjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjZmZmZmZmIj4gPHBhdGggZD0iIE0xMSwgOUgxM1Y3SDExTTEyLCAyMEM3LjU5LCAyMCA0LCAxNi40MSA0LCAxMkM0LCA3LjU5IDcuNTksIDQgMTIsIDRDMTYuNDEsIDQgMjAsIDcuNTkgMjAsIDEyQzIwLCAxNi40MSAxNi40MSwgMjAgMTIsIDIwTTEyLCAyQTEwLCAxMCAwIDAsIDAgMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMjJBMTAsIDEwIDAgMCwgMCAyMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMk0xMSwgMTdIMTNWMTFIMTFWMTdaIiAvPiA8L3N2Zz4g"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`BadgeAttachment renders an svg element with a predefined icon 1`] = `
|
||||
<div>
|
||||
<a
|
||||
class=""
|
||||
data-placement="left"
|
||||
data-tooltip="Search-based results - click to see how these results are calculated and how to get precise intelligence with LSIF."
|
||||
href="https://docs.sourcegraph.com/user/code_intelligence/basic_code_intelligence"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<svg
|
||||
class="mdi-icon "
|
||||
fill="currentColor"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
>
|
||||
<path
|
||||
d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
@ -1,6 +1,7 @@
|
||||
import * as React from 'react'
|
||||
import { anyOf, isInstanceOf } from '../util/types'
|
||||
import * as H from 'history'
|
||||
import { isExternalLink } from '../util/url'
|
||||
|
||||
/**
|
||||
* Returns a click handler that will make sure clicks on in-app links are handled on the client
|
||||
@ -23,7 +24,7 @@ export const createLinkClickHandler = (history: H.History): React.MouseEventHand
|
||||
const href = typeof anchor.href === 'string' ? anchor.href : anchor.href.baseVal
|
||||
|
||||
// Check if URL is outside the app
|
||||
if (!href.startsWith(window.location.origin)) {
|
||||
if (isExternalLink(href)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
4
shared/src/globals.d.ts
vendored
4
shared/src/globals.d.ts
vendored
@ -14,6 +14,10 @@ declare module '*.scss' {
|
||||
const cssModule: string
|
||||
export default cssModule
|
||||
}
|
||||
declare module '*.css' {
|
||||
const cssModule: string
|
||||
export default cssModule
|
||||
}
|
||||
|
||||
/**
|
||||
* Set by shared/dev/jest-environment.js
|
||||
|
||||
@ -4,100 +4,97 @@
|
||||
position: absolute;
|
||||
min-width: 6rem;
|
||||
max-width: 32rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
z-index: 100;
|
||||
transition: opacity 100ms ease-in-out;
|
||||
// Make sure content doesn't leak behind border-radius
|
||||
overflow: hidden;
|
||||
|
||||
$animation-duration: 100ms;
|
||||
transition: opacity $animation-duration ease-in-out;
|
||||
&__close-button,
|
||||
&__badge {
|
||||
// We want text to wrap around the close button and badges.
|
||||
float: right;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
&__close-button {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
padding: 0.25rem;
|
||||
border-radius: 100%;
|
||||
background: transparent;
|
||||
z-index: 1;
|
||||
border: none;
|
||||
background-color: rgba($body-bg-color-dark, 0.8);
|
||||
opacity: 0;
|
||||
transition: opacity $animation-duration ease-in-out;
|
||||
}
|
||||
&:hover &__close-button {
|
||||
opacity: 1;
|
||||
}
|
||||
// The close button should stay in place when scrolling a large hover content.
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
&__row {
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0;
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid var(--border-color);
|
||||
// Overlay alert background
|
||||
z-index: 1;
|
||||
|
||||
// When loading, we want the loader to be centered in the hover overlay,
|
||||
// not centered within the space left of the close button.
|
||||
&--loading {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__contents {
|
||||
flex: 1 1 auto;
|
||||
padding: 0.5rem;
|
||||
// Make very large MarkupContents scroll.
|
||||
overflow-y: auto;
|
||||
max-height: 10rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
&__alert {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 0.5rem;
|
||||
// We use <hr>s as a divider between multiple contents.
|
||||
// This has the nice property of having floating buttons that text wraps around.
|
||||
// stylelint-disable-next-line selector-max-compound-selectors
|
||||
hr {
|
||||
margin: 0.5rem -0.5rem;
|
||||
overflow: visible;
|
||||
background: var(--border-color);
|
||||
border: none;
|
||||
// The <hr> acts like a border, which should always be exactly 1px
|
||||
// stylelint-disable-next-line declaration-property-unit-whitelist
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
&__alert-close {
|
||||
float: right;
|
||||
}
|
||||
|
||||
// __content elements are a subset of the __row elements
|
||||
// (the ones that contain markdown or code, but not errors or the actions)
|
||||
&__content {
|
||||
display: contents;
|
||||
max-height: 15rem;
|
||||
padding: 0.5rem;
|
||||
overflow-x: auto;
|
||||
word-wrap: normal;
|
||||
|
||||
// Descendant selectors are needed here to style rendered markdown
|
||||
// stylelint-disable selector-max-compound-selectors
|
||||
|
||||
// <hr>s must looks the same as using the using multiple deprecated MarkedStrings
|
||||
hr {
|
||||
margin: 0.5rem -0.5rem;
|
||||
background: var(--border-color);
|
||||
border: none;
|
||||
// The <hr> acts like a border, which should always be exactly 1px
|
||||
// stylelint-disable-next-line declaration-property-unit-whitelist
|
||||
height: 1px;
|
||||
}
|
||||
p,
|
||||
pre {
|
||||
margin-bottom: 0.5rem;
|
||||
overflow: auto;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
pre,
|
||||
code {
|
||||
white-space: pre;
|
||||
padding: 0;
|
||||
// We want code to wrap, not scroll (but whitespace needs to be preserved).
|
||||
white-space: pre-wrap;
|
||||
// Any other value would create a new block formatting context,
|
||||
// which would prevent wrapping around the floating buttons.
|
||||
overflow: visible;
|
||||
}
|
||||
// stylelint-enable selector-max-compound-selectors
|
||||
}
|
||||
|
||||
&__badge {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
text-align: right;
|
||||
&__alert {
|
||||
padding: 0.5rem;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
&:hover &__badge--offset {
|
||||
right: 1.5rem;
|
||||
|
||||
&__alert-close {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@ -106,37 +103,23 @@
|
||||
}
|
||||
|
||||
&__action {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&__action,
|
||||
&__actions-placeholder {
|
||||
flex: 1 1 auto;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&__loader-row {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__loader-row,
|
||||
&__hover-error,
|
||||
&__content-error,
|
||||
&__alert-below {
|
||||
&__hover-error {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
&__alert-below {
|
||||
margin: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
.hover-overlay {
|
||||
&__close-button {
|
||||
background-color: rgba($body-bg-color-light, 0.8);
|
||||
}
|
||||
margin: -0.5rem;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
289
shared/src/hover/HoverOverlay.story.tsx
Normal file
289
shared/src/hover/HoverOverlay.story.tsx
Normal file
@ -0,0 +1,289 @@
|
||||
import { storiesOf } from '@storybook/react'
|
||||
import React from 'react'
|
||||
import { action } from '@storybook/addon-actions'
|
||||
import { boolean } from '@storybook/addon-knobs'
|
||||
import { createMemoryHistory } from 'history'
|
||||
import { HoverOverlay, HoverOverlayClassProps } from './HoverOverlay'
|
||||
import { MarkupKind } from '@sourcegraph/extension-api-classes'
|
||||
import { NOOP_TELEMETRY_SERVICE } from '../telemetry/telemetryService'
|
||||
import { of } from 'rxjs'
|
||||
import { registerHighlightContributions } from '../highlight/contributions'
|
||||
import { PlatformContext } from '../platform/context'
|
||||
|
||||
import webStyles from '../../../web/src/SourcegraphWebApp.scss'
|
||||
import bitbucketStyles from '@atlassian/aui/dist/aui/css/aui.css'
|
||||
import browserExtensionStyles from '../../../browser/src/app.scss'
|
||||
import { BadgeAttachmentRenderOptions, MarkupContent, Badged } from 'sourcegraph'
|
||||
|
||||
registerHighlightContributions()
|
||||
|
||||
const { add } = storiesOf('HoverOverlay', module)
|
||||
|
||||
const history = createMemoryHistory()
|
||||
const NOOP_EXTENSIONS_CONTROLLER = { executeCommand: () => Promise.resolve() }
|
||||
const NOOP_PLATFORM_CONTEXT: Pick<PlatformContext, 'forceUpdateTooltip' | 'settings'> = {
|
||||
forceUpdateTooltip: () => undefined,
|
||||
settings: of({ final: {}, subjects: [] }),
|
||||
}
|
||||
|
||||
const commonProps = () => ({
|
||||
showCloseButton: boolean('showCloseButton', true),
|
||||
location: history.location,
|
||||
telemetryService: NOOP_TELEMETRY_SERVICE,
|
||||
extensionsController: NOOP_EXTENSIONS_CONTROLLER,
|
||||
platformContext: NOOP_PLATFORM_CONTEXT,
|
||||
isLightTheme: true,
|
||||
overlayPosition: { top: 16, left: 16 },
|
||||
onAlertDismissed: action('onAlertDismissed'),
|
||||
onCloseButtonClick: action('onCloseButtonClick'),
|
||||
})
|
||||
const webHoverOverlayClassProps: HoverOverlayClassProps = {
|
||||
className: 'card',
|
||||
iconClassName: 'icon-inline',
|
||||
iconButtonClassName: 'btn btn-icon',
|
||||
actionItemClassName: 'btn btn-secondary',
|
||||
infoAlertClassName: 'alert alert-info',
|
||||
errorAlertClassName: 'alert alert-danger',
|
||||
}
|
||||
const bitbucketClassProps: HoverOverlayClassProps = {
|
||||
className: 'aui-dialog',
|
||||
actionItemClassName: 'aui-button hover-action-item--bitbucket-server',
|
||||
iconButtonClassName: 'aui-button btn-icon--bitbucket-server',
|
||||
infoAlertClassName: 'aui-message aui-message-info',
|
||||
errorAlertClassName: 'aui-message aui-message-error',
|
||||
iconClassName: 'aui-icon',
|
||||
}
|
||||
|
||||
const FIXTURE_BADGE: BadgeAttachmentRenderOptions = {
|
||||
kind: 'info',
|
||||
hoverMessage:
|
||||
'Search-based results - click to see how these results are calculated and how to get precise intelligence with LSIF.',
|
||||
linkURL: 'https://docs.sourcegraph.com/user/code_intelligence/basic_code_intelligence',
|
||||
}
|
||||
|
||||
const LEGACY_FIXTURE_BADGE = {
|
||||
icon:
|
||||
'data:image/svg+xml;base64,IDxzdmcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBzdHlsZT0id2lkdGg6MjRweDtoZWlnaHQ6MjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjZmZmZmZmIj4gPHBhdGggZD0iIE0xMSwgOUgxM1Y3SDExTTEyLCAyMEM3LjU5LCAyMCA0LCAxNi40MSA0LCAxMkM0LCA3LjU5IDcuNTksIDQgMTIsIDRDMTYuNDEsIDQgMjAsIDcuNTkgMjAsIDEyQzIwLCAxNi40MSAxNi40MSwgMjAgMTIsIDIwTTEyLCAyQTEwLCAxMCAwIDAsIDAgMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMjJBMTAsIDEwIDAgMCwgMCAyMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMk0xMSwgMTdIMTNWMTFIMTFWMTdaIiAvPiA8L3N2Zz4g',
|
||||
light: {
|
||||
icon:
|
||||
'data:image/svg+xml;base64,IDxzdmcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyBzdHlsZT0id2lkdGg6MjRweDtoZWlnaHQ6MjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjMDAwMDAwIj4gPHBhdGggZD0iIE0xMSwgOUgxM1Y3SDExTTEyLCAyMEM3LjU5LCAyMCA0LCAxNi40MSA0LCAxMkM0LCA3LjU5IDcuNTksIDQgMTIsIDRDMTYuNDEsIDQgMjAsIDcuNTkgMjAsIDEyQzIwLCAxNi40MSAxNi40MSwgMjAgMTIsIDIwTTEyLCAyQTEwLCAxMCAwIDAsIDAgMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMjJBMTAsIDEwIDAgMCwgMCAyMiwgMTJBMTAsIDEwIDAgMCwgMCAxMiwgMk0xMSwgMTdIMTNWMTFIMTFWMTdaIiAvPiA8L3N2Zz4g',
|
||||
},
|
||||
hoverMessage:
|
||||
'Search-based results - click to see how these results are calculated and how to get precise intelligence with LSIF.',
|
||||
linkURL: 'https://docs.sourcegraph.com/user/code_intelligence/basic_code_intelligence',
|
||||
} as BadgeAttachmentRenderOptions
|
||||
|
||||
const FIXTURE_CONTENT: Badged<MarkupContent> = {
|
||||
value:
|
||||
'```typescript\nexport interface TestInterface<A, B, C>\n```\n\n' +
|
||||
'---\n\nVeniam voluptate quis magna mollit aliqua enim id ea fugiat. Aliqua anim eiusmod nisi excepteur.\n',
|
||||
kind: MarkupKind.Markdown,
|
||||
badge: FIXTURE_BADGE,
|
||||
}
|
||||
|
||||
const FIXTURE_CONTENT_LONG_CODE = {
|
||||
...FIXTURE_CONTENT,
|
||||
value:
|
||||
'```typescript\nexport interface LongTestInterface<A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z>\n```\n\n' +
|
||||
'---\n\nNisi id deserunt culpa dolore aute pariatur ut amet veniam. Proident id Lorem reprehenderit veniam sunt velit.\n',
|
||||
}
|
||||
|
||||
const FIXTURE_CONTENT_LONG_TEXT_ONLY = {
|
||||
...FIXTURE_CONTENT,
|
||||
value:
|
||||
'Mollit ea esse magna incididunt aliquip mollit non reprehenderit veniam anim. Veniam in dolor elit sint aliqua non cillum. Est sit pariatur ut cupidatat magna dolore. Sint et culpa voluptate ad sit eu ea dolor. Dolore Lorem cillum esse pariatur elit dolore dolor quis fugiat labore non. Elit nostrud minim aliqua adipisicing laborum ad sunt velit amet. In voluptate est voluptate labore consectetur proident. Nostrud exercitation ut officia enim minim tempor qui adipisicing sunt et occaecat anim irure. Culpa irure reprehenderit reprehenderit dolore sint aliquip non ex excepteur ipsum dolor. Et qui anim officia magna enim laboris enim exercitation pariatur. Cillum consequat elit dolore tempor magna exercitation ad laborum consequat aute consequat.',
|
||||
}
|
||||
|
||||
const FIXTURE_ACTIONS = [
|
||||
{
|
||||
action: {
|
||||
id: 'goToDefinition.preloaded',
|
||||
title: 'Go to definition',
|
||||
command: 'open',
|
||||
commandArguments: ['/github.com/sourcegraph/codeintellify/-/blob/src/hoverifier.ts?subtree=true#L57:1'],
|
||||
},
|
||||
},
|
||||
{
|
||||
action: {
|
||||
id: 'findReferences',
|
||||
title: 'Find references',
|
||||
command: 'open',
|
||||
commandArguments: [
|
||||
'/github.com/sourcegraph/codeintellify/-/blob/src/hoverifier.ts?subtree=true#L57:18&tab=references',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
add('Loading', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError="loading"
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Error', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={
|
||||
new Error(
|
||||
'Something terrible happened: Eiusmod voluptate deserunt in sint cillum pariatur laborum eiusmod.'
|
||||
)
|
||||
}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Common content', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Legacy badge', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={{
|
||||
contents: [{ ...FIXTURE_CONTENT, badge: LEGACY_FIXTURE_BADGE }],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Only actions', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={null}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Long code', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT_LONG_CODE],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Long text only', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT_LONG_TEXT_ONLY],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Multiple MarkupContents', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT, FIXTURE_CONTENT, FIXTURE_CONTENT],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('With alert', () => (
|
||||
<>
|
||||
<style>{webStyles}</style>
|
||||
<div className="theme-light">
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...webHoverOverlayClassProps}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT],
|
||||
alerts: [
|
||||
{
|
||||
type: 'info',
|
||||
content: (
|
||||
<>
|
||||
This is a test alert. Enim esse quis commodo ex. Pariatur tempor laborum officia
|
||||
irure est do est laborum nostrud cillum. Cupidatat id consectetur et eiusmod Lorem
|
||||
proident cupidatat ullamco dolor nostrud. Cupidatat sit do dolor aliqua labore ad
|
||||
laboris cillum deserunt dolor. Sunt labore veniam Lorem reprehenderit quis occaecat
|
||||
sint do mollit aliquip. Consectetur mollit mollit magna eiusmod duis ex. Sint nisi
|
||||
labore labore nulla laboris.
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
onAlertDismissed={action('onAlertDismissed')}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
))
|
||||
|
||||
add('Bitbucket styles', () => (
|
||||
<>
|
||||
<style>{bitbucketStyles}</style>
|
||||
<style>{browserExtensionStyles}</style>
|
||||
<HoverOverlay
|
||||
{...commonProps()}
|
||||
{...bitbucketClassProps}
|
||||
hoverOrError={{
|
||||
contents: [FIXTURE_CONTENT],
|
||||
}}
|
||||
actionsOrError={FIXTURE_ACTIONS}
|
||||
/>
|
||||
</>
|
||||
))
|
||||
@ -1,12 +1,8 @@
|
||||
import { HoverAttachment } from '@sourcegraph/codeintellify/lib/types'
|
||||
import { MarkupKind } from '@sourcegraph/extension-api-classes'
|
||||
import { registerLanguage } from 'highlight.js/lib/core'
|
||||
import * as H from 'history'
|
||||
import { castArray } from 'lodash'
|
||||
import React from 'react'
|
||||
import renderer from 'react-test-renderer'
|
||||
import { createRenderer } from 'react-test-renderer/shallow'
|
||||
import { HoverMerged } from '../api/client/types/hover'
|
||||
import { NOOP_TELEMETRY_SERVICE } from '../telemetry/telemetryService'
|
||||
import { HoverOverlay, HoverOverlayProps } from './HoverOverlay'
|
||||
import { NEVER } from 'rxjs'
|
||||
@ -214,69 +210,4 @@ describe('HoverOverlay', () => {
|
||||
)
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('hover content rendering', () => {
|
||||
const renderMarkdownHover = (hover: HoverAttachment & HoverMerged): string | null => {
|
||||
// TODO this test depends on internals of the HoverOverlay.
|
||||
// If we want to test this rendering, it would be better to
|
||||
// extract the markdown rendering into another small component
|
||||
// and unit test that in isolation
|
||||
const r = renderShallow(<HoverOverlay {...commonProps} hoverOrError={hover} />)
|
||||
const contents = castArray(r.props.children).find(element =>
|
||||
element?.props?.className?.includes('hover-overlay__contents')
|
||||
)
|
||||
if (!contents) {
|
||||
return null
|
||||
}
|
||||
|
||||
const grabContent = (c: any) => {
|
||||
if (c.props && c.props.className && c.props.className.includes('hover-overlay__content')) {
|
||||
if (typeof c.props.children === 'string') {
|
||||
return c.props.children
|
||||
}
|
||||
return c.props.dangerouslySetInnerHTML.__html
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
return castArray(contents.props.children)
|
||||
.map(c => {
|
||||
// Grab un-badged content
|
||||
const content = grabContent(c)
|
||||
if (content !== '') {
|
||||
return content
|
||||
}
|
||||
// Grab badged content in the grand-child level
|
||||
if (c.props && c.props.className && c.props.className.includes('e2e-tooltip-badged-content')) {
|
||||
return castArray(c.props.children).map(grabContent).join('').trim()
|
||||
}
|
||||
return ''
|
||||
})
|
||||
.join('')
|
||||
.trim()
|
||||
}
|
||||
|
||||
const renderPlainTextHover = (hover: HoverAttachment & HoverMerged): React.ReactChild[] =>
|
||||
renderer
|
||||
.create(<HoverOverlay {...commonProps} hoverOrError={hover} />)
|
||||
.root.find(c => c.props && c.props.className && c.props.className.includes('hover-overlay__content'))
|
||||
.props.children.map((c: renderer.ReactTestInstance) => c.props.children)
|
||||
|
||||
test('MarkupKind.Markdown', () => {
|
||||
expect(renderMarkdownHover({ contents: [{ kind: MarkupKind.Markdown, value: '*v*' }] })).toEqual(
|
||||
'<p><em>v</em></p>'
|
||||
)
|
||||
})
|
||||
|
||||
test('MarkupKind.PlainText', () => {
|
||||
expect(renderPlainTextHover({ contents: [{ kind: MarkupKind.PlainText, value: 'v<' }] })).toEqual(['v<'])
|
||||
})
|
||||
|
||||
test('code', () => {
|
||||
registerLanguage('testlang', x => ({}))
|
||||
expect(
|
||||
renderMarkdownHover({ contents: [{ kind: MarkupKind.Markdown, value: '```testlang\n<>\n```' }] })
|
||||
).toEqual('<pre><code class="language-testlang"><></code></pre>')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -29,7 +29,7 @@ export type HoverData<A extends string> = HoverMerged & HoverAlerts<A>
|
||||
export interface HoverOverlayClassProps {
|
||||
/** An optional class name to apply to the outermost element of the HoverOverlay */
|
||||
className?: string
|
||||
closeButtonClassName?: string
|
||||
iconButtonClassName?: string
|
||||
|
||||
iconClassName?: string
|
||||
|
||||
@ -171,76 +171,70 @@ export class HoverOverlay<A extends string> extends React.PureComponent<HoverOve
|
||||
className={classNames('hover-overlay', className)}
|
||||
ref={hoverRef}
|
||||
>
|
||||
{showCloseButton && (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames('hover-overlay__close-button', this.props.closeButtonClassName)}
|
||||
onClick={onCloseButtonClick ? transformMouseEvent(onCloseButtonClick) : undefined}
|
||||
>
|
||||
<CloseIcon className="icon-inline" />
|
||||
</button>
|
||||
)}
|
||||
<div className="hover-overlay__contents">
|
||||
<div className={classNames('hover-overlay__contents')}>
|
||||
{showCloseButton && (
|
||||
<button
|
||||
type="button"
|
||||
className={classNames(
|
||||
'hover-overlay__close-button',
|
||||
this.props.iconButtonClassName,
|
||||
hoverOrError === LOADING && 'hover-overlay__close-button--loading'
|
||||
)}
|
||||
onClick={onCloseButtonClick ? transformMouseEvent(onCloseButtonClick) : undefined}
|
||||
>
|
||||
<CloseIcon className={this.props.iconClassName} />
|
||||
</button>
|
||||
)}
|
||||
{hoverOrError === LOADING ? (
|
||||
<div className="hover-overlay__row hover-overlay__loader-row">
|
||||
<LoadingSpinner className="icon-inline" />
|
||||
<div className="hover-overlay__loader-row">
|
||||
<LoadingSpinner className={this.props.iconClassName} />
|
||||
</div>
|
||||
) : isErrorLike(hoverOrError) ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'hover-overlay__row',
|
||||
'hover-overlay__hover-error',
|
||||
this.props.errorAlertClassName
|
||||
)}
|
||||
>
|
||||
<div className={classNames('hover-overlay__hover-error', this.props.errorAlertClassName)}>
|
||||
{upperFirst(hoverOrError.message)}
|
||||
</div>
|
||||
) : hoverOrError === null ? (
|
||||
// Show some content to give the close button space
|
||||
// and communicate to the user we couldn't find a hover.
|
||||
<em>No hover information available.</em>
|
||||
) : (
|
||||
hoverOrError?.contents.map((content, i) => {
|
||||
if (content.kind === 'markdown') {
|
||||
try {
|
||||
// Offset first badge when the close button is shown to avoid conflict.
|
||||
const offsetBadge = showCloseButton && i === 0
|
||||
return (
|
||||
<div className="hover-overlay__row e2e-tooltip-badged-content" key={i}>
|
||||
{'badge' in content && content.badge && this.state.showBadges && (
|
||||
<div
|
||||
className={classNames(
|
||||
'hover-overlay__badge',
|
||||
'e2e-hover-badge',
|
||||
offsetBadge && 'hover-overlay__badge--offset'
|
||||
)}
|
||||
>
|
||||
<BadgeAttachment
|
||||
attachment={content.badge}
|
||||
isLightTheme={this.props.isLightTheme}
|
||||
/>
|
||||
</div>
|
||||
<React.Fragment key={i}>
|
||||
{i !== 0 && <hr />}
|
||||
|
||||
{content.badge && this.state.showBadges && (
|
||||
<BadgeAttachment
|
||||
className="hover-overlay__badge e2e-hover-badge"
|
||||
iconClassName={this.props.iconClassName}
|
||||
iconButtonClassName={this.props.iconButtonClassName}
|
||||
attachment={content.badge}
|
||||
isLightTheme={this.props.isLightTheme}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: renderMarkdown(content.value),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
} catch (err) {
|
||||
return (
|
||||
<div
|
||||
className={classNames('hover-overlay__row', this.props.errorAlertClassName)}
|
||||
key={i}
|
||||
>
|
||||
<div className={classNames(this.props.errorAlertClassName)} key={i}>
|
||||
{upperFirst(asError(err).message)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="hover-overlay__content hover-overlay__row" key={i}>
|
||||
<span className="hover-overlay__content" key={i}>
|
||||
{content.value}
|
||||
</div>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
)}
|
||||
@ -249,11 +243,7 @@ export class HoverOverlay<A extends string> extends React.PureComponent<HoverOve
|
||||
<div className="hover-overlay__alerts">
|
||||
{hoverOrError.alerts.map(({ content, type }) => (
|
||||
<div
|
||||
className={classNames(
|
||||
'hover-overlay__row',
|
||||
'hover-overlay__alert',
|
||||
this.props.infoAlertClassName
|
||||
)}
|
||||
className={classNames('hover-overlay__alert', this.props.infoAlertClassName)}
|
||||
key={type}
|
||||
>
|
||||
<div className="hover-overlay__alert-content">
|
||||
@ -275,7 +265,7 @@ export class HoverOverlay<A extends string> extends React.PureComponent<HoverOve
|
||||
actionsOrError !== LOADING &&
|
||||
!isErrorLike(actionsOrError) &&
|
||||
actionsOrError.length > 0 && (
|
||||
<div className="hover-overlay__actions hover-overlay__row">
|
||||
<div className="hover-overlay__actions">
|
||||
{actionsOrError.map((action, i) => (
|
||||
<ActionItem
|
||||
key={i}
|
||||
|
||||
@ -14,7 +14,11 @@ exports[`HoverOverlay actions and hover empty 1`] = `
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__contents"
|
||||
/>
|
||||
>
|
||||
<em>
|
||||
No hover information available.
|
||||
</em>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -34,7 +38,7 @@ exports[`HoverOverlay actions and hover error 1`] = `
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row hover-overlay__hover-error"
|
||||
className="hover-overlay__hover-error"
|
||||
>
|
||||
M2
|
||||
</div>
|
||||
@ -58,10 +62,10 @@ exports[`HoverOverlay actions and hover loading 1`] = `
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row hover-overlay__loader-row"
|
||||
className="hover-overlay__loader-row"
|
||||
>
|
||||
<div
|
||||
className="loading-spinner icon-inline"
|
||||
className="loading-spinner "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -83,10 +87,8 @@ exports[`HoverOverlay actions and hover present 1`] = `
|
||||
<div
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row e2e-tooltip-badged-content"
|
||||
>
|
||||
<div
|
||||
<React.Fragment>
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
@ -95,10 +97,10 @@ exports[`HoverOverlay actions and hover present 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
<div
|
||||
className="hover-overlay__actions hover-overlay__row"
|
||||
className="hover-overlay__actions"
|
||||
>
|
||||
<ActionItem
|
||||
action={
|
||||
@ -182,10 +184,8 @@ exports[`HoverOverlay actions error, hover present 1`] = `
|
||||
<div
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row e2e-tooltip-badged-content"
|
||||
>
|
||||
<div
|
||||
<React.Fragment>
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
@ -194,7 +194,7 @@ exports[`HoverOverlay actions error, hover present 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -233,7 +233,7 @@ exports[`HoverOverlay actions present 1`] = `
|
||||
className="hover-overlay__contents"
|
||||
/>
|
||||
<div
|
||||
className="hover-overlay__actions hover-overlay__row"
|
||||
className="hover-overlay__actions"
|
||||
>
|
||||
<ActionItem
|
||||
action={
|
||||
@ -297,15 +297,13 @@ exports[`HoverOverlay actions present, hover loading 1`] = `
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row hover-overlay__loader-row"
|
||||
className="hover-overlay__loader-row"
|
||||
>
|
||||
<LoadingSpinner
|
||||
className="icon-inline"
|
||||
/>
|
||||
<LoadingSpinner />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="hover-overlay__actions hover-overlay__row"
|
||||
className="hover-overlay__actions"
|
||||
>
|
||||
<ActionItem
|
||||
action={
|
||||
@ -367,10 +365,8 @@ exports[`HoverOverlay actions, hover and alert present 1`] = `
|
||||
<div
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row e2e-tooltip-badged-content"
|
||||
>
|
||||
<div
|
||||
<React.Fragment>
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
@ -379,13 +375,13 @@ exports[`HoverOverlay actions, hover and alert present 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
<div
|
||||
className="hover-overlay__alerts"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row hover-overlay__alert"
|
||||
className="hover-overlay__alert"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__alert-content"
|
||||
@ -415,7 +411,7 @@ exports[`HoverOverlay actions, hover and alert present 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="hover-overlay__actions hover-overlay__row"
|
||||
className="hover-overlay__actions"
|
||||
>
|
||||
<ActionItem
|
||||
action={
|
||||
@ -480,7 +476,7 @@ exports[`HoverOverlay hover error 1`] = `
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row hover-overlay__hover-error"
|
||||
className="hover-overlay__hover-error"
|
||||
>
|
||||
M
|
||||
</div>
|
||||
@ -504,13 +500,13 @@ exports[`HoverOverlay hover error, actions present 1`] = `
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row hover-overlay__hover-error"
|
||||
className="hover-overlay__hover-error"
|
||||
>
|
||||
M
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="hover-overlay__actions hover-overlay__row"
|
||||
className="hover-overlay__actions"
|
||||
>
|
||||
<ActionItem
|
||||
action={
|
||||
@ -573,10 +569,10 @@ exports[`HoverOverlay hover loading 1`] = `
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row hover-overlay__loader-row"
|
||||
className="hover-overlay__loader-row"
|
||||
>
|
||||
<div
|
||||
className="loading-spinner icon-inline"
|
||||
className="loading-spinner "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -598,19 +594,15 @@ exports[`HoverOverlay hover present 1`] = `
|
||||
<div
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row e2e-tooltip-badged-content"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>v</p>
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>v</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -630,10 +622,8 @@ exports[`HoverOverlay hover present, actions loading 1`] = `
|
||||
<div
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row e2e-tooltip-badged-content"
|
||||
>
|
||||
<div
|
||||
<React.Fragment>
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
@ -642,7 +632,7 @@ exports[`HoverOverlay hover present, actions loading 1`] = `
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -662,32 +652,25 @@ exports[`HoverOverlay multiple hovers present 1`] = `
|
||||
<div
|
||||
className="hover-overlay__contents"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__row e2e-tooltip-badged-content"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>v</p>
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>v</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="hover-overlay__row e2e-tooltip-badged-content"
|
||||
>
|
||||
<div
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>v2</p>
|
||||
}
|
||||
/>
|
||||
<hr />
|
||||
<span
|
||||
className="hover-overlay__content e2e-tooltip-content"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<p>v2</p>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -11,7 +11,6 @@
|
||||
@import './components/ResultContainer';
|
||||
@import './components/Toggle';
|
||||
@import './components/Tabs';
|
||||
@import './components/BadgeAttachment';
|
||||
@import './extensions/ExtensionStatus';
|
||||
@import './hover/HoverOverlay';
|
||||
@import './notifications/NotificationItem';
|
||||
|
||||
@ -15,7 +15,7 @@ export const WebHoverOverlay: React.FunctionComponent<HoverOverlayProps<never>>
|
||||
{...props}
|
||||
className="card"
|
||||
iconClassName="icon-inline"
|
||||
closeButtonClassName="btn btn-icon"
|
||||
iconButtonClassName="btn btn-icon"
|
||||
actionItemClassName="btn btn-secondary"
|
||||
infoAlertClassName="alert alert-info"
|
||||
errorAlertClassName="alert alert-danger"
|
||||
|
||||
79
yarn.lock
79
yarn.lock
@ -11,6 +11,23 @@
|
||||
call-me-maybe "^1.0.1"
|
||||
js-yaml "^3.13.1"
|
||||
|
||||
"@atlassian/aui@^7.10.1":
|
||||
version "7.10.1"
|
||||
resolved "https://registry.npmjs.org/@atlassian/aui/-/aui-7.10.1.tgz#21fae26d599ffc7d4e35d4f603e5747a5ac913ce"
|
||||
integrity sha512-KNCaQA40mdH22/yzk0JQ78j7Z0OCx5t0q1S/JJay6o+gDmbpizAaZAxzDdoptzvMsV2BjddehzJPYg5wcmIgJQ==
|
||||
dependencies:
|
||||
backbone "1.1.2"
|
||||
clipboard-js "0.2.0"
|
||||
css.escape "1.5.0"
|
||||
fancy-file-input "2.0.3"
|
||||
object-assign "4.0.1"
|
||||
skatejs "0.13.17"
|
||||
skatejs-template-html "0.0.0"
|
||||
tether atlassian/tether#amd-with-global
|
||||
trim-extra-html-whitespace "1.3.0"
|
||||
underscore "1.6.0"
|
||||
webcomponents.js "0.7.20"
|
||||
|
||||
"@babel/code-frame@7.0.0":
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
|
||||
@ -2097,7 +2114,8 @@
|
||||
integrity sha512-KWxkyphmlwam8kfYPSmoitKQRMGQCsr1ZRmNZgijT7ABKaVyk/+I5ezt2J213tM04Hi0vyg4L7iH1VCkNvm2Jw==
|
||||
|
||||
"@sourcegraph/extension-api-types@link:packages/@sourcegraph/extension-api-types":
|
||||
version "2.1.0"
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@sourcegraph/prettierrc@^3.0.3":
|
||||
version "3.0.3"
|
||||
@ -5182,6 +5200,13 @@ bach@^1.0.0:
|
||||
async-settle "^1.0.0"
|
||||
now-and-later "^2.0.0"
|
||||
|
||||
backbone@1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npmjs.org/backbone/-/backbone-1.1.2.tgz#c2c04c66bf87268fb82c177acebeff7d37ba6f2d"
|
||||
integrity sha1-wsBMZr+HJo+4LBd6zr7/fTe6by0=
|
||||
dependencies:
|
||||
underscore ">=1.5.0"
|
||||
|
||||
backo2@1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947"
|
||||
@ -6314,6 +6339,11 @@ cli-width@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
|
||||
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
|
||||
|
||||
clipboard-js@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/clipboard-js/-/clipboard-js-0.2.0.tgz#ba1a6092ccbce43ccc9817407737692e432f409b"
|
||||
integrity sha1-uhpgksy85DzMmBdAdzdpLkMvQJs=
|
||||
|
||||
clipboard@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz#836dafd66cf0fea5d71ce5d5b0bf6e958009112d"
|
||||
@ -7139,6 +7169,11 @@ css-what@2.1, css-what@^2.1.2:
|
||||
resolved "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d"
|
||||
integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==
|
||||
|
||||
css.escape@1.5.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.0.tgz#95984d7887ce4ca90684e813966f42d1ef87ecea"
|
||||
integrity sha1-lZhNeIfOTKkGhOgTlm9C0e+H7Oo=
|
||||
|
||||
cssesc@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
|
||||
@ -9054,6 +9089,11 @@ fake-tag@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/fake-tag/-/fake-tag-1.0.1.tgz#1d59da482240a02bd83500ca98976530ed154b0d"
|
||||
integrity sha512-qmewZoBpa71mM+y6oxXYW/d1xOYQmeIvnEXAt1oCmdP0sqcogWYLepR87QL1jQVLSVMVYDq2cjY6ec/Wu8/4pg==
|
||||
|
||||
fancy-file-input@2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/fancy-file-input/-/fancy-file-input-2.0.3.tgz#4b2d6a6313b8a62127e52a548a8d6995f46446ce"
|
||||
integrity sha512-9StOIYtZWoGGWG/OAuI3PmFDNI+TRiTb7nYyglQL2OZPbmp2lw0/qwIEoRD3s5YtNyf078oDTY4xIIDQzk/pdA==
|
||||
|
||||
fancy-log@^1.3.2, fancy-log@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7"
|
||||
@ -15068,6 +15108,11 @@ oauth-sign@~0.9.0:
|
||||
resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
|
||||
|
||||
object-assign@4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz#99504456c3598b5cad4fc59c26e8a9bb107fe0bd"
|
||||
integrity sha1-mVBEVsNZi1ytT8WcJuipuxB/4L0=
|
||||
|
||||
object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
@ -18863,6 +18908,16 @@ sisteransi@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c"
|
||||
integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==
|
||||
|
||||
skatejs-template-html@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.npmjs.org/skatejs-template-html/-/skatejs-template-html-0.0.0.tgz#e990c1a7d4b58b7305ffcc3338939bf402023df7"
|
||||
integrity sha1-6ZDBp9S1i3MF/8wzOJOb9AICPfc=
|
||||
|
||||
skatejs@0.13.17:
|
||||
version "0.13.17"
|
||||
resolved "https://registry.npmjs.org/skatejs/-/skatejs-0.13.17.tgz#7a21fbb3434da45e52b47b61647168ee9e778071"
|
||||
integrity sha1-eiH7s0NNpF5StHthZHFo7p53gHE=
|
||||
|
||||
slash@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
|
||||
@ -19128,7 +19183,8 @@ source-map@^0.7.3:
|
||||
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
|
||||
|
||||
"sourcegraph@link:packages/sourcegraph-extension-api":
|
||||
version "24.4.0"
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
space-separated-tokens@^1.0.0:
|
||||
version "1.1.2"
|
||||
@ -20127,6 +20183,10 @@ test-exclude@^6.0.0:
|
||||
glob "^7.1.4"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
tether@atlassian/tether#amd-with-global:
|
||||
version "0.6.5"
|
||||
resolved "https://codeload.github.com/atlassian/tether/tar.gz/bf85430889b5231fbe5b383416cce6281225bf06"
|
||||
|
||||
text-table@0.2.0, text-table@^0.2.0, text-table@~0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
|
||||
@ -20415,6 +20475,11 @@ treeify@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8"
|
||||
integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==
|
||||
|
||||
trim-extra-html-whitespace@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/trim-extra-html-whitespace/-/trim-extra-html-whitespace-1.3.0.tgz#b47efb0d1a5f2a56a85cc45cea525651e93404cf"
|
||||
integrity sha1-tH77DRpfKlaoXMRc6lJWUek0BM8=
|
||||
|
||||
trim-newlines@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
||||
@ -20627,6 +20692,11 @@ unc-path-regex@^0.1.2:
|
||||
resolved "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
|
||||
|
||||
underscore@1.6.0, underscore@>=1.5.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
|
||||
integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
|
||||
|
||||
undertaker-registry@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50"
|
||||
@ -21322,6 +21392,11 @@ web-namespaces@^1.1.2:
|
||||
resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-1.1.2.tgz#c8dc267ab639505276bae19e129dbd6ae72b22b4"
|
||||
integrity sha512-II+n2ms4mPxK+RnIxRPOw3zwF2jRscdJIUE9BfkKHm4FYEg9+biIoTMnaZF5MpemE3T+VhMLrhbyD4ilkPCSbg==
|
||||
|
||||
webcomponents.js@0.7.20:
|
||||
version "0.7.20"
|
||||
resolved "https://registry.npmjs.org/webcomponents.js/-/webcomponents.js-0.7.20.tgz#36727218fbf59433ae10139e59eaf3cfdddc11c5"
|
||||
integrity sha1-NnJyGPv1lDOuEBOeWerzz93cEcU=
|
||||
|
||||
webext-additional-permissions@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/webext-additional-permissions/-/webext-additional-permissions-1.0.0.tgz#fe8977f702bcb7a3aa23d09efc87f308135b4fca"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user