import React, { useState, useEffect, useRef, FormEvent } from 'react' import { FormControl, IconButton, Select, SegmentedControl } from '@primer/react' import { CheckIcon, CopyIcon, InfoIcon } from '@primer/octicons-react' import { announce } from '@primer/live-region-element' import Cookies from '@/frame/components/lib/cookies' import cx from 'classnames' import hljs from 'highlight.js/lib/core' import json from 'highlight.js/lib/languages/json' import javascript from 'highlight.js/lib/languages/javascript' import { generateExampleOptions } from '@/rest/lib/code-example-utils' import hljsCurl from 'highlightjs-curl' import { useTranslation } from '@/languages/components/useTranslation' import useClipboard from '@/rest/components/useClipboard' import { getShellExample, getGHExample, getJSExample, } from '@/rest/components/get-rest-code-samples' import styles from './RestCodeSamples.module.scss' import { RestMethod } from './RestMethod' import type { Operation, ExampleT } from './types' import { ResponseKeys, CodeSampleKeys } from './types' import { useVersion } from '@/versions/components/useVersion' import { useMainContext } from '@/frame/components/context/MainContext' type Props = { slug: string operation: Operation heading: string } const responseSelectOptions = Object.values(ResponseKeys) // Add as needed. It's pretty cheap to add but please don't use // highlight.js import that loads all and everything. hljs.registerLanguage('json', json) hljs.registerLanguage('javascript', javascript) hljs.registerLanguage('curl', hljsCurl) function getLanguageHighlight(selectedLanguage: string) { return selectedLanguage === CodeSampleKeys.javascript ? 'javascript' : 'curl' } function highlightElement(element: HTMLElement) { element.className = 'hljs' // If the element was already highlighted, remove the dataset property // otherwise the `hljs.highlightElement` function will not highlight. delete element.dataset.highlighted hljs.highlightElement(element) } export function RestCodeSamples({ operation, slug, heading }: Props) { const { t } = useTranslation(['rest_reference']) const { isEnterpriseServer, isEnterpriseCloud } = useVersion() // Refs to track the request example, response example // and the first render const requestCodeExample = useRef(null) const responseCodeExample = useRef(null) const firstRender = useRef(true) const scrollRef = useRef(null) const { currentVersion } = useVersion() const { allVersions } = useMainContext() // Get format examples for each language const languageExamples = operation.codeExamples.map((sample) => ({ description: sample.request.description, curl: getShellExample(operation, sample, currentVersion, allVersions), javascript: getJSExample(operation, sample, currentVersion, allVersions), ghcli: getGHExample(operation, sample, currentVersion, allVersions), response: sample.response, request: sample.request, })) // Menu options for the language selector const languageSelectOptions: CodeSampleKeys[] = [CodeSampleKeys.curl] // Management Console, GHES Manage API, and GitHub Models // operations are not supported by Octokit if ( operation.category !== 'models' && operation.subcategory !== 'management-console' && operation.subcategory !== 'manage-ghes' ) { languageSelectOptions.push(CodeSampleKeys.javascript) // Not all examples support the GH CLI language option. If any of // the examples don't support it, we don't show GH CLI as an option. if (!languageExamples.some((example) => example.ghcli === undefined)) { languageSelectOptions.push(CodeSampleKeys.ghcli) } } // Menu options for the example selector const exampleSelectOptions = generateExampleOptions(languageExamples) const [selectedLanguage, setSelectedLanguage] = useState(languageSelectOptions[0]) const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0]) const [selectedResponse, setSelectedResponse] = useState(responseSelectOptions[0]) const isSingleExample = languageExamples.length === 1 const displayedExample: ExampleT = languageExamples[selectedExample.languageIndex] const handleExampleSelection = (event: FormEvent) => { setSelectedExample(exampleSelectOptions[Number(event.currentTarget.value)]) } const handleResponseSelection = (responseKey: ResponseKeys) => { setSelectedResponse(responseKey) } const handleLanguageSelection = (languageKey: CodeSampleKeys) => { setSelectedLanguage(languageKey) Cookies.set('codeSampleLanguagePreferred', languageKey) } // Change the language based on cookies useEffect(() => { // If the user previously selected a language preference and the language // is available in this component set it as the selected language const cookieValue = Cookies.get('codeSampleLanguagePreferred') const preferredCodeLanguage = languageSelectOptions.find((item) => item === cookieValue) if (cookieValue && preferredCodeLanguage) { setSelectedLanguage(cookieValue as CodeSampleKeys) } }, []) // Handle syntax highlighting when the language changes or // a cookie is set useEffect(() => { const reqElem = requestCodeExample.current // Do not highlight on the first render because the // intersection observer syntax highlighting // (ClientSideHighlightJS) will have already handled highlighting if (reqElem && !firstRender.current) { highlightElement(reqElem) } }, [selectedLanguage]) // Handle syntax highlighting and scroll position when the language changes or // a cookie is set, changing the default language useEffect(() => { const reqElem = responseCodeExample.current const scrollElem = scrollRef.current // Reset scroll position to the top when switching between example response and // response schema if (scrollElem) { scrollElem.scrollTop = 0 } // Do not highlight on the first render because the // intersection observer syntax highlighting // (ClientSideHighlightJS) will have already handled highlighting if (reqElem && !firstRender.current) { highlightElement(reqElem) } }, [selectedResponse]) // Handle highlighting when there is more than one example and // the example changes. useEffect(() => { const reqElem = requestCodeExample.current if (reqElem) { highlightElement(reqElem) } const resElem = responseCodeExample.current if (resElem) { highlightElement(resElem) } }, [selectedExample]) // Keep track of the first render so we can skip highlighting useEffect(() => { if (firstRender.current) { firstRender.current = false } }, []) const [isCopied, setCopied] = useClipboard(displayedExample[selectedLanguage] as string, { successDuration: 1400, }) let displayedExampleResponse = JSON.stringify(displayedExample.response.example, null, 2) let displayedExampleSchema = JSON.stringify(displayedExample.response.schema, null, 2) if (isEnterpriseServer) { displayedExampleResponse = displayedExampleResponse && displayedExampleResponse.replaceAll('api.github.com', 'HOSTNAME') displayedExampleSchema = displayedExampleSchema && displayedExampleSchema.replaceAll('api.github.com', 'HOSTNAME') } return ( <>

