Overhaul tooltip close button & badge styles (#10956)

This commit is contained in:
Felix Becker 2020-05-27 08:47:18 +02:00 committed by GitHub
parent 9b97ad3752
commit 72469d8ef9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 685 additions and 501 deletions

View File

@ -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
},
}

View File

@ -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

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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],
},

View File

@ -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

View File

@ -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',
},

View File

@ -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;
}

View File

@ -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;

View File

@ -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",

View File

@ -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);
}
}
}

View File

@ -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>
`)
}

View File

@ -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()
})
})

View File

@ -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>
)
}

View File

@ -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>
`;

View File

@ -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
}

View File

@ -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

View File

@ -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;
}
}

View 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}
/>
</>
))

View File

@ -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">&lt;&gt;</code></pre>')
})
})
})

View File

@ -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}

View File

@ -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>
`;

View File

@ -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';

View File

@ -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"

View File

@ -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"