You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

179 lines
4.9 KiB

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import cn from 'classnames';
import {
ClasserProvider,
SandpackCodeViewer,
SandpackProvider,
SandpackThemeProvider,
} from '@codesandbox/sandpack-react';
import rangeParser from 'parse-numeric-range';
import {CodeBlockLightTheme} from '../Sandpack/Themes';
import styles from './CodeBlock.module.css';
interface InlineHiglight {
step: number;
line: number;
startColumn: number;
endColumn: number;
}
const CodeBlock = React.forwardRef(
(
{
children,
className = 'language-js',
metastring,
noMargin,
noMarkers,
}: {
children: string;
className?: string;
metastring: string;
noMargin?: boolean;
noMarkers?: boolean;
},
ref?: React.Ref<HTMLDivElement>
) => {
const getDecoratedLineInfo = () => {
if (!metastring) {
return [];
}
const linesToHighlight = getHighlightLines(metastring);
const highlightedLineConfig = linesToHighlight.map((line) => {
return {
className: 'bg-github-highlight',
line,
};
});
const inlineHighlightLines = getInlineHighlights(metastring, children);
const inlineHighlightConfig = inlineHighlightLines.map(
(line: InlineHiglight) => ({
...line,
elementAttributes: {'data-step': `${line.step}`},
className: cn(
'code-step bg-opacity-10 relative rounded-md p-1 ml-2',
{
'pl-3 before:content-[attr(data-step)] before:block before:w-4 before:h-4 before:absolute before:top-1 before:-left-2 before:rounded-full before:text-white before:text-center before:text-xs before:leading-4': !noMarkers,
'bg-blue-40 before:bg-blue-40': line.step === 1,
'bg-yellow-40 before:bg-yellow-40': line.step === 2,
'bg-green-40 before:bg-green-40': line.step === 3,
'bg-purple-40 before:bg-purple-40': line.step === 4,
}
),
})
);
return highlightedLineConfig.concat(inlineHighlightConfig);
};
// e.g. "language-js"
const language = className.substring(9);
const filename = '/index.' + language;
const decorators = getDecoratedLineInfo();
return (
<div
className={cn(
'rounded-lg h-full w-full overflow-x-auto flex items-center bg-white shadow-lg',
!noMargin && 'my-8'
)}>
<SandpackProvider
customSetup={{
entry: filename,
files: {
[filename]: {
code: children.trimEnd(),
},
},
}}>
<SandpackThemeProvider theme={CodeBlockLightTheme}>
<ClasserProvider
classes={{
'sp-cm': styles.codeViewer,
}}>
<SandpackCodeViewer
ref={ref}
showLineNumbers={false}
decorators={decorators}
/>
</ClasserProvider>
</SandpackThemeProvider>
</SandpackProvider>
</div>
);
}
);
export default CodeBlock;
/**
*
* @param metastring string provided after the language in a markdown block
* @returns array of lines to highlight
* @example
* ```js {1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active
* ...
* ```
*
* -> The metastring is `{1-3,7} [[1, 1, 20, 33], [2, 4, 4, 8]] App.js active`
*/
function getHighlightLines(metastring: string): number[] {
const HIGHLIGHT_REGEX = /{([\d,-]+)}/;
const parsedMetastring = HIGHLIGHT_REGEX.exec(metastring);
if (!parsedMetastring) {
return [];
}
return rangeParser(parsedMetastring[1]);
}
/**
*
* @param metastring string provided after the language in a markdown block
* @returns InlineHighlight[]
* @example
* ```js {1-3,7} [[1, 1, 'count'], [2, 4, 'setCount']] App.js active
* ...
* ```
*
* -> The metastring is `{1-3,7} [[1, 1, 'count', [2, 4, 'setCount']] App.js active`
*/
function getInlineHighlights(metastring: string, code: string) {
const INLINE_HIGHT_REGEX = /(\[\[.*\]\])/;
const parsedMetastring = INLINE_HIGHT_REGEX.exec(metastring);
if (!parsedMetastring) {
return [];
}
const lines = code.split('\n');
const encodedHiglights = JSON.parse(parsedMetastring[1]);
return encodedHiglights.map(([step, lineNo, substr, fromIndex]: any[]) => {
const line = lines[lineNo - 1];
let index = line.indexOf(substr);
const lastIndex = line.lastIndexOf(substr);
if (index !== lastIndex) {
if (fromIndex === undefined) {
throw Error(
"Found '" +
substr +
"' twice. Specify fromIndex as the fourth value in the tuple."
);
}
index = line.indexOf(substr, fromIndex);
}
if (index === -1) {
throw Error("Could not find: '" + substr + "'");
}
return {
step,
line: lineNo,
startColumn: index,
endColumn: index + substr.length,
};
});
}