1
0
mirror of synced 2026-02-01 12:01:41 -05:00

Merge pull request #8997 from github/repo-sync

repo sync
This commit is contained in:
Octomerger Bot
2021-08-13 05:53:42 +10:00
committed by GitHub
15 changed files with 223 additions and 341 deletions

View File

@@ -51,7 +51,11 @@ export const Breadcrumbs = ({ variant = 'default' }: Props) => {
{breadcrumb.title}
</Link>
),
i !== arr.length - 1 ? <span className="color-text-tertiary">/</span> : null,
i !== arr.length - 1 ? (
<span className="color-text-tertiary" key={`${i}-slash`}>
/
</span>
) : null,
]
})}
</nav>

View File

@@ -1,6 +1,6 @@
import Head from 'next/head'
import { SidebarNav } from 'components/SidebarNav'
import { SidebarNav } from 'components/sidebar/SidebarNav'
import { Header } from 'components/page-header/Header'
import { SmallFooter } from 'components/page-footer/SmallFooter'
import { ScrollButton } from 'components/ScrollButton'

View File

@@ -1,107 +0,0 @@
import { useRouter } from 'next/router'
import { LinkExternalIcon, MarkGithubIcon } from '@primer/octicons-react'
import { Link } from 'components/Link'
import { useTranslation } from './hooks/useTranslation'
import { useMainContext } from './context/MainContext'
import { SidebarProduct } from './product/SidebarProduct'
import { AllProductsLink } from './product/AllProductsLink'
import { useVersion } from './hooks/useVersion'
export const SidebarNav = () => {
const router = useRouter()
const { error, relativePath, isFPT } = useMainContext()
const { t } = useTranslation('header')
return (
<div className="d-none d-lg-block color-bg-tertiary position-sticky top-0 overflow-y-auto root">
<div
className="d-flex flex-items-center p-4 position-sticky top-0 color-bg-tertiary"
style={{ zIndex: 3 }}
id="github-logo"
role="banner"
>
<Link
href={`/${router.locale}`}
className="color-text-primary"
aria-hidden="true"
tabIndex={-1}
>
<MarkGithubIcon size={32} />
</Link>
<Link
href={`/${router.locale}`}
className="h4-mktg color-text-primary no-underline no-wrap pl-2 flex-auto"
>
{t('github_docs')}
</Link>
</div>
<nav>
{error === '404' || relativePath === 'index.md' ? (
<ul className="sidebar-products mt-4">
{!isFPT && <AllProductsLink />}
<SidebarHomepage />
</ul>
) : (
<ul className="sidebar-products">
<SidebarProduct />
</ul>
)}
</nav>
<style jsx>
{`
.root {
width: 286px;
height: 100vh;
flex-shrink: 0;
padding-bottom: 32px;
}
`}
</style>
</div>
)
}
const SidebarHomepage = () => {
const router = useRouter()
const { currentVersion } = useVersion()
const { activeProducts, isFPT } = useMainContext()
return (
<>
{activeProducts.map((product) => {
if (!isFPT && !product.versions?.includes(currentVersion) && !product.external) {
return null
}
const href = `${!product.external ? `/${router.locale}` : ''}${
product.versions?.includes(currentVersion) && !isFPT
? `/${currentVersion}/${product.id}`
: product.href
}`
return (
<li
key={product.id}
title={`${product.name}${product.external ? '(External Site)' : ''}`}
className="sidebar-product"
>
<a
href={href}
target={product.external ? '_blank' : undefined}
className="f4 pl-4 pr-5 py-2 color-text-primary"
>
{product.name}
{product.external && (
<span className="ml-1">
<LinkExternalIcon size="small" />
</span>
)}
</a>
</li>
)
})}
</>
)
}

View File

