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, LanguageOptionT } from './types' type Props = { slug: string operation: Operation } const GHCLIKEY = 'ghcli' const JSKEY = 'javascript' const CURLKEY = 'curl' // 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) const responseSelectOptions = [ { key: 'example', text: 'Example response' }, { key: 'schema', text: 'Response schema' }, ] function getLanguageHighlight(selectedLanguage: string) { return selectedLanguage === JSKEY ? 'javascript' : 'curl' } export function RestCodeSamples({ operation, slug }: Props) { const { t } = useTranslation('products') // 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: LanguageOptionT[] = [ { key: CURLKEY, text: 'cURL' }, { key: JSKEY, text: '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({ key: GHCLIKEY, text: 'GitHub CLI' }) } // Menu options for the example selector const exampleSelectOptions = languageExamples.map((example, index) => ({ text: example.description, // maps to the index of the example in the languageExamples array languageIndex: index, })) const [selectedLanguage, setSelectedLanguage] = useState( languageSelectOptions[0].key ) const [selectedExample, setSelectedExample] = useState(exampleSelectOptions[0]) const [selectedResponse, setSelectedResponse] = useState(responseSelectOptions[0].key) 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: string) => { setSelectedResponse(responseKey) } const handleLanguageSelection = (languageKey: keyof ExampleT) => { setSelectedLanguage(languageKey) Cookies.set('codeSampleLanguagePreferred', languageKey, { sameSite: 'strict', secure: true, }) } 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.key === cookieValue) if (cookieValue && preferredCodeLanguage) { setSelectedLanguage(cookieValue as keyof ExampleT) } }, []) // 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, }) return ( <>

{`${t('rest.reference.code_samples')}`}

{/* Display an example selector if more than one example */} {!isSingleExample && (
Select the example type
)} {/* Request example section */}
{languageSelectOptions.map((option) => ( { handleLanguageSelection(option.key) }} href={option.key} selected={option.key === selectedLanguage} className="pr-3 mr-0 keyboard-focus" > {option.text} ))}
{/* Example requests */}
{displayedExample[selectedLanguage]}
{/* Response section */}
{displayedExample.response.schema ? ( {responseSelectOptions.map((option) => { if (!displayedExample.response.schema) return null return ( { handleResponseSelection(option.key) }} href={option.key} selected={option.key === selectedResponse} className="pr-3 mr-0 keyboard-focus ml-2" > {option.text} ) })} ) : null}
{/* Status code */} {displayedExample.response.statusCode && (
{`Status: ${displayedExample.response.statusCode}`}
)} {/* Example response */} {displayedExample.response.example && (
{selectedResponse === 'example' ? JSON.stringify(displayedExample.response.example, null, 2) : JSON.stringify(displayedExample.response.schema, null, 2)}
)}
) }