1
0
mirror of synced 2026-01-04 18:06:26 -05:00

Merge branch 'main' into patch-2

This commit is contained in:
Ramya Parimi
2021-10-01 06:29:27 -05:00
committed by GitHub
80 changed files with 23915 additions and 14095 deletions

View File

@@ -136,8 +136,7 @@ jobs:
target_url: ACTIONS_RUN_LOG
})
- if: ${{ github.repository == 'github/docs-internal' }}
name: Check out repo's default branch
- name: Check out repo's default branch
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
with:
# To prevent issues with cloning early access content later

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -37,7 +37,7 @@ export function Link(props: Props) {
}
return (
<NextLink href={href || ''} locale={locale || false}>
<NextLink href={locale ? `/${locale}${href}` : href || ''} locale={locale || false}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a rel={isExternal ? 'noopener' : ''} {...restProps} />
</NextLink>

View File

@@ -171,7 +171,7 @@ export function Search({
)}
>
{results.length > 0 ? (
<ol data-testid="search-results" className="d-block mt-2">
<ol data-testid="search-results" className="d-block mt-4">
{results.map(({ url, breadcrumbs, heading, title, content }, index) => {
const isActive = index === activeHit
return (

View File

@@ -0,0 +1,126 @@
import { useRouter } from 'next/router'
import cx from 'classnames'
import { Dropdown, Heading, Details, Box, Text, useDetails } from '@primer/components'
import { ArrowRightIcon, ChevronDownIcon } from '@primer/octicons-react'
import { Link } from 'components/Link'
import { useMainContext } from 'components/context/MainContext'
import { useVersion } from 'components/hooks/useVersion'
import { useTranslation } from 'components/hooks/useTranslation'
type Props = {
hideLabel?: boolean
variant?: 'default' | 'compact' | 'inline'
popoverVariant?: 'inline' | 'dropdown'
}
export const VersionPicker = ({ variant = 'default', popoverVariant, hideLabel }: Props) => {
const router = useRouter()
const { currentVersion } = useVersion()
const { allVersions, page, enterpriseServerVersions } = useMainContext()
const { getDetailsProps, setOpen } = useDetails({ closeOnOutsideClick: true })
const { t } = useTranslation('pages')
if (page.permalinks && page.permalinks.length <= 1) {
return null
}
return (
<>
{!hideLabel && (
<Heading as="span" fontSize={1} className="d-none d-xl-inline-block mb-1">
{t('article_version')}
</Heading>
)}
<div>
<Details
{...getDetailsProps()}
className={cx(
'position-relative details-reset',
variant === 'inline' ? 'd-block' : 'd-inline-block'
)}
data-testid="article-version-picker"
>
{(variant === 'compact' || variant === 'inline') && (
<summary
className="d-block btn btn-invisible color-text-primary"
aria-haspopup="true"
aria-label="Toggle version list"
>
{variant === 'inline' ? (
<div className="d-flex flex-items-center flex-justify-between">
<Text>{allVersions[currentVersion].versionTitle}</Text>
<ChevronDownIcon size={24} className="arrow ml-md-1" />
</div>
) : (
<>
<Text>{allVersions[currentVersion].versionTitle}</Text>
<Dropdown.Caret />
</>
)}
</summary>
)}
{variant === 'default' && (
<summary aria-haspopup="true" className="btn btn-sm">
<Text>{allVersions[currentVersion].versionTitle}</Text>
<Dropdown.Caret />
</summary>
)}
{popoverVariant === 'inline' ? (
<Box py="2">
{(page.permalinks || []).map((permalink) => {
return (
<Dropdown.Item key={permalink.href} onClick={() => setOpen(false)}>
<Link href={permalink.href}>{permalink.pageVersionTitle}</Link>
</Dropdown.Item>
)
})}
<Box mt={1}>
<Link
onClick={() => {
setOpen(false)
}}
href={`/${router.locale}/${enterpriseServerVersions[0]}/admin/all-releases`}
className="f6 no-underline color-text-tertiary pl-3 pr-2 no-wrap"
>
{t('all_enterprise_releases')}{' '}
<ArrowRightIcon verticalAlign="middle" size={15} className="mr-2" />
</Link>
</Box>
</Box>
) : (
<Dropdown.Menu direction="sw" style={{ width: 'unset' }}>
{(page.permalinks || []).map((permalink) => {
return (
<Dropdown.Item key={permalink.href} onClick={() => setOpen(false)}>
<Link href={permalink.href}>{permalink.pageVersionTitle}</Link>
</Dropdown.Item>
)
})}
<Box
borderColor="border.default"
borderTopWidth={1}
borderTopStyle="solid"
mt={2}
pt={2}
pb={1}
>
<Link
onClick={() => {
setOpen(false)
}}
href={`/${router.locale}/${enterpriseServerVersions[0]}/admin/all-releases`}
className="f6 no-underline color-text-tertiary pl-3 pr-2 no-wrap"
>
{t('all_enterprise_releases')}{' '}
<ArrowRightIcon verticalAlign="middle" size={15} className="mr-2" />
</Link>
</Box>
</Dropdown.Menu>
)}
</Details>
</div>
</>
)
}

View File

@@ -1,44 +0,0 @@
@import "@primer/css/layout/index.scss";
@import "@primer/css/support/variables/layout.scss";
@import "@primer/css/marketing/support/variables.scss";
.container {
max-width: 720px;
@include breakpoint(xl) {
max-width: none;
display: grid;
grid-template-rows: auto 1fr;
grid-template-columns: minmax(500px, 720px) minmax(220px, 1fr);
grid-template-areas:
"top right-sidebar"
"bottom right-sidebar";
column-gap: $spacer-6;
}
@include breakpoint(xl) {
column-gap: $spacer-9;
}
}
.sidebar {
grid-area: right-sidebar;
}
.sidebarContent {
@include breakpoint(xl) {
position: sticky;
top: $spacer-4;
max-height: calc(100vh - #{$spacer-4});
overflow-y: auto;
padding-bottom: $spacer-4;
}
}
.head {
grid-area: top;
}
.content {
grid-area: bottom;
}

View File

@@ -1,30 +1,78 @@
import React from 'react'
import cx from 'classnames'
import styles from './ArticleGridLayout.module.scss'
import styled from 'styled-components'
import { Box, themeGet } from '@primer/components'
type Props = {
head?: React.ReactNode
intro?: React.ReactNode
topperSidebar?: React.ReactNode
topper?: React.ReactNode
toc?: React.ReactNode
children?: React.ReactNode
className?: string
}
export const ArticleGridLayout = ({ head, toc, children, className }: Props) => {
export const ArticleGridLayout = ({
intro,
topperSidebar,
topper,
toc,
children,
className,
}: Props) => {
return (
<div className={cx(styles.container, className)}>
{/* head */}
{head && <div className={styles.head}>{head}</div>}
{/* toc */}
<Container className={className}>
{topper && <Box gridArea="topper">{topper}</Box>}
{topperSidebar && <Box gridArea="topper-sidebar">{topperSidebar}</Box>}
{toc && (
<div className={cx(styles.sidebar, 'border-bottom border-xl-0 pb-4 mb-5 pb-xl-0 mb-xl-0')}>
<div className={styles.sidebarContent}>{toc}</div>
</div>
<SidebarContent
gridArea="sidebar"
alignSelf="flex-start"
className="border-bottom border-xl-0 pb-4 mb-5 pb-xl-0 mb-xl-0"
>
{toc}
</SidebarContent>
)}
{/* content */}
<div data-search="article-body" className={styles.content}>
{intro && <Box gridArea="intro">{intro}</Box>}
<Box gridArea="content" data-search="article-body">
{children}
</div>
</div>
</Box>
</Container>
)
}
const Container = styled(Box)`
max-width: 720px;
display: grid;
grid-template-areas:
'topper'
'topper-sidebar'
'intro'
'sidebar'
'content';
row-gap: ${themeGet('space.2')};
@media (min-width: ${themeGet('breakpoints.3')}) {
max-width: none;
grid-template-rows: auto 1fr;
grid-template-columns: minmax(500px, 720px) minmax(220px, 1fr);
grid-template-areas:
'topper topper-sidebar'
'intro sidebar'
'content sidebar';
column-gap: ${themeGet('space.9')};
row-gap: 0;
}
`
const SidebarContent = styled(Box)`
@media (min-width: ${themeGet('breakpoints.3')}) {
position: sticky;
padding-top: ${themeGet('space.4')};
top: 0;
max-height: calc(100vh - ${themeGet('space.4')});
overflow-y: auto;
padding-bottom: ${themeGet('space.4')};
}
`

View File

@@ -1,12 +1,12 @@
import { useRouter } from 'next/router'
import cx from 'classnames'
import { Heading } from '@primer/components'
import { ZapIcon, InfoIcon } from '@primer/octicons-react'
import { Callout } from 'components/ui/Callout'
import { Link } from 'components/Link'
import { DefaultLayout } from 'components/DefaultLayout'
import { ArticleTopper } from 'components/article/ArticleTopper'
import { ArticleTitle } from 'components/article/ArticleTitle'
import { useArticleContext } from 'components/context/ArticleContext'
import { useTranslation } from 'components/hooks/useTranslation'
@@ -14,6 +14,8 @@ import { LearningTrackNav } from './LearningTrackNav'
import { MarkdownContent } from 'components/ui/MarkdownContent'
import { Lead } from 'components/ui/Lead'
import { ArticleGridLayout } from './ArticleGridLayout'
import { VersionPicker } from 'components/VersionPicker'
import { Breadcrumbs } from 'components/Breadcrumbs'
// Mapping of a "normal" article to it's interactive counterpart
const interactiveAlternatives: Record<string, { href: string }> = {
@@ -44,12 +46,11 @@ export const ArticlePage = () => {
return (
<DefaultLayout>
<div className="container-xl px-3 px-md-6 my-4 my-lg-4">
<ArticleTopper />
<div className="container-xl px-3 px-md-6 my-4">
<ArticleGridLayout
className="mt-7"
head={
topper={<Breadcrumbs />}
topperSidebar={<VersionPicker />}
intro={
<>
<ArticleTitle>{title}</ArticleTitle>
@@ -124,11 +125,11 @@ export const ArticlePage = () => {
)}
{miniTocItems.length > 1 && (
<>
<h2 id="in-this-article" className="f5 mb-2">
<Heading as="h2" fontSize={1} id="in-this-article" className="mb-1">
<a className="Link--primary" href="#in-this-article">
{t('miniToc')}
</a>
</h2>
</Heading>
<ul className="list-style-none pl-0 f5 mb-0">
{miniTocItems.map((item) => {
return (

View File

@@ -4,7 +4,7 @@ type Props = {
export const ArticleTitle = ({ children }: Props) => {
return (
<div className="d-flex flex-items-baseline flex-justify-between">
<h1 className="my-4 border-bottom-0">{children}</h1>
<h1 className="mt-4 border-bottom-0">{children}</h1>
</div>
)
}

View File

@@ -1,18 +0,0 @@
import { Breadcrumbs } from 'components/Breadcrumbs'
import { ArticleVersionPicker } from 'components/article/ArticleVersionPicker'
export const ArticleTopper = () => {
return (
<div className="d-lg-flex flex-justify-between">
<div className="d-block d-lg-none mb-2">
<ArticleVersionPicker />
</div>
<div className="d-flex flex-items-center">
<Breadcrumbs />
</div>
<div className="d-none d-lg-block">
<ArticleVersionPicker />
</div>
</div>
)
}

View File

@@ -1,52 +0,0 @@
import { useRouter } from 'next/router'
import { Dropdown } from '@primer/components'
import { Link } from 'components/Link'
import { useMainContext } from 'components/context/MainContext'
import { useVersion } from 'components/hooks/useVersion'
import { useTranslation } from 'components/hooks/useTranslation'
export const ArticleVersionPicker = () => {
const router = useRouter()
const { currentVersion } = useVersion()
const { allVersions, page, enterpriseServerVersions } = useMainContext()
const { t } = useTranslation('pages')
if (page.permalinks && page.permalinks.length <= 1) {
return null
}
return (
<Dropdown
css={`
ul {
width: unset;
}
`}
data-testid="article-version-picker"
>
<summary className="btn btn-outline p-2 outline-none">
<span className="d-md-none d-xl-inline-block">{t('article_version')}</span>{' '}
{allVersions[currentVersion].versionTitle}
<Dropdown.Caret />
</summary>
<Dropdown.Menu direction="sw">
{(page.permalinks || []).map((permalink) => {
return (
<Dropdown.Item key={permalink.href}>
<Link href={permalink.href}>{permalink.pageVersionTitle}</Link>
</Dropdown.Item>
)
})}
<div className="pb-1">
<Link
href={`/${router.locale}/${enterpriseServerVersions[0]}/admin/all-releases`}
className="f6 no-underline color-text-tertiary pl-3 pr-2 no-wrap"
>
See all Enterprise releases
</Link>
</div>
</Dropdown.Menu>
</Dropdown>
)
}

View File

@@ -6,7 +6,7 @@ type Props = {
}
export function LearningTrackNav({ track }: Props) {
const { t } = useTranslation('learning_track_nav')
const { prevGuide, nextGuide, trackName } = track
const { prevGuide, nextGuide, trackName, trackProduct } = track
return (
<div
data-testid="learning-track-nav"
@@ -17,7 +17,7 @@ export function LearningTrackNav({ track }: Props) {
<>
<span className="f6 color-text-secondary">{t('prevGuide')}</span>
<a
href={`${prevGuide.href}?learn=${trackName}`}
href={`${prevGuide.href}?learn=${trackName}&learnProduct=${trackProduct}`}
className="text-bold color-text-secondary"
>
{prevGuide.title}
@@ -31,7 +31,7 @@ export function LearningTrackNav({ track }: Props) {
<>
<span className="f6 color-text-secondary">{t('nextGuide')}</span>
<a
href={`${nextGuide.href}?learn=${trackName}`}
href={`${nextGuide.href}?learn=${trackName}&learnProduct=${trackProduct}`}
className="text-bold color-text-secondary text-right f4"
>
{nextGuide.title}

View File

@@ -2,6 +2,7 @@ import { createContext, useContext } from 'react'
export type LearningTrack = {
trackName?: string
trackProduct?: string
prevGuide?: { href: string; title: string }
nextGuide?: { href: string; title: string }
}

View File

@@ -62,10 +62,6 @@ export type MainContextT = {
article?: BreadcrumbT
}
activeProducts: Array<ProductT>
community_redirect: {
name: string
href: string
}
currentProduct?: ProductT
currentLayoutName: string
isHomepageVersion: boolean
@@ -114,7 +110,6 @@ export const getMainContext = (req: any, res: any): MainContextT => {
return {
breadcrumbs: req.context.breadcrumbs || {},
activeProducts: req.context.activeProducts,
community_redirect: req.context.page?.community_redirect || {},
currentProduct: req.context.productMap[req.context.currentProduct] || null,
currentLayoutName: req.context.currentLayoutName,
isHomepageVersion: req.context.page?.documentType === 'homepage',

View File

@@ -3,6 +3,7 @@ import pick from 'lodash/pick'
export type FeaturedTrack = {
trackName: string
trackProduct: string
title: string
description: string
guides?: Array<{ href: string; page?: { type: string }; title: string; intro: string }>
@@ -47,14 +48,14 @@ export const getProductSubLandingContextFromRequest = (req: any): ProductSubLand
title: req.context.productMap[req.context.currentProduct].name,
featuredTrack: page.featuredTrack
? {
...pick(page.featuredTrack, ['title', 'description', 'trackName']),
...pick(page.featuredTrack, ['title', 'description', 'trackName', 'trackProduct']),
guides: (page.featuredTrack?.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
}),
}
: null,
learningTracks: (page.learningTracks || []).map((track: any) => ({
...pick(track, ['title', 'description', 'trackName']),
...pick(track, ['title', 'description', 'trackName', 'trackProduct']),
guides: (track.guides || []).map((guide: any) => {
return pick(guide, ['title', 'intro', 'href', 'page.type'])
}),

View File

@@ -38,7 +38,6 @@ export const useTocLandingContext = (): TocLandingContextT => {
}
export const getTocLandingContextFromRequest = (req: any): TocLandingContextT => {
const isEarlyAccess = req.context.page?.documentType === 'early-access'
return {
title: req.context.page.titlePlainText,
productCallout: req.context.page.product || '',
@@ -49,7 +48,7 @@ export const getTocLandingContextFromRequest = (req: any): TocLandingContextT =>
variant: req.context.genericTocFlat ? 'expanded' : 'compact',
featuredLinks: getFeaturedLinksFromReq(req),
renderedPage: isEarlyAccess ? req.context.renderedPage : '',
renderedPage: req.context.renderedPage,
currentLearningTrack: req.context.currentLearningTrack,
}
}

View File

@@ -1,93 +0,0 @@
import cx from 'classnames'
import { useRouter } from 'next/router'
import { Dropdown, Details, useDetails } from '@primer/components'
import { ChevronDownIcon } from '@primer/octicons-react'
import { Link } from 'components/Link'
import { useMainContext } from 'components/context/MainContext'
import { useVersion } from 'components/hooks/useVersion'
type Props = {
variant?: 'inline'
}
export const HomepageVersionPicker = ({ variant }: Props) => {
const router = useRouter()
const { currentVersion } = useVersion()
const { getDetailsProps } = useDetails({})
const { allVersions, page, enterpriseServerVersions } = useMainContext()
if (page.permalinks && page.permalinks.length <= 1) {
return null
}
const label = allVersions[currentVersion].versionTitle
if (variant === 'inline') {
return (
<Details {...getDetailsProps()} className="details-reset">
<summary className="outline-none" aria-label="Toggle language list">
<div className="d-flex flex-items-center flex-justify-between py-2">
<span>{label}</span>
<ChevronDownIcon size={24} className="arrow ml-md-1" />
</div>
</summary>
<div>
{(page.permalinks || []).map((permalink) => {
return (
<Link
key={permalink.href}
href={permalink.href}
className={cx(
'd-block py-2',
permalink.href === router.asPath
? 'color-text-link text-underline active'
: 'Link--primary no-underline'
)}
>
{permalink.pageVersionTitle}
</Link>
)
})}
<Link
href={`/${router.locale}/${enterpriseServerVersions[0]}/admin/all-releases`}
className="f6 no-underline color-text-tertiary no-wrap"
>
See all Enterprise releases
</Link>
</div>
</Details>
)
}
return (
<Dropdown
css={`
ul {
width: unset;
}
`}
>
<summary>
{label}
<Dropdown.Caret />
</summary>
<Dropdown.Menu direction="sw">
{(page.permalinks || []).map((permalink) => {
return (
<Dropdown.Item key={permalink.href}>
<Link href={permalink.href}>{permalink.pageVersionTitle}</Link>
</Dropdown.Item>
)
})}
<div className="pb-1">
<Link
href={`/${router.locale}/${enterpriseServerVersions[0]}/admin/all-releases`}
className="f6 no-underline color-text-tertiary pl-3 pr-2 no-wrap"
>
See all Enterprise releases
</Link>
</div>
</Dropdown.Menu>
</Dropdown>
)
}

View File

@@ -1,7 +1,8 @@
import { DefaultLayout } from 'components/DefaultLayout'
import { TableOfContents } from 'components/landing/TableOfContents'
import { useTocLandingContext } from 'components/context/TocLandingContext'
import { ArticleTopper } from 'components/article/ArticleTopper'
import { VersionPicker } from 'components/VersionPicker'
import { Breadcrumbs } from 'components/Breadcrumbs'
import { ArticleTitle } from 'components/article/ArticleTitle'
import { MarkdownContent } from 'components/ui/MarkdownContent'
import { ArticleList } from 'components/landing/ArticleList'
@@ -9,7 +10,7 @@ import { useTranslation } from 'components/hooks/useTranslation'
import { ArticleGridLayout } from 'components/article/ArticleGridLayout'
import { Callout } from 'components/ui/Callout'
import { Lead } from 'components/ui/Lead'
import { LearningTrackNav } from '../article/LearningTrackNav'
import { LearningTrackNav } from 'components/article/LearningTrackNav'
export const TocLanding = () => {
const {
@@ -26,10 +27,8 @@ export const TocLanding = () => {
return (
<DefaultLayout>
<div className="container-xl px-3 px-md-6 my-4 my-lg-4">
<ArticleTopper />
<ArticleGridLayout className="mt-7">
<div className="container-xl px-3 px-md-6 my-4">
<ArticleGridLayout topper={<Breadcrumbs />} topperSidebar={<VersionPicker />}>
<ArticleTitle>{title}</ArticleTitle>
{introPlainText && <Lead>{introPlainText}</Lead>}
@@ -59,7 +58,7 @@ export const TocLanding = () => {
)}
{renderedPage && (
<div id="article-contents">
<div id="article-contents" className="mb-5">
<MarkdownContent>{renderedPage}</MarkdownContent>
</div>
)}

View File

@@ -7,18 +7,29 @@ import { useMainContext } from 'components/context/MainContext'
export const Support = () => {
const { isEnterprise } = useVersion()
const { t } = useTranslation('support')
const { community_redirect } = useMainContext()
const { relativePath } = useMainContext()
let updatedCommunityLink = ''
if (
relativePath?.startsWith('codespaces') ||
relativePath?.startsWith('discussions') ||
relativePath?.startsWith('sponsors')
) {
const product = relativePath.substring(0, relativePath.indexOf('/'))
updatedCommunityLink = `https://github.com/github/feedback/discussions/categories/${product}-feedback`
}
return (
<div>
<h3 className="mb-2 f4">{t`still_need_help`}</h3>
<a
id="ask-community"
href={community_redirect.href || 'https://github.community/'}
href={updatedCommunityLink === '' ? 'https://github.community/' : updatedCommunityLink}
className="btn btn-outline mr-4 mt-2"
>
<PeopleIcon size="small" className="octicon mr-1" />
{Object.keys(community_redirect).length === 0 ? t`ask_community` : community_redirect.name}
{updatedCommunityLink === '' ? t`ask_community` : 'Provide GitHub Feedback'}
</a>
<a
id="contact-us"

View File

@@ -2,7 +2,6 @@ import { useState } from 'react'
import cx from 'classnames'
import { useRouter } from 'next/router'
import { MarkGithubIcon, ThreeBarsIcon, XIcon } from '@primer/octicons-react'
import { ButtonOutline } from '@primer/components'
import { Link } from 'components/Link'
import { useMainContext } from 'components/context/MainContext'
@@ -10,8 +9,8 @@ import { LanguagePicker } from './LanguagePicker'
import { HeaderNotifications } from 'components/page-header/HeaderNotifications'
import { ProductPicker } from 'components/page-header/ProductPicker'
import { useTranslation } from 'components/hooks/useTranslation'
import { HomepageVersionPicker } from 'components/landing/HomepageVersionPicker'
import { Search } from 'components/Search'
import { VersionPicker } from 'components/VersionPicker'
export const Header = () => {
const router = useRouter()
@@ -31,25 +30,23 @@ export const Header = () => {
<div className="border-bottom color-border-secondary no-print">
{error !== '404' && <HeaderNotifications />}
<header
className="container-xl px-3 px-md-6 pt-3 pb-3 position-relative"
style={{ zIndex: 2 }}
>
<header className={cx('container-xl px-3 px-md-6 pt-3 pb-3 position-relative z-3')}>
{/* desktop header */}
<div className="d-none d-lg-flex flex-justify-end" data-testid="desktop-header">
<div
className="d-none d-lg-flex flex-justify-end flex-items-center"
data-testid="desktop-header"
>
{showVersionPicker && (
<div className="py-2 mr-4">
<HomepageVersionPicker />
<div className="mr-2">
<VersionPicker hideLabel={true} variant="compact" />
</div>
)}
<div className="py-2">
<LanguagePicker />
</div>
<LanguagePicker />
{/* <!-- GitHub.com homepage and 404 page has a stylized search; Enterprise homepages do not --> */}
{relativePath !== 'index.md' && error !== '404' && (
<div className="d-inline-block ml-4">
<div className="d-inline-block ml-3">
<Search updateSearchParams={updateSearchParams} isOverlay={true} />
</div>
)}
@@ -72,14 +69,14 @@ export const Header = () => {
</div>
<div>
<ButtonOutline
<button
className="btn"
data-testid="mobile-menu-button"
css
onClick={() => setIsMenuOpen(!isMenuOpen)}
aria-label="Navigation Menu"
>
{isMenuOpen ? <XIcon size="small" /> : <ThreeBarsIcon size="small" />}
</ButtonOutline>
</button>
</div>
</div>
@@ -87,31 +84,33 @@ export const Header = () => {
<div className="relative">
<div
className={cx(
'width-full position-absolute left-0 right-0 color-shadow-large color-bg-primary px-3 px-md-6 pb-3',
'width-full position-absolute left-0 right-0 color-shadow-large color-bg-primary px-2 px-md-4 pb-3',
isMenuOpen ? 'd-block' : 'd-none'
)}
>
<div className="mt-3 mb-2">
<h4 className="f5 text-normal color-text-secondary">{t('explore_by_product')}</h4>
<h4 className="f5 text-normal color-text-secondary ml-3">
{t('explore_by_product')}
</h4>
<ProductPicker />
</div>
{/* <!-- Versions picker that only appears in the header on landing pages --> */}
{showVersionPicker && (
<div className="border-top py-2">
<HomepageVersionPicker variant="inline" />
</div>
<>
<div className="border-top my-2 mx-3" />
<VersionPicker hideLabel={true} variant="inline" popoverVariant={'inline'} />
</>
)}
{/* <!-- Language picker - 'English', 'Japanese', etc --> */}
<div className="border-top py-2">
<LanguagePicker variant="inline" />
</div>
<div className="border-top my-2 mx-3" />
<LanguagePicker variant="inline" />
{/* <!-- GitHub.com homepage and 404 page has a stylized search; Enterprise homepages do not --> */}
{relativePath !== 'index.md' && error !== '404' && (
<div className="pt-3 border-top">
<div className="my-2 pt-3 mx-3">
<Search updateSearchParams={updateSearchParams} />
</div>
)}

View File

@@ -1,6 +1,5 @@
import cx from 'classnames'
import { useRouter } from 'next/router'
import { Dropdown, Details, useDetails } from '@primer/components'
import { Box, Dropdown, Details, Text, useDetails } from '@primer/components'
import { ChevronDownIcon } from '@primer/octicons-react'
import { Link } from 'components/Link'
@@ -12,76 +11,63 @@ type Props = {
export const LanguagePicker = ({ variant }: Props) => {
const router = useRouter()
const { languages } = useLanguages()
const { getDetailsProps } = useDetails({})
const { getDetailsProps, setOpen } = useDetails({ closeOnOutsideClick: true })
const locale = router.locale || 'en'
const langs = Object.values(languages)
const selectedLang = languages[locale]
if (variant === 'inline') {
return (
<Details {...getDetailsProps()} className="details-reset">
<summary className="outline-none" aria-label="Toggle language list">
<div className="d-flex flex-items-center flex-justify-between py-2">
<span>{selectedLang.nativeName || selectedLang.name}</span>
<Details {...getDetailsProps()} data-testid="language-picker">
<summary
className="d-block btn btn-invisible color-text-primary"
aria-label="Toggle language list"
>
<div className="d-flex flex-items-center flex-justify-between">
<Text>{selectedLang.nativeName || selectedLang.name}</Text>
<ChevronDownIcon size={24} className="arrow ml-md-1" />
</div>
</summary>
<div>
<Box mt={1}>
{langs.map((lang) => {
if (lang.wip) {
return null
}
return (
<Link
key={lang.code}
href={router.asPath}
locale={lang.code}
disableClientTransition={true}
className={cx(
'd-block py-2',
lang.code === router.locale
? 'color-text-link text-underline active'
: 'Link--primary no-underline'
)}
>
{lang.nativeName ? (
<>
{lang.nativeName} ({lang.name})
</>
) : (
lang.name
)}
</Link>
<Dropdown.Item onClick={() => setOpen(false)} key={lang.code}>
<Link href={router.asPath} locale={lang.code}>
{lang.nativeName ? (
<>
{lang.nativeName} ({lang.name})
</>
) : (
lang.name
)}
</Link>
</Dropdown.Item>
)
})}
</div>
</Box>
</Details>
)
}
return (
<Dropdown
css={`
ul {
width: unset;
}
`}
data-testid="language-picker"
>
<summary>
{selectedLang.nativeName || selectedLang.name}
<Details {...getDetailsProps()} data-testid="language-picker" className="position-relative">
<summary className="d-block btn btn-invisible color-text-primary">
<Text>{selectedLang.nativeName || selectedLang.name}</Text>
<Dropdown.Caret />
</summary>
<Dropdown.Menu direction="sw">
<Dropdown.Menu direction="sw" style={{ width: 'unset' }}>
{langs.map((lang) => {
if (lang.wip) {
return null
}
return (
<Dropdown.Item key={lang.code}>
<Link href={router.asPath} locale={lang.code} disableClientTransition={true}>
<Dropdown.Item key={lang.code} onClick={() => setOpen(false)}>
<Link href={router.asPath} locale={lang.code}>
{lang.nativeName ? (
<>
{lang.nativeName} ({lang.name})
@@ -94,6 +80,6 @@ export const LanguagePicker = ({ variant }: Props) => {
)
})}
</Dropdown.Menu>
</Dropdown>
</Details>
)
}

View File

@@ -1,53 +1,48 @@
import { useRouter } from 'next/router'
import cx from 'classnames'
import { Link } from 'components/Link'
import { useMainContext } from 'components/context/MainContext'
import { ChevronDownIcon, LinkExternalIcon } from '@primer/octicons-react'
import { Details, useDetails } from '@primer/components'
import { Box, Dropdown, Details, useDetails } from '@primer/components'
// Product Picker - GitHub.com, Enterprise Server, etc
export const ProductPicker = () => {
const router = useRouter()
const { activeProducts, currentProduct } = useMainContext()
const { getDetailsProps } = useDetails({})
const { getDetailsProps, setOpen } = useDetails({ closeOnOutsideClick: true })
return (
<Details {...getDetailsProps()} className="details-reset">
<summary
className="color-text-link outline-none"
className="d-block color-text-primary btn btn-invisible"
role="button"
aria-label="Toggle products list"
>
<div id="current-product" className="d-flex flex-items-center flex-justify-between py-2">
{/* <!-- Product switcher - GitHub.com, Enterprise Server, etc -->
<!-- 404 and 500 error layouts are not real pages so we need to hardcode the name for those --> */}
<div
data-testid="current-product"
data-current-product-path={currentProduct?.href}
className="d-flex flex-items-center flex-justify-between"
>
<span>{currentProduct?.name || 'All Products'}</span>
<ChevronDownIcon size={24} className="arrow ml-md-1" />
</div>
</summary>
<div id="homepages" style={{ zIndex: 6 }}>
<Box data-testid="product-picker-list" py="2" style={{ zIndex: 6 }}>
{activeProducts.map((product) => {
return (
<Link
key={product.id}
href={`${product.external ? '' : `/${router.locale}`}${product.href}`}
className={cx(
'd-block py-2',
product.id === currentProduct?.id
? 'color-text-link text-underline active'
: 'Link--primary no-underline'
)}
>
{product.name}
{product.external && (
<span className="ml-1">
<LinkExternalIcon size="small" />
</span>
)}
</Link>
<Dropdown.Item key={product.id} onClick={() => setOpen(false)}>
<Link href={`${product.external ? '' : `/${router.locale}`}${product.href}`}>
{product.name}
{product.external && (
<span className="ml-1">
<LinkExternalIcon size="small" />
</span>
)}
</Link>
</Dropdown.Item>
)
})}
</div>
</Box>
</Details>
)
}

View File

@@ -31,17 +31,16 @@ export const CodeLanguagePicker = ({ variant }: Props) => {
}
return (
<SelectMenu css className="position-relative">
<Button as="summary" css>
<SelectMenu className="position-relative">
<Button as="summary">
{currentLanguage.label} <Dropdown.Caret />
</Button>
<SelectMenu.Modal css style={{ minWidth: 300 }} align="right">
<SelectMenu.Header css>Programming Language</SelectMenu.Header>
<SelectMenu.Modal style={{ minWidth: 300 }} align="right">
<SelectMenu.Header>Programming Language</SelectMenu.Header>
<SelectMenu.List>
{codeLanguages.map((language) => (
<SelectMenu.Item
key={language.id}
css
as="a"
href={`${routePath}?langId=${language.id}`}
selected={language.id === currentLanguage.id}

View File

@@ -33,7 +33,9 @@ export const LearningTrack = ({ track }: Props) => {
<a
className="d-inline-flex btn no-wrap mt-3 mt-md-0 flex-items-center flex-justify-center"
role="button"
href={`${track?.guides && track?.guides[0].href}?learn=${track?.trackName}`}
href={`${track?.guides && track?.guides[0].href}?learn=${
track?.trackName
}&learnProduct=${track?.trackProduct}`}
>
<span>{t('start')}</span>
<ArrowRightIcon size={20} className="ml-2" />
@@ -44,7 +46,7 @@ export const LearningTrack = ({ track }: Props) => {
<div key={guide.href + track?.trackName}>
<a
className="Box-row d-flex flex-items-center color-text-primary no-underline"
href={`${guide.href}?learn=${track?.trackName}`}
href={`${guide.href}?learn=${track?.trackName}&learnProduct=${track?.trackProduct}`}
>
<div
className="color-bg-tertiary d-inline-flex mr-4 circle flex-items-center flex-justify-center"

View File

@@ -16,7 +16,7 @@ export const SubLandingHero = () => {
const guideItems = featuredTrack?.guides?.map((guide) => (
<li className="px-2 d-flex flex-shrink-0" key={guide.href} style={{ width: cardWidth }}>
<Link
href={`${guide.href}?learn=${featuredTrack.trackName}`}
href={`${guide.href}?learn=${featuredTrack.trackName}&learnProduct=${featuredTrack.trackProduct}`}
className="d-inline-block Box p-5 color-bg-primary color-border-primary no-underline"
>
<div className="d-flex flex-justify-between flex-items-center">
@@ -71,7 +71,7 @@ export const SubLandingHero = () => {
<Link
className="d-inline-flex flex-items-center flex-justify-center btn px-4 py-2 f5 no-underline text-bold"
role="button"
href={`${featuredTrack.guides[0].href}?learn=${featuredTrack.trackName}`}
href={`${featuredTrack.guides[0].href}?learn=${featuredTrack.trackName}&learnProduct=${featuredTrack.trackProduct}`}
>
{t(`start_path`)}
<ArrowRightIcon size={20} className="ml-2" />

View File

@@ -78,6 +78,7 @@ The `github` context contains information about the workflow run and the event t
| `github.repository_owner` | `string` | The repository owner's name. For example, `Codertocat`. |
| `github.run_id` | `string` | {% data reusables.github-actions.run_id_description %} |
| `github.run_number` | `string` | {% data reusables.github-actions.run_number_description %} |
| `github.run_attempt` | `string` | A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. |
| `github.server_url` | `string` | Returns the URL of the GitHub server. For example: `https://github.com`. |
| `github.sha` | `string` | The commit SHA that triggered the workflow run. |
| `github.token` | `string` | A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the `GITHUB_TOKEN` secret. For more information, see "[Authenticating with the GITHUB_TOKEN](/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token)." |

View File

@@ -79,34 +79,6 @@ You can use a fallback number regardless of whether you've configured authentica
After setup, the backup device will receive a confirmation SMS.
## Adding a fallback authentication method with Recover Accounts Elsewhere
You can generate an extra authentication credential for your account and store it with a partner recovery provider.
### About Recover Accounts Elsewhere
With Recover Accounts Elsewhere, you can add an extra security factor to your {% data variables.product.product_name %} account in case you lose access to your two-factor authentication method or recovery codes.
Recover Accounts Elsewhere lets you associate your {% data variables.product.product_name %} account with your Facebook account. You can store an authentication credential in the form of an _account recovery token_ for your {% data variables.product.product_name %} account with Facebook.
If you lose access to your {% data variables.product.product_name %} account because you no longer have access to your two-factor authentication method or recovery codes, you can retrieve your account recovery token from the recovery provider to help prove that you're the owner of your {% data variables.product.product_name %} account.
After you retrieve your token, {% data variables.contact.contact_support %} may be able to disable two-factor authentication for your account. Then, you can provide or reset your password to regain access to your account.
When you generate or retrieve an account recovery token, an event is added to your account's audit log. For more information, see "[Reviewing your security log](/articles/reviewing-your-security-log)."
### Generating and storing an account recovery token
You can generate an account recovery token and store it with a partner recovery provider.
1. Sign in to your Facebook account, then return to {% data variables.product.product_name %}.
{% data reusables.user_settings.access_settings %}
{% data reusables.user_settings.security %}
4. To generate a new token, under "Recovery tokens," click **Store new token**. ![Button for storing a new recovery token](/assets/images/help/settings/store-new-recovery-token.png)
5. Read the information about account recovery tokens, then click **Connect with https://www.facebook.com**. ![Button for connecting a recovery token with Facebook](/assets/images/help/settings/connect-recovery-token-with-facebook.png)
6. After you're redirected to Facebook, read the information about turning on account recovery with Facebook before you click **Save as [_YOUR NAME_]**. (If you save multiple tokens within a short period of time, Facebook may skip this confirmation step after you save your first token.)
![Facebook page with button for turning on account recovery](/assets/images/help/settings/security-turn-on-rae-facebook.png)
{% endif %}
## Further reading

View File

@@ -78,6 +78,12 @@ You can use your two-factor authentication credentials or two-factor authenticat
## Authenticating with an account recovery token
{% warning %}
**Warning:** Account recovery tokens are deprecated and will be disabled on **December 1st, 2021**. Please ensure you have configured other two-factor recovery methods. For more information, see "[Configuring two-factor authentication recovery methods](/articles/configuring-two-factor-authentication-recovery-methods)."
{% endwarning %}
If you lose access to the two-factor authentication methods for your {% data variables.product.product_name %} account, you can retrieve your account recovery token from a partner recovery provider and ask {% data variables.product.prodname_dotcom %} Support to review it.
If you don't have access to your two-factor authentication methods or recovery codes and you've stored an account recovery token with Facebook using Recover Accounts Elsewhere, you may be able to use your token to regain access to your account.

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -26,9 +26,6 @@ changelog:
examples_source: data/product-examples/discussions/community-examples.yml
product_video: 'https://www.youtube-nocookie.com/embed/IpBw2SJkFyk'
layout: product-landing
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/discussions-feedback'
versions:
fpt: '*'
children:

View File

@@ -277,6 +277,14 @@ The footnote will render like this:
![Rendered footnote](/assets/images/site/rendered-footnote.png)
{% endif %}
## Hiding content with comments
You can tell {% data variables.product.product_name %} to hide content from the rendered Markdown by placing the content in an HTML comment.
<pre>
&lt;!-- This content will not appear in the rendered Markdown --&gt;
</pre>
## Ignoring Markdown formatting
You can tell {% data variables.product.product_name %} to ignore (or escape) Markdown formatting by using `\` before the Markdown character.
@@ -287,13 +295,13 @@ You can tell {% data variables.product.product_name %} to ignore (or escape) Mar
For more information, see Daring Fireball's "[Markdown Syntax](https://daringfireball.net/projects/markdown/syntax#backslash)."
## Hiding content with comments
{% ifversion fpt or ghes > 3.2 or ghae-issue-5232 %}
You can tell {% data variables.product.product_name %} to hide content from the rendered Markdown by placing the content in an HTML comment.
## Disabling Markdown rendering
<pre>
&lt;!-- This content will not appear in the rendered Markdown --&gt;
</pre>
{% data reusables.repositories.disabling-markdown-rendering %}
{% endif %}
## Further reading

View File

@@ -190,6 +190,14 @@ You can click {% octicon "file" aria-label="The paper icon" %} to see the change
![Rendered Prose changes](/assets/images/help/repository/rendered_prose_changes.png)
{% ifversion fpt or ghes > 3.2 or ghae-issue-5232 %}
### Disabling Markdown rendering
{% data reusables.repositories.disabling-markdown-rendering %}
{% endif %}
### Visualizing attribute changes
We provide a tooltip

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->
<!---->

View File

@@ -9,6 +9,7 @@
access token.
- term: account recovery token
description: >-
**Deprecated and will be disabled December 1st, 2021.**
The authentication credential stored as part of an account recovery setup called Recover Accounts Elsewhere that allows you to store this backup credential.
- term: API preview
description: >-
@@ -597,6 +598,7 @@
the HEAD of that branch to the result.
- term: Recover Accounts Elsewhere
description: >-
**Deprecated and will be disabled December 1st, 2021.**
Allows users to add an extra security factor to their GitHub account in case they
lose access to their two-factor authentication method or recovery codes.
Users can associate their GitHub account with their Facebook account by

View File

@@ -6,6 +6,7 @@ sections:
bugs:
- 'Resque worker counts were displayed incorrectly during maintenance mode. {% comment %} https://github.com/github/enterprise2/pull/26898, https://github.com/github/enterprise2/pull/26883 {% endcomment %}'
- 'Allocated memcached memory could be zero in clustering mode. {% comment %} https://github.com/github/enterprise2/pull/26927, https://github.com/github/enterprise2/pull/26832 {% endcomment %}'
- 'Fixes {% data variables.product.prodname_pages %} builds so they take into account the NO_PROXY setting of the appliance. This is relevant to appliances configured with an HTTP proxy only. (update 2021-09-30) {% comment %} https://github.com/github/pages/pull/3360 {% endcomment %}'
known_issues:
- On a freshly set up {% data variables.product.prodname_ghe_server %} without any users, an attacker could create the first admin user.
- Custom firewall rules are removed during the upgrade process.

View File

@@ -7,6 +7,7 @@ sections:
- 'Resque worker counts were displayed incorrectly during maintenance mode. {% comment %} https://github.com/github/enterprise2/pull/26899, https://github.com/github/enterprise2/pull/26883 {% endcomment %}'
- 'Allocated memcached memory could be zero in clustering mode. {% comment %} https://github.com/github/enterprise2/pull/26928, https://github.com/github/enterprise2/pull/26832 {% endcomment %}'
- 'Non-empty binary files displayed an incorrect file type and size on the pull request "Files" tab. {% comment %} https://github.com/github/github/pull/192810, https://github.com/github/github/pull/172284, https://github.com/github/coding/issues/694 {% endcomment %}'
- 'Fixes {% data variables.product.prodname_pages %} builds so they take into account the NO_PROXY setting of the appliance. This is relevant to appliances configured with an HTTP proxy only. (update 2021-09-30) {% comment %} https://github.com/github/pages/pull/3360 {% endcomment %}'
known_issues:
- The {% data variables.product.prodname_registry %} npm registry no longer returns a time value in metadata responses. This was done to allow for substantial performance improvements. We continue to have all the data necessary to return a time value as part of the metadata response and will resume returning this value in the future once we have solved the existing performance issues.
- On a freshly set up {% data variables.product.prodname_ghe_server %} without any users, an attacker could create the first admin user.

View File

@@ -0,0 +1,5 @@
When viewing a Markdown file, you can click {% octicon "code" aria-label="The code icon" %} at the top of the file to disable Markdown rendering and view the file's source instead.
![Display Markdown as source](/assets/images/help/writing/display-markdown-as-source.png)
Disabling Markdown rendering enables you to use source view features, such as line linking, which is not possible when viewing rendered Markdown files.

View File

@@ -40,9 +40,10 @@ toc:
guides: Guides
whats_new: What's new
pages:
article_version: 'Article version:'
article_version: 'Article version'
miniToc: In this article
contributor_callout: This article is contributed and maintained by
all_enterprise_releases: All Enterprise releases
errors:
oops: Ooops!
something_went_wrong: It looks like something went wrong.

View File

@@ -149,14 +149,6 @@ export const schema = {
interactive: {
type: 'boolean',
},
// Redirect Community Support link to Public Discussions
community_redirect: {
type: 'object',
properties: {
name: 'string',
href: 'string',
},
},
// Platform-specific content preference
defaultPlatform: {
type: 'string',

View File

@@ -34,6 +34,7 @@ export default async function processLearningTracks(rawLearningTracks, context)
const learningTrack = {
trackName: renderedTrackName,
trackProduct: context.currentProduct || null,
title: await renderContent(track.title, context, renderOpts),
description: await renderContent(track.description, context, renderOpts),
// getLinkData respects versioning and only returns guides available in the current version;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -13,14 +13,24 @@ export default async function learningTrack(req, res, next) {
const trackName = req.query.learn
if (!trackName) return noTrack()
const tracksPerProduct = req.context.site.data['learning-tracks'][req.context.currentProduct]
let trackProduct = req.context.currentProduct
let tracksPerProduct = req.context.site.data['learning-tracks'][trackProduct]
// If there are no learning tracks for the current product, try and fall
// back to the learning track product set as a URL parameter. This handles
// the case where a learning track has guide paths for a different product
// than the current learning track product.
if (!tracksPerProduct) {
trackProduct = req.query.learnProduct
tracksPerProduct = req.context.site.data['learning-tracks'][trackProduct]
}
if (!tracksPerProduct) return noTrack()
const track = req.context.site.data['learning-tracks'][req.context.currentProduct][trackName]
const track = req.context.site.data['learning-tracks'][trackProduct][trackName]
if (!track) return noTrack()
const currentLearningTrack = { trackName }
const currentLearningTrack = { trackName, trackProduct }
const guidePath = getPathWithoutLanguage(getPathWithoutVersion(req.pagePath))
let guideIndex = track.guides.findIndex((path) => path === guidePath)

412
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@
"dependencies": {
"@alex_neo/jest-expect-message": "^1.0.5",
"@hapi/accept": "^5.0.2",
"@primer/components": "^28.5.0",
"@primer/components": "^29.1.1",
"@primer/css": "^17.9.0",
"@primer/octicons": "^15.1.0",
"@primer/octicons-react": "^15.1.0",
@@ -214,7 +214,7 @@
"prevent-pushes-to-main": "node script/prevent-pushes-to-main.js",
"rest-dev": "script/rest/update-files.js && npm run dev",
"start": "run-script-os",
"start:win32": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' --signal SIGKILL nodemon server.mjs",
"start:win32": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon --signal SIGKILL server.mjs",
"start:default": "cross-env NODE_ENV=development ENABLED_LANGUAGES='en,ja' nodemon server.mjs",
"start-all-languages": "cross-env NODE_ENV=development nodemon server.mjs",
"sync-search": "start-server-and-test sync-search-server 4002 sync-search-indices",

View File

@@ -68,6 +68,9 @@
.z-2 {
z-index: 2;
}
.z-3 {
z-index: 3;
}
/* Blue primary button
------------------------------------------------------------------------------*/

View File

@@ -7,7 +7,7 @@ describe('header', () => {
test('includes localized meta tags', async () => {
const $ = await getDOM('/en')
expect($('meta[name="next-head-count"]').length).toBe(1)
expect($('link[rel="alternate"]').length).toBeGreaterThan(2)
})
test("includes a link to the homepage (in the current page's language)", async () => {
@@ -26,26 +26,30 @@ describe('header', () => {
)
expect(
$(
'[data-testid=language-picker] a[href="/ja/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule"]'
'[data-testid=desktop-header] [data-testid=language-picker] a[href="/ja/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/managing-a-branch-protection-rule"]'
).length
).toBe(1)
})
test('display the native name and the English name for each translated language', async () => {
const $ = await getDOM('/en')
expect($('[data-testid=language-picker] a[href="/en/"]').text().trim()).toBe('English')
expect($('[data-testid=language-picker] a[href="/cn/"]').text().trim()).toBe(
'简体中文 (Simplified Chinese)'
)
expect($('[data-testid=language-picker] a[href="/ja/"]').text().trim()).toBe(
'日本語 (Japanese)'
)
expect(
$('[data-testid=desktop-header] [data-testid=language-picker] a[href="/en"]').text().trim()
).toBe('English')
expect(
$('[data-testid=desktop-header] [data-testid=language-picker] a[href="/cn"]').text().trim()
).toBe('简体中文 (Simplified Chinese)')
expect(
$('[data-testid=desktop-header] [data-testid=language-picker] a[href="/ja"]').text().trim()
).toBe('日本語 (Japanese)')
})
test('emphasize the current language', async () => {
const $ = await getDOM('/en')
expect($('[data-testid=language-picker] a[href="/en/"]').length).toBe(1)
expect($('[data-testid=language-picker] a[href="/ja/"]').length).toBe(1)
expect($('[data-testid=desktop-header] [data-testid=language-picker] summary').text()).toBe(
'English'
)
})
})
@@ -136,15 +140,15 @@ describe('header', () => {
const $ = await getDOM(
'/en/github/importing-your-projects-to-github/importing-source-code-to-github/about-github-importer'
)
const github = $('#homepages a.active[href="/en/github"]')
const github = $('[data-testid=current-product][data-current-product-path="/github"]')
expect(github.length).toBe(1)
expect(github.text().trim()).toBe('GitHub')
expect(github.attr('class').includes('active')).toBe(true)
const ghe = $(`#homepages a[href="/en/enterprise-server@${latest}/admin"]`)
const ghe = $(
`[data-testid=product-picker-list] a[href="/en/enterprise-server@${latest}/admin"]`
)
expect(ghe.length).toBe(1)
expect(ghe.text().trim()).toBe('Enterprise administrators')
expect(ghe.attr('class').includes('active')).toBe(false)
})
// Skipped. See issues/923
@@ -152,17 +156,21 @@ describe('header', () => {
const $ = await getDOM(
'/ja/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests'
)
expect($('#homepages a.active[href="/ja/repositories"]').length).toBe(1)
expect($(`#homepages a[href="/ja/enterprise-server@${latest}/admin"]`).length).toBe(1)
expect(
$('[data-testid=current-product][data-current-product-path="/repositories"]').length
).toBe(1)
expect(
$(`[data-testid=product-picker-list] a[href="/ja/enterprise-server@${latest}/admin"]`)
.length
).toBe(1)
})
test('emphasizes the product that corresponds to the current page', async () => {
const $ = await getDOM(
`/en/enterprise/${oldestSupported}/user/github/importing-your-projects-to-github/importing-source-code-to-github/importing-a-git-repository-using-the-command-line`
`/en/enterprise-server@${oldestSupported}/github/importing-your-projects-to-github/importing-source-code-to-github/importing-a-git-repository-using-the-command-line`
)
expect($(`#homepages a.active[href="/en/enterprise-server@${latest}/admin"]`).length).toBe(0)
expect($('#homepages a[href="/en/github"]').length).toBe(1)
expect($('#homepages a.active[href="/en/github"]').length).toBe(1)
expect($('[data-testid=current-product]').text()).toBe('GitHub')
})
})
})

View File

@@ -3,9 +3,9 @@ import { jest } from '@jest/globals'
jest.setTimeout(3 * 60 * 1000)
describe.skip('learning tracks', () => {
describe('learning tracks', () => {
test('render first track as feature track', async () => {
const $ = await getDOM('/en/actions/guides')
const $ = await getDOM('/en/code-security/guides')
expect($('[data-testid=feature-track]')).toHaveLength(1)
const href = $('[data-testid=feature-track] li a').first().attr('href')
const found = href.match(/.*\?learn=(.*)/i)
@@ -19,7 +19,7 @@ describe.skip('learning tracks', () => {
})
test('render other tracks', async () => {
const $ = await getDOM('/en/actions/guides')
const $ = await getDOM('/en/code-security/guides')
expect($('[data-testid=learning-track]').length).toBeGreaterThanOrEqual(4)
$('[data-testid=learning-track]').each((i, trackElem) => {
const href = $(trackElem).find('.Box-header a').first().attr('href')
@@ -37,16 +37,16 @@ describe.skip('learning tracks', () => {
})
})
describe.skip('navigation banner', () => {
describe('navigation banner', () => {
test('render navigation banner when url includes correct learning track name', async () => {
const $ = await getDOM(
'/en/actions/guides/setting-up-continuous-integration-using-workflow-templates?learn=continuous_integration'
'/en/code-security/security-advisories/creating-a-security-advisory?learn=security_advisories'
)
expect($('[data-testid=learning-track-nav]')).toHaveLength(1)
const $navLinks = $('[data-testid=learning-track-nav] a')
expect($navLinks).toHaveLength(2)
$navLinks.each((i, elem) => {
expect($(elem).attr('href')).toEqual(expect.stringContaining('?learn=continuous_integration'))
expect($(elem).attr('href')).toEqual(expect.stringContaining('?learn=security_advisories'))
})
})
@@ -62,6 +62,47 @@ describe.skip('navigation banner', () => {
})
})
test('render navigation banner when url belongs to a multi-product learning track', async () => {
// This is a `code-security` product learning track and it includes a guide
// path that belongs to the `repositories` product.
const $ = await getDOM(
'/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-security-and-analysis-settings-for-your-repository?learn=dependabot_alerts&learnProduct=code-security'
)
expect($('[data-testid=learning-track-nav]')).toHaveLength(1)
const $navLinks = $('[data-testid=learning-track-nav] a')
expect($navLinks).toHaveLength(2)
$navLinks.each((i, elem) => {
expect($(elem).attr('href')).toEqual(
expect.stringContaining('?learn=dependabot_alerts&learnProduct=code-security')
)
})
})
test('render navigation banner when url belongs to a learning track and has an incorrect `learnProduct` param', async () => {
// This is a `code-security` product learning track so the path should
// work as-is and we won't check `learnProduct`.
const $ = await getDOM(
'/en/code-security/supply-chain-security/managing-vulnerabilities-in-your-projects-dependencies/viewing-and-updating-vulnerable-dependencies-in-your-repository?learn=dependabot_alerts&learnProduct=not_real'
)
expect($('[data-testid=learning-track-nav]')).toHaveLength(1)
const $navLinks = $('[data-testid=learning-track-nav] a')
expect($navLinks).toHaveLength(2)
$navLinks.each((i, elem) => {
expect($(elem).attr('href')).toEqual(
expect.stringContaining('?learn=dependabot_alerts&learnProduct=code-security')
)
})
})
test('does not include banner with multi-product learning track and when url has incorrect `learnProduct` param', async () => {
// This is a `code-security` product learning track and it includes a guide
// path that belongs to the `repositories` product.
const $ = await getDOM(
'/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-security-and-analysis-settings-for-your-repository?learn=dependabot_alerts&learnProduct=not_real'
)
expect($('[data-testid=learning-track-nav]')).toHaveLength(0)
})
test('does not include banner when url does not include `learn` param', async () => {
const $ = await getDOM(
'/en/actions/guides/setting-up-continuous-integration-using-workflow-templates'

View File

@@ -545,7 +545,7 @@ describe('server', () => {
$(
`[data-testid=article-version-picker] a[href="/en/enterprise-server@${enterpriseServerReleases.latest}/${articlePath}"]`
).length
).toBe(2)
).toBe(1)
// 2.13 predates this feature, so it should be excluded:
expect(
$(`[data-testid=article-version-picker] a[href="/en/enterprise/2.13/user/${articlePath}"]`)

View File

@@ -25,28 +25,34 @@ describe('products module', () => {
describe('mobile-only products nav', () => {
test('renders current product on various product pages for each product', async () => {
// Note the unversioned homepage at `/` does not have a product selected in the mobile dropdown
expect((await getDOM('/github'))('#current-product').text().trim()).toBe('GitHub')
expect((await getDOM('/github'))('[data-testid=current-product]').text().trim()).toBe('GitHub')
// Enterprise server
expect((await getDOM('/en/enterprise/admin'))('#current-product').text().trim()).toBe(
'Enterprise administrators'
)
expect(
(await getDOM('/en/enterprise/admin'))('[data-testid=current-product]').text().trim()
).toBe('Enterprise administrators')
expect(
(
await getDOM(
'/en/enterprise/user/github/importing-your-projects-to-github/importing-source-code-to-github/importing-a-git-repository-using-the-command-line'
)
)('#current-product')
)('[data-testid=current-product]')
.text()
.trim()
).toBe('GitHub')
expect((await getDOM('/desktop'))('#current-product').text().trim()).toBe('GitHub Desktop')
expect((await getDOM('/desktop'))('[data-testid=current-product]').text().trim()).toBe(
'GitHub Desktop'
)
expect((await getDOM('/actions'))('#current-product').text().trim()).toBe('GitHub Actions')
expect((await getDOM('/actions'))('[data-testid=current-product]').text().trim()).toBe(
'GitHub Actions'
)
// localized
expect((await getDOM('/ja/desktop'))('#current-product').text().trim()).toBe('GitHub Desktop')
expect((await getDOM('/ja/desktop'))('[data-testid=current-product]').text().trim()).toBe(
'GitHub Desktop'
)
})
})

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -26,9 +26,6 @@ changelog:
examples_source: data/product-examples/discussions/community-examples.yml
product_video: 'https://www.youtube-nocookie.com/embed/IpBw2SJkFyk'
layout: product-landing
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/discussions-feedback'
versions:
fpt: '*'
children:

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -26,9 +26,6 @@ changelog:
examples_source: data/product-examples/discussions/community-examples.yml
product_video: 'https://www.youtube-nocookie.com/embed/IpBw2SJkFyk'
layout: product-landing
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/discussions-feedback'
versions:
fpt: '*'
children:

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -26,9 +26,6 @@ changelog:
examples_source: data/product-examples/discussions/community-examples.yml
product_video: 'https://www.youtube-nocookie.com/embed/IpBw2SJkFyk'
layout: product-landing
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/discussions-feedback'
versions:
fpt: '*'
children:

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -26,9 +26,6 @@ changelog:
examples_source: data/product-examples/discussions/community-examples.yml
product_video: 'https://www.youtube-nocookie.com/embed/IpBw2SJkFyk'
layout: product-landing
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/discussions-feedback'
versions:
fpt: '*'
children:

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -26,9 +26,6 @@ changelog:
examples_source: data/product-examples/discussions/community-examples.yml
product_video: 'https://www.youtube-nocookie.com/embed/IpBw2SJkFyk'
layout: product-landing
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/discussions-feedback'
versions:
fpt: '*'
children:

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->

View File

@@ -24,9 +24,6 @@ featuredLinks:
- /codespaces/setting-up-your-codespace/personalizing-codespaces-for-your-account
popularHeading: Set up your project
product_video: 'https://www.youtube-nocookie.com/embed/_W9B7qc9lVc'
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/codespaces-feedback'
redirect_from:
- /github/developing-online-with-github-codespaces
- /github/developing-online-with-codespaces

View File

@@ -26,9 +26,6 @@ changelog:
examples_source: data/product-examples/discussions/community-examples.yml
product_video: 'https://www.youtube-nocookie.com/embed/IpBw2SJkFyk'
layout: product-landing
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/discussions-feedback'
versions:
fpt: '*'
children:

View File

@@ -34,9 +34,6 @@ children:
- /receiving-sponsorships-through-github-sponsors
- /integrating-with-github-sponsors
- /guides
community_redirect:
name: Provide GitHub Feedback
href: 'https://github.com/github/feedback/discussions/categories/sponsors-feedback'
---
<!---->