152
components/article/ArticlePage.tsx
Normal file
152
components/article/ArticlePage.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import cx from 'classnames'
|
||||
|
||||
import { DefaultLayout } from 'components/DefaultLayout'
|
||||
import { ArticleVersionPicker } from 'components/article/ArticleVersionPicker'
|
||||
import { Breadcrumbs } from 'components/Breadcrumbs'
|
||||
import { ArticleTitle } from 'components/article/ArticleTitle'
|
||||
import { useArticleContext } from 'components/context/ArticleContext'
|
||||
import { InfoIcon } from '@primer/octicons-react'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
import { LearningTrackNav } from './LearningTrackNav'
|
||||
|
||||
export const ArticlePage = () => {
|
||||
const {
|
||||
title,
|
||||
intro,
|
||||
renderedPage,
|
||||
contributor,
|
||||
permissions,
|
||||
includesPlatformSpecificContent,
|
||||
defaultPlatform,
|
||||
product,
|
||||
miniTocItems,
|
||||
currentLearningTrack,
|
||||
} = useArticleContext()
|
||||
const { t } = useTranslation('pages')
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<div className="container-xl px-3 px-md-6 my-4 my-lg-4">
|
||||
<article className="markdown-body">
|
||||
<div className="d-lg-flex flex-justify-between">
|
||||
<div className="d-block d-lg-none">
|
||||
<ArticleVersionPicker />
|
||||
</div>
|
||||
<div className="d-flex flex-items-center">
|
||||
<Breadcrumbs />
|
||||
</div>
|
||||
<div className="d-none d-lg-block">
|
||||
<ArticleVersionPicker />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="article-grid-container mt-2">
|
||||
<div className="article-grid-head">
|
||||
<ArticleTitle>{title}</ArticleTitle>
|
||||
|
||||
{contributor && (
|
||||
<div className="contributor-callout border rounded-1 mb-4 p-3 color-border-info color-bg-info f5">
|
||||
<p>
|
||||
<span className="mr-2">
|
||||
<InfoIcon />
|
||||
</span>
|
||||
{t('contributor_callout')} <a href={contributor.URL}>{contributor.name}</a>.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{intro && (
|
||||
<div className="lead-mktg">
|
||||
<p>{intro}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{permissions && (
|
||||
<div
|
||||
className="permissions-statement"
|
||||
dangerouslySetInnerHTML={{ __html: permissions }}
|
||||
/>
|
||||
)}
|
||||
|
||||
{includesPlatformSpecificContent && (
|
||||
<nav
|
||||
className="UnderlineNav my-3"
|
||||
data-default-platform={defaultPlatform || undefined}
|
||||
>
|
||||
<div className="UnderlineNav-body">
|
||||
<a href="#" className="UnderlineNav-item platform-switcher" data-platform="mac">
|
||||
Mac
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="UnderlineNav-item platform-switcher"
|
||||
data-platform="windows"
|
||||
>
|
||||
Windows
|
||||
</a>
|
||||
<a
|
||||
href="#"
|
||||
className="UnderlineNav-item platform-switcher"
|
||||
data-platform="linux"
|
||||
>
|
||||
Linux
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
|
||||
{product && (
|
||||
<div
|
||||
className="product-callout border rounded-1 mb-4 p-3 color-border-success color-bg-success"
|
||||
dangerouslySetInnerHTML={{ __html: product }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="article-grid-toc border-bottom border-xl-0 pb-4 mb-5 pb-xl-0 mb-xl-0">
|
||||
<div className="article-grid-toc-content">
|
||||
{miniTocItems.length > 1 && (
|
||||
<>
|
||||
<h2 id="in-this-article" className="f5 mb-2">
|
||||
<a className="Link--primary" href="#in-this-article">
|
||||
{t('miniToc')}
|
||||
</a>
|
||||
</h2>
|
||||
<ul className="list-style-none pl-0 f5 mb-0">
|
||||
{miniTocItems.map((item) => {
|
||||
return (
|
||||
<li
|
||||
key={item.contents}
|
||||
className={cx(
|
||||
`ml-${item.indentationLevel * 3}`,
|
||||
item.platform,
|
||||
'mb-2 lh-condensed'
|
||||
)}
|
||||
dangerouslySetInnerHTML={{ __html: item.contents }}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="markdown-body">
|
||||
<div
|
||||
id="article-contents"
|
||||
className="article-grid-body"
|
||||
dangerouslySetInnerHTML={{ __html: renderedPage }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{currentLearningTrack?.trackName ? (
|
||||
<div className="d-block mt-4 markdown-body">
|
||||
<LearningTrackNav track={currentLearningTrack} />
|
||||
</div>
|
||||
) : null}
|
||||
</article>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
)
|
||||
}
|
||||
@@ -7,7 +7,7 @@ type Props = {
|
||||
export const ArticleTitle = ({ children }: Props) => {
|
||||
return (
|
||||
<div className="d-flex flex-items-baseline flex-justify-between">
|
||||
<h1 className="my-4">{children}</h1>
|
||||
<h1 className="my-4 border-bottom-0">{children}</h1>
|
||||
<div className="d-none d-lg-block ml-2">
|
||||
<Tooltip aria-label="Print this article" noDelay direction="n">
|
||||
<Link
|
||||
|
||||
41
components/article/LearningTrackNav.tsx
Normal file
41
components/article/LearningTrackNav.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { LearningTrack } from 'components/context/ArticleContext'
|
||||
import { useTranslation } from 'components/hooks/useTranslation'
|
||||
|
||||
type Props = {
|
||||
track: LearningTrack
|
||||
}
|
||||
export function LearningTrackNav({ track }: Props) {
|
||||
const { t } = useTranslation('learning_track_nav')
|
||||
const { prevGuide, nextGuide, trackName } = track
|
||||
return (
|
||||
<div className="py-3 px-4 rounded color-bg-primary border-gradient--purple-pink d-flex flex-justify-between learning-track-nav">
|
||||
<span className="d-flex flex-column">
|
||||
{prevGuide && (
|
||||
<>
|
||||
<span className="f6 color-text-secondary">{t('prevGuide')}</span>
|
||||
<a
|
||||
href={`${prevGuide.href}?learn=${trackName}`}
|
||||
className="text-bold color-text-secondary"
|
||||
>
|
||||
{prevGuide.title}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
|
||||
<span className="d-flex flex-column flex-items-end">
|
||||
{nextGuide && (
|
||||
<>
|
||||
<span className="f6 color-text-secondary">{t('nextGuide')}</span>
|
||||
<a
|
||||
href={`${nextGuide.href}?learn=${trackName}`}
|
||||
className="text-bold color-text-secondary text-right"
|
||||
>
|
||||
{nextGuide.title}
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
61
components/context/ArticleContext.tsx
Normal file
61
components/context/ArticleContext.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
export type LearningTrack = {
|
||||
trackName?: string
|
||||
prevGuide?: { href: string; title: string }
|
||||
nextGuide?: { href: string; title: string }
|
||||
}
|
||||
|
||||
export type MiniTocItem = {
|
||||
indentationLevel: number
|
||||
platform: string
|
||||
contents: string
|
||||
}
|
||||
|
||||
export type ArticleContextT = {
|
||||
title: string
|
||||
intro: string
|
||||
renderedPage: string
|
||||
miniTocItems: Array<MiniTocItem>
|
||||
contributor: { name: string; URL: string } | null
|
||||
permissions?: string
|
||||
includesPlatformSpecificContent: boolean
|
||||
defaultPlatform?: string
|
||||
product?: string
|
||||
currentLearningTrack?: LearningTrack
|
||||
}
|
||||
|
||||
export const ArticleContext = createContext<ArticleContextT | null>(null)
|
||||
|
||||
export const useArticleContext = (): ArticleContextT => {
|
||||
const context = useContext(ArticleContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error('"useArticleContext" may only be used inside "ArticleContext.Provider"')
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export const getArticleContextFromRequest = (req: any): ArticleContextT => {
|
||||
const page = req.context.page
|
||||
return {
|
||||
title: page.titlePlainText,
|
||||
intro: page.introPlainText,
|
||||
renderedPage: req.context.renderedPage || '',
|
||||
miniTocItems:
|
||||
(req.context.miniTocItems || []).map((item: any) => {
|
||||
return {
|
||||
indentationLevel: item.indentationLevel || 0,
|
||||
platform: item.platform || '',
|
||||
contents: item.contents || '',
|
||||
}
|
||||
}) || [],
|
||||
contributor: page.contributor || null,
|
||||
permissions: page.permissions || '',
|
||||
includesPlatformSpecificContent: page.includesPlatformSpecificContent || false,
|
||||
defaultPlatform: page.defaultPlatform || '',
|
||||
product: page.product || '',
|
||||
currentLearningTrack: req.context.currentLearningTrack,
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,7 @@ toc:
|
||||
pages:
|
||||
article_version: 'Article version:'
|
||||
miniToc: In this article
|
||||
contributor_callout: This article is contributed and maintained by
|
||||
errors:
|
||||
oops: Ooops!
|
||||
something_went_wrong: It looks like something went wrong.
|
||||
@@ -176,7 +177,6 @@ product_sublanding:
|
||||
learning_track_nav:
|
||||
prevGuide: Previous guide
|
||||
nextGuide: Next guide
|
||||
contributor_callout: This article is contributed and maintained by
|
||||
toggle_images:
|
||||
off: Images are off, click to show
|
||||
on: Images are on, click to hide
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
{% if page.contributor %}
|
||||
<div class="contributor-callout border rounded-1 mb-4 p-3 color-border-info color-bg-info f5">
|
||||
<p><span class="mr-2">{% octicon "info" %}</span>{% data ui.contributor_callout %} <a href="{{ page.contributor.URL }}">{{ page.contributor.name }}</a>.</p>
|
||||
<p><span class="mr-2">{% octicon "info" %}</span>{% data ui.pages.contributor_callout %} <a href="{{ page.contributor.URL }}">{{ page.contributor.name }}</a>.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ class Page {
|
||||
this.intro = await renderContent(this.rawIntro, context)
|
||||
this.introPlainText = await renderContent(this.rawIntro, context, { textOnly: true })
|
||||
this.title = await renderContent(this.rawTitle, context, { textOnly: true, encodeEntities: true })
|
||||
this.titlePlainText = await renderContent(this.rawTitle, context, { textOnly: true })
|
||||
this.shortTitle = await renderContent(this.shortTitle, context, { textOnly: true, encodeEntities: true })
|
||||
this.product_video = await renderContent(this.raw_product_video, context, { textOnly: true })
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ module.exports = async function renderPage (req, res, next) {
|
||||
}
|
||||
|
||||
// Create string for <title> tag
|
||||
context.page.fullTitle = context.page.title
|
||||
context.page.fullTitle = context.page.titlePlainText
|
||||
|
||||
// add localized ` - GitHub Docs` suffix to <title> tag (except for the homepage)
|
||||
if (!patterns.homepagePath.test(req.path)) {
|
||||
@@ -194,6 +194,8 @@ module.exports = async function renderPage (req, res, next) {
|
||||
|
||||
// Hand rendering over to NextJS when appropriate
|
||||
if (renderWithNextjs) {
|
||||
req.context.renderedPage = context.renderedPage
|
||||
req.context.miniTocItems = context.miniTocItems
|
||||
return nextHandleRequest(req, res)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,20 @@ import {
|
||||
MainContext,
|
||||
getMainContextFromRequest,
|
||||
} from 'components/context/MainContext'
|
||||
|
||||
import {
|
||||
getProductLandingContextFromRequest,
|
||||
ProductLandingContextT,
|
||||
ProductLandingContext,
|
||||
} from 'components/context/ProductLandingContext'
|
||||
|
||||
import {
|
||||
getArticleContextFromRequest,
|
||||
ArticleContextT,
|
||||
ArticleContext,
|
||||
} from 'components/context/ArticleContext'
|
||||
import { ArticlePage } from 'components/article/ArticlePage'
|
||||
|
||||
import { ProductLanding } from 'components/landing/ProductLanding'
|
||||
import { TocLanding } from 'components/landing/TocLanding'
|
||||
import {
|
||||
@@ -23,8 +31,14 @@ type Props = {
|
||||
mainContext: MainContextT
|
||||
productLandingContext: ProductLandingContextT
|
||||
tocLandingContext: TocLandingContextT
|
||||
articleContext: ArticleContextT
|
||||
}
|
||||
const GlobalPage = ({ mainContext, productLandingContext, tocLandingContext }: Props) => {
|
||||
const GlobalPage = ({
|
||||
mainContext,
|
||||
productLandingContext,
|
||||
tocLandingContext,
|
||||
articleContext,
|
||||
}: Props) => {
|
||||
const { currentLayoutName, page, relativePath } = mainContext
|
||||
|
||||
let content
|
||||
@@ -49,7 +63,11 @@ const GlobalPage = ({ mainContext, productLandingContext, tocLandingContext }: P
|
||||
</TocLandingContext.Provider>
|
||||
)
|
||||
} else {
|
||||
content = <p>article / fallback rendering</p>
|
||||
content = (
|
||||
<ArticleContext.Provider value={articleContext}>
|
||||
<ArticlePage />
|
||||
</ArticleContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return <MainContext.Provider value={mainContext}>{content}</MainContext.Provider>
|
||||
@@ -65,6 +83,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (context) =>
|
||||
mainContext: getMainContextFromRequest(req),
|
||||
productLandingContext: getProductLandingContextFromRequest(req),
|
||||
tocLandingContext: getTocLandingContextFromRequest(req),
|
||||
articleContext: getArticleContextFromRequest(req),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user