@@ -207,38 +207,11 @@ function initExitEvent() {
document.addEventListener('visibilitychange', sendExit)
}
function initNavigateEvent() {
if (!document.querySelector('.sidebar-products')) return
Array.from(document.querySelectorAll('.sidebar-products details')).forEach((details) =>
details.addEventListener('toggle', (evt) => {
const target = evt.target as HTMLDetailsElement
sendEvent({
type: EventType.navigate,
navigate_label: `details ${target.open ? 'open' : 'close'}: ${
target?.querySelector('summary')?.innerText
}`,
})
})
)
document.querySelector('.sidebar-products')?.addEventListener('click', (evt) => {
const target = evt.target as HTMLElement
const link = target.closest('a') as HTMLAnchorElement
if (!link) return
sendEvent({
type: EventType.navigate,
navigate_label: `link: ${link.href}`,
})
})
}
export default function initializeEvents() {
initPageEvent() // must come first
initExitEvent()
initLinkEvent()
initClipboardEvent()
initNavigateEvent()
// print event in ./print.js
// survey event in ./survey.js
// experiment event in ./experiment.js

View File

@@ -0,0 +1,53 @@
import { useRouter } from 'next/router'
import { LinkExternalIcon } from '@primer/octicons-react'
import { useVersion } from 'components/hooks/useVersion'
import { useMainContext } from 'components/context/MainContext'
import { Link } from 'components/Link'
import { AllProductsLink } from './AllProductsLink'
export const SidebarHomepage = () => {
const router = useRouter()
const { currentVersion } = useVersion()
const { activeProducts, isFPT } = useMainContext()
return (
<ul data-testid="sidebar" className="mt-4">
{!isFPT && <AllProductsLink />}
{activeProducts.map((product) => {
if (!isFPT && !product.versions?.includes(currentVersion) && !product.external) {
return null
}
const href = `${!product.external ? `/${router.locale}` : ''}${
product.versions?.includes(currentVersion) && !isFPT
? `/${currentVersion}/${product.id}`
: product.href
}`
return (
<li
key={product.id}
title={`${product.name}${product.external ? '(External Site)' : ''}`}
className="my-3"
>
<Link
href={href}
target={product.external ? '_blank' : undefined}
className="f4 pl-4 pr-5 py-2 color-text-primary no-underline"
>
{product.name}
{product.external && (
<span className="ml-1">
<LinkExternalIcon size="small" />
</span>
)}
</Link>
</li>
)
})}
</ul>
)
}

View File

@@ -0,0 +1,46 @@
import { useRouter } from 'next/router'
import { MarkGithubIcon } from '@primer/octicons-react'
import { Link } from 'components/Link'
import { useTranslation } from 'components/hooks/useTranslation'
import { useMainContext } from 'components/context/MainContext'
import { SidebarProduct } from './SidebarProduct'
import { SidebarHomepage } from './SidebarHomepage'
export const SidebarNav = () => {
const router = useRouter()
const { error, relativePath } = useMainContext()
const { t } = useTranslation('header')
return (
<div
className="d-none d-lg-block color-bg-tertiary position-sticky top-0 overflow-y-auto flex-shrink-0 pb-5"
style={{ width: 286, height: '100vh' }}
>
<div
className="d-flex flex-items-center p-4 position-sticky top-0 color-bg-tertiary"
style={{ zIndex: 3 }}
id="github-logo"
role="banner"
>
<Link
href={`/${router.locale}`}
className="color-text-primary"
aria-hidden="true"
tabIndex={-1}
>
<MarkGithubIcon size={32} />
</Link>
<Link
href={`/${router.locale}`}
className="h4-mktg color-text-primary no-underline no-wrap pl-2 flex-auto"
>
{t('github_docs')}
</Link>
</div>
<nav>
{error === '404' || relativePath === 'index.md' ? <SidebarHomepage /> : <SidebarProduct />}
</nav>
</div>
)
}

View File

@@ -0,0 +1,13 @@
.sidebarArticle::before {
content: "";
position: absolute;
left: 26px;
height: 100%;
border-left: 1px solid var(--color-text-primary);
width: 1px;
top: 0;
}
.sidebarArticleActive::before {
border-left-width: 2px;
}

View File

@@ -1,17 +1,21 @@
import { useRouter } from 'next/router'
import cx from 'classnames'
import { useState, useEffect } from 'react'
import { useState, useEffect, SyntheticEvent } from 'react'
import { ChevronDownIcon } from '@primer/octicons-react'
import { Link } from 'components/Link'
import { ProductTreeNode, useMainContext } from 'components/context/MainContext'
import { AllProductsLink } from 'components/product/AllProductsLink'
import { AllProductsLink } from 'components/sidebar/AllProductsLink'
import { EventType, sendEvent } from 'components/lib/events'
import styles from './SidebarProduct.module.scss'
export const SidebarProduct = () => {
const router = useRouter()
const { currentProductTree } = useMainContext()
useEffect(() => {
const activeArticle = document.querySelector('.sidebar-article.active')
const activeArticle = document.querySelector('[data-is-current-page=true]')
// Setting to the top doesn't give enough context of surrounding categories
activeArticle?.scrollIntoView({ block: 'center' })
// scrollIntoView affects some articles that are very low in the sidebar
@@ -26,16 +30,16 @@ export const SidebarProduct = () => {
const productTitle = currentProductTree.renderedShortTitle || currentProductTree.renderedFullTitle
const routePath = `/${router.locale}${router.asPath.split('?')[0]}` // remove query string
const hasExactCategory = currentProductTree.childPages.find(({ href }) =>
const hasExactCategory = !!currentProductTree.childPages.find(({ href }) =>
routePath.includes(href)
)
return (
<>
<ul data-testid="sidebar" className={styles.container}>
<AllProductsLink />
{!currentProductTree.page.hidden && (
<>
<li title="" className="sidebar-product mb-2">
<li data-testid="sidebar-product" title={productTitle} className="my-2">
<Link
href={currentProductTree.href}
className="pl-4 pr-5 pb-1 f4 color-text-primary no-underline"
@@ -44,25 +48,21 @@ export const SidebarProduct = () => {
</Link>
</li>
<li>
<ul className="sidebar-categories list-style-none">
<li className="my-3">
<ul className="list-style-none">
{currentProductTree.childPages.map((childPage, i) => {
const isStandaloneCategory = childPage.page.documentType === 'article'
const childTitle = childPage.renderedShortTitle || childPage.renderedFullTitle
const isActive = routePath.includes(`${childPage.href}/`)
const isCurrent = routePath === childPage.href
const defaultOpen = hasExactCategory ? isActive || isCurrent : false
const isActive = routePath.includes(childPage.href) || routePath === childPage.href
const defaultOpen = hasExactCategory ? isActive : false
return (
<li
key={childPage.href + i}
className={cx(
'sidebar-category py-1',
isActive && 'active',
isCurrent && 'is-current-page',
isStandaloneCategory && 'standalone-category'
)}
data-is-active-category={isActive}
data-is-current-page={isActive && isStandaloneCategory}
className={cx('py-1', isActive && 'color-bg-info')}
>
{isStandaloneCategory ? (
<Link
@@ -86,7 +86,7 @@ export const SidebarProduct = () => {
</li>
</>
)}
</>
</ul>
)
}
@@ -100,8 +100,46 @@ const CollapsibleSection = (props: SectionProps) => {
const { routePath, defaultOpen, title, page } = props
const [isOpen, setIsOpen] = useState(defaultOpen)
const onToggle = (e: SyntheticEvent) => {
const newIsOpen = (e.target as HTMLDetailsElement).open
setIsOpen(newIsOpen)
sendEvent({
type: EventType.navigate,
navigate_label: `details ${newIsOpen ? 'open' : 'close'}: ${title}`,
})
}
// The lowest level page link displayed in the tree
const renderTerminalPageLink = (page: ProductTreeNode) => {
const title = page.renderedShortTitle || page.renderedFullTitle
const isCurrent = routePath === page.href
return (
<li
data-testid="sidebar-article"
data-is-current-page={isCurrent}
key={page.href}
className={cx(
'position-relative',
styles.sidebarArticle,
isCurrent && ['text-bold', styles.sidebarArticleActive]
)}
>
<Link
href={page.href}
className={cx(
'd-block pl-6 pr-5 py-1 no-underline',
isCurrent ? 'color-text-link' : 'color-text-primary'
)}
>
{title}
</Link>
</li>
)
}
return (
<details open={defaultOpen} onToggle={(e) => setIsOpen((e.target as HTMLDetailsElement).open)}>
<details open={defaultOpen} onToggle={onToggle} className="details-reset">
<summary>
<div className="d-flex flex-justify-between">
<div className="pl-4 pr-1 py-2 f6 text-uppercase d-block flex-auto mr-3 color-text-primary no-underline text-bold">
@@ -115,9 +153,9 @@ const CollapsibleSection = (props: SectionProps) => {
{
<>
{/* <!-- some categories have maptopics with child articles --> */}
{/* <!-- some pages have nested child pages (formerly known as a mapTopic) --> */}
{page.childPages[0]?.page.documentType === 'mapTopic' ? (
<ul className="sidebar-topics list-style-none position-relative">
<ul className="list-style-none position-relative">
{page.childPages.map((childPage, i) => {
const childTitle = childPage.renderedShortTitle || childPage.renderedFullTitle
@@ -125,42 +163,17 @@ const CollapsibleSection = (props: SectionProps) => {
const isCurrent = routePath === childPage.href
return (
<li
key={childPage.href + i}
className={cx(
'sidebar-maptopic',
isActive && 'active',
isCurrent && 'is-current-page'
)}
>
<details open={isActive} onToggle={(e) => e.stopPropagation()}>
<li key={childPage.href + i} data-is-current-page={isCurrent}>
<details
open={isActive}
onToggle={(e) => e.stopPropagation()}
className="details-reset"
>
<summary>
<div className={cx('pl-4 pr-5 py-2 no-underline')}>{childTitle}</div>
</summary>
<ul className="sidebar-articles my-2">
{childPage.childPages.map((grandchildPage, i, arr) => {
const grandchildTitle =
grandchildPage.renderedShortTitle || grandchildPage.renderedFullTitle
const isLast = i === arr.length - 1
const isActive = routePath === grandchildPage.href
return (
<li
key={grandchildPage.href + i}
className={cx('sidebar-article', isActive && 'active')}
>
<Link
href={grandchildPage.href}
className={cx(
'pl-6 pr-5 py-1 no-underline',
isLast && 'pb-2',
isActive && 'color-text-link'
)}
>
{grandchildTitle}
</Link>
</li>
)
})}
<ul data-testid="sidebar-article-group" className="my-2 pb-2">
{childPage.childPages.map(renderTerminalPageLink)}
</ul>
</details>
</li>
@@ -168,36 +181,9 @@ const CollapsibleSection = (props: SectionProps) => {
})}
</ul>
) : page.childPages[0]?.page.documentType === 'article' ? (
<ul className="sidebar-articles list-style-none">
<ul data-testid="sidebar-article-group" className="list-style-none pb-2">
{/* <!-- some categories have no maptopics, only articles --> */}
{page.childPages.map((childPage, i, arr) => {
const childTitle = childPage.renderedShortTitle || childPage.renderedFullTitle
const isLast = i === arr.length - 1
const isActive = routePath.includes(childPage.href)
const isCurrent = routePath === childPage.href
return (
<li
key={childPage.href + i}
className={cx(
'sidebar-article',
isActive && 'active',
isCurrent && 'is-current-page'
)}
>
<Link
href={childPage.href}
className={cx(
'pl-6 pr-5 py-1 no-underline',
isLast && 'pb-2',
isActive && 'color-text-link'
)}
>
{childTitle}
</Link>
</li>
)
})}
{page.childPages.map(renderTerminalPageLink)}
</ul>
) : null}
</>

View File

@@ -1,4 +1,4 @@
.procedural-image-wrapper {
.markdown-body .procedural-image-wrapper {
display: block;
padding: 10px 0;
margin: 20px auto 0 auto;
@@ -6,7 +6,7 @@
max-width: calc(100% - 32px);
}
.procedural-image-wrapper img {
.markdown-body .procedural-image-wrapper img {
border-radius: 5px;
border: 2px solid var(--color-auto-gray-2);
width: auto;
@@ -16,11 +16,10 @@
}
// make sure images that contain emoji render at the expected size
img[src*="https://github.githubassets.com/images/icons/emoji"]
.markdown-body img[src*="https://github.githubassets.com/images/icons/emoji"]
{
height: 20;
width: 20;
align: absmiddle;
height: 20px;
width: 20px;
}
.markdown-body img {

View File

@@ -22,7 +22,6 @@ $marketing-font-path: "/assets/fonts/inter/";
@import "release-notes.scss";
@import "search.scss";
@import "shadows.scss";
@import "sidebar.scss";
@import "summary.scss";
@import "syntax-highlighting.scss";
@import "tables.scss";

View File

@@ -1,97 +0,0 @@
.sidebar {
width: 260px;
position: sticky;
top: 0;
padding-bottom: $spacer-5;
overflow-y: auto;
height: 100vh;
flex-shrink: 0;
@include breakpoint(xl) {
width: 280px;
}
}
.sidebar-products > li {
margin: 4px 0;
}
.sidebar-products a,
.sidebar-products .arrow {
text-decoration: none;
display: block;
line-height: 1.4;
}
.sidebar-category,
.sidebar-product {
> a,
summary a {
opacity: 0.8;
}
}
.sidebar-category.active {
background-color: rgba(#959da5, 0.1); // --color-auto-gray-4
// We can't do rgba(var(--color-auto-gray-4, 0.1) quite yet
// as the browsers are still working on supporting that combination
}
.sidebar-article {
position: relative;
&::before {
content: "";
position: absolute;
left: $spacer-4;
height: 100%;
border-left: 1px solid var(--color-text-primary);
width: 1px;
top: 0;
}
&.active {
&::before {
border-left: 2px solid var(--color-text-primary);
}
}
}
.is-current-page > a {
font-weight: bolder;
}
// only display child lists of active elements in sidebar
.sidebar-product.active > ul,
.sidebar-category.active > ul,
.sidebar-maptopic.active > ul {
display: block;
}
.sidebar-category {
> ul {
display: none;
}
}
.sidebar-maptopic > ul {
display: none;
}
.sidebar-maptopic > details > summary > div,
.sidebar-article > a {
color: var(--color-text-secondary);
}
.sidebar-maptopic > details > summary > div:hover,
.sidebar-article > a:hover {
color: var(--color-text-primary);
transition: color 0.2s ease;
}
.sidebar-category summary {
list-style: none;
}
summary::-webkit-details-marker {
display: none;
}

View File

@@ -462,7 +462,9 @@ describe.skip('next/link client-side navigation', () => {
response.url().startsWith('http://localhost:4001/_next/data')
),
page.waitForNavigation({ waitUntil: 'networkidle2' }),
page.click('.sidebar-articles:nth-child(2) .sidebar-article:nth-child(1) a'),
page.click(
'[data-testid=sidebar-article-group]:nth-child(2) [data-testid=sidebar-article]:nth-child(1) a'
),
])
expect(response.status()).toBe(200)

View File

@@ -38,7 +38,7 @@ describe('server', () => {
test('renders the homepage with links to exptected products in both the sidebar and page body', async () => {
const $ = await getDOM('/en')
const sidebarItems = $('.sidebar-products li a').get()
const sidebarItems = $('[data-testid=sidebar] li a').get()
const sidebarTitles = sidebarItems.map((el) => $(el).text().trim())
const sidebarHrefs = sidebarItems.map((el) => $(el).attr('href'))
@@ -73,7 +73,7 @@ describe('server', () => {
test('renders the Enterprise homepage with links to exptected products in both the sidebar and page body', async () => {
const $ = await getDOM(`/en/enterprise-server@${enterpriseServerReleases.latest}`)
const sidebarItems = $('.sidebar-products li a').get()
const sidebarItems = $('[data-testid=sidebar] li a').get()
const sidebarTitles = sidebarItems.map((el) => $(el).text().trim())
const sidebarHrefs = sidebarItems.map((el) => $(el).attr('href'))

View File

@@ -15,34 +15,45 @@ describe('sidebar', () => {
})
test('highlights active product on Enterprise pages', async () => {
expect($enterprisePage('.sidebar-products li.sidebar-product').length).toBe(1)
expect($enterprisePage('.sidebar-products li.sidebar-product > a').text().trim()).toBe(
'Enterprise administrators'
)
expect($enterprisePage('[data-testid=sidebar] [data-testid=sidebar-product]').length).toBe(1)
expect(
$enterprisePage('[data-testid=sidebar] [data-testid=sidebar-product] > a').text().trim()
).toBe('Enterprise administrators')
})
test('highlights active product on GitHub pages', async () => {
expect($githubPage('.sidebar-products li.sidebar-product').length).toBe(1)
expect($githubPage('.sidebar-products li.sidebar-product > a').text().trim()).toBe('GitHub')
expect($githubPage('[data-testid=sidebar] [data-testid=sidebar-product]').length).toBe(1)
expect(
$githubPage('[data-testid=sidebar] [data-testid=sidebar-product] > a').text().trim()
).toBe('GitHub')
})
test('includes links to external products like the CLI, Atom, Electron, and CodeQL', async () => {
expect($homePage('.sidebar-products a[href="https://cli.github.com/manual"]')).toHaveLength(1)
expect($homePage('.sidebar-products a[href="https://atom.io/docs"]')).toHaveLength(1)
expect($homePage('.sidebar-products a[href="https://electronjs.org/docs"]')).toHaveLength(1)
expect($homePage('.sidebar-products a[href="https://codeql.github.com/docs"]')).toHaveLength(1)
expect($homePage('[data-testid=sidebar] a[href="https://cli.github.com/manual"]')).toHaveLength(
1
)
expect($homePage('[data-testid=sidebar] a[href="https://atom.io/docs"]')).toHaveLength(1)
expect($homePage('[data-testid=sidebar] a[href="https://electronjs.org/docs"]')).toHaveLength(1)
expect(
$homePage('[data-testid=sidebar] a[href="https://codeql.github.com/docs"]')
).toHaveLength(1)
})
test('adds an `is-current-page` class to the sidebar link to the current page', async () => {
test('adds `data-is-current-page` and `data-is-active-category` properties to the sidebar link for the current page', async () => {
const url =
'/en/github/setting-up-and-managing-your-github-user-account/managing-user-account-settings'
const $ = await getDOM(url)
expect($('.sidebar-products .is-current-page').length).toBe(1)
expect($('.sidebar-products .is-current-page a').attr('href')).toContain(url)
expect($('[data-testid=sidebar] [data-is-active-category=true]').length).toBe(1)
expect($('[data-testid=sidebar] [data-is-current-page=true]').length).toBe(1)
expect($('[data-testid=sidebar] [data-is-current-page=true] a').attr('href')).toContain(url)
})
test('does not display Early Access as a product', async () => {
expect($homePage('.sidebar-products li.sidebar-product[title*="Early"]').length).toBe(0)
expect($homePage('.sidebar-products li.sidebar-product[title*="early"]').length).toBe(0)
expect(
$homePage('[data-testid=sidebar] [data-testid=sidebar-product][title*="Early"]').length
).toBe(0)
expect(
$homePage('[data-testid=sidebar] [data-testid=sidebar-product][title*="early"]').length
).toBe(0)
})
})