{heading}

{isEnterpriseCloud && selectedLanguage === CodeSampleKeys.curl ? (

) : null}

{isSingleExample ? t('request_example') : t('request_examples')}

{/* Display an example selector if more than one example */} {!isSingleExample && (
Select the example type
)} {/* Request example section */}
{languageSelectOptions.map((optionKey) => ( { e.preventDefault() handleLanguageSelection(optionKey) }} onKeyDown={(event: React.KeyboardEvent) => { if (event.key === 'Enter') { handleLanguageSelection(optionKey) } }} > {t(`code_sample_options.${optionKey}`)} ))}
{ setCopied() announce('Copied!') }} >
{/* Example requests */}
{displayedExample[selectedLanguage]}
{/* Response section */}

{displayedExample.response.schema ? ( {responseSelectOptions.map((optionKey) => ( { e.preventDefault() handleResponseSelection(optionKey) }} onKeyDown={(event: React.KeyboardEvent) => { if (event.key === 'Enter') { handleResponseSelection(optionKey) } }} > {t(`response_options.${optionKey}`)} ))} ) : null}
{/* Status code */} {displayedExample.response.statusCode && (
{`Status: ${displayedExample.response.statusCode}`}
)} {/* Example response */} {displayedExample.response.example && (
{selectedResponse === ResponseKeys.example ? displayedExampleResponse : displayedExampleSchema}
)}
) }