mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 19:51:50 +00:00
Add extension hovers to Bitbucket Server/Data Center v7+ (#25020)
This commit is contained in:
parent
246c9c6581
commit
3b08d82a89
@ -12,7 +12,7 @@ import { createNotificationClassNameGetter } from '../shared/getNotificationClas
|
||||
import { ViewResolver } from '../shared/views'
|
||||
|
||||
import { getContext } from './context'
|
||||
import { diffDOMFunctions, singleFileDOMFunctions } from './domFunctions'
|
||||
import { diffDOMFunctions, newDiffDOMFunctions, singleFileDOMFunctions } from './domFunctions'
|
||||
import {
|
||||
resolveCommitViewFileInfo,
|
||||
resolveCompareFileInfo,
|
||||
@ -25,13 +25,16 @@ import { isCommitsView, isCompareView, isPullRequestView, isSingleFileView } fro
|
||||
/**
|
||||
* Gets or creates the toolbar mount for allcode views.
|
||||
*/
|
||||
export const getToolbarMount = (codeView: HTMLElement): HTMLElement => {
|
||||
export const getToolbarMount = (
|
||||
codeView: HTMLElement,
|
||||
fileToolbarSelector = '.file-toolbar .secondary'
|
||||
): HTMLElement => {
|
||||
const existingMount = codeView.querySelector<HTMLElement>('.sg-toolbar-mount')
|
||||
if (existingMount) {
|
||||
return existingMount
|
||||
}
|
||||
|
||||
const fileActions = codeView.querySelector<HTMLElement>('.file-toolbar .secondary')
|
||||
const fileActions = codeView.querySelector<HTMLElement>(fileToolbarSelector)
|
||||
if (!fileActions) {
|
||||
throw new Error('Unable to find mount location')
|
||||
}
|
||||
@ -80,10 +83,6 @@ const createPositionAdjuster = (
|
||||
return of(newPosition)
|
||||
}
|
||||
|
||||
const toolbarButtonProps = {
|
||||
className: 'aui-button',
|
||||
}
|
||||
|
||||
/**
|
||||
* A code view spec for single file code view in the "source" view (not diff).
|
||||
*/
|
||||
@ -92,14 +91,14 @@ const singleFileSourceCodeView: Omit<CodeView, 'element'> = {
|
||||
dom: singleFileDOMFunctions,
|
||||
resolveFileInfo: resolveFileInfoForSingleFileSourceView,
|
||||
getPositionAdjuster: () => createPositionAdjuster(singleFileDOMFunctions),
|
||||
toolbarButtonProps,
|
||||
}
|
||||
|
||||
const baseDiffCodeView: Omit<CodeView, 'element' | 'resolveFileInfo'> = {
|
||||
getToolbarMount,
|
||||
dom: diffDOMFunctions,
|
||||
getPositionAdjuster: () => createPositionAdjuster(diffDOMFunctions),
|
||||
toolbarButtonProps,
|
||||
// Bitbucket diff views are not tokenized.
|
||||
overrideTokenize: true,
|
||||
}
|
||||
/**
|
||||
* A code view spec for a single file "diff to previous" view
|
||||
@ -158,6 +157,31 @@ const codeViewResolver: ViewResolver<CodeView> = {
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* New diff code view resolver.
|
||||
* As of Bitbucket v7.11.2, this is only used for the pull request page.
|
||||
*/
|
||||
const diffCodeViewResolver: ViewResolver<CodeView> = {
|
||||
selector: '.change-view',
|
||||
resolveView: element => ({ element, ...newDiffCodeView }),
|
||||
}
|
||||
|
||||
const newDiffToolbarButtonProps = {
|
||||
listItemClass: 'action-nav-item--bitbucket-server-new-diff',
|
||||
actionItemClass: 'action-item--bitbucket-server-new-diff',
|
||||
}
|
||||
|
||||
/**
|
||||
* New diff code view element.
|
||||
* As of Bitbucket v7.11.2, this is only used for the pull request page.
|
||||
*/
|
||||
const newDiffCodeView: Omit<CodeView, 'element'> = {
|
||||
resolveFileInfo: resolvePullRequestFileInfo,
|
||||
getToolbarMount: codeView => getToolbarMount(codeView, '.change-header .diff-actions'),
|
||||
toolbarButtonProps: newDiffToolbarButtonProps,
|
||||
dom: newDiffDOMFunctions,
|
||||
}
|
||||
|
||||
const getCommandPaletteMount: MountGetter = (container: HTMLElement): HTMLElement | null => {
|
||||
const headerElement = querySelectorOrSelf(container, '.aui-header-primary .aui-nav')
|
||||
if (!headerElement) {
|
||||
@ -208,7 +232,7 @@ export const bitbucketServerCodeHost: CodeHost = {
|
||||
type: 'bitbucket-server',
|
||||
name: 'Bitbucket Server',
|
||||
check: checkIsBitbucket,
|
||||
codeViewResolvers: [codeViewResolver],
|
||||
codeViewResolvers: [codeViewResolver, diffCodeViewResolver],
|
||||
getCommandPaletteMount,
|
||||
notificationClassNames,
|
||||
commandPaletteClassProps: {
|
||||
|
||||
@ -106,3 +106,71 @@ export const diffDOMFunctions: DOMFunctions = {
|
||||
},
|
||||
isFirstCharacterDiffIndicator: () => false,
|
||||
}
|
||||
|
||||
const newGetDiffLineElementFromLineNumber = (codeView: HTMLElement, line: number, part?: DiffPart): HTMLElement => {
|
||||
for (const lineNumberElement of codeView.querySelectorAll<HTMLElement>('.diff-line-number')) {
|
||||
// For unchanged lines, `textContent` will be e.g. ' 4 4 ' (in one element).
|
||||
const lineNumberString = lineNumberElement.textContent?.trim().split(' ')[0]
|
||||
if (!lineNumberString) {
|
||||
continue
|
||||
}
|
||||
const lineNumber = parseInt(lineNumberString, 10)
|
||||
if (!isNaN(lineNumber) && lineNumber === line) {
|
||||
const lineElement = lineNumberElement
|
||||
.closest('tr')
|
||||
?.querySelector<HTMLElement>(`.diff-line[data-diff-line="${part === 'head' ? 'TO' : 'FROM'}"]`)
|
||||
|
||||
if (!lineElement) {
|
||||
throw new Error('Could not find lineElem from lineNumElem')
|
||||
}
|
||||
return lineElement
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Could not locate line number element for line ${line}, part: ${String(part)}`)
|
||||
}
|
||||
|
||||
export const newDiffDOMFunctions: DOMFunctions = {
|
||||
getCodeElementFromTarget: target => {
|
||||
const container = target.closest<HTMLElement>('.diff-line')
|
||||
return container
|
||||
},
|
||||
getLineNumberFromCodeElement: codeElement => {
|
||||
const lineNumberElement = codeElement
|
||||
.closest('tr')
|
||||
?.querySelector<HTMLElement>('.diff-gutter .diff-line-number')
|
||||
|
||||
if (lineNumberElement) {
|
||||
const lineNumber = parseInt((lineNumberElement.textContent || '').trim(), 10)
|
||||
if (!isNaN(lineNumber)) {
|
||||
return lineNumber
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Could not find line number element for code element')
|
||||
},
|
||||
getDiffCodePart: codeElement => {
|
||||
const diffLines = codeElement.closest('tr')?.querySelectorAll<HTMLElement>('.diff-line')
|
||||
|
||||
if (!diffLines || diffLines.length === 0) {
|
||||
// Shouldn't happen since `codeElement` should have the .diff-line class.
|
||||
throw new Error('Could not find diff lines for code element')
|
||||
}
|
||||
if (diffLines.length === 1) {
|
||||
return codeElement.classList.contains('added-line') ? 'head' : 'base'
|
||||
}
|
||||
|
||||
const [baseLine, headLine] = diffLines
|
||||
if (codeElement === baseLine) {
|
||||
return 'base'
|
||||
}
|
||||
if (codeElement === headLine) {
|
||||
return 'head'
|
||||
}
|
||||
|
||||
// Fallback: we can use head hovers for unchanged lines from base side.
|
||||
return codeElement.classList.contains('removed-line') ? 'base' : 'head'
|
||||
},
|
||||
getLineElementFromLineNumber: newGetDiffLineElementFromLineNumber,
|
||||
getCodeElementFromLineNumber: newGetDiffLineElementFromLineNumber,
|
||||
}
|
||||
|
||||
@ -267,7 +267,8 @@ export const getFileInfoWithoutCommitIDsFromMultiFileDiffCodeView = (
|
||||
baseFilePath: string
|
||||
} => {
|
||||
// Get the file path from the breadcrumbs
|
||||
const breadcrumbsElement = codeViewElement.querySelector('.breadcrumbs')
|
||||
const breadcrumbsElement =
|
||||
codeViewElement.querySelector('.breadcrumbs') ?? codeViewElement.querySelector('.file-breadcrumbs')
|
||||
if (!breadcrumbsElement) {
|
||||
throw new Error('Could not find diff code view breadcrumbs element through selector .breadcrumbs')
|
||||
}
|
||||
|
||||
@ -40,6 +40,27 @@
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.action-nav-item--bitbucket-server-new-diff {
|
||||
margin: 0 8px 0 2px;
|
||||
}
|
||||
|
||||
.action-item--bitbucket-server-new-diff {
|
||||
background-color: rgba(9, 30, 66, 0.04);
|
||||
color: rgba(66, 82, 110);
|
||||
transition: background 0.1s ease-out 0s, box-shadow 0.15s cubic-bezier(0.47, 0.03, 0.49, 1.38) 0s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 3px;
|
||||
height: 100%;
|
||||
width: 24px;
|
||||
padding: 0 2px;
|
||||
|
||||
&:hover {
|
||||
background: rgba(9, 30, 66, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
// Use flexbox instead of float, so we can handle wrapping action items
|
||||
.file-toolbar {
|
||||
display: flex;
|
||||
|
||||
@ -68,15 +68,10 @@ export function createFileActionsToolbarMount(codeView: HTMLElement): HTMLElemen
|
||||
return mountElement
|
||||
}
|
||||
|
||||
const toolbarButtonProps = {
|
||||
className: 'btn btn-sm tooltipped tooltipped-s',
|
||||
}
|
||||
|
||||
const diffCodeView: Omit<CodeView, 'element'> = {
|
||||
dom: diffDomFunctions,
|
||||
getToolbarMount: createFileActionsToolbarMount,
|
||||
resolveFileInfo: resolveDiffFileInfo,
|
||||
toolbarButtonProps,
|
||||
getScrollBoundaries: codeView => {
|
||||
const fileHeader = codeView.querySelector<HTMLElement>('.file-header')
|
||||
if (!fileHeader) {
|
||||
@ -95,7 +90,6 @@ const singleFileCodeView: Omit<CodeView, 'element'> = {
|
||||
dom: singleFileDOMFunctions,
|
||||
getToolbarMount: createFileActionsToolbarMount,
|
||||
resolveFileInfo,
|
||||
toolbarButtonProps,
|
||||
getSelections: getSelectionsFromHash,
|
||||
observeSelections: observeSelectionsFromHash,
|
||||
}
|
||||
@ -138,7 +132,6 @@ const searchResultCodeViewResolver = toCodeViewResolver('.code-list-item', {
|
||||
dom: searchCodeSnippetDOMFunctions,
|
||||
getPositionAdjuster: getSnippetPositionAdjuster,
|
||||
resolveFileInfo: resolveSnippetFileInfo,
|
||||
toolbarButtonProps,
|
||||
})
|
||||
|
||||
const snippetCodeView: Omit<CodeView, 'element'> = {
|
||||
|
||||
@ -15,10 +15,6 @@ import { getCommandPaletteMount } from './extensions'
|
||||
import { resolveCommitFileInfo, resolveDiffFileInfo, resolveFileInfo } from './fileInfo'
|
||||
import { getPageInfo, GitLabPageKind, getFilePathsFromCodeView } from './scrape'
|
||||
|
||||
const toolbarButtonProps = {
|
||||
className: 'btn btn-default btn-sm',
|
||||
}
|
||||
|
||||
export function checkIsGitlab(): boolean {
|
||||
return !!document.head.querySelector('meta[content="GitLab"]')
|
||||
}
|
||||
@ -65,7 +61,6 @@ const singleFileCodeView: Omit<CodeView, 'element'> = {
|
||||
dom: singleFileDOMFunctions,
|
||||
getToolbarMount,
|
||||
resolveFileInfo,
|
||||
toolbarButtonProps,
|
||||
getSelections: getSelectionsFromHash,
|
||||
observeSelections: observeSelectionsFromHash,
|
||||
}
|
||||
@ -82,7 +77,6 @@ const mergeRequestCodeView: Omit<CodeView, 'element'> = {
|
||||
dom: diffDOMFunctions,
|
||||
getToolbarMount,
|
||||
resolveFileInfo: resolveDiffFileInfo,
|
||||
toolbarButtonProps,
|
||||
getScrollBoundaries: getFileTitle,
|
||||
}
|
||||
|
||||
@ -90,7 +84,6 @@ const commitCodeView: Omit<CodeView, 'element'> = {
|
||||
dom: diffDOMFunctions,
|
||||
getToolbarMount,
|
||||
resolveFileInfo: resolveCommitFileInfo,
|
||||
toolbarButtonProps,
|
||||
getScrollBoundaries: getFileTitle,
|
||||
}
|
||||
|
||||
|
||||
@ -81,9 +81,6 @@ const getPositionAdjuster = (
|
||||
})
|
||||
)
|
||||
|
||||
const toolbarButtonProps = {
|
||||
className: 'button grey button-grey has-icon has-text phui-button-default msl',
|
||||
}
|
||||
export const commitCodeView = {
|
||||
dom: diffDomFunctions,
|
||||
resolveFileInfo: resolveRevisionFileInfo,
|
||||
@ -106,7 +103,6 @@ export const commitCodeView = {
|
||||
|
||||
return mount
|
||||
},
|
||||
toolbarButtonProps,
|
||||
}
|
||||
|
||||
export const diffCodeView = {
|
||||
@ -129,7 +125,6 @@ export const diffCodeView = {
|
||||
mountLocation.prepend(mount, ' ')
|
||||
return mount
|
||||
},
|
||||
toolbarButtonProps,
|
||||
isDiff: true,
|
||||
}
|
||||
|
||||
@ -163,7 +158,6 @@ const diffusionSourceCodeViewResolver = toCodeViewResolver('.diffusion-source',
|
||||
|
||||
return mount
|
||||
},
|
||||
toolbarButtonProps,
|
||||
})
|
||||
|
||||
// Matches Diffusion single file code views on recent Phabricator versions.
|
||||
|
||||
@ -1000,6 +1000,7 @@ export function handleCodeHost({
|
||||
getPositionAdjuster,
|
||||
getToolbarMount,
|
||||
toolbarButtonProps,
|
||||
overrideTokenize,
|
||||
} = codeViewEvent
|
||||
|
||||
const initializeModelAndViewerForFileInfo = async (
|
||||
@ -1211,7 +1212,9 @@ export function handleCodeHost({
|
||||
positionEvents: of(element).pipe(
|
||||
findPositionsFromEvents({
|
||||
domFunctions,
|
||||
tokenize: codeHost.codeViewsRequireTokenization !== false,
|
||||
tokenize: !!(typeof overrideTokenize === 'boolean'
|
||||
? overrideTokenize
|
||||
: codeHost.codeViewsRequireTokenization),
|
||||
})
|
||||
),
|
||||
resolveContext,
|
||||
@ -1219,6 +1222,7 @@ export function handleCodeHost({
|
||||
scrollBoundaries: codeViewEvent.getScrollBoundaries
|
||||
? codeViewEvent.getScrollBoundaries(codeViewEvent.element)
|
||||
: [],
|
||||
overrideTokenize,
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -1232,6 +1236,10 @@ export function handleCodeHost({
|
||||
render(
|
||||
<CodeViewToolbar
|
||||
{...codeHost.codeViewToolbarClassProps}
|
||||
actionItemClass={
|
||||
codeViewEvent.toolbarButtonProps?.actionItemClass ??
|
||||
codeHost.codeViewToolbarClassProps?.actionItemClass
|
||||
}
|
||||
hideActions={hideActions}
|
||||
fileInfoOrError={diffOrBlobInfo}
|
||||
sourcegraphURL={sourcegraphURL}
|
||||
|
||||
@ -37,6 +37,11 @@ export interface CodeView {
|
||||
element: HTMLElement
|
||||
/** The DOMFunctions for the code view. */
|
||||
dom: DOMFunctions
|
||||
/**
|
||||
* Whether this code view needs to be tokenized.
|
||||
* Used in favor of the `codeViewsRequireTokenization` value for the code host.
|
||||
*/
|
||||
overrideTokenize?: boolean
|
||||
/**
|
||||
* Finds or creates a DOM element where we should inject the
|
||||
* `CodeViewToolbar`. This function is responsible for ensuring duplicate
|
||||
|
||||
@ -19,7 +19,8 @@ import { OpenDiffOnSourcegraph } from './OpenDiffOnSourcegraph'
|
||||
import { OpenOnSourcegraph } from './OpenOnSourcegraph'
|
||||
|
||||
export interface ButtonProps {
|
||||
className?: string
|
||||
listItemClass?: string
|
||||
actionItemClass?: string
|
||||
}
|
||||
|
||||
export interface CodeViewToolbarClassProps extends ActionNavItemsClassProps {
|
||||
@ -46,6 +47,9 @@ export interface CodeViewToolbarProps
|
||||
*/
|
||||
fileInfoOrError: DiffOrBlobInfo<FileInfoWithContent> | ErrorLike
|
||||
|
||||
/**
|
||||
* Code-view specific className overrides.
|
||||
*/
|
||||
buttonProps?: ButtonProps
|
||||
onSignInClose: () => void
|
||||
location: H.Location
|
||||
@ -57,7 +61,11 @@ export const CodeViewToolbar: React.FunctionComponent<CodeViewToolbarProps> = pr
|
||||
{!props.hideActions && (
|
||||
<ActionsNavItems
|
||||
{...props}
|
||||
listItemClass={classNames('code-view-toolbar__item', props.listItemClass)}
|
||||
listItemClass={classNames(
|
||||
'code-view-toolbar__item',
|
||||
props.buttonProps?.listItemClass ?? props.listItemClass
|
||||
)}
|
||||
actionItemClass={classNames(props.buttonProps?.actionItemClass ?? props.actionItemClass)}
|
||||
menu={ContributableMenu.EditorTitle}
|
||||
extensionsController={props.extensionsController}
|
||||
platformContext={props.platformContext}
|
||||
@ -70,18 +78,23 @@ export const CodeViewToolbar: React.FunctionComponent<CodeViewToolbarProps> = pr
|
||||
<SignInButton
|
||||
sourcegraphURL={props.sourcegraphURL}
|
||||
onSignInClose={props.onSignInClose}
|
||||
className={props.actionItemClass}
|
||||
className={classNames(props.buttonProps?.actionItemClass ?? props.actionItemClass)}
|
||||
iconClassName={props.actionItemIconClass}
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<>
|
||||
{!('blob' in props.fileInfoOrError) && props.fileInfoOrError.head && props.fileInfoOrError.base && (
|
||||
<li className={classNames('code-view-toolbar__item', props.listItemClass)}>
|
||||
<li
|
||||
className={classNames(
|
||||
'code-view-toolbar__item',
|
||||
props.buttonProps?.listItemClass ?? props.listItemClass
|
||||
)}
|
||||
>
|
||||
<OpenDiffOnSourcegraph
|
||||
ariaLabel="View file diff on Sourcegraph"
|
||||
platformContext={props.platformContext}
|
||||
className={props.actionItemClass}
|
||||
className={classNames(props.buttonProps?.actionItemClass ?? props.actionItemClass)}
|
||||
iconClassName={props.actionItemIconClass}
|
||||
openProps={{
|
||||
sourcegraphURL: props.sourcegraphURL,
|
||||
@ -100,10 +113,15 @@ export const CodeViewToolbar: React.FunctionComponent<CodeViewToolbarProps> = pr
|
||||
// Only show the "View file" button if we were able to fetch the file contents
|
||||
// from the Sourcegraph instance
|
||||
'blob' in props.fileInfoOrError && props.fileInfoOrError.blob.content !== undefined && (
|
||||
<li className={classNames('code-view-toolbar__item', props.listItemClass)}>
|
||||
<li
|
||||
className={classNames(
|
||||
'code-view-toolbar__item',
|
||||
props.buttonProps?.actionItemClass ?? props.listItemClass
|
||||
)}
|
||||
>
|
||||
<OpenOnSourcegraph
|
||||
ariaLabel="View file on Sourcegraph"
|
||||
className={props.actionItemClass}
|
||||
className={classNames(props.buttonProps?.actionItemClass ?? props.actionItemClass)}
|
||||
iconClassName={props.actionItemIconClass}
|
||||
openProps={{
|
||||
sourcegraphURL: props.sourcegraphURL,
|
||||
|
||||
@ -327,7 +327,7 @@
|
||||
"@reach/visually-hidden": "^0.15.2",
|
||||
"@sentry/browser": "^6.13.2",
|
||||
"@slimsag/react-shortcuts": "^1.2.1",
|
||||
"@sourcegraph/codeintellify": "^7.2.2",
|
||||
"@sourcegraph/codeintellify": "^7.3.0",
|
||||
"@sourcegraph/extension-api-classes": "^1.1.0",
|
||||
"@sourcegraph/react-loading-spinner": "0.0.7",
|
||||
"@sqs/jsonc-parser": "^1.0.3",
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@ -3383,16 +3383,16 @@
|
||||
dependencies:
|
||||
prop-types "^15.6.2"
|
||||
|
||||
"@sourcegraph/codeintellify@^7.2.2":
|
||||
version "7.2.2"
|
||||
resolved "https://registry.npmjs.org/@sourcegraph/codeintellify/-/codeintellify-7.2.2.tgz#6035801a6928d9f61d69fd0dcc12d2ad37284b3b"
|
||||
integrity sha512-L88Bdid3gjADRgRSutO+tZkRgEbHPH/tOWoqNC+NTh6onCGpsBSBWpy7mauvDI3GTyhuGsgFBFsXpksp8oHbZA==
|
||||
"@sourcegraph/codeintellify@^7.3.0":
|
||||
version "7.3.0"
|
||||
resolved "https://registry.npmjs.org/@sourcegraph/codeintellify/-/codeintellify-7.3.0.tgz#bfe3018f9a7db6741120724495ca47e971f51603"
|
||||
integrity sha512-Y8b66rxNiUr7SbT9agyjGgGFVivlwF5KMScnDYrRyuvkXmjxEankMdlRczjGXAxHdiH5+SFPOq86ILe9i+qnVA==
|
||||
dependencies:
|
||||
"@sourcegraph/event-positions" "^1.0.4"
|
||||
"@sourcegraph/extension-api-types" "^2.1.0"
|
||||
lodash "^4.17.10"
|
||||
rxjs "^6.5.5"
|
||||
sourcegraph "^24.0.0 || ^25.0.0"
|
||||
sourcegraph "^24.0.0 || ^25.0.0 || ^25.0.0"
|
||||
ts-key-enum "^2.0.0"
|
||||
|
||||
"@sourcegraph/eslint-config@^0.25.1":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user