import React from 'react'; import { css, cx } from '@leafygreen-ui/emotion'; import { uiColors } from '@leafygreen-ui/palette'; import { useSyntaxContext } from './SyntaxContext'; interface TokenProps { kind?: string; children: React.ReactNode; } function Token({ kind, children }: TokenProps) { const className = kind ? `lg-highlight-${kind}` : ''; return {children}; } type TreeItem = | null | undefined | string | Array | TokenObject; function isArray(item: any): item is Array { return item != null && item instanceof Array; } function isObject(item: any): item is object { return item != null && typeof item === 'object' && !(item instanceof Array); } function isString(item: any): item is string { return item != null && typeof item === 'string'; } export function processToken(token: TreeItem, key?: number): React.ReactNode { if (token == null) { return null; } if (isString(token)) { return token; } if (isArray(token)) { return token.map(processToken); } if (isObject(token)) { return ( {processToken(token.children)} ); } return token; } const cellStyle = css` border-spacing: 0; padding: 0; vertical-align: top; &:first-of-type { padding-left: 12px; } &:last-of-type { padding-right: 12px; } `; function getHighlightedRowStyle(darkMode: boolean) { let backgroundColor: string, backgroundImage: string, borderColor: string; if (darkMode) { backgroundColor = 'transparent'; backgroundImage = `linear-gradient(90deg, ${uiColors.gray.dark3}, transparent)`; borderColor = uiColors.gray.dark3; } else { backgroundColor = uiColors.yellow.light3; backgroundImage = 'none'; borderColor = uiColors.yellow.light2; } return css` background-color: ${backgroundColor}; background-image: ${backgroundImage}; // Selects all children of a highlighted row, and adds a border top & > td { border-top: 1px solid ${borderColor}; } // Selects following rows after a highlighted row, and adds a border top // We don't add border bottoms here to support consecutive highlighted rows. & + tr > td { border-top: 1px solid ${borderColor}; } // Remove borders between consecutive highlighted rows & + & > td { border-top: 0; } // If the highlighted row is the last child, then we add a border bottom &:last-child > td { border-bottom: 1px solid ${borderColor}; } `; } interface LineTableRowProps { lineNumber?: number; children: React.ReactNode; highlighted?: boolean; darkMode: boolean; } export function LineTableRow({ lineNumber, highlighted, darkMode, children, }: LineTableRowProps) { const numberColor = uiColors.gray[darkMode ? 'dark1' : 'light1']; const highlightedNumberColor = darkMode ? uiColors.gray.light2 : uiColors.yellow.dark2; return ( {lineNumber && ( {lineNumber} )} {children} ); } export function treeToLines( children: Array, ): Array> { const lines: Array> = []; let currentLineIndex = 0; // Create a new line, if no lines exist yet if (lines[currentLineIndex] == null) { lines[currentLineIndex] = []; } children.forEach(child => { if (isString(child)) { // If the current element is a string that includes a line break, we need to handle it differently if (child.includes('\n')) { child.split('').forEach(fragment => { if (fragment === '\n') { // If the fragment is a new line character, we create a new line currentLineIndex++; lines[currentLineIndex] = []; } else { const currentIndexInLine = lines[currentLineIndex].length - 1; if (isString(lines[currentLineIndex][currentIndexInLine])) { // If the last element in the line is a string, we append this string to it lines[currentLineIndex][currentIndexInLine] += fragment; } else { // Otherwise, we push the string fragment on its own lines[currentLineIndex].push(fragment); } } }); } else { // We don't need to do anything special in the case where the string doesn't contain a line break lines[currentLineIndex].push(child); } } // Line breaks aren't a part of token objects, so we can assume those objects go on the current line if (isObject(child)) { lines[currentLineIndex].push(child); } }); return lines; } interface TableContentProps { lines: Array>; } export function TableContent({ lines }: TableContentProps) { const { highlightLines, showLineNumbers, darkMode } = useSyntaxContext(); const trimmedLines = [...lines]; // Strip empty lines from the beginning of code blocks while (trimmedLines[0]?.length === 0) { trimmedLines.shift(); } // Strip empty lines from the end of code blocks while (trimmedLines[trimmedLines.length - 1]?.length === 0) { trimmedLines.pop(); } return ( <> {trimmedLines.map((line, index) => { const currentLineNumber = index + 1; const highlightLine = highlightLines.includes(currentLineNumber); let displayLineNumber; if (showLineNumbers) { displayLineNumber = currentLineNumber; } const processedLine = line?.length ? ( line.map(processToken) ) : ( // We create placeholder content when a line break appears to preserve the line break's height // It needs to be inline-block for the table row to not collapse.
); return ( {processedLine} ); })} ); } const plugin: HighlightPluginEventCallbacks = { 'after:highlight': function (result) { const { rootNode } = result.emitter; result.react = ; }, }; export default plugin;