import { useState, useEffect, useRef, FormEvent } from 'react' import { FormControl, Select, Tooltip, UnderlineNav } from '@primer/react' import { CheckIcon, CopyIcon } from '@primer/octicons-react' import Cookies from 'js-cookie' 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 hljsCurl from 'highlightjs-curl' import { useTranslation } from 'components/hooks/useTranslation' import useClipboard from 'components/hooks/useClipboard' import { getShellExample, getGHExample, getJSExample } from 'components/lib/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 'components/hooks/useVersion' 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' } export function RestCodeSamples({ operation, slug, heading }: Props) { const { t } = useTranslation('products') const { isEnterpriseServer } = 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) // Get format examples for each language const languageExamples = operation.codeExamples.map((sample) => ({ description: sample.request.description, curl: getShellExample(operation, sample), javascript: getJSExample(operation, sample), ghcli: getGHExample(operation, sample), response: sample.response, })) // Menu options for the language selector const languageSelectOptions: CodeSampleKeys[] = [CodeSampleKeys.curl] // Management Console operations are not supported by Octokit if (operation.subcategory !== 'management-console') { 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 // We show the media type in the examples menu items for each example if // there's more than one example and if the media types aren't all the same // for the examples (e.g. if all examples have content type `application/json`, // we won't show that information in the menu items). const showExampleOptionMediaType = languageExamples.length > 1 && !languageExamples.every( (example) => example.response.contentType === languageExamples[0].response.contentType ) const exampleSelectOptions = languageExamples.map((example, index) => ({ text: showExampleOptionMediaType ? `${example.description} (${example.response.contentType})` : example.description, // maps to the index of the example in the languageExamples array languageIndex: index, })) const [selectedLanguage, setSelectedLanguage] = useState(languageSelectOptions[0]) const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0]) const [selectedResponse, setSelectedResponse] = useState(responseSelectOptions[0]) const [responseMaxHeight, setResponseMaxHeight] = useState(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, { sameSite: 'strict', secure: document.location.protocol !== 'http:', }) } const handleResponseResize = () => { if (requestCodeExample.current) { const requestCodeHeight = requestCodeExample.current.clientHeight || 0 const { innerHeight: height } = window if (responseCodeExample) { // 520 pixels roughly accounts for the space taken up by the // nav bar, headers, language picker, method section, and response // picker setResponseMaxHeight(height - requestCodeHeight - 520) } } } // 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 higlighting 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) { reqElem.className = 'hljs' hljs.highlightElement(reqElem) handleResponseResize() } }, [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) { reqElem.className = 'hljs' hljs.highlightElement(reqElem) } }, [selectedResponse]) // Handle highlighting when there is more than one example and // the example changes. useEffect(() => { const reqElem = requestCodeExample.current if (reqElem) { reqElem.className = 'hljs' hljs.highlightElement(reqElem) } const resElem = responseCodeExample.current if (resElem) { resElem.className = 'hljs' hljs.highlightElement(resElem) } }, [selectedExample]) // Keep track of the first render so we can skip highlighting useEffect(() => { if (firstRender.current) { firstRender.current = false } }, []) // Handle the resizing of the response section when the window is resized useEffect(() => { handleResponseResize() window.addEventListener('resize', handleResponseResize) return () => { window.removeEventListener('resize', handleResponseResize) } }) 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}

{/* Display an example selector if more than one example */} {!isSingleExample && (
Select the example type
)} {/* Request example section */}
{languageSelectOptions.map((optionKey) => ( { handleLanguageSelection(optionKey) }} selected={optionKey === selectedLanguage} className="pr-3 mr-0" sx={{ cursor: 'pointer', }} > {t(`rest.reference.code_sample_options.${optionKey}`)} ))}
{/* Example requests */}
{displayedExample[selectedLanguage]}
{/* Response section */}
{displayedExample.response.schema ? ( {responseSelectOptions.map((optionKey) => { if (!displayedExample.response.schema) return null return ( { handleResponseSelection(optionKey) }} selected={optionKey === selectedResponse} className="pr-3 mr-0 ml-2" sx={{ cursor: 'pointer', }} > {t(`rest.reference.response_options.${optionKey}`)} ) })} ) : null}
{/* Status code */} {displayedExample.response.statusCode && (
{`Status: ${displayedExample.response.statusCode}`}
)} {/* Example response */} {displayedExample.response.example && (
{selectedResponse === ResponseKeys.example ? displayedExampleResponse : displayedExampleSchema}
)}
) }