1
0
mirror of synced 2025-12-22 11:26:57 -05:00

Merge branch 'main' into issue_1981

This commit is contained in:
Sarita Iyer
2021-05-13 16:50:49 -04:00
committed by GitHub
504 changed files with 6948 additions and 1640 deletions

4
.babelrc Normal file
View File

@@ -0,0 +1,4 @@
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}

View File

@@ -0,0 +1,33 @@
---
name: Batch Status Update
description: Batch Status Update
body:
- type: dropdown
attributes:
label: Status
options:
- label: "GREEN \U0001F7E2 (All good, smooth sailing)"
value: 'Status: GREEN'
- label:
"YELLOW \U0001F7E1 (We've identified areas of concern, but have them under
control at this time)"
value: 'Status: YELLOW'
- label: "RED \U0001F534 (We need help, there are blockers beyond our control)"
value: 'Status: RED'
- label: GREY ⚪️ (Not started, paused, not currently being worked on)
value: 'Status: GREY'
- label: "BLACK ⚫️ (We shipped it \U0001F389)"
value: 'Status: BLACK'
- type: input
attributes:
label: Target date
format: date
- type: textarea
attributes:
label: Update
placeholder: A few words on how it's going
- type: input
attributes:
label: 'Attribution'
value: '_created with :heart: by typing_ `/status`'
format: text

View File

@@ -1,29 +0,0 @@
---
name: A brief status update
description: A brief status update.
body:
- type: dropdown
attributes:
label: Status
options:
- label: 'GREY ⚪️ (Not started, paused, not currently being worked on)'
value: 'Status: GREY'
- label: 'GREEN 🟢 (All good, smooth sailing)'
value: 'Status: GREEN'
- label: "YELLOW \U0001F7E1 (On track, with hurdles to work through)"
value: 'Status: YELLOW'
- label: "RED \U0001F534 (BLOCKED)"
value: 'Status: RED'
- label: "BLACK ⚫️ (We shipped it \U0001F389)"
value: 'Status: BLACK'
- type: textarea
attributes:
label: Update Summary
placeholder:
Brief summary of the status and next steps. Any blockers should be
called out specifically.
- type: input
attributes:
label: 'Attribution'
value: '_created with :heart: by typing_ `/status`'
format: text

View File

