@@ -45,7 +45,7 @@ export const DefaultLayout = (props: Props) => {
|
||||
</Head>
|
||||
<SidebarNav />
|
||||
|
||||
<main className="width-full">
|
||||
<main className="flex-1 min-width-0">
|
||||
<Header />
|
||||
<DeprecationBanner />
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ export const Header = () => {
|
||||
const showVersionPicker =
|
||||
relativePath === 'index.md' ||
|
||||
currentLayoutName === 'product-landing' ||
|
||||
currentLayoutName === 'product-sublanding'
|
||||
currentLayoutName === 'product-sublanding' ||
|
||||
currentLayoutName === 'release-notes'
|
||||
|
||||
return (
|
||||
<div className="border-bottom color-border-secondary no-print">
|
||||
|
||||
@@ -87,6 +87,7 @@ export const HeaderNotifications = () => {
|
||||
const isLast = i === allNotifications.length - 1
|
||||
return (
|
||||
<div
|
||||
key={content}
|
||||
className={cx(
|
||||
'header-notifications text-center f5 color-text-primary py-4 px-6',
|
||||
type === NotificationType.TRANSLATION && 'translation_notice color-bg-info',
|
||||
|
||||
@@ -59,6 +59,7 @@ type EnterpriseServerReleases = {
|
||||
isOldestReleaseDeprecated: boolean
|
||||
oldestSupported: string
|
||||
nextDeprecationDate: string
|
||||
supported: Array<string>
|
||||
}
|
||||
export type MainContextT = {
|
||||
breadcrumbs: {
|
||||
@@ -154,6 +155,7 @@ export const getMainContextFromRequest = (req: any): MainContextT => {
|
||||
'isOldestReleaseDeprecated',
|
||||
'oldestSupported',
|
||||
'nextDeprecationDate',
|
||||
'supported',
|
||||
]),
|
||||
enterpriseServerVersions: req.context.enterpriseServerVersions,
|
||||
currentLanguage: req.context.currentLanguage,
|
||||
|
||||
@@ -52,6 +52,12 @@ export type ProductLandingContextT = {
|
||||
changelogUrl?: string
|
||||
whatsNewChangelog?: Array<{ href: string; title: string; date: string }>
|
||||
tocItems: Array<TocItem>
|
||||
releases: Array<{
|
||||
version: string
|
||||
firstPreviousRelease: string
|
||||
secondPreviousRelease: string
|
||||
patches: Array<{ date: string; version: string }>
|
||||
}>
|
||||
}
|
||||
|
||||
export const ProductLandingContext = createContext<ProductLandingContextT | null>(null)
|
||||
@@ -89,6 +95,7 @@ export const getProductLandingContextFromRequest = (req: any): ProductLandingCon
|
||||
changelogUrl: req.context.changelogUrl || [],
|
||||
productCodeExamples: req.context.productCodeExamples || [],
|
||||
productCommunityExamples: req.context.productCommunityExamples || [],
|
||||
releases: req.context.releases || [],
|
||||
|
||||
productUserExamples: (req.context.productUserExamples || []).map(
|
||||
({ user, description }: any) => ({
|
||||
|
||||
25
components/hooks/useOnScreen.tsx
Normal file
25
components/hooks/useOnScreen.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useState, useEffect, MutableRefObject, RefObject } from 'react'
|
||||
|
||||
export function useOnScreen<T extends Element>(
|
||||
ref: MutableRefObject<T | undefined> | RefObject<T>,
|
||||
rootMargin: string = '0px'
|
||||
): boolean {
|
||||
const [isIntersecting, setIntersecting] = useState(false)
|
||||
useEffect(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
([entry]) => {
|
||||
setIntersecting(entry.isIntersecting)
|
||||
},
|
||||
{
|
||||
rootMargin,
|
||||
}
|
||||
)
|
||||
if (ref.current) {
|
||||
observer.observe(ref.current)
|
||||
}
|
||||
return () => {
|
||||
ref.current && observer.unobserve(ref.current)
|
||||
}
|
||||
}, [])
|
||||
return isIntersecting
|
||||
}
|
||||
@@ -3,10 +3,15 @@ import { useRouter } from 'next/router'
|
||||
type VersionInfo = {
|
||||
currentVersion: string
|
||||
isEnterprise: boolean
|
||||
isEnterpriseServer: boolean
|
||||
}
|
||||
const DEFAULT_VERSION = 'free-pro-team@latest'
|
||||
export const useVersion = (): VersionInfo => {
|
||||
const router = useRouter()
|
||||
const currentVersion = (router.query.versionId as string) || DEFAULT_VERSION
|
||||
return { currentVersion, isEnterprise: currentVersion.includes('enterprise') }
|
||||
return {
|
||||
currentVersion,
|
||||
isEnterprise: currentVersion.includes('enterprise'),
|
||||
isEnterpriseServer: currentVersion.includes('enterprise-server'),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,13 @@ import { CodeExamples } from 'components/landing/CodeExamples'
|
||||
import { LandingSection } from 'components/landing/LandingSection'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { ProductArticlesList } from 'components/landing/ProductArticlesList'
|
||||
import { ProductReleases } from 'components/landing/ProductReleases'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useVersion } from 'components/hooks/useVersion'
|
||||
|
||||
export const ProductLanding = () => {
|
||||
const router = useRouter()
|
||||
const { isEnterpriseServer } = useVersion()
|
||||
const {
|
||||
shortTitle,
|
||||
guideCards,
|
||||
@@ -49,9 +54,11 @@ export const ProductLanding = () => {
|
||||
</LandingSection>
|
||||
)}
|
||||
|
||||
{/* {% if currentVersion contains 'enterprise-server' and currentProduct == 'admin' %}
|
||||
{% include product-releases %}
|
||||
{% endif %} */}
|
||||
{router.query.productId === 'admin' && isEnterpriseServer && (
|
||||
<LandingSection title={t('supported_releases')} className="my-6">
|
||||
<ProductReleases />
|
||||
</LandingSection>
|
||||
)}
|
||||
|
||||
{guideCards.length > 0 && (
|
||||
<div className="color-bg-tertiary py-6 my-8">
|
||||
|
||||
68
components/landing/ProductReleases.tsx
Normal file
68
components/landing/ProductReleases.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { ArrowRightIcon, ArrowUpIcon, FileIcon, ListUnorderedIcon } from '@primer/octicons-react'
|
||||
import { useMainContext } from 'components/context/MainContext'
|
||||
import { useProductLandingContext } from 'components/context/ProductLandingContext'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { Link } from 'components/Link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export function ProductReleases() {
|
||||
const { t } = useTranslation('product_landing')
|
||||
const router = useRouter()
|
||||
const { enterpriseServerReleases, allVersions } = useMainContext()
|
||||
const { releases } = useProductLandingContext()
|
||||
const currentPath = router.asPath.split('?')[0]
|
||||
return (
|
||||
<div>
|
||||
<div className="d-lg-flex gutter-lg flex-items-stretch">
|
||||
{releases.map((release) => {
|
||||
const releaseNumber = release.version
|
||||
if (!enterpriseServerReleases.supported.includes(releaseNumber)) {
|
||||
return null
|
||||
}
|
||||
const releaseVersion = `enterprise-server@${releaseNumber}`
|
||||
const latestPatch = release.patches[0]
|
||||
const firstPreviousVersion = `enterprise-server@${release.firstPreviousRelease}`
|
||||
const secondPreviousVersion = `enterprise-server@${release.secondPreviousRelease}`
|
||||
return (
|
||||
<div key={releaseNumber} className="col-lg-4 col-12 mb-3">
|
||||
<div className="Box color-shadow-medium height-full d-block hover-shadow-large no-underline color-text-primary p-5">
|
||||
<h2>{allVersions[releaseVersion].versionTitle}</h2>
|
||||
<p className="mt-2 mb-4 color-text-tertiary">
|
||||
<ListUnorderedIcon />{' '}
|
||||
<Link
|
||||
href={`/${router.locale}/${releaseVersion}/admin/release-notes#${latestPatch.version}`}
|
||||
>
|
||||
{t('release_notes_for')} {latestPatch.version}
|
||||
</Link>{' '}
|
||||
({latestPatch.date})
|
||||
</p>
|
||||
<p className="mt-2 mb-4 color-text-tertiary">
|
||||
<ArrowUpIcon /> {t('upgrade_from')}{' '}
|
||||
<Link
|
||||
href={`/${router.locale}/${firstPreviousVersion}/admin/enterprise-management/upgrading-github-enterprise-server`}
|
||||
>
|
||||
{release.firstPreviousRelease}
|
||||
</Link>{' '}
|
||||
or{' '}
|
||||
<Link
|
||||
href={`/${router.locale}/${secondPreviousVersion}/admin/enterprise-management/upgrading-github-enterprise-server`}
|
||||
>
|
||||
{release.secondPreviousRelease}
|
||||
</Link>
|
||||
</p>
|
||||
<p className="mt-2 mb-4 color-text-tertiary">
|
||||
<FileIcon />{' '}
|
||||
<Link href={`/${router.locale}/${releaseVersion}`}>{t('browse_all_docs')}</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<Link href={`${currentPath}/release-notes}`} className="btn btn-outline float-right">
|
||||
{t('explore_release_notes')} <ArrowRightIcon />
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
59
components/release-notes/GHAEReleaseNotePatch.tsx
Normal file
59
components/release-notes/GHAEReleaseNotePatch.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useRef, useEffect } from 'react'
|
||||
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { useOnScreen } from 'components/hooks/useOnScreen'
|
||||
import { PatchNotes } from './PatchNotes'
|
||||
import { ReleaseNotePatch } from './types'
|
||||
|
||||
type Props = { patch: ReleaseNotePatch; didEnterView: () => void }
|
||||
export function GHAEReleaseNotePatch({ patch, didEnterView }: Props) {
|
||||
const { t } = useTranslation('release_notes')
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const onScreen = useOnScreen(containerRef, '-40% 0px -50%')
|
||||
useEffect(() => {
|
||||
if (onScreen) {
|
||||
didEnterView()
|
||||
}
|
||||
}, [onScreen])
|
||||
|
||||
const bannerText = patch.currentWeek
|
||||
? t('banner_text_current')
|
||||
: `${t('banner_text_past')} ${patch.friendlyDate}.`
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="mb-10 color-bg-secondary pb-6 border-bottom border-top"
|
||||
id={patch.date}
|
||||
>
|
||||
<header
|
||||
style={{ zIndex: 1 }}
|
||||
className="container-xl position-sticky top-0 color-bg-secondary border-bottom px-3 pt-4 pb-2"
|
||||
>
|
||||
<div className="d-flex flex-items-center">
|
||||
<h2 className="border-bottom-0 m-0 p-0">{patch.title}</h2>
|
||||
|
||||
{patch.release_candidate && (
|
||||
<span
|
||||
className="IssueLabel color-bg-warning-inverse color-text-inverse ml-3"
|
||||
style={{ whiteSpace: 'pre' }}
|
||||
>
|
||||
Release Candidate
|
||||
</span>
|
||||
)}
|
||||
|
||||
<button className="js-print btn-link ml-3 text-small text-bold">Print</button>
|
||||
</div>
|
||||
<p className="color-text-secondary mt-1">
|
||||
{patch.friendlyDate} - {bannerText}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div className="container-xl px-3">
|
||||
<div className="mt-3" dangerouslySetInnerHTML={{ __html: patch.intro }} />
|
||||
|
||||
<PatchNotes patch={patch} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
89
components/release-notes/GHAEReleaseNotes.tsx
Normal file
89
components/release-notes/GHAEReleaseNotes.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useState } from 'react'
|
||||
import cx from 'classnames'
|
||||
import { ChevronDownIcon } from '@primer/octicons-react'
|
||||
import { GHAEReleaseNotePatch } from './GHAEReleaseNotePatch'
|
||||
import { GHAEReleaseNotesContextT } from './types'
|
||||
|
||||
type GitHubAEProps = {
|
||||
context: GHAEReleaseNotesContextT
|
||||
}
|
||||
export function GHAEReleaseNotes({ context }: GitHubAEProps) {
|
||||
const { releaseNotes, releases, currentVersion } = context
|
||||
const [focusedPatch, setFocusedPatch] = useState('')
|
||||
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<article className="min-width-0 flex-1">
|
||||
<div className="d-flex flex-items-center flex-justify-between color-bg-primary px-5 py-2">
|
||||
<div></div>
|
||||
<h1 className="f4 py-3 m-0">{currentVersion.planTitle} release notes</h1>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
<div className="markdown-body">
|
||||
{releaseNotes.map((patch) => {
|
||||
return (
|
||||
<GHAEReleaseNotePatch
|
||||
key={patch.version}
|
||||
patch={patch}
|
||||
didEnterView={() => setFocusedPatch(patch.version)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<aside
|
||||
className="markdown-body position-sticky top-0 d-none d-md-block border-left no-print color-bg-primary flex-shrink-0"
|
||||
style={{ width: 260, height: '100vh' }}
|
||||
>
|
||||
<nav className="height-full overflow-auto">
|
||||
<ul className="list-style-none pl-0 text-bold">
|
||||
{releases.map((release) => {
|
||||
return (
|
||||
<li key={release.version} className="border-bottom">
|
||||
<details
|
||||
className="my-0 details-reset release-notes-version-picker"
|
||||
aria-current="page"
|
||||
open
|
||||
>
|
||||
<summary className="px-3 py-4 my-0 d-flex flex-items-center flex-justify-between">
|
||||
{release.version}
|
||||
<div className="d-flex">
|
||||
<span className="color-text-tertiary text-mono text-small text-normal mr-1">
|
||||
{release.patches.length} releases
|
||||
</span>
|
||||
<ChevronDownIcon />
|
||||
</div>
|
||||
</summary>
|
||||
<ul className="color-bg-tertiary border-top list-style-none py-4 px-0 my-0">
|
||||
{release.patches.map((patch) => {
|
||||
const isActive = patch.version === focusedPatch
|
||||
return (
|
||||
<li
|
||||
key={patch.version}
|
||||
className={cx(
|
||||
'js-release-notes-patch-link px-3 my-0 py-1',
|
||||
isActive && 'selected'
|
||||
)}
|
||||
>
|
||||
<a
|
||||
href={`#${patch.date}`}
|
||||
className="d-flex flex-items-center flex-justify-between"
|
||||
>
|
||||
{patch.friendlyDate}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
110
components/release-notes/GHESReleaseNotePatch.tsx
Normal file
110
components/release-notes/GHESReleaseNotePatch.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { PatchNotes } from './PatchNotes'
|
||||
import { Link } from 'components/Link'
|
||||
import { CurrentVersion, ReleaseNotePatch, GHESMessage } from './types'
|
||||
import { useOnScreen } from 'components/hooks/useOnScreen'
|
||||
|
||||
type Props = {
|
||||
patch: ReleaseNotePatch
|
||||
currentVersion: CurrentVersion
|
||||
latestPatch: string
|
||||
latestRelease: string
|
||||
message: GHESMessage
|
||||
didEnterView: () => void
|
||||
}
|
||||
export function GHESReleaseNotePatch({
|
||||
patch,
|
||||
currentVersion,
|
||||
latestPatch,
|
||||
latestRelease,
|
||||
message,
|
||||
didEnterView,
|
||||
}: Props) {
|
||||
const { t } = useTranslation('header')
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const onScreen = useOnScreen(containerRef, '-40% 0px -50%')
|
||||
useEffect(() => {
|
||||
if (onScreen) {
|
||||
didEnterView()
|
||||
}
|
||||
}, [onScreen])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="mb-10 color-bg-secondary pb-6 border-bottom border-top"
|
||||
id={patch.version}
|
||||
>
|
||||
<header
|
||||
style={{ zIndex: 1 }}
|
||||
className="container-xl position-sticky top-0 color-bg-secondary border-bottom px-3 pt-4 pb-2"
|
||||
>
|
||||
<div className="d-flex flex-items-center">
|
||||
<h2 className="border-bottom-0 m-0 p-0">
|
||||
{currentVersion.versionTitle}.{patch.patchVersion}
|
||||
</h2>
|
||||
|
||||
{patch.release_candidate && (
|
||||
<span
|
||||
className="IssueLabel color-bg-warning-inverse color-text-inverse ml-3"
|
||||
style={{ whiteSpace: 'pre' }}
|
||||
>
|
||||
Release Candidate
|
||||
</span>
|
||||
)}
|
||||
|
||||
{currentVersion.plan == 'enterprise-server' && (
|
||||
<Link
|
||||
href={`https://enterprise.github.com/releases/${patch.downloadVersion}/download`}
|
||||
className="ml-3 text-small text-bold"
|
||||
>
|
||||
Download
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<button className="js-print btn-link ml-3 text-small text-bold">Print</button>
|
||||
</div>
|
||||
|
||||
<p className="color-text-secondary mt-1">{dayjs(patch.date).format('MMMM, DD, YYYY')}</p>
|
||||
|
||||
{patch.version !== latestPatch && currentVersion.currentRelease === latestRelease && (
|
||||
<p className="color-text-secondary mt-1">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: message.ghes_release_notes_upgrade_patch_only }}
|
||||
/>{' '}
|
||||
{t('notices.release_notes_use_latest')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{patch.version === latestPatch && currentVersion.currentRelease !== latestRelease && (
|
||||
<p className="color-text-secondary mt-1">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: message.ghes_release_notes_upgrade_release_only }}
|
||||
/>{' '}
|
||||
{t('notices.release_notes_use_latest')}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{patch.version !== latestPatch && currentVersion.currentRelease !== latestRelease && (
|
||||
<p className="color-text-secondary mt-1">
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: message.ghes_release_notes_upgrade_patch_and_release,
|
||||
}}
|
||||
/>{' '}
|
||||
{t('notices.release_notes_use_latest')}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className="container-xl px-3">
|
||||
<div className="mt-3" dangerouslySetInnerHTML={{ __html: patch.intro }} />
|
||||
|
||||
<PatchNotes patch={patch} withReleaseNoteLabel />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
170
components/release-notes/GHESReleaseNotes.tsx
Normal file
170
components/release-notes/GHESReleaseNotes.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { useState } from 'react'
|
||||
import cx from 'classnames'
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
LinkExternalIcon,
|
||||
} from '@primer/octicons-react'
|
||||
import { useMainContext } from 'components/context/MainContext'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { Link } from 'components/Link'
|
||||
import { GHESReleaseNotesContextT } from './types'
|
||||
import { GHESReleaseNotePatch } from './GHESReleaseNotePatch'
|
||||
|
||||
type Props = {
|
||||
context: GHESReleaseNotesContextT
|
||||
}
|
||||
export function GHESReleaseNotes({ context }: Props) {
|
||||
const { currentLanguage, currentProduct } = useMainContext()
|
||||
const [focusedPatch, setFocusedPatch] = useState('')
|
||||
const {
|
||||
prevRelease,
|
||||
nextRelease,
|
||||
latestPatch,
|
||||
latestRelease,
|
||||
currentVersion,
|
||||
releaseNotes,
|
||||
releases,
|
||||
message,
|
||||
} = context
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<article className="min-width-0 flex-1">
|
||||
<div className="d-flex flex-items-center flex-justify-between color-bg-primary text-bold px-5 py-2">
|
||||
{prevRelease ? (
|
||||
<Link
|
||||
className="btn btn-outline"
|
||||
href={`/${currentLanguage}/${currentVersion.plan}@${prevRelease}/${currentProduct}/release-notes`}
|
||||
>
|
||||
<ChevronLeftIcon /> {prevRelease}
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
|
||||
<h1 className="f4 py-3 m-0">
|
||||
{currentVersion.planTitle} {currentVersion.currentRelease} release notes
|
||||
</h1>
|
||||
|
||||
{nextRelease ? (
|
||||
<Link
|
||||
className="btn btn-outline"
|
||||
href={`/${currentLanguage}/${currentVersion.plan}@${nextRelease}/${currentProduct}/release-notes`}
|
||||
>
|
||||
{nextRelease} <ChevronRightIcon />
|
||||
</Link>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="markdown-body">
|
||||
{releaseNotes.map((patch) => {
|
||||
return (
|
||||
<GHESReleaseNotePatch
|
||||
key={patch.version}
|
||||
patch={patch}
|
||||
currentVersion={currentVersion}
|
||||
latestPatch={latestPatch}
|
||||
latestRelease={latestRelease}
|
||||
message={message}
|
||||
didEnterView={() => {
|
||||
setFocusedPatch(patch.version)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<aside
|
||||
className="markdown-body position-sticky top-0 d-none d-md-block border-left no-print color-bg-primary flex-shrink-0"
|
||||
style={{ width: 260, height: '100vh' }}
|
||||
>
|
||||
<nav className="height-full overflow-auto">
|
||||
<ul className="list-style-none pl-0 text-bold">
|
||||
{releases.map((release) => {
|
||||
const releaseLink = `/${currentLanguage}/${currentVersion.plan}@${release.version}/${currentProduct?.id}/release-notes`
|
||||
|
||||
if (!release.patches || release.patches.length === 0) {
|
||||
return (
|
||||
<li key={release.version} className="border-bottom">
|
||||
<Link
|
||||
href={releaseLink}
|
||||
className="Link--primary no-underline px-3 py-4 my-0 d-flex flex-items-center flex-justify-between"
|
||||
>
|
||||
{release.version}
|
||||
<LinkExternalIcon />
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
if (release.version === currentVersion.currentRelease) {
|
||||
return (
|
||||
<li key={release.version} className="border-bottom">
|
||||
<details
|
||||
className="my-0 details-reset release-notes-version-picker"
|
||||
aria-current="page"
|
||||
open
|
||||
>
|
||||
<summary className="px-3 py-4 my-0 d-flex flex-items-center flex-justify-between">
|
||||
{release.version}
|
||||
<div className="d-flex">
|
||||
<span className="color-text-tertiary text-mono text-small text-normal mr-1">
|
||||
{release.patches.length} releases
|
||||
</span>
|
||||
<ChevronDownIcon />
|
||||
</div>
|
||||
</summary>
|
||||
<ul className="color-bg-tertiary border-top list-style-none py-4 px-0 my-0">
|
||||
{release.patches.map((patch) => {
|
||||
const isActive = patch.version === focusedPatch
|
||||
return (
|
||||
<li
|
||||
key={patch.version}
|
||||
className={cx(
|
||||
'js-release-notes-patch-link px-3 my-0 py-1',
|
||||
isActive && 'selected'
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
href={`${releaseLink}#${patch.version}`}
|
||||
className="d-flex flex-items-center flex-justify-between"
|
||||
>
|
||||
{patch.version}
|
||||
<span className="color-text-tertiary text-mono text-small text-normal">
|
||||
{dayjs(patch.date).format('MMMM DD, YYYY')}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</details>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={release.version} className="border-bottom">
|
||||
<Link
|
||||
className="px-3 py-4 my-0 d-flex flex-items-center flex-justify-between"
|
||||
href={releaseLink}
|
||||
>
|
||||
{release.version}
|
||||
<span className="color-text-tertiary text-mono text-small text-normal mr-1">
|
||||
{release.patches.length} releases
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
84
components/release-notes/PatchNotes.tsx
Normal file
84
components/release-notes/PatchNotes.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import cx from 'classnames'
|
||||
import slugger from 'github-slugger'
|
||||
import { ReleaseNotePatch } from './types'
|
||||
import { Link } from 'components/Link'
|
||||
|
||||
const SectionToLabelMap: Record<string, string> = {
|
||||
features: 'Features',
|
||||
bugs: 'Bug fixes',
|
||||
known_issues: 'Known issues',
|
||||
security_fixes: 'Security fixes',
|
||||
changes: 'Changes',
|
||||
deprecations: 'Deprecations',
|
||||
backups: 'Backups',
|
||||
}
|
||||
|
||||
type Props = {
|
||||
patch: ReleaseNotePatch
|
||||
withReleaseNoteLabel?: boolean
|
||||
}
|
||||
export function PatchNotes({ patch, withReleaseNoteLabel }: Props) {
|
||||
return (
|
||||
<>
|
||||
{Object.entries(patch.sections).map(([key, sectionItems], i, arr) => {
|
||||
const isLast = i === arr.length - 1
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={cx(
|
||||
`release-notes-section-${key}`,
|
||||
'py-6 d-block d-xl-flex gutter-xl flex-items-baseline',
|
||||
!withReleaseNoteLabel && 'mx-6',
|
||||
!isLast && 'border-bottom'
|
||||
)}
|
||||
>
|
||||
{withReleaseNoteLabel && (
|
||||
<div className="col-12 col-xl-3 mb-5">
|
||||
<span className="px-3 py-2 text-small text-bold text-uppercase text-mono color-text-inverse release-notes-section-label">
|
||||
{SectionToLabelMap[key] || 'INVALID SECTION'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<ul className={cx(withReleaseNoteLabel && 'col-xl-9', 'col-12 release-notes-list')}>
|
||||
{sectionItems.map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
return (
|
||||
<li key={item} className="release-notes-list-item">
|
||||
<span dangerouslySetInnerHTML={{ __html: item }} />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
const slug = item.heading ? slugger.slug(item.heading) : ''
|
||||
return (
|
||||
<li key={slug} className="release-notes-list-item list-style-none">
|
||||
<h4
|
||||
id={slug}
|
||||
className="release-notes-section-heading text-uppercase text-bold"
|
||||
>
|
||||
<Link href={`#${slug}`} className="text-inherit">
|
||||
{item.heading}
|
||||
</Link>
|
||||
</h4>
|
||||
|
||||
<ul className="pl-0 pb-4 mt-2 release-notes-list">
|
||||
{item.notes.map((note) => {
|
||||
return (
|
||||
<li
|
||||
key={note}
|
||||
className="release-notes-list-item"
|
||||
dangerouslySetInnerHTML={{ __html: note }}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)
|
||||
}
|
||||
49
components/release-notes/types.ts
Normal file
49
components/release-notes/types.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
export type CurrentVersion = {
|
||||
plan: string
|
||||
planTitle: string
|
||||
versionTitle: string
|
||||
currentRelease: string
|
||||
}
|
||||
|
||||
export type GHESMessage = {
|
||||
ghes_release_notes_upgrade_patch_only: string
|
||||
ghes_release_notes_upgrade_release_only: string
|
||||
ghes_release_notes_upgrade_patch_and_release: string
|
||||
}
|
||||
|
||||
type ReleaseNoteSection =
|
||||
| {
|
||||
heading?: string
|
||||
notes: Array<string>
|
||||
}
|
||||
| string
|
||||
|
||||
export type ReleaseNotePatch = {
|
||||
patchVersion: string
|
||||
version: string
|
||||
downloadVersion: string
|
||||
intro: string
|
||||
date: string
|
||||
friendlyDate: string
|
||||
title: string
|
||||
release_candidate?: boolean
|
||||
currentWeek: boolean
|
||||
sections: Record<string, Array<ReleaseNoteSection>>
|
||||
}
|
||||
|
||||
export type GHAEReleaseNotesContextT = {
|
||||
releaseNotes: Array<ReleaseNotePatch>
|
||||
releases: Array<{ version: string; patches: Array<ReleaseNotePatch> }>
|
||||
currentVersion: CurrentVersion
|
||||
}
|
||||
|
||||
export type GHESReleaseNotesContextT = {
|
||||
latestPatch: string
|
||||
prevRelease?: string
|
||||
nextRelease?: string
|
||||
latestRelease: string
|
||||
currentVersion: CurrentVersion
|
||||
releaseNotes: Array<ReleaseNotePatch>
|
||||
releases: Array<{ version: string; patches: Array<ReleaseNotePatch> }>
|
||||
message: GHESMessage
|
||||
}
|
||||
@@ -20,9 +20,9 @@ header:
|
||||
early_access: 📣 Please <b>do not share</b> this URL publicly. This page contains content about an early access feature.
|
||||
release_notes_use_latest: Please use the latest release for the latest security, performance, and bug fixes.
|
||||
# GHES release notes
|
||||
ghes_release_notes_upgrade_patch_only: 📣 This is not the <a href="#{{ latestPatch }}">latest patch release</a> of Enterprise Server. {% data ui.header.notices.release_notes_use_latest %}
|
||||
ghes_release_notes_upgrade_release_only: 📣 This is not the <a href="/enterprise-server@{{ latestRelease }}/admin/release-notes">latest release</a> of Enterprise Server. {% data ui.header.notices.release_notes_use_latest %}
|
||||
ghes_release_notes_upgrade_patch_and_release: 📣 This is not the <a href="#{{ latestPatch }}">latest patch release</a> of this release series, and this is not the <a href="/enterprise-server@{{ latestRelease }}/admin/release-notes">latest release</a> of Enterprise Server. {% data ui.header.notices.release_notes_use_latest %}
|
||||
ghes_release_notes_upgrade_patch_only: 📣 This is not the <a href="#{{ latestPatch }}">latest patch release</a> of Enterprise Server.
|
||||
ghes_release_notes_upgrade_release_only: 📣 This is not the <a href="/enterprise-server@{{ latestRelease }}/admin/release-notes">latest release</a> of Enterprise Server.
|
||||
ghes_release_notes_upgrade_patch_and_release: 📣 This is not the <a href="#{{ latestPatch }}">latest patch release</a> of this release series, and this is not the <a href="/enterprise-server@{{ latestRelease }}/admin/release-notes">latest release</a> of Enterprise Server.
|
||||
release_notes:
|
||||
banner_text_current: These changes will roll out over the next one week.
|
||||
banner_text_past: These changes rolled out to enterprises during the week of
|
||||
|
||||
@@ -54,15 +54,15 @@
|
||||
<p class="color-text-secondary mt-1">{{ patch.date | date: "%B %d, %Y" }}</p>
|
||||
|
||||
{% if patch.version != latestPatch and currentVersionObject.currentRelease == latestRelease %}
|
||||
<p class="color-text-secondary mt-1">{% data ui.header.notices.ghes_release_notes_upgrade_patch_only %}</p>
|
||||
<p class="color-text-secondary mt-1">{% data ui.header.notices.ghes_release_notes_upgrade_patch_only %} {% data ui.header.notices.release_notes_use_latest %}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if patch.version == latestPatch and currentVersionObject.currentRelease != latestRelease %}
|
||||
<p class="color-text-secondary mt-1">{% data ui.header.notices.ghes_release_notes_upgrade_release_only %}</p>
|
||||
<p class="color-text-secondary mt-1">{% data ui.header.notices.ghes_release_notes_upgrade_release_only %} {% data ui.header.notices.release_notes_use_latest %}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if patch.version != latestPatch and currentVersionObject.currentRelease != latestRelease %}
|
||||
<p class="color-text-secondary mt-1">{% data ui.header.notices.ghes_release_notes_upgrade_patch_and_release %}</p>
|
||||
<p class="color-text-secondary mt-1">{% data ui.header.notices.ghes_release_notes_upgrade_patch_and_release %} {% data ui.header.notices.release_notes_use_latest %}</p>
|
||||
{% endif %}
|
||||
</header>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export default function releaseNotes () {
|
||||
if (window.next) return
|
||||
const patches = Array.from(document.querySelectorAll('.js-release-notes-patch'))
|
||||
if (patches.length === 0) return
|
||||
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -93,6 +93,7 @@
|
||||
"@graphql-inspector/core": "^2.5.0",
|
||||
"@graphql-tools/load": "^6.2.8",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/lodash": "^4.14.169",
|
||||
"@types/react": "^17.0.6",
|
||||
"@types/react-dom": "^17.0.5",
|
||||
@@ -4040,6 +4041,12 @@
|
||||
"integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/github-slugger": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz",
|
||||
"integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
@@ -27663,6 +27670,12 @@
|
||||
"integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==",
|
||||
"devOptional": true
|
||||
},
|
||||
"@types/github-slugger": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz",
|
||||
"integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/graceful-fs": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
"@graphql-inspector/core": "^2.5.0",
|
||||
"@graphql-tools/load": "^6.2.8",
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"@types/github-slugger": "^1.3.0",
|
||||
"@types/lodash": "^4.14.169",
|
||||
"@types/react": "^17.0.6",
|
||||
"@types/react-dom": "^17.0.5",
|
||||
|
||||
81
pages/[versionId]/admin/release-notes.tsx
Normal file
81
pages/[versionId]/admin/release-notes.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { GetServerSideProps } from 'next'
|
||||
import { Liquid } from 'liquidjs'
|
||||
|
||||
const liquid = new Liquid()
|
||||
|
||||
import {
|
||||
MainContextT,
|
||||
MainContext,
|
||||
getMainContextFromRequest,
|
||||
} from 'components/context/MainContext'
|
||||
import { DefaultLayout } from 'components/DefaultLayout'
|
||||
import { GHAEReleaseNotes } from 'components/release-notes/GHAEReleaseNotes'
|
||||
import { GHESReleaseNotes } from 'components/release-notes/GHESReleaseNotes'
|
||||
import {
|
||||
CurrentVersion,
|
||||
GHAEReleaseNotesContextT,
|
||||
GHESReleaseNotesContextT,
|
||||
} from 'components/release-notes/types'
|
||||
|
||||
type Props = {
|
||||
mainContext: MainContextT
|
||||
ghaeContext: GHAEReleaseNotesContextT
|
||||
ghesContext: GHESReleaseNotesContextT
|
||||
currentVersion: CurrentVersion
|
||||
}
|
||||
export default function ReleaseNotes({
|
||||
mainContext,
|
||||
ghesContext,
|
||||
ghaeContext,
|
||||
currentVersion,
|
||||
}: Props) {
|
||||
return (
|
||||
<MainContext.Provider value={mainContext}>
|
||||
<DefaultLayout>
|
||||
{currentVersion.plan === 'enterprise-server' && <GHESReleaseNotes context={ghesContext} />}
|
||||
|
||||
{currentVersion.plan === 'github-ae' && <GHAEReleaseNotes context={ghaeContext} />}
|
||||
</DefaultLayout>
|
||||
</MainContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async (context) => {
|
||||
const req = context.req as any
|
||||
const currentVersion = req.context.allVersions[req.context.currentVersion]
|
||||
const { latestPatch = '', latestRelease = '' } = req.context
|
||||
return {
|
||||
props: {
|
||||
mainContext: getMainContextFromRequest(req),
|
||||
currentVersion,
|
||||
ghesContext: {
|
||||
currentVersion,
|
||||
latestPatch,
|
||||
latestRelease,
|
||||
prevRelease: req.context.prevRelease || '',
|
||||
nextRelease: req.context.nextRelease || '',
|
||||
releaseNotes: req.context.releaseNotes,
|
||||
releases: req.context.releases,
|
||||
message: {
|
||||
ghes_release_notes_upgrade_patch_only: liquid.parseAndRenderSync(
|
||||
req.context.site.data.ui.header.notices.ghes_release_notes_upgrade_patch_only,
|
||||
{ latestPatch, latestRelease }
|
||||
),
|
||||
ghes_release_notes_upgrade_release_only: liquid.parseAndRenderSync(
|
||||
req.context.site.data.ui.header.notices.ghes_release_notes_upgrade_release_only,
|
||||
{ latestPatch, latestRelease }
|
||||
),
|
||||
ghes_release_notes_upgrade_patch_and_release: liquid.parseAndRenderSync(
|
||||
req.context.site.data.ui.header.notices.ghes_release_notes_upgrade_patch_and_release,
|
||||
{ latestPatch, latestRelease }
|
||||
),
|
||||
},
|
||||
},
|
||||
ghaeContext: {
|
||||
currentVersion,
|
||||
releaseNotes: req.context.releaseNotes,
|
||||
releases: req.context.releases,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,10 @@ ul.release-notes-list li.release-notes-list-item {
|
||||
|
||||
.release-notes-section-heading {
|
||||
font-size: 15px !important;
|
||||
scroll-margin-top: 280px !important;
|
||||
@include breakpoint(sm) {
|
||||
scroll-margin-top: 200px !important;
|
||||
}
|
||||
}
|
||||
|
||||
details[open].release-notes-version-picker
|
||||
@@ -36,7 +40,7 @@ $colors-list: (
|
||||
security_fixes: var(--color-auto-pink-5),
|
||||
changes: var(--color-auto-green-5),
|
||||
deprecations: var(--color-auto-purple-5),
|
||||
backups: var(--color-auto-orange-5)
|
||||
backups: var(--color-auto-orange-5),
|
||||
);
|
||||
|
||||
@each $key, $val in $colors-list {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["*.d.ts", "**/*.ts", "**/*.tsx"]
|
||||
|
||||
Reference in New Issue
Block a user