Skip to content

Commit

Permalink
Refactor to add RestReferencePage (github#25781)
Browse files Browse the repository at this point in the history
* refactor to add RestReferencePage

* remove structured data from article page

* add back clientsidehightlight js and remove structuredcontent

* update endpoint available for github apps page to use RestReferencePage

* check for minitocs

* use children for ArticlePage for overview endpoints page and move category data to RestReferencePage

* create MiniTocs component

* add MiniTocs

* fix unique key issue

* break into out into own div

* remove some unnecessary ts

* refactor looping section

* simplify markdown component usage

* add key prop

* add key prop

* add unique key

Co-authored-by: Rachael Sewell <rachmari@github.com>
  • Loading branch information
gracepark and rachmari authored Mar 8, 2022
1 parent 8ecfaf9 commit 1a6bee4
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 156 deletions.
106 changes: 8 additions & 98 deletions components/article/ArticlePage.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import { useState, useEffect, ReactNode } from 'react'
import { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import cx from 'classnames'
import { ActionList, Heading } from '@primer/react'

import { ZapIcon, InfoIcon, ShieldLockIcon } from '@primer/octicons-react'
import { Callout } from 'components/ui/Callout'

import { Link } from 'components/Link'
import { DefaultLayout } from 'components/DefaultLayout'
import { ArticleTitle } from 'components/article/ArticleTitle'
import { MiniTocItem, useArticleContext } from 'components/context/ArticleContext'
import { useArticleContext } from 'components/context/ArticleContext'
import { useTranslation } from 'components/hooks/useTranslation'
import { LearningTrackNav } from './LearningTrackNav'
import { MarkdownContent } from 'components/ui/MarkdownContent'
import { Lead } from 'components/ui/Lead'
import { ArticleGridLayout } from './ArticleGridLayout'
import { PlatformPicker } from 'components/article/PlatformPicker'
import { ToolPicker } from 'components/article/ToolPicker'
import { MiniTocs } from 'components/ui/MiniTocs'

const ClientSideRedirectExceptions = dynamic(() => import('./ClientsideRedirectExceptions'), {
ssr: false,
})
const ClientSideHighlightJS = dynamic(() => import('./ClientSideHighlightJS'), { ssr: false })

// Mapping of a "normal" article to it's interactive counterpart
Expand Down Expand Up @@ -49,12 +45,11 @@ const interactiveAlternatives: Record<string, { href: string }> = {
href: '/codespaces/setting-up-your-project-for-codespaces/setting-up-your-project-for-codespaces?langId=py',
},
}

export type StructuredContentT = {
structuredContent?: ReactNode
type Props = {
children?: React.ReactNode
}

export const ArticlePage = ({ structuredContent }: StructuredContentT) => {
export const ArticlePage = ({ children }: Props) => {
const { asPath } = useRouter()
const {
title,
Expand All @@ -69,58 +64,9 @@ export const ArticlePage = ({ structuredContent }: StructuredContentT) => {
miniTocItems,
currentLearningTrack,
} = useArticleContext()
const renderedContent = structuredContent || renderedPage
const { t } = useTranslation('pages')
const currentPath = asPath.split('?')[0]

const renderTocItem = (item: MiniTocItem) => {
return (
<ActionList.Item
as="li"
key={item.contents}
className={item.platform}
sx={{ listStyle: 'none', padding: '2px' }}
>
<div className={cx('lh-condensed d-block width-full')}>
<div dangerouslySetInnerHTML={{ __html: item.contents }} />
{item.items && item.items.length > 0 ? (
<ul className="ml-3">{item.items.map(renderTocItem)}</ul>
) : null}
</div>
</ActionList.Item>
)
}

// We have some one-off redirects for rest api docs
// currently those are limited to the repos page, but
// that will grow soon as we restructure the rest api docs.
// This is a workaround to updating the hardcoded links
// directly in the REST API code in a separate repo, which
// requires many file changes and teams to sign off.
// While the organization is turbulent, we can do this.
// Once it's more settled, we can refactor the rest api code
// to leverage the OpenAPI urls rather than hardcoded urls.
// The code below determines if we should bother loading this redirecting
// component at all.
// The reason this isn't done at the server-level is because there you
// can't possibly access the URL hash. That's only known in client-side
// code.
const [loadClientsideRedirectExceptions, setLoadClientsideRedirectExceptions] = useState(false)
useEffect(() => {
const { hash, pathname } = window.location
// Today, Jan 2022, it's known explicitly what the pathname.
// In the future there might be more.
// Hopefully, we can some day delete all of this and no longer
// be dependent on the URL hash to do the redirect.
if (
hash &&
(pathname.endsWith('/rest/reference/repos') ||
pathname.endsWith('/rest/reference/enterprise-admin'))
) {
setLoadClientsideRedirectExceptions(true)
}
}, [])

// If the page contains `[data-highlight]` blocks, these pages need
// syntax highlighting. But not every page needs it, so it's conditionally
// lazy-loaded on the client.
Expand All @@ -139,29 +85,8 @@ export const ArticlePage = ({ structuredContent }: StructuredContentT) => {
// consecutive one does.
}, [asPath])

// Scrollable code blocks in our REST API docs and elsewhere aren't accessible
// via keyboard navigation without setting tabindex="0". But we don't want to set
// this attribute on every `<pre>` code block, only the ones where there are scroll
// bars because the content isn't all visible.
useEffect(() => {
const codeBlocks = document.querySelectorAll<HTMLPreElement>('pre')

codeBlocks.forEach((codeBlock) => {
if (
codeBlock.scrollWidth > codeBlock.clientWidth ||
codeBlock.scrollHeight > codeBlock.clientHeight
) {
codeBlock.setAttribute('tabindex', '0')
}
})
}, [])

return (
<DefaultLayout>
{/* Doesn't matter *where* this is included because it will
never render anything. It always just return null. */}
{loadClientsideRedirectExceptions && <ClientSideRedirectExceptions />}

{/* Doesn't matter *where* this is included because it will
never render anything. It always just return null. */}
{lazyLoadHighlightJS && <ClientSideHighlightJS />}
Expand Down Expand Up @@ -220,28 +145,13 @@ export const ArticlePage = ({ structuredContent }: StructuredContentT) => {
</div>
)}
{miniTocItems.length > 1 && (
<>
<Heading as="h2" id="in-this-article" className="mb-1" sx={{ fontSize: 1 }}>
<Link href="#in-this-article">{t('miniToc')}</Link>
</Heading>

<ActionList
key={title}
items={miniTocItems.map((items, i) => {
return {
key: title + i,
text: title,
renderItem: () => <ul>{renderTocItem(items)}</ul>,
}
})}
/>
</>
<MiniTocs pageTitle={title} miniTocItems={miniTocItems} />
)}
</>
}
>
<div id="article-contents">
<MarkdownContent>{renderedContent}</MarkdownContent>
<MarkdownContent>{children || renderedPage}</MarkdownContent>
{effectiveDate && (
<div className="mt-4" id="effectiveDate">
Effective as of:{' '}
Expand Down
4 changes: 4 additions & 0 deletions components/rest/RestParameterTable.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
}

tbody {
tr td {
width: auto;
}

tr td:first-child {
font-weight: bold;
}
Expand Down
159 changes: 159 additions & 0 deletions components/rest/RestReferencePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'

import { DefaultLayout } from 'components/DefaultLayout'
import { ArticleTitle } from 'components/article/ArticleTitle'
import { useMainContext } from 'components/context/MainContext'
import { MarkdownContent } from 'components/ui/MarkdownContent'
import { Lead } from 'components/ui/Lead'
import { ArticleGridLayout } from 'components/article/ArticleGridLayout'
import { MiniTocItem } from 'components/context/ArticleContext'
import { RestCategoryOperationsT } from './types'
import { RestOperation } from './RestOperation'
import { MiniTocs } from 'components/ui/MiniTocs'

const ClientSideHighlightJS = dynamic(() => import('components/article/ClientSideHighlightJS'), {
ssr: false,
})

const ClientSideRedirectExceptions = dynamic(
() => import('components/article/ClientsideRedirectExceptions'),
{
ssr: false,
}
)

export type StructuredContentT = {
descriptions: any
introContent: string
restOperations: RestCategoryOperationsT
miniTocItems?: MiniTocItem[]
}

export const RestReferencePage = ({
descriptions,
introContent,
restOperations,
miniTocItems,
}: StructuredContentT) => {
const { asPath } = useRouter()
const { page } = useMainContext()
const subcategories = Object.keys(restOperations)

// We have some one-off redirects for rest api docs
// currently those are limited to the repos page, but
// that will grow soon as we restructure the rest api docs.
// This is a workaround to updating the hardcoded links
// directly in the REST API code in a separate repo, which
// requires many file changes and teams to sign off.
// While the organization is turbulent, we can do this.
// Once it's more settled, we can refactor the rest api code
// to leverage the OpenAPI urls rather than hardcoded urls.
// The code below determines if we should bother loading this redirecting
// component at all.
// The reason this isn't done at the server-level is because there you
// can't possibly access the URL hash. That's only known in client-side
// code.
const [loadClientsideRedirectExceptions, setLoadClientsideRedirectExceptions] = useState(false)
useEffect(() => {
const { hash, pathname } = window.location
// Today, Jan 2022, it's known explicitly what the pathname.
// In the future there might be more.
// Hopefully, we can some day delete all of this and no longer
// be dependent on the URL hash to do the redirect.
if (
hash &&
(pathname.endsWith('/rest/reference/repos') ||
pathname.endsWith('/rest/reference/enterprise-admin'))
) {
setLoadClientsideRedirectExceptions(true)
}
}, [])

// Scrollable code blocks in our REST API docs and elsewhere aren't accessible
// via keyboard navigation without setting tabindex="0". But we don't want to set
// this attribute on every `<pre>` code block, only the ones where there are scroll
// bars because the content isn't all visible.
useEffect(() => {
const codeBlocks = document.querySelectorAll<HTMLPreElement>('pre')

codeBlocks.forEach((codeBlock) => {
if (
codeBlock.scrollWidth > codeBlock.clientWidth ||
codeBlock.scrollHeight > codeBlock.clientHeight
) {
codeBlock.setAttribute('tabindex', '0')
}
})
}, [])

// If the page contains `[data-highlight]` blocks, these pages need
// syntax highlighting. But not every page needs it, so it's conditionally
// lazy-loaded on the client.
const [lazyLoadHighlightJS, setLazyLoadHighlightJS] = useState(false)
useEffect(() => {
// It doesn't need to use querySelector because all we care about is if
// there is greater than zero of these in the DOM.
// Note! This "core selector", which determines whether to bother
// or not, needs to match what's used inside ClientSideHighlightJS.tsx
if (document.querySelector('[data-highlight]')) {
setLazyLoadHighlightJS(true)
}

// Important to depend on the current path because the first page you
// load, before any client-side navigation, might not need it, but the
// consecutive one does.
}, [asPath])

return (
<DefaultLayout>
{/* Doesn't matter *where* this is included because it will
never render anything. It always just return null. */}
{loadClientsideRedirectExceptions && <ClientSideRedirectExceptions />}
{lazyLoadHighlightJS && <ClientSideHighlightJS />}

<div className="container-xl px-3 px-md-6 my-4">
<ArticleGridLayout
topper={<ArticleTitle>{page.title}</ArticleTitle>}
intro={
<>
{page.introPlainText && (
<Lead data-testid="lead" data-search="lead">
{page.introPlainText}
</Lead>
)}
</>
}
toc={
<>
{miniTocItems && miniTocItems.length > 1 && (
<MiniTocs pageTitle={page.title} miniTocItems={miniTocItems} />
)}
</>
}
>
<div key={`restCategory-introContent`}>
<div dangerouslySetInnerHTML={{ __html: introContent }} />
</div>
<div id="article-contents">
<MarkdownContent>
{subcategories.map((subcategory, index) => (
<div key={`restCategory-${index}`}>
<div dangerouslySetInnerHTML={{ __html: descriptions[subcategory] }} />
{restOperations[subcategory].map((operation, index) => (
<RestOperation
key={`restOperation-${index}`}
operation={operation}
index={index}
/>
))}
</div>
))}
</MarkdownContent>
</div>
</ArticleGridLayout>
</div>
</DefaultLayout>
)
}
4 changes: 4 additions & 0 deletions components/rest/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,7 @@ export interface ChildParameter {
description: string
type: string
}

export interface RestCategoryOperationsT {
[subcategory: string]: Operation[]
}
52 changes: 52 additions & 0 deletions components/ui/MiniTocs/MiniTocs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import cx from 'classnames'
import { ActionList, Heading } from '@primer/react'

import { MiniTocItem } from 'components/context/ArticleContext'
import { Link } from 'components/Link'
import { useTranslation } from 'components/hooks/useTranslation'

export type MiniTocsPropsT = {
pageTitle: string
miniTocItems: MiniTocItem[]
}

const renderTocItem = (item: MiniTocItem) => {
return (
<ActionList.Item
as="li"
key={item.contents}
className={item.platform}
sx={{ listStyle: 'none', padding: '2px' }}
>
<div className={cx('lh-condensed d-block width-full')}>
<div dangerouslySetInnerHTML={{ __html: item.contents }} />
{item.items && item.items.length > 0 ? (
<ul className="ml-3">{item.items.map(renderTocItem)}</ul>
) : null}
</div>
</ActionList.Item>
)
}

export function MiniTocs({ pageTitle, miniTocItems }: MiniTocsPropsT) {
const { t } = useTranslation('pages')

return (
<>
<Heading as="h2" id="in-this-article" className="mb-1" sx={{ fontSize: 1 }}>
<Link href="#in-this-article">{t('miniToc')}</Link>
</Heading>

<ActionList
key={pageTitle}
items={miniTocItems.map((items, i) => {
return {
key: pageTitle + i,
text: pageTitle,
renderItem: () => <ul>{renderTocItem(items)}</ul>,
}
})}
/>
</>
)
}
Loading

0 comments on commit 1a6bee4

Please sign in to comment.