@@ -12,19 +12,20 @@ Thanks again!
### Why:
Closes [issue link]
<!--
- If there's an existing issue for your change, please link to it.
- If there's _not_ an existing issue, please open one first to make it more likely that this update will be accepted: https://github.com/github/docs/issues/new/choose. -->
**Closes [issue link]**
### What's being changed:
<!-- Share artifacts of the changes, be they code snippets, GIFs or screenshots; whatever shares the most context. -->
### Check off the following:
- [ ] I have reviewed my changes in staging. (look for the **deploy-to-heroku** link in your pull request, then click **View deployment**)
- [ ] For content changes, I have reviewed the [localization checklist](https://github.com/github/docs/blob/main/contributing/localization-checklist.md)
- [ ] For content changes, I have reviewed the [Content style guide for GitHub Docs](https://github.com/github/docs/blob/main/contributing/content-style-guide.md).
- [ ] I have reviewed my changes in staging (look for the **deploy-to-heroku** link in your pull request, then click **View deployment**).
- [ ] For content changes, I have completed the [self-review checklist](https://github.com/github/docs/blob/main/CONTRIBUTING.md#self-review).
### Writer impact (This section is for GitHub staff members only):

View File

@@ -0,0 +1,232 @@
# TODO: Convert to JavaScript for language consistency
import json
import logging
import os
import requests
# Constants
endpoint = 'https://api.github.com/graphql'
# ID of the github/github repo
github_repo_id = "MDEwOlJlcG9zaXRvcnkz"
# ID of the docs-reviewers team
docs_reviewers_id = "MDQ6VGVhbTQzMDMxMzk="
# ID of the "Docs content first responder" board
docs_project_id = "MDc6UHJvamVjdDQ1NzI0ODI="
# ID of the "OpenAPI review requests" column on the "Docs content first responder" board
docs_column_id = "PC_lAPNJr_OAEXFQs4A2OFq"
# 100 is an educated guess of how many PRs are opened in a day on the github/github repo
# If we are missing PRs, either increase this number or increase the frequency at which this script is run
num_prs_to_search = 100
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def find_open_prs_for_repo(repo_id: str, num_prs: int):
"""Return data about a specified number of open PRs for a specified repo
Arguments:
repo_id: The node ID of the repo to search
num_prs: The max number of PRs to return
Returns:
Returns a JSON object of this structure:
{
"data": {
"node": {
"pullRequests": {
"nodes": [
{
"id": str,
"isDraft": bool,
"reviewRequests": {
"nodes": [
{
"requestedReviewer": {
"id": str
}
}...
]
},
"projectCards": {
"nodes": [
{
"project": {
"id": str
}
}...
]
}
}...
]
}
}
}
}
"""
query = """query ($repo_id: ID!, $num_prs: Int!) {
node(id: $repo_id) {
... on Repository {
pullRequests(last: $num_prs, states: OPEN) {
nodes {
id
isDraft
reviewRequests(first: 10) {
nodes {
requestedReviewer {
... on Team {
id
}
}
}
}
projectCards(first: 10) {
nodes {
project {
id
}
}
}
}
}
}
}
}
"""
variables = {
"repo_id": github_repo_id,
"num_prs": num_prs
}
response = requests.post(
endpoint,
json={'query': query, 'variables': variables},
headers = {'Authorization': f"bearer {os.environ['TOKEN']}"}
)
response.raise_for_status()
json_response = json.loads(response.text)
if 'errors' in json_response:
raise RuntimeError(f'Error in GraphQL response: {json_response}')
return json_response
def add_prs_to_board(prs_to_add: list, column_id: str):
"""Adds PRs to a column of a project board
Arguments:
prs_to_add: A list of PR node IDs
column_id: The node ID of the column to add the PRs to
Returns:
Nothing
"""
logger.info(f"adding: {prs_to_add}")
mutation = """mutation($pr_id: ID!, $column_id: ID!) {
addProjectCard(input:{contentId: $pr_id, projectColumnId: $column_id}) {
projectColumn {
name
}
}
}"""
for pr_id in prs_to_add:
logger.info(f"Attempting to add {pr_id} to board")
variables = {
"pr_id": pr_id,
"column_id": column_id
}
response = requests.post(
endpoint,
json={'query': mutation, 'variables': variables},
headers = {'Authorization': f"bearer {os.environ['TOKEN']}"}
)
json_response = json.loads(response.text)
if 'errors' in json_response:
logger.info(f"GraphQL error when adding {pr_id}: {json_response}")
def filter_prs(data, reviewer_id: str, project_id):
"""Given data about the draft state, reviewers, and project boards for PRs,
return just the PRs that are:
- not draft
- are requesting a review for the specified team
- are not already on the specified project board
Arguments:
data: A JSON object of this structure:
{
"data": {
"node": {
"pullRequests": {
"nodes": [
{
"id": str,
"isDraft": bool,
"reviewRequests": {
"nodes": [
{
"requestedReviewer": {
"id": str
}
}...
]
},
"projectCards": {
"nodes": [
{
"project": {
"id": str
}
}...
]
}
}...
]
}
}
}
}
reviewer_id: The node ID of the reviewer to filter for
project_id: The project ID of the project to filter against
Returns:
A list of node IDs of the PRs that met the requirements
"""
pr_data = data['data']['node']['pullRequests']['nodes']
prs_to_add = []
for pr in pr_data:
if (
not pr['isDraft'] and
reviewer_id in [req_rev['requestedReviewer']['id'] for req_rev in pr['reviewRequests']['nodes'] if req_rev['requestedReviewer']] and
project_id not in [proj_card['project']['id'] for proj_card in pr['projectCards']['nodes']]
):
prs_to_add.append(pr['id'])
return prs_to_add
def main():
query_data = find_open_prs_for_repo(github_repo_id, num_prs_to_search)
prs_to_add = filter_prs(query_data, docs_reviewers_id, docs_project_id)
add_prs_to_board(prs_to_add, docs_column_id)
if __name__ == "__main__":
main()

View File

@@ -9,6 +9,7 @@ module.exports = [
"actions/github-script@626af12fe9a53dc2972b48385e7fe7dec79145c9", // v3.0.0
"actions/labeler@5f867a63be70efff62b767459b009290364495eb", // v2.2.0
"actions/setup-node@c46424eee26de4078d34105d3de3cc4992202b1e", // v2.1.4
"actions/setup-python@dc73133d4da04e56a135ae2246682783cc7c7cb6", // v2.2.2
"ruby/setup-ruby@fdcfbcf14ec9672f6f615cb9589a1bc5dd69d262", // v1.64.1
"actions/stale@9d6f46564a515a9ea11e7762ab3957ee58ca50da", // v3.0.16
"alex-page/github-project-automation-plus@fdb7991b72040d611e1123d2b75ff10eda9372c9",

34
.github/review-template.md vendored Normal file
View File

@@ -0,0 +1,34 @@
## Author self-review
- [ ] The changes in this PR meet the user experience and goals outlined in the content design plan.
- [ ] I've compared my PR's source changes to staging and reviewed for versioning issues, redirects, the [style guide](https://github.com/github/docs/blob/main/contributing/content-style-guide.md), [content model](https://github.com/github/docs-content-strategy/blob/main/content-design/models.md), or [localization checklist](https://github.com/github/docs/blob/main/contributing/localization-checklist.md) rendering problems, typos, and wonky screenshots.
- [ ] I've worked through build failures and tests are passing.
- [ ] For REST API content, I've verified that endpoints, parameters, and responses are correct and work as expected and provided curl samples below.
For more information, check out our [full review guidelines and checklist](https://github.com/github/docs-content/blob/main/docs-content-docs/docs-content-workflows/reviews-and-feedback/review-process.md).
## Review request
### Summary
_Help reviewers understand this project and its context by writing a paragraph summarizing its goals and intended user experience and explaining how the PR meets those goals._
[Content design plan](LINK HERE)
### Docs Content review
_Give Docs Content any extra context, highlight areas for them to consider in their review, and ask them questions you need answered to ship the PR._
### Technical review
_Ping in technical reviewers, asking them to review whether content is technically accurate and right for the audience._
_Highlight areas for them to consider in their review and ask them questions you need answered to ship the PR._
### Content changes
[PR on staging](LINK HERE)
_Give a high-level overview of the changes in your PR and how they support the overall goals of the PR. Share links to important articles or changes in source and on staging. If your PR is large or complex, use a table to highlight changes with high user impact._
### Notes
_Discuss test failures, versioning issues, or anything else reviewers should know to consider the overall user experience of the PR._

View File

@@ -0,0 +1,36 @@
name: Add review template
# **What it does**: When a specific label is added to a PR, adds the contents of .github/review-template.md as a comment in the PR
# **Why we have it**: To help Docs Content team members ensure that their PR is ready for review
# **Who does it impact**: docs-internal maintainers and contributors
on:
pull_request:
types:
- labeled
jobs:
comment-that-approved:
name: Add review template
runs-on: ubuntu-latest
if: github.event.label.name == 'docs-content-ready-for-review' && github.repository == 'github/docs-internal'
steps:
- name: check out repo content
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
# Jump through some hoops to work with a multi-line file
- name: Store review template in variable
run: |
TEMPLATE=$(cat .github/review-template.md)
echo "TEMPLATE<<EOF" >> $GITHUB_ENV
echo "$TEMPLATE" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Comment on the PR
run: |
gh pr comment $PR --body "$TEMPLATE"
env:
GITHUB_TOKEN: ${{secrets.DOCUBOT_FR_PROJECT_BOARD_WORKFLOWS_REPO_ORG_READ_SCOPES}}
PR: ${{ github.event.pull_request.html_url }}
TEMPLATE: ${{ env.TEMPLATE }}

View File

@@ -0,0 +1,35 @@
name: Add docs-reviewers request to FR board
# **What it does**: Adds PRs in github/github that requested a review from docs-reviewers to the FR board
# **Why we have it**: To catch docs-reviewers requests in github/github
# **Who does it impact**: docs-content maintainers
on:
workflow_dispatch:
schedule:
- cron: '25 4 * * *'
jobs:
add-requests-to-board:
name: Add requests to board
runs-on: ubuntu-latest
steps:
- name: Check out repo content
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Set up Python 3.9
uses: actions/setup-python@dc73133d4da04e56a135ae2246682783cc7c7cb6
with:
python-version: '3.9'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install requests
- name: Run script
run: |
python .github/actions-scripts/fr-add-docs-reviewers-requests.py
env:
TOKEN: ${{ secrets.DOCS_BOT }}

View File

@@ -51,7 +51,7 @@ jobs:
with:
github-token: ${{ secrets.DOCUBOT_READORG_REPO_WORKFLOW_SCOPES }}
script: |
var column_id = 13445932;
var column_id = 12860544;
try {
github.projects.createCard({
column_id: column_id,

View File

@@ -158,12 +158,12 @@ We (usually the docs team, but sometimes GitHub product managers, engineers, or
You should always review your own PR first.
For content changes, make sure that you:
- [ ] Confirm that the changes address every part of the content design plan from your issue (if there are differences, explain them).
- [ ] Confirm that the changes meet the user experience and goals outlined in the content design plan (if there is one).
- [ ] Compare your pull request's source changes to staging to confirm that the output matches the source and that everything is rendering as expected. This helps spot issues like typos, content that doesn't follow the style guide, or content that isn't rendering due to versioning problems. Remember that lists and tables can be tricky.
- [ ] Review the content for technical accuracy.
- [ ] Review the entire pull request using the [localization checklist](contributing/localization-checklist.md).
- [ ] Copy-edit the changes for grammar, spelling, and adherence to the style guide.
- [ ] Copy-edit the changes for grammar, spelling, and adherence to the [style guide](https://github.com/github/docs/blob/main/contributing/content-style-guide.md).
- [ ] Check new or updated Liquid statements to confirm that versioning is correct.
- [ ] Check that all of your changes render correctly in staging. Remember, that lists and tables can be tricky.
- [ ] If there are any failing checks in your PR, troubleshoot them until they're all passing.
### Pull request template

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 316 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 786 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,42 @@
import cx from 'classnames'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useMainContext } from './context/MainContext'
export type BreadcrumbT = {
title: string
documentType?: string
href?: string
}
type Props = {}
export const Breadcrumbs = (props: Props) => {
const router = useRouter()
const pathWithLocale = `/${router.locale}${router.asPath}`
const { breadcrumbs } = useMainContext()
return (
<nav className="breadcrumbs f5" aria-label="Breadcrumb">
{Object.values(breadcrumbs).map((breadcrumb) => {
const title = `${breadcrumb.documentType}: ${breadcrumb.title}`
return !breadcrumb.href ? (
<span key={title} title={title}>
{breadcrumb.title}
</span>
) : (
<Link key={title} href={breadcrumb.href}>
<a
title={title}
className={cx(
'd-inline-block',
pathWithLocale === breadcrumb.href && 'color-text-tertiary'
)}
>
{breadcrumb.title}
</a>
</Link>
)
})}
</nav>
)
}

View File

@@ -0,0 +1,29 @@
import { GitPullRequestIcon } from '@primer/octicons-react'
import { useMainContext } from 'components/context/MainContext'
import { useTranslation } from 'components/hooks/useTranslation'
export const Contribution = () => {
const { relativePath } = useMainContext()
const { t } = useTranslation('contribution_cta')
const contribution_href = relativePath
? `https://github.com/github/docs/edit/main/content/${relativePath}`
: 'https://github.com/github/docs'
return (
<div className="f5 contribution">
<h2 className="f4">{t`title`}</h2>
<p className="color-text-secondary f6">{t`body`}</p>
<a className="btn btn-outline" href={contribution_href}>
<GitPullRequestIcon size="small" className="octicon mr-1" />
{t`button`}
</a>
<p className="color-text-secondary f6 mt-2">
{t`or`}{' '}
<a href="https://github.com/github/docs/blob/main/CONTRIBUTING.md" target="_blank">
{t`to_guidelines`}
</a>
</p>
</div>
)
}

View File

@@ -0,0 +1,35 @@
import Head from 'next/head'
// import { Sidebar } from 'components/Sidebar'
import { Header } from 'components/Header'
import { SmallFooter } from 'components/SmallFooter'
import { ScrollButton } from 'components/ScrollButton'
import { SupportSection } from 'components/SupportSection'
import { DeprecationBanner } from 'components/DeprecationBanner'
import { useMainContext } from 'components/context/MainContext'
type Props = { children?: React.ReactNode }
export const DefaultLayout = (props: Props) => {
const { builtAssets, expose } = useMainContext()
return (
<div className="d-lg-flex">
<Head>
<link rel="stylesheet" href={builtAssets.main.css} />
<script id="expose" type="application/json" dangerouslySetInnerHTML={{ __html: expose }} />
<script src={builtAssets.main.js} />
</Head>
{/* <Sidebar /> */}
<main className="width-full">
<Header />
<DeprecationBanner />
{props.children}
<SupportSection />
<SmallFooter />
<ScrollButton />
</main>
</div>
)
}

View File

@@ -0,0 +1,38 @@
import { useMainContext } from 'components/context/MainContext'
import { useVersion } from 'components/hooks/useVersion'
export const DeprecationBanner = () => {
const { data, enterpriseServerReleases } = useMainContext()
const { currentVersion } = useVersion()
if (!currentVersion.includes(enterpriseServerReleases.oldestSupported)) {
return null
}
const message = enterpriseServerReleases.isOldestReleaseDeprecated
? data.reusables.enterprise_deprecation.version_was_deprecated
: data.reusables.enterprise_deprecation.version_will_be_deprecated
return (
<div className="deprecation-banner border rounded-1 mb-2 color-bg-warning p-3 color-border-warning f5">
<p>
<b>
<span dangerouslySetInnerHTML={{ __html: message }} />{' '}
<span
data-date={enterpriseServerReleases.nextDeprecationDate}
data-format="%B %d, %Y"
title={enterpriseServerReleases.nextDeprecationDate}
>
{enterpriseServerReleases.nextDeprecationDate}
</span>
.
</b>{' '}
<span
dangerouslySetInnerHTML={{
__html: data.reusables.enterprise_deprecation.deprecation_details,
}}
/>
</p>
</div>
)
}

113
components/Header.tsx Normal file
View File

@@ -0,0 +1,113 @@
import { useState } from 'react'
import Link from 'next/link'
import cx from 'classnames'
import { css } from 'styled-components'
import { useRouter } from 'next/router'
import { ChevronDownIcon, MarkGithubIcon, ThreeBarsIcon, XIcon } from '@primer/octicons-react'
import { ButtonOutline } from '@primer/components'
import { useMainContext } from './context/MainContext'
import { LanguagePicker } from './LanguagePicker'
import { HeaderNotifications } from 'components/HeaderNotifications'
import { MobileProductDropdown } from 'components/MobileProductDropdown'
import { useTranslation } from 'components/hooks/useTranslation'
export const Header = () => {
const router = useRouter()
const { currentProduct, relativePath, error } = useMainContext()
const { t } = useTranslation(['header', 'homepage'])
const [isMenuOpen, setIsMenuOpen] = useState(false)
return (
<div className="border-bottom color-border-secondary no-print">
{error !== '404' && <HeaderNotifications />}
<header className="container-xl px-3 px-md-6 pt-3 pb-2 position-relative d-flex flex-justify-between width-full">
<div
className="d-flex flex-items-center d-lg-none"
style={{ zIndex: 3 }}
id="github-logo-mobile"
role="banner"
>
<Link href={`/${router.locale}`}>
<a aria-hidden="true" tabIndex={-1}>
<MarkGithubIcon size={32} className="color-icon-primary" />
</a>
</Link>
<Link href={`/${router.locale}`}>
<a className="h4-mktg color-text-primary no-underline no-wrap pl-2">
{t('github_docs')}
</a>
</Link>
</div>
<div className="width-full">
<div className="d-inline-block width-full d-md-flex" style={{ zIndex: 1 }}>
<div className="float-right d-md-none position-relative" style={{ zIndex: 3 }}>
<ButtonOutline css onClick={() => setIsMenuOpen(!isMenuOpen)}>
{isMenuOpen ? <XIcon size="small" /> : <ThreeBarsIcon size="small" />}
</ButtonOutline>
</div>
<div
style={{ zIndex: 2 }}
className={cx('nav-mobile-dropdown width-full', isMenuOpen && 'js-open')}
>
<div className="d-md-flex flex-justify-between flex-items-center">
<div className="py-2 py-md-0 d-md-inline-block">
<h4 className="text-mono f5 text-normal color-text-secondary d-md-none">
{t('explore_by_product')}
</h4>
<details className="dropdown-withArrow position-relative details details-reset d-md-none close-when-clicked-outside">
<summary
className="nav-desktop-productDropdownButton color-text-link py-2"
role="button"
aria-label="Toggle products list"
>
<div
id="current-product"
className="d-flex flex-items-center flex-justify-between"
style={{ paddingTop: 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 --> */}
{currentProduct.name}
<ChevronDownIcon size={24} className="arrow ml-md-1" />
</div>
</summary>
<MobileProductDropdown />
</details>
</div>
{/* <!-- Versions picker that only appears in the header on landing pages --> */}
{/* {% include header-version-switcher %} */}
<div className="d-md-inline-block">
{/* <!-- Language picker - 'English', 'Japanese', etc --> */}
<div className="border-top border-md-top-0 py-2 py-md-0 d-md-inline-block">
<LanguagePicker />
</div>
{/* <!-- GitHub.com homepage and 404 page has a stylized search; Enterprise homepages do not --> */}
{relativePath !== 'index.md' && error !== '404'}
<div
className="pt-3 pt-md-0 d-md-inline-block ml-md-3 border-top border-md-top-0"
dangerouslySetInnerHTML={{
__html: `
<div id="search-input-container" aria-hidden="true"></div>
<div id="search-results-container"></div>
<div class="search-overlay-desktop"></div>
`,
}}
/>
</div>
</div>
</div>
</div>
</div>
</header>
</div>
)
}

View File

@@ -0,0 +1,89 @@
import { useRouter } from 'next/router'
import cx from 'classnames'
import { useMainContext } from 'components/context/MainContext'
import { useTranslation } from 'components/hooks/useTranslation'
import { ExcludesNull } from 'components/lib/ExcludesNull'
import { useVersion } from './hooks/useVersion'
enum NotificationType {
RELEASE = 'RELEASE',
TRANSLATION = 'TRANSLATION',
EARLY_ACCESS = 'EARLY_ACCESS',
}
type Notif = {
content: string
type: NotificationType
}
export const HeaderNotifications = () => {
const router = useRouter()
const { currentVersion } = useVersion()
const { relativePath, allVersions, data, languages, currentLanguage } = useMainContext()
const { t } = useTranslation('header')
const translationNotices: Array<Notif> = []
if (router.locale !== 'en') {
if (relativePath?.includes('/site-policy')) {
translationNotices.push({
type: NotificationType.TRANSLATION,
content: data.reusables.policies.translation,
})
} else if (languages[currentLanguage].wip !== true) {
translationNotices.push({
type: NotificationType.TRANSLATION,
content: t('notices.localization_complete'),
})
} else if (languages[currentLanguage].wip) {
translationNotices.push({
type: NotificationType.TRANSLATION,
content: t('notices.localization_in_progress'),
})
}
}
const releaseNotices: Array<Notif> = []
if (currentVersion === 'github-ae@latest') {
releaseNotices.push({
type: NotificationType.RELEASE,
content: t('notices.ghae_silent_launch'),
})
} else if (currentVersion === data.variables.release_candidate.version) {
releaseNotices.push({
type: NotificationType.RELEASE,
content: `${allVersions[currentVersion].versionTitle}${t('notices.release_candidate')}`,
})
}
const allNotifications: Array<Notif> = [
...translationNotices,
...releaseNotices,
// ONEOFF EARLY ACCESS NOTICE
(relativePath || '').includes('early-access/')
? {
type: NotificationType.EARLY_ACCESS,
content: t('notices.early_access'),
}
: null,
].filter(ExcludesNull)
return (
<>
{allNotifications.map(({ type, content }, i) => {
const isLast = i !== allNotifications.length - 1
return (
<div
className={cx(
'header-notifications text-center f5 color-text-primary py-4 px-6',
type === NotificationType.TRANSLATION && 'translation_notice color-bg-info',
type === NotificationType.RELEASE && 'release_notice color-bg-info',
type === NotificationType.EARLY_ACCESS && 'early_access color-bg-danger',
!isLast && 'border-bottom color-border-tertiary'
)}
dangerouslySetInnerHTML={{ __html: content }}
/>
)
})}
</>
)
}

View File

@@ -0,0 +1,83 @@
import { ThumbsdownIcon, ThumbsupIcon } from '@primer/octicons-react'
import { useTranslation } from 'components/hooks/useTranslation'
export const Helpfulness = () => {
const { t } = useTranslation('helpfulness')
return (
<form className="js-helpfulness f5">
<h2 data-help-start data-help-yes data-help-no className="mb-1 f4">
{t`able_to_find`}
</h2>
<p className="f6">
<a href="/github/site-policy/github-privacy-statement">Privacy policy</a>
</p>
<p className="radio-group" data-help-start data-help-yes data-help-no>
<input
hidden
id="helpfulness-yes"
type="radio"
name="helpfulness-vote"
value="Yes"
aria-label={t('yes')}
/>
<label className="btn x-radio-label mr-1" htmlFor="helpfulness-yes">
<ThumbsupIcon size={24} className="color-text-tertiary" />
</label>
<input
hidden
id="helpfulness-no"
type="radio"
name="helpfulness-vote"
value="No"
aria-label={t`no`}
/>
<label className="btn x-radio-label" htmlFor="helpfulness-no">
<ThumbsdownIcon size={24} className="color-text-tertiary" />
</label>
</p>
<p className="color-text-secondary f6" hidden data-help-yes>
{t('yes_feedback')}
</p>
<p className="color-text-secondary f6" hidden data-help-no>
{t('no_feedback')}
</p>
<input type="text" className="d-none" name="helpfulness-token" aria-hidden="true" />
<p hidden data-help-no>
<label className="d-block mb-1 f6" htmlFor="helpfulness-comment">
<span>{t('comment_label')}</span>
<span className="text-normal color-text-tertiary float-right ml-1">{t('optional')}</span>
</label>
<textarea
className="form-control input-sm width-full"
name="helpfulness-comment"
id="helpfulness-comment"
></textarea>
</p>
<p>
<label className="d-block mb-1 f6" htmlFor="helpfulness-email" hidden data-help-no>
{t('email_label')}
<span className="text-normal color-text-tertiary float-right ml-1">{t('optional')}</span>
</label>
<input
type="email"
className="form-control input-sm width-full"
name="helpfulness-email"
id="helpfulness-email"
placeholder={t('email_placeholder')}
hidden
data-help-yes
data-help-no
/>
</p>
<p className="text-right" hidden data-help-yes data-help-no>
<button type="submit" className="btn btn-sm">
{t('send')}
</button>
</p>
<p className="color-text-secondary f6" hidden data-help-end>
{t('feedback')}
</p>
</form>
)
}

View File

@@ -0,0 +1,42 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Dropdown } from '@primer/components'
import { useMainContext } from './context/MainContext'
export const LanguagePicker = () => {
const router = useRouter()
const { languages } = useMainContext()
const locale = router.locale || 'en'
const langs = Object.values(languages)
const selectedLang = languages[locale]
return (
<div className="ml-4 d-flex flex-justify-center flex-items-center">
<Dropdown css>
<summary>
{selectedLang.nativeName || selectedLang.name}
<Dropdown.Caret />
</summary>
<Dropdown.Menu direction="sw">
{langs.map((lang) => {
return (
<Dropdown.Item key={lang.code}>
<Link href={router.asPath} locale={lang.hreflang}>
<a>
{lang.nativeName ? (
<>
{lang.nativeName} ({lang.name})
</>
) : (
lang.name
)}
</a>
</Link>
</Dropdown.Item>
)
})}
</Dropdown.Menu>
</Dropdown>
</div>
)
}

View File

@@ -0,0 +1,44 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { LinkExternalIcon } from '@primer/octicons-react'
import cx from 'classnames'
import { useMainContext } from 'components/context/MainContext'
export const MobileProductDropdown = () => {
const router = useRouter()
const { activeProducts, currentProduct } = useMainContext()
return (
<div
id="homepages"
className="position-md-absolute nav-desktop-productDropdown p-md-4 left-md-n4 top-md-6"
style={{ zIndex: 6 }}
>
{activeProducts.map((product) => {
return (
<Link
key={product.id}
href={`${product.external ? '' : `/${router.locale}`}${product.href}`}
>
<a
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>
)}
</a>
</Link>
)
})}
</div>
)
}

View File

@@ -0,0 +1,9 @@
import { ChevronUpIcon } from '@primer/octicons-react'
export const ScrollButton = () => {
return (
<button className="arrow-for-scrolling-top" id="js-scroll-top">
<ChevronUpIcon />
</button>
)
}

View File

@@ -0,0 +1,48 @@
import { MarkGithubIcon } from '@primer/octicons-react'
import { useTranslation } from 'components/hooks/useTranslation'
export const SmallFooter = () => {
const { t } = useTranslation('footer')
return (
<footer className="py-6 text-small">
<div className="container-xl d-flex px-3 px-md-6">
<ul className="d-flex list-style-none flex-wrap flex-justify-center flex-xl-justify-start">
<li className="d-flex mr-xl-3 color-text-secondary">
<MarkGithubIcon className="mr-2 mr-xl-3" size={20} />
<span>&copy; {new Date().getFullYear()} GitHub, Inc.</span>
</li>
<li className="ml-3">
<a href="/github/site-policy/github-terms-of-service">{t`terms`}</a>
</li>
<li className="ml-3">
<a href="/github/site-policy/github-privacy-statement">{t`privacy`} </a>
</li>
<li className="ml-3">
<a href="https://github.com/security">{t`product.links.security`}</a>
</li>
<li className="ml-3">
<a href="https://www.githubstatus.com/">{t`support.links.status`}</a>
</li>
<li className="ml-3">
<a href="/">{t`support.links.help`}</a>
</li>
<li className="ml-3">
<a href="https://support.github.com">{t`support.links.contact_github`}</a>
</li>
<li className="ml-3">
<a href="https://github.com/pricing">{t`product.links.pricing`}</a>
</li>
<li className="ml-3">
<a href="/developers">{t`platform.links.developer_api`}</a>
</li>
<li className="ml-3">
<a href="https://services.github.com/">{t`support.links.training`}</a>
</li>
<li className="ml-3">
<a href="https://github.com/about">{t`company.links.about`}</a>
</li>
</ul>
</div>
</footer>
)
}

31
components/Support.tsx Normal file
View File

@@ -0,0 +1,31 @@
import { PeopleIcon, CommentDiscussionIcon } from '@primer/octicons-react'
import { useTranslation } from 'components/hooks/useTranslation'
import { useVersion } from 'components/hooks/useVersion'
export const Support = () => {
const { isEnterprise } = useVersion()
const { t } = useTranslation('support')
return (
<div>
<h3 className="mb-2 f4">{t`still_need_help`}</h3>
<a id="ask-community" href="https://github.community" className="btn btn-outline mr-4 mt-2">
<PeopleIcon size="small" className="octicon mr-1" />
{t`ask_community`}
</a>
<a
id="contact-us"
href={
isEnterprise
? 'https://enterprise.github.com/support'
: 'https://support.github.com/contact'
}
className="btn btn-outline mt-2"
>
<CommentDiscussionIcon size="small" className="octicon mr-1" />
{t`contact_support`}
</a>
</div>
)
}

View File

@@ -0,0 +1,30 @@
import { Helpfulness } from 'components/Helpfulness'
import { Contribution } from 'components/Contribution'
import { Support } from 'components/Support'
import { useMainContext } from './context/MainContext'
import { useVersion } from 'components/hooks/useVersion'
export const SupportSection = () => {
const { currentVersion } = useVersion()
const { enterpriseServerReleases } = useMainContext()
const isDeprecated =
enterpriseServerReleases.isOldestReleaseDeprecated &&
currentVersion.includes(enterpriseServerReleases.oldestSupported)
return (
<section className="mt-lg-9 py-7 px-3 px-md-6 no-print color-bg-tertiary">
<div className="container-xl gutter-lg-spacious clearfix">
<div className="col-12 col-lg-6 col-xl-4 mb-6 mb-xl-0 float-left">
{!isDeprecated && <Helpfulness />}
</div>
<div className="col-12 col-lg-6 col-xl-4 mb-6 mb-xl-0 float-left">
{!isDeprecated && <Contribution />}
</div>
<div className="col-12 col-lg-12 col-xl-4 float-left">
<Support />
</div>
</div>
</section>
)
}

View File

@@ -0,0 +1,25 @@
import React, { ReactNode, ReactHTML } from 'react'
import cx from 'classnames'
type Props = {
as?: keyof ReactHTML
maxLines: number
children: ReactNode
className?: string
}
export const TruncateLines = (props: Props) => {
const Component = props.as || 'div'
return (
<Component className={cx('root', props.className)}>
{props.children}
<style jsx>{`
.root {
display: -webkit-box;
-webkit-line-clamp: ${props.maxLines};
-webkit-box-orient: vertical;
overflow: hidden;
}
`}</style>
</Component>
)
}

View File

@@ -0,0 +1,44 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Dropdown } from '@primer/components'
import { useMainContext } from './context/MainContext'
import { useVersion } from './hooks/useVersion'
export const VersionPicker = () => {
const router = useRouter()
const { currentVersion } = useVersion()
const { allVersions } = useMainContext()
const versions = Object.values(allVersions)
const activeVersion = allVersions[currentVersion]
return (
<div className="ml-4 d-flex flex-justify-center flex-items-center">
<Dropdown css>
<summary>
{activeVersion.versionTitle}
<Dropdown.Caret />
</summary>
<Dropdown.Menu direction="sw">
{versions.map((version) => {
return (
<Dropdown.Item key={version.version}>
<Link
href={{
pathname: router.pathname,
query: {
...router.query,
versionId: version.version,
},
}}
>
<a>{version.versionTitle}</a>
</Link>
</Dropdown.Item>
)
})}
</Dropdown.Menu>
</Dropdown>
</div>
)
}

View File

@@ -0,0 +1,116 @@
import { createContext, useContext } from 'react'
import type { BreadcrumbT } from 'components/Breadcrumbs'
type ProductT = {
external: boolean
href: string
id: string
name: string
}
type LanguageItem = {
name: string
nativeName: string
code: string
hreflang: string
wip?: boolean
}
type VersionItem = {
version: string
versionTitle: string
}
type DataT = {
ui: Record<string, any>
reusables: {
enterprise_deprecation: {
version_was_deprecated: string
version_will_be_deprecated: string
deprecation_details: string
isOldestReleaseDeprecated: boolean
}
policies: {
translation: string
}
}
variables: {
release_candidate: { version: string }
}
}
type EnterpriseServerReleases = {
isOldestReleaseDeprecated: boolean
oldestSupported: string
nextDeprecationDate: string
}
export type MainContextT = {
breadcrumbs: Record<string, BreadcrumbT>
builtAssets: { main: { css: string; js: string } }
expose: string
activeProducts: Array<ProductT>
currentProduct: ProductT
currentLayoutName: string
data: DataT
airGap?: boolean
error: string
currentCategory?: string
relativePath?: string
enterpriseServerReleases: EnterpriseServerReleases
currentLanguage: string
languages: Record<string, LanguageItem>
allVersions: Record<string, VersionItem>
}
export const getMainContextFromRequest = (req: any): MainContextT => {
return {
builtAssets: req.context.builtAssets,
expose: req.context.expose,
breadcrumbs: req.context.breadcrumbs || {},
activeProducts: req.context.activeProducts,
currentProduct: req.context.productMap[req.context.currentProduct],
currentLayoutName: req.context.currentLayoutName,
error: req.context.error || '',
data: {
ui: req.context.site.data.ui,
reusables: {
enterprise_deprecation: req.context.site.data.reusables.enterprise_deprecation,
policies: req.context.site.data.reusables.policies,
},
variables: {
release_candidate: req.context.site.data.variables.release_candidate,
},
},
airGap: req.context.AIRGAP || false,
currentCategory: req.context.currentCategory || '',
relativePath: req.context.page.relativePath,
enterpriseServerReleases: req.context.enterpriseServerReleases,
currentLanguage: req.context.currentLanguage,
languages: Object.fromEntries(
Object.entries(req.context.languages).map(([key, entry]: any) => {
return [
key,
{
name: entry.name,
nativeName: entry.nativeName || '',
code: entry.code,
hreflang: entry.hreflang,
},
]
})
),
allVersions: req.context.allVersions,
}
}
export const MainContext = createContext<MainContextT | null>(null)
export const useMainContext = (): MainContextT => {
const context = useContext(MainContext)
if (!context) {
throw new Error('"useMainContext" may only be used inside "MainContext.Provider"')
}
return context
}

View File

@@ -0,0 +1,126 @@
import { createContext, useContext } from 'react'
import pick from 'lodash/pick'
export type FeaturedLink = {
title: string
href: string
intro?: string
authors?: Array<string>
hideIntro?: boolean
date?: string
}
export type CodeExample = {
title: string
description: string
languages: string // single comma separated string
href: string
tags: Array<string>
}
export type ProductLandingContextT = {
title: string
introPlainText: string
shortTitle: string
intro: string
beta_product: boolean
// primaryAction: LinkButtonT
// secondaryAction?: LinkButtonT
introLinks: {
quickstart?: string
reference?: string
overview?: string
}
product_video?: string
// featuredLinks?: {
// guides: Array<FeaturedLink>
// popular: Array<FeaturedLink>
// guideCards: Array<FeaturedLink>
// }
guideCards: Array<FeaturedLink>
productCodeExamples: Array<CodeExample>
productUserExamples: Array<{ username: string; description: string }>
productCommunityExamples: Array<{ repo: string; description: string }>
featuredArticles: Array<{
label: string // Guides
viewAllHref?: string // If provided, adds a "View All ->" to the header
articles: Array<FeaturedLink>
}>
changelog: { label: string; prefix: string }
changelogUrl?: string
whatsNewChangelog?: Array<{ href: string; title: string; date: string }>
}
export const ProductLandingContext = createContext<ProductLandingContextT | null>(null)
export const useProductLandingContext = (): ProductLandingContextT => {
const context = useContext(ProductLandingContext)
if (!context) {
throw new Error(
'"useProductLandingContext" may only be used inside "ProductLandingContext.Provider"'
)
}
return context
}
export const getProductLandingContextFromRequest = (req: any): ProductLandingContextT => {
const { currentCategory, currentPath, data } = req.context
return {
...pick(req.context.page, [
'title',
'shortTitle',
'introPlainText',
'beta_product',
'intro',
'product_video',
'changelog',
]),
whatsNewChangelog: req.context.whatsNewChangelog,
changelogUrl: req.context.changelogUrl,
productCodeExamples: req.context.productCodeExamples || [],
productCommunityExamples: req.context.productCommunityExamples || [],
productUserExamples: (req.context.productUserExamples || []).map(
({ user, description }: any) => ({
username: user,
description,
})
),
introLinks: Object.fromEntries(
Object.entries(req.context.page.introLinks || {}).filter(([key, val]) => !!val)
),
guideCards: (req.context.featuredLinks.guideCards || []).map((link: any) => {
return {
href: link.href,
title: link.title,
intro: link.intro,
authors: link.page.authors || [],
}
}),
featuredArticles: Object.entries(req.context.featuredLinks)
.filter(([key]) => {
return key === 'guides' || key === 'popular'
})
.map(([key, links]: any) => {
return {
label: req.context.site.data.ui.toc[key],
viewAllHref: key === 'guides' && !currentCategory ? `${currentPath}/${key}` : '',
articles: links.map((link: any) => {
return {
hideIntro: key === 'popular',
href: link.href,
title: link.title,
intro: link.intro,
authors: link.page.authors || [],
}
}),
}
}),
}
}

View File

@@ -0,0 +1,41 @@
import { useMainContext } from 'components/context/MainContext'
import get from 'lodash/get'
// The idea of this component is to mimic a popular i18n library (i18next)
// so that we can set ourselves up to transition to it (or a similar library) in the future
export const useTranslation = (namespaces: string | Array<string>) => {
const { data } = useMainContext()
// this can eventually be an object constructed from the input namespaces param above, but for now everything is already loaded
const loadedData: any = data.ui
return {
// The compiled string supports prefixing with a namespace such as `my-namespace:path.to.value`
t: (strings: TemplateStringsArray | string, ...values: Array<any>) => {
const key = typeof strings === 'string' ? strings : String.raw(strings, ...values)
const splitKey = key.split(':')
if (splitKey.length > 2) {
throw new Error('Multiple ":" not allowed in translation lookup path')
}
if (splitKey.length === 2) {
const [namespace, path] = splitKey
return get(loadedData[namespace], path)
}
const [path] = splitKey
if (Array.isArray(namespaces)) {
for (const namespace of namespaces) {
const val = get(loadedData[namespace], path)
if (val !== undefined) {
return val
}
}
return undefined
} else {
return get(loadedData[namespaces], path)
}
},
}
}

View File

@@ -0,0 +1,12 @@
import { useRouter } from 'next/router'
type VersionInfo = {
currentVersion: string
isEnterprise: 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') }
}

View File

@@ -0,0 +1,35 @@
import { RepoIcon } from '@primer/octicons-react'
import { CodeExample } from 'components/context/ProductLandingContext'
type Props = {
example: CodeExample
}
export const CodeExampleCard = ({ example }: Props) => {
return (
<a
className="Box d-flex flex-column flex-justify-between height-full color-shadow-medium hover-shadow-large no-underline color-text-primary"
href={`https://github.com/${example.href}`}
>
<div className="p-4">
<h4>{example.title}</h4>
<p className="mt-2 mb-4 color-text-tertiary">{example.description}</p>
<div className="d-flex flex-wrap">
{example.tags.map((tag) => {
return (
<span
key={tag}
className="IssueLabel color-text-inverse color-bg-info-inverse mr-2 mb-1"
>
{tag}
</span>
)
})}
</div>
</div>
<footer className="border-top p-4 color-text-secondary d-flex flex-items-center">
<RepoIcon className="flex-shrink-0" />
<span className="ml-2 text-mono text-small color-text-link">{example.href}</span>
</footer>
</a>
)
}

Some files were not shown because too many files have changed in this diff Show More