diff --git a/client/browser/src/shared/code-hosts/bitbucket-cloud/codeHost.ts b/client/browser/src/shared/code-hosts/bitbucket-cloud/codeHost.ts index ebc538f6144..ef14f6e841e 100644 --- a/client/browser/src/shared/code-hosts/bitbucket-cloud/codeHost.ts +++ b/client/browser/src/shared/code-hosts/bitbucket-cloud/codeHost.ts @@ -188,6 +188,7 @@ export const bitbucketCloudCodeHost: CodeHost = { }, hoverOverlayClassProps: { className: styles.hoverOverlay, + closeButtonClassName: styles.close, badgeClassName: styles.badge, actionItemClassName: styles.hoverOverlayActionItem, iconClassName: styles.icon, diff --git a/client/browser/src/shared/code-hosts/github/codeHost.tsx b/client/browser/src/shared/code-hosts/github/codeHost.tsx index cc473ac3049..7cd2ccf975f 100644 --- a/client/browser/src/shared/code-hosts/github/codeHost.tsx +++ b/client/browser/src/shared/code-hosts/github/codeHost.tsx @@ -744,6 +744,7 @@ export const githubCodeHost: GithubCodeHost = { className: 'Box', actionItemClassName: 'btn btn-sm btn-secondary', actionItemPressedClassName: 'active', + closeButtonClassName: 'btn-octicon p-0 hover-overlay__close-button--github', badgeClassName: classNames('label', styles.hoverOverlayBadge), getAlertClassName: createNotificationClassNameGetter(notificationClassNames, 'flash-full'), iconClassName, diff --git a/client/browser/src/shared/code-hosts/gitlab/codeHost.ts b/client/browser/src/shared/code-hosts/gitlab/codeHost.ts index e0d2e026496..dcc28e88fc6 100644 --- a/client/browser/src/shared/code-hosts/gitlab/codeHost.ts +++ b/client/browser/src/shared/code-hosts/gitlab/codeHost.ts @@ -269,6 +269,7 @@ export const gitlabCodeHost = subtypeOf()({ className: classNames('card', styles.hoverOverlay), actionItemClassName: 'btn btn-secondary', actionItemPressedClassName: 'active', + closeButtonClassName: 'btn btn-transparent p-0 btn-icon--gitlab', iconClassName: 'square s16', getAlertClassName: createNotificationClassNameGetter(notificationClassNames), }, diff --git a/client/browser/src/shared/code-hosts/phabricator/codeHost.ts b/client/browser/src/shared/code-hosts/phabricator/codeHost.ts index cf12c1209fc..f0fe777b29a 100644 --- a/client/browser/src/shared/code-hosts/phabricator/codeHost.ts +++ b/client/browser/src/shared/code-hosts/phabricator/codeHost.ts @@ -199,6 +199,7 @@ export const phabricatorCodeHost: CodeHost = { hoverOverlayClassProps: { className: classNames('aphront-dialog-view', styles.hoverOverlay), actionItemClassName: classNames('button grey', styles.hoverOverlayActionItem), + closeButtonClassName: 'button grey btn-icon--phabricator', iconClassName: styles.hoverOverlayActionItemIcon, getAlertClassName: createNotificationClassNameGetter(notificationClassNames), }, diff --git a/client/codeintellify/src/hoverifier.ts b/client/codeintellify/src/hoverifier.ts index bc12963f86c..0adaf21b42a 100644 --- a/client/codeintellify/src/hoverifier.ts +++ b/client/codeintellify/src/hoverifier.ts @@ -33,7 +33,9 @@ import { mergeMap, delay, startWith, + tap, } from 'rxjs/operators' +import { Key } from 'ts-key-enum' import { asError, ErrorLike, isErrorLike } from '@sourcegraph/common' import { Position, Range } from '@sourcegraph/extension-api-types' @@ -47,6 +49,7 @@ import { convertNode, DiffPart, DOMFunctions, + findElementWithOffset, getCodeElementsInRange, getTokenAtPositionOrRange, HoveredToken, @@ -78,6 +81,14 @@ export interface HoverifierOptions { relativeElement?: HTMLElement }> + pinOptions?: { + /** Emit on this Observable to pin the popover. */ + pins: Subscribable + + /** * Emit on this Observable when the close button in the HoverOverlay was clicked */ + closeButtonClicks: Subscribable + } + hoverOverlayElements: Subscribable /** @@ -235,6 +246,9 @@ export interface HoverifyOptions * @template A The type of an action. */ export interface HoverState { + /** The currently hovered token */ + hoveredToken?: HoveredToken & C + /** * The currently hovered and highlighted HTML element. */ @@ -250,6 +264,8 @@ export interface HoverState { */ hoverOverlayProps?: Pick, Exclude, 'actionComponent'>> + pinned?: boolean + /** * The highlighted range, which is the range in the hover result or else the range of the hovered token. */ @@ -271,6 +287,8 @@ export interface HoverState { interface InternalHoverifierState { hoverOrError?: typeof LOADING | (HoverAttachment & D) | null | ErrorLike + pinned: boolean + /** The desired position of the hover overlay */ hoverOverlayPosition?: { left: number } & ({ top: number } | { bottom: number }) @@ -312,7 +330,7 @@ interface InternalHoverifierState { * (because there is no content, or because it is still loading). */ const shouldRenderOverlay = (state: InternalHoverifierState<{}, {}, {}>): boolean => - !state.mouseIsMoving && + !(!state.pinned && state.mouseIsMoving) && ((!!state.hoverOrError && state.hoverOrError !== LOADING) || (!!state.actionsOrError && state.actionsOrError !== LOADING && @@ -328,6 +346,7 @@ const shouldRenderOverlay = (state: InternalHoverifierState<{}, {}, {}>): boolea const internalToExternalState = ( internalState: InternalHoverifierState ): HoverState => ({ + hoveredToken: internalState.hoveredToken, hoveredTokenElement: internalState.hoveredTokenElement, actionsOrError: internalState.actionsOrError, selectedPosition: internalState.selectedPosition, @@ -340,6 +359,7 @@ const internalToExternalState = ( actionsOrError: internalState.actionsOrError, } : undefined, + pinned: internalState.pinned, }) /** The time in ms after which to show a loader if the result has not returned yet */ @@ -395,6 +415,7 @@ export type ContextResolver = (hoveredToken: HoveredToken) => * @template A The type of an action. */ export function createHoverifier({ + pinOptions, hoverOverlayElements, hoverOverlayRerenders, getHover, @@ -408,6 +429,7 @@ export function createHoverifier({ // Shared between all hoverified code views const container = createObservableStateContainer>({ hoveredTokenElement: undefined, + pinned: false, hoveredToken: undefined, hoverOrError: undefined, hoverOverlayPosition: undefined, @@ -444,6 +466,7 @@ export function createHoverifier({ ): event is MouseEventTrigger & { eventType: T } => event.eventType === type const allCodeMouseMoves = allPositionsFromEvents.pipe(filter(isEventType('mousemove')), suppressWhileOverlayShown()) const allCodeMouseOvers = allPositionsFromEvents.pipe(filter(isEventType('mouseover')), suppressWhileOverlayShown()) + const allCodeClicks = allPositionsFromEvents.pipe(filter(isEventType('click'))) const allPositionJumps = new Subject>() @@ -484,6 +507,8 @@ export function createHoverifier({ ...rest, })), debounceTime(MOUSEOVER_DELAY), + // Do not consider mouseovers while overlay is pinned + filter(() => !container.values.pinned), switchMap(({ adjustPosition, codeView, resolveContext, position, ...rest }) => adjustPosition && position ? from( @@ -505,6 +530,51 @@ export function createHoverifier({ ), share() ) + /** + * Emits DOM elements at new positions found in the URL. When pinning is + * disabled, this does not emit at all because the tooltip doesn't get + * pinned at the jump target. + */ + const jumpTargets = allPositionJumps.pipe( + // Only use line and character for comparison + map(({ position: { line, character, part }, ...rest }) => ({ + position: { line, character, part }, + ...rest, + })), + // Ignore same values + // It's important to do this before filtering otherwise navigating from + // a position, to a line-only position, back to the first position would get ignored + distinctUntilChanged((a, b) => isEqual(a, b)), + map(({ position, codeView, dom, overrideTokenize, ...rest }) => { + let cell: HTMLElement | null + let target: HTMLElement | undefined + let part: DiffPart | undefined + if (isPosition(position)) { + cell = dom.getCodeElementFromLineNumber(codeView, position.line, position.part) + if (cell) { + target = findElementWithOffset( + cell, + { offsetStart: position.character }, + shouldTokenize({ tokenize, overrideTokenize }) + ) + if (target) { + part = dom.getDiffCodePart?.(target) + } else { + console.warn('Could not find target for position in file', position) + } + } + } + return { + ...rest, + eventType: 'jump' as const, + target, + position: { ...position, part }, + codeView, + dom, + overrideTokenize, + } + }) + ) // REPOSITIONING // On every componentDidUpdate (after the component was rerendered, e.g. from a hover state update) resposition @@ -516,7 +586,7 @@ export function createHoverifier({ from(hoverOverlayRerenders) .pipe( // with the latest target that came from either a mouseover, click or location change (whatever was the most recent) - withLatestFrom(merge(codeMouseOverTargets)), + withLatestFrom(merge(codeMouseOverTargets, jumpTargets)), map( ([ { hoverOverlayElement, relativeElement }, @@ -586,7 +656,7 @@ export function createHoverifier({ ) /** Emits new positions including context at which a tooltip needs to be shown from clicks, mouseovers and URL changes. */ - const resolvedPositionEvents = merge(codeMouseOverTargets).pipe( + const resolvedPositionEvents = merge(codeMouseOverTargets, jumpTargets).pipe( map(({ position, resolveContext, eventType, ...rest }) => ({ ...rest, eventType, @@ -650,9 +720,9 @@ export function createHoverifier({ hoveredTokenElement, scrollBoundaries, ...rest - }: Omit, 'mouseIsMoving' | 'hoverOverlayIsFixed'> & + }: Omit, 'mouseIsMoving' | 'pinned'> & Omit, 'resolveContext' | 'dom'> & { codeView: HTMLElement }): Observable< - Omit, 'mouseIsMoving' | 'hoverOverlayIsFixed'> & { codeView: HTMLElement } + Omit, 'mouseIsMoving' | 'pinned'> & { codeView: HTMLElement } > => { const result = of({ hoveredTokenElement, ...rest }) if (!hoveredTokenElement || !scrollBoundaries) { @@ -666,7 +736,7 @@ export function createHoverifier({ mapTo({ ...rest, hoveredTokenElement, - hoverOverlayIsFixed: false, + pinned: false, hoverOrError: undefined, hoveredToken: undefined, actionsOrError: undefined, @@ -960,12 +1030,28 @@ export function createHoverifier({ const resetHover = (): void => { container.update({ hoverOverlayPosition: undefined, + pinned: false, hoverOrError: undefined, hoveredToken: undefined, actionsOrError: undefined, }) } + // Pin on request. + subscription.add(pinOptions?.pins.subscribe(() => container.update({ pinned: true }))) + + // Unpin on close, ESC, or click. + subscription.add( + merge( + pinOptions?.closeButtonClicks ?? EMPTY, + fromEvent(window, 'keydown').pipe( + filter(event => event.key === Key.Escape), + tap(event => event.preventDefault()) + ), + allCodeClicks + ).subscribe(() => resetHover()) + ) + // LOCATION CHANGES subscription.add( allPositionJumps.subscribe( diff --git a/client/shared/src/hover/CopyLinkIcon.tsx b/client/shared/src/hover/CopyLinkIcon.tsx new file mode 100644 index 00000000000..f38c8da6902 --- /dev/null +++ b/client/shared/src/hover/CopyLinkIcon.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +export const CopyLinkIcon: React.FunctionComponent = () => ( + + + +) diff --git a/client/shared/src/hover/HoverOverlay.module.scss b/client/shared/src/hover/HoverOverlay.module.scss index 903f033c27e..5d211ed4629 100644 --- a/client/shared/src/hover/HoverOverlay.module.scss +++ b/client/shared/src/hover/HoverOverlay.module.scss @@ -99,6 +99,24 @@ padding-right: var(--hover-overlay-horizontal-padding); } +.actions-container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.actions-copy-link { + margin-right: var(--hover-overlay-horizontal-padding); + font-size: 0.75rem; + color: var(--link-color); + border-style: none; + background: transparent; + + > span:last-child { + padding-left: 0.2rem; + } +} + .actions-inner { margin-right: auto; white-space: nowrap; diff --git a/client/shared/src/hover/HoverOverlay.tsx b/client/shared/src/hover/HoverOverlay.tsx index 33a100c8296..f63b9b36b5b 100644 --- a/client/shared/src/hover/HoverOverlay.tsx +++ b/client/shared/src/hover/HoverOverlay.tsx @@ -1,9 +1,11 @@ import React, { CSSProperties } from 'react' import classNames from 'classnames' +import CloseIcon from 'mdi-react/CloseIcon' import { isErrorLike, sanitizeClass } from '@sourcegraph/common' -import { Card } from '@sourcegraph/wildcard' +// eslint-disable-next-line no-restricted-imports +import { Card, Icon } from '@sourcegraph/wildcard' import { ActionItem, ActionItemComponentProps } from '../actions/ActionItem' import { NotificationType } from '../api/extension/extensionHostApi' @@ -11,6 +13,8 @@ import { PlatformContextProps } from '../platform/context' import { TelemetryProps } from '../telemetry/telemetryService' import { ThemeProps } from '../theme' +import { CopyLinkIcon } from './CopyLinkIcon' +import { toNativeEvent } from './helpers' import type { HoverContext, HoverOverlayBaseProps, GetAlertClassName, GetAlertVariant } from './HoverOverlay.types' import { HoverOverlayAlerts, HoverOverlayAlertsProps } from './HoverOverlayAlerts' import { HoverOverlayContents } from './HoverOverlayContents' @@ -22,11 +26,15 @@ import style from './HoverOverlayContents.module.scss' const LOADING = 'loading' as const +const transformMouseEvent = (handler: (event: MouseEvent) => void) => (event: React.MouseEvent) => + handler(toNativeEvent(event)) + export type { HoverContext } export interface HoverOverlayClassProps { /** An optional class name to apply to the outermost element of the HoverOverlay */ className?: string + closeButtonClassName?: string iconClassName?: string badgeClassName?: string @@ -58,10 +66,23 @@ export interface HoverOverlayProps /** A ref callback to get the root overlay element. Use this to calculate the position. */ hoverRef?: React.Ref + pinOptions?: PinOptions + /** Show Sourcegraph logo alongside prompt */ useBrandedLogo?: boolean } +export interface PinOptions { + /** Whether to show the close button for the hover overlay */ + showCloseButton: boolean + + /** Called when the close button is clicked */ + onCloseButtonClick?: () => void + + /** Called when the copy link button is clicked */ + onCopyLinkButtonClick?: () => void +} + const getOverlayStyle = (overlayPosition: HoverOverlayProps['overlayPosition']): CSSProperties => { if (!overlayPosition) { return { @@ -90,9 +111,11 @@ export const HoverOverlay: React.FunctionComponent + {pinOptions?.showCloseButton && ( + + )} )} - {actionsOrError !== undefined && - actionsOrError !== null && - actionsOrError !== LOADING && - !isErrorLike(actionsOrError) && - actionsOrError.length > 0 && ( -
-
- {actionsOrError.map((action, index) => ( - - ))} -
+
+ {actionsOrError !== undefined && + actionsOrError !== null && + actionsOrError !== LOADING && + !isErrorLike(actionsOrError) && + actionsOrError.length > 0 && ( +
+
+ {actionsOrError.map((action, index) => ( + + ))} +
- {useBrandedLogo && } -
+ {useBrandedLogo && } +
+ )} + + {pinOptions && ( + )} +
) } diff --git a/client/shared/src/hover/__snapshots__/HoverOverlay.test.tsx.snap b/client/shared/src/hover/__snapshots__/HoverOverlay.test.tsx.snap index 423dbad4207..7c3770cf98e 100644 --- a/client/shared/src/hover/__snapshots__/HoverOverlay.test.tsx.snap +++ b/client/shared/src/hover/__snapshots__/HoverOverlay.test.tsx.snap @@ -17,6 +17,9 @@ exports[`HoverOverlay actions and hover empty 1`] = ` No hover information available. +
`; @@ -40,6 +43,9 @@ exports[`HoverOverlay actions and hover error 1`] = ` M2 +
`; @@ -63,6 +69,9 @@ exports[`HoverOverlay actions and hover loading 1`] = ` /> +
`; @@ -90,19 +99,23 @@ exports[`HoverOverlay actions and hover present 1`] = ` @@ -122,6 +135,9 @@ exports[`HoverOverlay actions empty 1`] = ` class="hoverOverlayContents" data-testid="hover-overlay-contents" /> +
`; @@ -150,6 +166,9 @@ exports[`HoverOverlay actions error, hover present 1`] = ` +
`; @@ -165,6 +184,9 @@ exports[`HoverOverlay actions loading 1`] = ` class="hoverOverlayContents" data-testid="hover-overlay-contents" /> +
`; @@ -181,19 +203,23 @@ exports[`HoverOverlay actions present 1`] = ` data-testid="hover-overlay-contents" /> @@ -220,19 +246,23 @@ exports[`HoverOverlay actions present, hover loading 1`] = ` @@ -299,19 +329,23 @@ exports[`HoverOverlay actions, hover and alert present 1`] = ` @@ -339,6 +373,9 @@ exports[`HoverOverlay hover error 1`] = ` M +
`; @@ -363,19 +400,23 @@ exports[`HoverOverlay hover error, actions present 1`] = ` @@ -401,6 +442,9 @@ exports[`HoverOverlay hover loading 1`] = ` /> +
`; @@ -427,6 +471,9 @@ exports[`HoverOverlay hover present 1`] = ` +
`; @@ -453,6 +500,9 @@ exports[`HoverOverlay hover present, actions loading 1`] = ` +
`; @@ -490,6 +540,9 @@ exports[`HoverOverlay multiple hovers present 1`] = ` +
`; diff --git a/client/web/src/components/WebHoverOverlay/WebHoverOverlay.tsx b/client/web/src/components/WebHoverOverlay/WebHoverOverlay.tsx index 591fb7e7674..f60d47fb28d 100644 --- a/client/web/src/components/WebHoverOverlay/WebHoverOverlay.tsx +++ b/client/web/src/components/WebHoverOverlay/WebHoverOverlay.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect } from 'react' +import classNames from 'classnames' import { fromEvent } from 'rxjs' import { finalize, tap } from 'rxjs/operators' @@ -121,6 +122,7 @@ export const WebHoverOverlay: React.FunctionComponent> = [hoverOverlayElements] ) - const codeViewElements = useMemo(() => new ReplaySubject(1), []) + const codeViewElementsSubject = useMemo(() => new ReplaySubject(1), []) + const codeViewElements = useMemo(() => codeViewElementsSubject.pipe(share()), [codeViewElementsSubject]) const codeViewReference = useRef() const nextCodeViewElement = useCallback( (codeView: HTMLElement | null) => { codeViewReference.current = codeView - codeViewElements.next(codeView) + codeViewElementsSubject.next(codeView) }, - [codeViewElements] + [codeViewElementsSubject] ) + // Emits on changes from URL search params + const urlSearchParameters = useMemo(() => new ReplaySubject(1), []) + const nextUrlSearchParameters = useCallback((value: URLSearchParams) => urlSearchParameters.next(value), [ + urlSearchParameters, + ]) + useEffect(() => { + nextUrlSearchParameters(new URLSearchParams(location.search)) + }, [nextUrlSearchParameters, location.search]) + // Emits on position changes from URL hash const locationPositions = useMemo(() => new ReplaySubject(1), []) const nextLocationPosition = useCallback( @@ -253,9 +277,47 @@ export const Blob: React.FunctionComponent> = const [decorationsOrError, setDecorationsOrError] = useState() + const popoverCloses = useMemo(() => new Subject(), []) + const nextPopoverClose = useCallback((click: void) => popoverCloses.next(click), [popoverCloses]) + + useObservable( + useMemo( + () => + popoverCloses.pipe( + withLatestFrom(urlSearchParameters), + tap(([, parameters]) => { + parameters.delete('popover') + props.history.push({ + ...location, + search: formatSearchParameters(parameters), + }) + }) + ), + [location, popoverCloses, props.history, urlSearchParameters] + ) + ) + + const popoverParameter = useMemo(() => urlSearchParameters.pipe(map(parameters => parameters.get('popover'))), [ + urlSearchParameters, + ]) + const hoverifier = useMemo( () => createHoverifier({ + pinOptions: { + pins: popoverParameter.pipe( + filter(value => value === 'pinned'), + mapTo(undefined) + ), + closeButtonClicks: merge( + popoverCloses, + popoverParameter.pipe( + pairwise(), + filter(([previous, next]) => previous === 'pinned' && next !== 'pinned'), + mapTo(undefined) + ) + ), + }, hoverOverlayElements, hoverOverlayRerenders: rerenders.pipe( withLatestFrom(hoverOverlayElements, blobElements), @@ -279,12 +341,13 @@ export const Blob: React.FunctionComponent> = getActions: context => getHoverActions({ extensionsController, platformContext }, context), }), [ - // None of these dependencies are likely to change + popoverParameter, + popoverCloses, + hoverOverlayElements, + rerenders, + blobElements, extensionsController, platformContext, - hoverOverlayElements, - blobElements, - rerenders, ] ) @@ -326,26 +389,24 @@ export const Blob: React.FunctionComponent> = query = toPositionOrRangeQueryParameter({ position }) } - if (position && !('character' in position)) { - // Only change the URL when clicking on blank space on the line (not on - // characters). Otherwise, this would interfere with go to definition. - props.history.push({ - ...location, - search: formatSearchParameters( - addLineRangeQueryParameter(new URLSearchParams(location.search), query) - ), - }) - } + const parameters = new URLSearchParams(location.search) + parameters.delete('popover') + nextPopoverClose() + + props.history.push({ + ...location, + search: formatSearchParameters(addLineRangeQueryParameter(parameters, query)), + }) }), mapTo(undefined) ), - [codeViewElements, hoverifier, props.history, location] + [codeViewElements, hoverifier.hoverState.selectedPosition, location, nextPopoverClose, props.history] ) ) // Trigger line highlighting after React has finished putting new lines into the DOM via // `dangerouslySetInnerHTML`. - useEffect(() => codeViewElements.next(codeViewReference.current)) + useEffect(() => codeViewElementsSubject.next(codeViewReference.current)) // Line highlighting when position in hash changes useObservable( @@ -438,11 +499,11 @@ export const Blob: React.FunctionComponent> = filter(isDefined), findPositionsFromEvents({ domFunctions }) ), - positionJumps: locationPositions.pipe( - withLatestFrom( - codeViewElements.pipe(filter(isDefined)), - blobElements.pipe(filter(isDefined)) - ), + positionJumps: combineLatest([ + locationPositions, + codeViewElements.pipe(filter(isDefined)), + blobElements.pipe(filter(isDefined)), + ]).pipe( map(([position, codeView, scrollElement]) => ({ position, // locationPositions is derived from componentUpdates, @@ -540,7 +601,8 @@ export const Blob: React.FunctionComponent> = ) // Passed to HoverOverlay - const hoverState = useObservable(hoverifier.hoverStateUpdates) || {} + const hoverState: Readonly> = + useObservable(hoverifier.hoverStateUpdates) || {} // Status bar const getStatusBarItems = useCallback( @@ -604,6 +666,38 @@ export const Blob: React.FunctionComponent> = ) ) + const pinOptions = useMemo( + () => ({ + showCloseButton: true, + onCloseButtonClick: nextPopoverClose, + onCopyLinkButtonClick: async () => { + const line = hoverifier.hoverState.hoveredToken?.line + const character = hoverifier.hoverState.hoveredToken?.character + if (line === undefined || character === undefined) { + return + } + const point = { line, character } + const range = { start: point, end: point } + const context = { position: point, range } + const search = new URLSearchParams(location.search) + search.set('popover', 'pinned') + props.history.push({ + search: formatSearchParameters( + addLineRangeQueryParameter(search, toPositionOrRangeQueryParameter(context)) + ), + }) + await navigator.clipboard.writeText(window.location.href) + }, + }), + [ + hoverifier.hoverState.hoveredToken?.line, + hoverifier.hoverState.hoveredToken?.character, + location.search, + nextPopoverClose, + props.history, + ] + ) + return ( <>
@@ -621,6 +715,7 @@ export const Blob: React.FunctionComponent> = nav={url => (props.nav ? props.nav(url) : props.history.push(url))} hoveredTokenElement={hoverState.hoveredTokenElement} hoverRef={nextOverlayElement} + pinOptions={pinOptions} extensionsController={extensionsController} /> )} @@ -636,7 +731,7 @@ export const Blob: React.FunctionComponent> = getCodeElementFromLineNumber={domFunctions.getCodeElementFromLineNumber} line={line} decorations={decorations} - codeViewElements={codeViewElements} + codeViewElements={codeViewElementsSubject} /> ) })