This commit is contained in:
Camden Cheek 2024-08-14 16:39:40 +00:00
commit ff9cb2b73e
28 changed files with 353 additions and 212 deletions

View File

@ -244,10 +244,10 @@
import { SymbolUsageKind } from '$lib/graphql-types'
import Icon from '$lib/Icon.svelte'
import LoadingSpinner from '$lib/LoadingSpinner.svelte'
import DisplayRepoName from '$lib/repo/DisplayRepoName.svelte'
import Scroller from '$lib/Scroller.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import LoadingSkeleton from '$lib/search/dynamicFilters/LoadingSkeleton.svelte'
import { displayRepoName, Occurrence } from '$lib/shared'
import { Occurrence } from '$lib/shared'
import { type SingleSelectTreeState, type TreeProvider } from '$lib/TreeView'
import TreeView, { setTreeContext } from '$lib/TreeView.svelte'
import type { DocumentInfo } from '$lib/web'
@ -321,8 +321,7 @@
<svelte:fragment let:entry>
{#if entry.type === 'repo'}
<span class="repo-entry" data-repo-name={entry.name}>
<CodeHostIcon repository={entry.name} />
{displayRepoName(entry.name)}
<DisplayRepoName repoName={entry.name} kind={undefined} />
</span>
{:else}
<span class="path-entry" data-repo-name={entry.repo} data-path={entry.path}>

View File

@ -5,9 +5,8 @@
import { observeIntersection } from '$lib/intersection-observer'
import { pathHrefFactory } from '$lib/path'
import DisplayPath from '$lib/path/DisplayPath.svelte'
import DisplayRepoName from '$lib/repo/DisplayRepoName.svelte'
import { fetchFileRangeMatches } from '$lib/search/api/highlighting'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import { displayRepoName } from '$lib/shared'
import type { ExplorePanel_Usage } from './ExplorePanel.gql'
@ -73,8 +72,7 @@
on:intersecting={event => (visible = visible || event.detail)}
>
<div class="header">
<CodeHostIcon {repository} />
<span class="repo-name"><DisplayPath path={displayRepoName(repository)} /></span>
<span class="repo-name"><DisplayRepoName repoName={repository} kind={undefined} /></span>
<span class="interpunct"></span>
<span class="file-name">
<DisplayPath
@ -134,14 +132,7 @@
.repo-name {
:global([data-path-container]) {
font-family: var(--font-family-base);
font-weight: 500;
font-size: var(--font-size-small);
gap: 0.25rem;
}
:global([data-path-item]) {
color: var(--text-title);
}
}
@ -150,6 +141,7 @@
}
.file-name {
font-size: var(--code-font-size);
:global([data-path-container]) {
flex-wrap: wrap;
}

View File

@ -18,18 +18,18 @@
import { nextSibling, onClickOutside, previousSibling } from '$lib/dom'
import { getGraphQLClient } from '$lib/graphql'
import Icon from '$lib/Icon.svelte'
import { inferSplitCodeHost } from '$lib/repo/codehost'
import CodeHostIcon from '$lib/repo/codehost/CodeHostIcon.svelte'
import FileIcon from '$lib/repo/FileIcon.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import EmphasizedLabel from '$lib/search/EmphasizedLabel.svelte'
import SymbolKindIcon from '$lib/search/SymbolKindIcon.svelte'
import { displayRepoName } from '$lib/shared'
import { isViewportMobile } from '$lib/stores'
import TabsHeader, { type Tab } from '$lib/TabsHeader.svelte'
import { Alert, Input } from '$lib/wildcard'
import Button from '$lib/wildcard/Button.svelte'
import { allHotkey, filesHotkey, reposHotkey, symbolsHotkey } from './keys'
import { type CompletionSource, createFuzzyFinderSource } from './sources'
import { isViewportMobile } from '$lib/stores'
export let open = false
export let scope = ''
@ -281,14 +281,14 @@
{:else if $source.value?.results}
{#each $source.value.results as item, index (item)}
{@const repo = item.repository.name}
{@const displayRepo = displayRepoName(repo)}
{@const { kind, displayName } = inferSplitCodeHost(repo, undefined)}
<li role="option" aria-selected={selectedOption === index} data-index={index}>
{#if item.type === 'repo'}
{@const matchOffset = repo.length - displayRepo.length}
{@const matchOffset = repo.length - displayName.length}
<a href="/{item.repository.name}" on:click={handleClick}>
<span class="icon"><CodeHostIcon repository={item.repository.name} /></span>
<span class="icon"><CodeHostIcon {kind} /></span>
<span class="label"
><EmphasizedLabel label={displayRepo} offset={matchOffset} /></span
><EmphasizedLabel label={displayName} offset={matchOffset} /></span
>
<span class="info">{repo}</span>
</a>
@ -297,7 +297,7 @@
<span class="icon"><SymbolKindIcon symbolKind={item.symbol.kind} /></span>
<span class="label"><EmphasizedLabel label={item.symbol.name} /></span>
<span class="info mono"
>{#if !useScope}{displayRepo} &middot; {/if}{item.file.path}</span
>{#if !useScope}{displayName} &middot; {/if}{item.file.path}</span
>
</a>
{:else if item.type == 'file'}
@ -309,7 +309,7 @@
><EmphasizedLabel label={fileName} offset={folderName.length + 1} /></span
>
<span class="info mono">
{#if !useScope}{displayRepo} &middot; {/if}
{#if !useScope}{displayName} &middot; {/if}
<EmphasizedLabel label={folderName} />
</span>
</a>

View File

@ -79,15 +79,14 @@
</span>
<style lang="scss">
[data-path-container] {
:where([data-path-container]) {
display: inline-flex;
align-items: center;
align-items: baseline;
gap: 0.125em;
white-space: pre-wrap;
font-weight: 400;
font-size: var(--code-font-size);
font-family: var(--code-font-family);
// Global so data-slash can be slotted in with the prefix
@ -98,7 +97,7 @@
}
}
[data-path-item] {
:where([data-path-item]) {
display: inline;
white-space: nowrap;
color: var(--text-body);

View File

@ -0,0 +1,68 @@
<script lang="ts" context="module">
import { Story } from '@storybook/addon-svelte-csf'
import type { ComponentProps } from 'svelte'
import { highlightRanges } from '$lib/dom'
import { ExternalServiceKind } from '$lib/graphql-types'
import DisplayRepoName from './DisplayRepoName.svelte'
export const meta = {
component: DisplayRepoName,
}
</script>
<script lang="ts">
const cases: ComponentProps<DisplayRepoName>[] = [
{ repoName: 'github.com/sourcegraph/sourcegraph', kind: undefined },
{ repoName: 'github.com/sourcegraph/sourcegraph', kind: ExternalServiceKind.GITHUB },
{ repoName: 'github.com/sourcegraph/sourcegraph', kind: ExternalServiceKind.GITLAB },
{ repoName: 'bitbucket.com/sourcegraph/sourcegraph', kind: ExternalServiceKind.BITBUCKETCLOUD },
{ repoName: 'bitbucket.com/sourcegraph/sourcegraph', kind: ExternalServiceKind.BITBUCKETSERVER },
{ repoName: 'bitbucket.com/sourcegraph/sourcegraph', kind: undefined },
{ repoName: 'gitlab.com/sourcegraph/sourcegraph', kind: undefined },
{ repoName: 'gitlab.com/sourcegraph/sourcegraph', kind: ExternalServiceKind.GITLAB },
{ repoName: 'mytestrepo', kind: undefined },
{ repoName: 'mytestrepo', kind: ExternalServiceKind.GITHUB },
{ repoName: 'ghe.sgdev.org/sourcegraph/sourcegraph', kind: undefined },
// TODO(camdencheek): This is a scenario where, if we knew the external URL,
// we could trim the hostname and put it in the tooltip. We can't safely do
// this right now because an admin can name their repos whatever they want,
// so we can't guarantee that the first element of a repo name is the host name
// of the code host.
{ repoName: 'ghe.sgdev.org/sourcegraph/sourcegraph', kind: ExternalServiceKind.GITHUB },
]
</script>
<Story name="Default">
<h2>DisplayRepoName props</h2>
<table>
{#each cases as testCase}
<tr>
<th>{testCase.repoName}</th>
<th>{testCase.kind}</th>
<td><div><DisplayRepoName {...testCase} /><div /></div></td>
</tr>
{/each}
</table>
</Story>
<Story name="Highlighting works">
<h2>"Graph" should be highlighted</h2>
<span use:highlightRanges={{ ranges: [[17, 22]] }}
><DisplayRepoName repoName="github.com/sourcegraph/conc" kind={undefined} /></span
>
</Story>
<style lang="scss">
td,
th {
padding: 0.5rem;
}
td > div {
width: min-content;
border: 1px solid black;
}
</style>

View File

@ -0,0 +1,59 @@
<script context="module" lang="ts">
</script>
<script lang="ts">
import { ExternalServiceKind } from '$lib/graphql-types'
import DisplayPath from '$lib/path/DisplayPath.svelte'
import Tooltip from '$lib/Tooltip.svelte'
import { inferSplitCodeHost } from './codehost'
import CodeHostIcon from './codehost/CodeHostIcon.svelte'
export let repoName: string
export let kind: ExternalServiceKind | undefined // No default to discourage inferring kind
$: ({ kind: inferredKind, codeHost, displayName } = inferSplitCodeHost(repoName, kind))
</script>
<!--
NOTE: the awkward formatting is intentional to ensure there is no extra
whitespace in the DOM. This is necessary for correctly highlighting
matches inside a repo name by offset.
-->
<span aria-label={repoName}
><!--
--><Tooltip tooltip={codeHost}
><!--
--><CodeHostIcon kind={inferredKind} inline /><!--
--></Tooltip
><!--
--><span
><!-- Include the tooltip text hidden in the DOM for the purpose of highlighting
--><span class="hidden"
>{codeHost ? codeHost + '/' : ''}</span
><!--
--><DisplayPath path={displayName} /><!--
--></span
><!--
--></span
>
<style lang="scss">
span {
display: inline-flex;
align-items: baseline;
gap: 0.5em;
:global([data-icon]) {
align-self: center;
}
:global([data-path-container]) {
font-family: var(--font-family-base);
}
.hidden {
display: none;
}
}
</style>

View File

@ -5,6 +5,7 @@
import ShrinkablePath from '$lib/path/ShrinkablePath.svelte'
import { DropdownMenu } from '$lib/wildcard'
import { getButtonClassName } from '$lib/wildcard/Button'
import MobileFileSidePanelOpenButton from './MobileFileSidePanelOpenButton.svelte'
export let repoName: string
@ -89,6 +90,7 @@
gap: 0.5rem;
align-items: center;
margin: 0;
font-size: var(--code-font-size);
:global([data-path-container]) {
gap: 0.375em !important;

View File

@ -2,7 +2,7 @@
import Avatar from '$lib/Avatar.svelte'
import { numberWithCommas } from '$lib/common'
import type { GitRefType } from '$lib/graphql-types'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import CodeHostIcon from '$lib/repo/codehost/CodeHostIcon.svelte'
import Timestamp from '$lib/Timestamp.svelte'
import Tooltip from '$lib/Tooltip.svelte'
import { Badge } from '$lib/wildcard'
@ -103,7 +103,7 @@
<a href={url}>
View on
{#if serviceKind}
<CodeHostIcon repository={serviceKind} disableTooltip />
<CodeHostIcon kind={serviceKind} inline />
{getHumanNameForCodeHost(serviceKind)}
{:else}
code host

View File

@ -14,13 +14,11 @@
<script lang="ts">
import Avatar from '$lib/Avatar.svelte'
import Icon from '$lib/Icon.svelte'
import { displayRepoName } from '$lib/shared'
import Timestamp from '$lib/Timestamp.svelte'
import Badge from '$lib/wildcard/Badge.svelte'
import DisplayRepoName from '../DisplayRepoName.svelte'
import RepoStars from '../RepoStars.svelte'
import { getHumanNameForCodeHost, getIconForCodeHost } from '../shared/codehost'
import type { RepoPopoverFragment } from './RepoPopover.gql'
@ -34,21 +32,11 @@
<div class="root">
{#if withHeader}
<div class="header">
<div class="left">
<Icon icon={ILucideGitMerge} aria-hidden --icon-color="var(--primary)" inline />
<h4>{displayRepoName(data.name)}</h4>
<Badge variant="outlineSecondary" small pill>
{data.isPrivate ? 'Private' : 'Public'}
</Badge>
</div>
<div class="right">
<Icon
icon={getIconForCodeHost(data.externalRepository.serviceType)}
--icon-color="var(--text-body)"
inline
/>
<small>{getHumanNameForCodeHost(data.externalRepository.serviceType)}</small>
</div>
<!-- TODO: we should be able to get the service kind (not the service type) from the backend -->
<h4><DisplayRepoName repoName={data.name} kind={undefined} /></h4>
<Badge variant="outlineSecondary" small pill>
{data.isPrivate ? 'Private' : 'Public'}
</Badge>
</div>
{/if}
@ -103,37 +91,13 @@
.header {
display: flex;
justify-content: space-between;
justify-content: flex-start;
align-items: center;
gap: 0.25rem;
.left {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 0.25rem;
h4 {
color: var(--text-title);
margin: 0;
}
small {
border: 1px solid var(--text-muted);
color: var(--text-muted);
padding: 0rem 0.5rem;
border-radius: 1rem;
}
}
.right {
display: flex;
justify-content: flex-end;
align-items: center;
gap: 0.25rem;
small {
color: var(--text-muted);
}
h4 {
color: var(--text-title);
margin: 0;
}
}

View File

@ -0,0 +1,33 @@
<script lang="ts">
import type { SvelteHTMLElements } from 'svelte/elements'
import type { ExternalServiceKind } from '$lib/graphql-types'
import Icon from '$lib/Icon.svelte'
import { getIconForServiceKind, getExternalServiceHumanName } from './index'
type $$Props = SvelteHTMLElements['svg'] & {
/**
* The service kind to render an icon for
*/
kind: ExternalServiceKind
/**
* Render the icon inline next to text.
*/
inline?: boolean
}
export let kind: $$Props['kind']
export let inline: $$Props['inline'] = false
</script>
<div>
<Icon aria-label={getExternalServiceHumanName(kind)} icon={getIconForServiceKind(kind)} {inline} {...$$restProps} />
</div>
<style lang="scss">
div {
display: contents;
--icon-color: currentColor;
}
</style>

View File

@ -0,0 +1,80 @@
import { ExternalServiceKind } from '$lib/graphql-types'
import type { IconComponent } from '$lib/Icon.svelte'
const icons: Partial<Record<ExternalServiceKind, IconComponent>> = {
GITHUB: ISimpleIconsGithub,
GITLAB: ISimpleIconsGitlab,
BITBUCKETSERVER: ISimpleIconsBitbucket,
BITBUCKETCLOUD: ISimpleIconsBitbucket,
}
/**
* Returns the SVG icon component for the given code host.
*/
export function getIconForServiceKind(kind: ExternalServiceKind): IconComponent {
return icons[kind] ?? ILucideFolderGit2
}
const humanNames: Record<ExternalServiceKind, string> = {
AWSCODECOMMIT: 'AWS CodeCommit',
AZUREDEVOPS: 'Azure DevOps',
BITBUCKETCLOUD: 'Bitbucket',
BITBUCKETSERVER: 'Bitbucket',
GERRIT: 'Gerrit',
GITHUB: 'GitHub',
GITLAB: 'GitLab',
GITOLITE: 'Gitolite',
GOMODULES: 'Go Module',
JVMPACKAGES: 'JVM Packages',
NPMPACKAGES: 'NPM Packages',
PAGURE: 'Pagure',
PERFORCE: 'Perforce',
PHABRICATOR: 'Phabricator',
PYTHONPACKAGES: 'Python Packages',
RUBYPACKAGES: 'Ruby Packages',
RUSTPACKAGES: 'Rust Packages',
OTHER: 'Unknown',
}
/**
* Returns the human-readable name for the given external service kind.
*/
export function getExternalServiceHumanName(kind: ExternalServiceKind): string {
return humanNames[kind]
}
const knownPrefixes: Partial<Record<string, ExternalServiceKind>> = {
'github.com': ExternalServiceKind.GITHUB,
'gitlab.com': ExternalServiceKind.GITLAB,
'bitbucket.com': ExternalServiceKind.BITBUCKETCLOUD,
}
const splitRepoNameRegex = /^(?<codehost>[^\.\/]+\.[^\.\/]+)\/(?<repo>.*)$/
/**
* Attempts to infer the code host kind from the repo name and split the repo
* name into a code host part and a display name part.
*
* If provided, the external service kind will be used instead of inferring the
* kind from the repo name. Over time, we should attempt to provide a kind in
* every place we display a repo name since guessing will not work for any
* non-well-known repos.
*
* NOTE: there is no guarantee that the first element of a repo name is the
* code host. Admins can name their repos whatever they want, so it's possible
* we will incorrectly split if the chosen name matches the regex pattern.
* We could theoretically use the external URLs to check this, but we do not
* always have that information handy so we don't do that now.
*/
export function inferSplitCodeHost(
repoName: string,
kind: ExternalServiceKind | undefined
): { kind: ExternalServiceKind; codeHost: string; displayName: string } {
const matched = splitRepoNameRegex.exec(repoName)
const codeHost = matched?.groups?.codehost ?? ''
return {
kind: kind ?? knownPrefixes[codeHost] ?? ExternalServiceKind.OTHER,
codeHost,
displayName: matched?.groups?.repo ?? repoName,
}
}

View File

@ -1,15 +0,0 @@
<script lang="ts">
import Icon from '$lib/Icon.svelte'
import { getIconForCodeHost } from '$lib/repo/shared/codehost'
import Tooltip from '$lib/Tooltip.svelte'
export let repository: string
export let codeHost: string | undefined = undefined
export let disableTooltip: boolean = false
$: hostName = repository.split('/')[0]
</script>
<Tooltip tooltip={disableTooltip ? '' : hostName}>
<Icon aria-label={hostName} icon={getIconForCodeHost(codeHost ?? hostName)} inline />
</Tooltip>

View File

@ -121,10 +121,11 @@
import KeyboardShortcut from '$lib/KeyboardShortcut.svelte'
import LanguageIcon from '$lib/LanguageIcon.svelte'
import Popover from '$lib/Popover.svelte'
import { inferSplitCodeHost } from '$lib/repo/codehost'
import CodeHostIcon from '$lib/repo/codehost/CodeHostIcon.svelte'
import RepoPopover, { fetchRepoPopoverData } from '$lib/repo/RepoPopover/RepoPopover.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import SymbolKindIcon from '$lib/search/SymbolKindIcon.svelte'
import { TELEMETRY_FILTER_TYPES, displayRepoName, scanSearchQuery, type Filter } from '$lib/shared'
import { TELEMETRY_FILTER_TYPES, scanSearchQuery, type Filter } from '$lib/shared'
import { settings } from '$lib/stores'
import { TELEMETRY_RECORDER } from '$lib/telemetry'
import { delay } from '$lib/utils'
@ -232,11 +233,12 @@
<Section items={sectionItems.repo} title="By repository" filterPlaceholder="Filter repositories">
<svelte:fragment slot="item" let:item>
{@const { kind: inferredKind, displayName } = inferSplitCodeHost(item.label, undefined)}
<Popover showOnHover let:registerTrigger placement="right-start">
<div use:registerTrigger>
<SectionItem {...item} on:select={() => handleFilterSelect('repo')}>
<CodeHostIcon slot="icon" disableTooltip repository={item.label} />
<span slot="label">{displayRepoName(item.label)}</span>
<CodeHostIcon slot="icon" inline kind={inferredKind} />
<span slot="label">{displayName}</span>
</SectionItem>
</div>
<svelte:fragment slot="content">

View File

@ -90,18 +90,3 @@ export { getModeFromPath } from '@sourcegraph/shared/src/languages'
export type { ActionItemAction } from '@sourcegraph/shared/src/actions/ActionItem'
export { repositoryInsertText } from '@sourcegraph/shared/src/search/query/completion-utils'
export { ThemeSetting, Theme } from '@sourcegraph/shared/src/theme-types'
// Copies of non-reusable code
// Currently defined in client/shared/src/components/RepoLink.tsx
/**
* Returns the friendly display form of the repository name (e.g., removing "github.com/").
*/
export function displayRepoName(repoName: string): string {
let parts = repoName.split('/')
if (parts.length > 0 && parts[0].includes('.')) {
parts = parts.slice(1) // remove hostname from repo name (reduce visual noise)
}
return parts.join('/')
}

View File

@ -1,8 +1,8 @@
<script lang="ts">
import type { LineOrPositionOrRange } from '$lib/common'
import CodeHostIcon from '$lib/repo/codehost/CodeHostIcon.svelte'
import { getHumanNameForCodeHost } from '$lib/repo/shared/codehost'
import { getExternalURL } from '$lib/repo/url'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import { TELEMETRY_RECORDER } from '$lib/telemetry'
import Tooltip from '$lib/Tooltip.svelte'
@ -25,7 +25,7 @@
on:click={handleOpenCodeHostClick}
>
{#if externalLink.serviceKind}
<CodeHostIcon repository={externalLink.serviceKind} disableTooltip />
<CodeHostIcon kind={externalLink.serviceKind} inline />
<span data-action-label>
{getHumanNameForCodeHost(externalLink.serviceKind)}
</span>

View File

@ -251,7 +251,7 @@ test.describe('file header', () => {
// We specifically check the textContent here because this is what is
// used to apply highlights. It must exactly equal the path (no additional
// whitespace) or the highlights will be incorrectly offset.
const pathContainer = page.locator('css=[data-path-container]').first()
const pathContainer = page.getByText('src/readme.md')
await expect(pathContainer).toHaveText(/^src\/readme.md$/)
})
@ -271,7 +271,7 @@ test.describe('repo menu', () => {
const url = `/${repoName}/-/blob/src/large-file-1.js`
await page.goto(url)
await page.getByRole('heading', { name: 'sourcegraph/sourcegraph' }).click()
await page.getByRole('button', { name: 'github.com/sourcegraph/sourcegraph' }).click()
await page.getByRole('menuitem', { name: 'Go to repository root' }).click()
await page.waitForURL(`/${repoName}`)
})

View File

@ -5,13 +5,12 @@
import { navigating } from '$app/stores'
import Commit from '$lib/Commit.svelte'
import LoadingSpinner from '$lib/LoadingSpinner.svelte'
import CodeHostIcon from '$lib/repo/codehost/CodeHostIcon.svelte'
import RepositoryRevPicker from '$lib/repo/RepositoryRevPicker.svelte'
import { getHumanNameForCodeHost } from '$lib/repo/shared/codehost'
import Scroller, { type Capture as ScrollerCapture } from '$lib/Scroller.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import { Alert, Badge } from '$lib/wildcard'
import RepositoryRevPicker from '$lib/repo/RepositoryRevPicker.svelte'
import type { PageData, Snapshot } from './$types'
export let data: PageData
@ -100,7 +99,7 @@
<a href={url}>
View on
{#if serviceKind}
<CodeHostIcon repository={serviceKind} disableTooltip />
<CodeHostIcon kind={serviceKind} inline />
{getHumanNameForCodeHost(serviceKind)}
{:else}
code host

View File

@ -174,7 +174,6 @@
<nav aria-label="repository" use:sizeToFit={{ grow, shrink }}>
<RepoMenu
repoName={data.repoName}
displayRepoName={data.displayRepoName}
repoURL={data.repoURL}
externalURL={data.resolvedRepository.externalURLs[0]?.url}
externalServiceKind={data.resolvedRepository.externalURLs[0]?.serviceKind ?? undefined}

View File

@ -3,7 +3,8 @@ import { error, redirect } from '@sveltejs/kit'
import { loadMarkdownSyntaxHighlighting } from '$lib/common'
import { getGraphQLClient, mapOrThrow, type GraphQLClient } from '$lib/graphql'
import { GitRefType } from '$lib/graphql-types'
import { CloneInProgressError, RepoNotFoundError, displayRepoName, parseRepoRevision } from '$lib/shared'
import { inferSplitCodeHost } from '$lib/repo/codehost'
import { CloneInProgressError, RepoNotFoundError, parseRepoRevision } from '$lib/shared'
import type { LayoutLoad } from './$types'
import {
@ -38,7 +39,7 @@ export const load: LayoutLoad = async ({ params, url, depends }) => {
repoURL: '/' + params.repo,
repoURLWithoutRevision: '/' + repoName,
repoName,
displayRepoName: displayRepoName(repoName),
displayRepoName: inferSplitCodeHost(repoName, undefined).displayName,
/**
* Revision from URL
*/

View File

@ -7,10 +7,10 @@
import Commit from '$lib/Commit.svelte'
import { pluralize } from '$lib/common'
import LoadingSpinner from '$lib/LoadingSpinner.svelte'
import CodeHostIcon from '$lib/repo/codehost/CodeHostIcon.svelte'
import FileDiff from '$lib/repo/FileDiff.svelte'
import { getHumanNameForCodeHost } from '$lib/repo/shared/codehost'
import Scroller, { type Capture as ScrollerCapture } from '$lib/Scroller.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import { isViewportMobile } from '$lib/stores'
import Alert from '$lib/wildcard/Alert.svelte'
import Badge from '$lib/wildcard/Badge.svelte'
@ -95,7 +95,7 @@
<a href={url}>
View on
{#if serviceKind}
<CodeHostIcon repository={serviceKind} disableTooltip />
<CodeHostIcon kind={serviceKind} inline />
{getHumanNameForCodeHost(serviceKind)}
{:else}
code host

View File

@ -3,7 +3,8 @@
import { invalidate } from '$app/navigation'
import HeroPage from '$lib/HeroPage.svelte'
import { displayRepoName, type CloneInProgressError } from '$lib/shared'
import { inferSplitCodeHost } from '$lib/repo/codehost'
import { type CloneInProgressError } from '$lib/shared'
// TODO: Find a way to make this type stricter
export let error: App.Error | null
@ -19,7 +20,7 @@
})
</script>
<HeroPage title={displayRepoName(repoName)} icon={ILucideFolderGit2}>
<HeroPage title={inferSplitCodeHost(repoName, undefined).displayName} icon={ILucideFolderGit2}>
<code><pre>{progress}</pre></code>
<!--TODO add DirectImportRepoAlert -->
</HeroPage>

View File

@ -1,10 +1,11 @@
<script lang="ts">
import { openFuzzyFinder } from '$lib/fuzzyfinder/FuzzyFinderContainer.svelte'
import { reposHotkey } from '$lib/fuzzyfinder/keys'
import type { ExternalServiceKind } from '$lib/graphql-types'
import Icon from '$lib/Icon.svelte'
import KeyboardShortcut from '$lib/KeyboardShortcut.svelte'
import DisplayRepoName from '$lib/repo/DisplayRepoName.svelte'
import { getHumanNameForCodeHost } from '$lib/repo/shared/codehost'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import { getButtonClassName } from '$lib/wildcard/Button'
import DropdownMenu from '$lib/wildcard/menu/DropdownMenu.svelte'
import MenuButton from '$lib/wildcard/menu/MenuButton.svelte'
@ -12,24 +13,16 @@
import MenuSeparator from '$lib/wildcard/menu/MenuSeparator.svelte'
export let repoName: string
export let displayRepoName: string
export let repoURL: string
export let externalURL: string | undefined
export let externalServiceKind: string | undefined
export let externalServiceKind: ExternalServiceKind | undefined
</script>
<DropdownMenu triggerButtonClass="{getButtonClassName({ variant: 'text' })} triggerButton">
<svelte:fragment slot="trigger">
<div class="trigger">
<CodeHostIcon repository={repoName} codeHost={externalServiceKind} />
<h2>
{#each displayRepoName.split('/') as segment, i}
{#if i > 0}<span class="slash">/</span>{/if}{segment}
{/each}
</h2>
</div>
</svelte:fragment>
<h2 slot="trigger">
<DisplayRepoName {repoName} kind={externalServiceKind} />
</h2>
<MenuLink href={repoURL}>
<div class="menu-item">
@ -63,8 +56,7 @@
{/if}
</small>
<div class="repo-name">
<CodeHostIcon repository={repoName} codeHost={externalServiceKind} />
<span>{displayRepoName}</span>
<DisplayRepoName {repoName} kind={externalServiceKind} />
</div>
<div class="external-link-icon">
<Icon icon={ILucideExternalLink} aria-hidden />
@ -78,25 +70,14 @@
:global(.triggerButton) {
border-radius: 0;
}
.trigger {
--icon-color: currentColor;
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
h2 {
font-size: var(--font-size-large);
h2 {
margin: 0;
font-size: var(--font-size-base);
:global([data-path-container]) {
font-weight: 500;
margin: 0;
.slash {
font-weight: 400;
color: var(--text-muted);
margin: 0.25em;
letter-spacing: -0.25px;
}
}
:global([data-slash]) {
font-weight: 400;
}
}

View File

@ -49,10 +49,14 @@ fragment ResolvedRepository on Repository {
abbrevName
}
externalURLs {
url
serviceKind
...RepositoryExternalLink
}
...RepoPage_ResolvedRevision
...BlobPage_ResolvedRevision
...CodySidebar_ResolvedRevision
}
fragment RepositoryExternalLink on ExternalLink {
url
serviceKind
}

View File

@ -22,12 +22,13 @@
import { buildSearchURLQuery } from '@sourcegraph/shared/src/util/url'
import { ExternalServiceKind } from '$lib/graphql-types'
import LoadingSpinner from '$lib/LoadingSpinner.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import { inferSplitCodeHost } from '$lib/repo/codehost'
import CodeHostIcon from '$lib/repo/codehost/CodeHostIcon.svelte'
import SearchInput from '$lib/search/input/SearchInput.svelte'
import { queryStateStore } from '$lib/search/state'
import SyntaxHighlightedQuery from '$lib/search/SyntaxHighlightedQuery.svelte'
import { displayRepoName } from '$lib/shared'
import { settings } from '$lib/stores'
import { TELEMETRY_RECORDER } from '$lib/telemetry'
import { Alert, Button, Markdown } from '$lib/wildcard'
@ -123,11 +124,13 @@
rel="noopener noreferrer"
on:click={handleExternalRepoLinkClick}
>
<CodeHostIcon repository={repo.name} />
<CodeHostIcon
kind={repo.externalURLs[0].serviceKind ?? ExternalServiceKind.OTHER}
/>
</a>
{/if}
<a href="/{repo.name}" on:click={handleRepoLinkClick}>
{displayRepoName(repo.name)}
{inferSplitCodeHost(repo.name, undefined).displayName}
</a>
</li>
{/each}

View File

@ -5,6 +5,7 @@ query CommunitySearchPage_SearchContext($spec: String!) {
name
externalURLs {
url
serviceKind
}
}
}

View File

@ -47,6 +47,7 @@
.path {
display: contents;
font-size: var(--code-font-size);
}
.interpunct {

View File

@ -2,9 +2,8 @@
import { highlightRanges } from '$lib/dom'
import { getGraphQLClient } from '$lib/graphql'
import Popover from '$lib/Popover.svelte'
import DisplayRepoName from '$lib/repo/DisplayRepoName.svelte'
import { default as RepoPopover, fetchRepoPopoverData } from '$lib/repo/RepoPopover/RepoPopover.svelte'
import CodeHostIcon from '$lib/search/CodeHostIcon.svelte'
import { displayRepoName } from '$lib/shared'
import { delay } from '$lib/utils'
import Alert from '$lib/wildcard/Alert.svelte'
@ -15,52 +14,38 @@
const client = getGraphQLClient()
$: href = `/${repoName}${rev ? `@${rev}` : ''}`
$: displayName = displayRepoName(repoName)
$: if (displayName !== repoName) {
// We only display part of the repository name, therefore we have to
// adjust the match ranges for highlighting
const delta = repoName.length - displayName.length
highlights = highlights.map(([start, end]) => [start - delta, end - delta])
}
</script>
<span class="root">
<CodeHostIcon repository={repoName} />
<!-- #key is needed here to recreate the link because use:highlightRanges changes the DOM -->
{#key highlights}
<Popover showOnHover let:registerTrigger placement="bottom-start">
<a class="repo-link" {href} use:highlightRanges={{ ranges: highlights }} use:registerTrigger>
{displayRepoName(repoName)}
{#if rev}
<small class="rev"> @ {rev}</small>
{/if}
</a>
<svelte:fragment slot="content">
{#await delay(fetchRepoPopoverData(client, repoName), 200) then data}
<RepoPopover {data} withHeader />
{:catch error}
<Alert variant="danger" size="slim">{error}</Alert>
{/await}
</svelte:fragment>
</Popover>
{/key}
</span>
<!-- #key is needed here to recreate the link because use:highlightRanges changes the DOM -->
{#key highlights}
<Popover showOnHover let:registerTrigger placement="bottom-start">
<a class="repo-link" {href} use:highlightRanges={{ ranges: highlights }} use:registerTrigger>
<DisplayRepoName {repoName} kind={undefined} />
{#if rev}
<small class="rev">&nbsp;@ {rev}</small>
{/if}
</a>
<svelte:fragment slot="content">
{#await delay(fetchRepoPopoverData(client, repoName), 200) then data}
<RepoPopover {data} withHeader />
{:catch error}
<Alert variant="danger" size="slim">{error}</Alert>
{/await}
</svelte:fragment>
</Popover>
{/key}
<style lang="scss">
.root {
--icon-color: currentColor;
display: inline-flex;
align-items: center;
gap: 0.375rem;
.repo-link {
align-self: baseline;
color: var(--body-color);
.repo-link {
display: flex;
color: var(--body-color);
font-weight: 500;
align-items: baseline;
:global([data-path-container]) {
font-weight: 500;
.rev {
color: var(--text-muted);
}
}
.rev {
color: var(--text-muted);
}
}
</style>

View File

@ -337,7 +337,7 @@ test.describe('search results', async () => {
await expect(page.locator('*:focus')).toContainText(pathMatch.path.split('/').at(-1) ?? '')
await page.keyboard.press('ArrowDown') // Check a down arrow too
await expect(page.locator('*:focus')).toContainText(repoMatch.repository.split('/').slice(1).join('/'))
await expect(page.getByRole('link', { name: repoMatch.repository })).toBeFocused()
await page.keyboard.press('j')
for (const symbol of symbolMatch.symbols) {
@ -360,9 +360,7 @@ test.describe('search results', async () => {
}
await page.keyboard.press('k')
await expect(page.locator('*:focus')).toContainText(repoMatch.repository.split('/').slice(1).join('/'), {
useInnerText: true,
})
await expect(page.getByRole('link', { name: repoMatch.repository })).toBeFocused()
await page.keyboard.press('ArrowUp') // Check an up arrow too
await expect(page.locator('*:focus')).toContainText(pathMatch.path.split('/').at(-1) ?? '')