mirror of
https://github.com/supabase/supabase.git
synced 2026-05-08 07:50:20 +08:00
462 lines
8.2 KiB
TypeScript
462 lines
8.2 KiB
TypeScript
import { type PropsWithChildren } from 'react'
|
|
import { type BundledLanguage, codeToTokens, type ThemedToken } from 'shiki'
|
|
import { createTwoslasher, type ExtraFiles, type NodeHover } from 'twoslash'
|
|
import { cn } from 'ui'
|
|
|
|
import { AnnotatedSpan, CodeCopyButton } from './CodeBlock.client'
|
|
import denoTypes from './types/lib.deno.d.ts.include'
|
|
|
|
const extraFiles: ExtraFiles = { 'deno.d.ts': denoTypes }
|
|
|
|
const twoslasher = createTwoslasher({ extraFiles })
|
|
const TWOSLASHABLE_LANGS: ReadonlyArray<string> = ['js', 'ts', 'javascript', 'typescript']
|
|
|
|
export async function CodeBlock({
|
|
className,
|
|
lang: langSetting,
|
|
lineNumbers = true,
|
|
contents,
|
|
children,
|
|
}: PropsWithChildren<{
|
|
className?: string
|
|
lang?: string
|
|
lineNumbers?: boolean
|
|
contents?: string
|
|
}>) {
|
|
let code = (contents || extractCode(children)).trim()
|
|
const lang = tryToBundledLanguage(langSetting) || extractLang(children)
|
|
|
|
let twoslashed = null as null | Map<number, Map<number, Array<NodeHover>>>
|
|
if (TWOSLASHABLE_LANGS.includes(lang)) {
|
|
try {
|
|
const { code: editedCode, nodes } = twoslasher(code)
|
|
const hoverNodes: Array<NodeHover> = nodes.filter((node) => node.type === 'hover')
|
|
twoslashed = annotationsByLine(hoverNodes)
|
|
code = editedCode
|
|
} catch (_err) {
|
|
// Silently ignore, if imports aren't defined type compilation fails
|
|
// Uncomment lines below to debug in dev
|
|
// console.log('\n==========CODE==========\n')
|
|
// console.log(code)
|
|
// console.error(_err.recommendation)
|
|
}
|
|
}
|
|
|
|
const { tokens } = await codeToTokens(code, {
|
|
lang,
|
|
themes: {
|
|
light: 'vitesse-light',
|
|
dark: 'vitesse-dark',
|
|
},
|
|
})
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'shiki',
|
|
'group',
|
|
'relative',
|
|
'not-prose',
|
|
'w-full overflow-hidden',
|
|
'border border-default rounded-lg',
|
|
'bg-200',
|
|
'text-sm',
|
|
className
|
|
)}
|
|
>
|
|
<pre>
|
|
<code className={lineNumbers ? 'flex' : ''}>
|
|
{lineNumbers && (
|
|
<div className="flex-shrink-0 select-none text-right text-muted bg-control py-6 px-2">
|
|
{tokens.map((_, idx) => (
|
|
<div key={idx} className="w-full">
|
|
{idx + 1}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
<div className={cn('p-6 overflow-x-auto', lineNumbers ? 'flex-grow' : '')}>
|
|
{tokens.map((line, idx) => (
|
|
<CodeLine key={idx} tokens={line} twoslash={twoslashed?.get(idx)} />
|
|
))}
|
|
</div>
|
|
</code>
|
|
</pre>
|
|
<CodeCopyButton
|
|
content={code.trim()}
|
|
className="hidden group-hover:block absolute top-2 right-2"
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function CodeLine({
|
|
tokens: rawTokens,
|
|
twoslash,
|
|
}: {
|
|
tokens: Array<ThemedToken>
|
|
twoslash?: Map<number, Array<NodeHover>>
|
|
}) {
|
|
let offset = 0
|
|
const tokens = rawTokens.map((token) => {
|
|
const newToken = { ...token, offset }
|
|
offset += token.content.length
|
|
return newToken
|
|
})
|
|
|
|
return (
|
|
<span className="block h-5">
|
|
{tokens.map((token) =>
|
|
twoslash?.has(token.offset) ? (
|
|
<AnnotatedSpan
|
|
key={token.offset}
|
|
token={token}
|
|
annotations={twoslash.get(token.offset)!}
|
|
/>
|
|
) : (
|
|
<span key={token.offset} style={token.htmlStyle}>
|
|
{token.content}
|
|
</span>
|
|
)
|
|
)}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
function extractCode(children: React.ReactNode): string {
|
|
if (typeof children === 'string') return children
|
|
const child = Array.isArray(children) ? children[0] : children
|
|
if (!!child && typeof child === 'object' && 'props' in child) {
|
|
const props = child.props
|
|
if (!!props && typeof props === 'object' && 'children' in props) {
|
|
const code = props.children
|
|
if (typeof code === 'string') return code
|
|
}
|
|
}
|
|
return ''
|
|
}
|
|
|
|
function extractLang(children: React.ReactNode): BundledLanguage | null {
|
|
if (typeof children === 'string') return null
|
|
const child = Array.isArray(children) ? children[0] : children
|
|
if (!!child && typeof child === 'object' && 'props' in child) {
|
|
const props = child.props
|
|
if (!!props && typeof props === 'object' && 'className' in props) {
|
|
const className = props.className
|
|
if (typeof className === 'string') {
|
|
const lang = className.split(' ').find((className) => className.startsWith('language-'))
|
|
return lang ? tryToBundledLanguage(lang.replace('language-', '')) : null
|
|
}
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function annotationsByLine(nodes: Array<NodeHover>): Map<number, Map<number, Array<NodeHover>>> {
|
|
const result = new Map()
|
|
nodes.forEach((node) => {
|
|
const line = node.line
|
|
const char = node.character
|
|
if (!result.has(line)) {
|
|
result.set(line, new Map())
|
|
}
|
|
if (!result.get(line).has(char)) {
|
|
result.get(line).set(char, [])
|
|
}
|
|
result.get(line).get(char).push(node)
|
|
})
|
|
return result
|
|
}
|
|
|
|
function tryToBundledLanguage(lang: string): BundledLanguage | null {
|
|
if (BUNDLED_LANGUAGES.includes(lang)) {
|
|
return lang as BundledLanguage
|
|
}
|
|
return null
|
|
}
|
|
|
|
const BUNDLED_LANGUAGES = [
|
|
'abap',
|
|
'actionscript-3',
|
|
'ada',
|
|
'adoc',
|
|
'angular-html',
|
|
'angular-ts',
|
|
'apache',
|
|
'apex',
|
|
'apl',
|
|
'applescript',
|
|
'ara',
|
|
'asciidoc',
|
|
'asm',
|
|
'astro',
|
|
'awk',
|
|
'ballerina',
|
|
'bash',
|
|
'bat',
|
|
'batch',
|
|
'be',
|
|
'beancount',
|
|
'berry',
|
|
'bibtex',
|
|
'bicep',
|
|
'blade',
|
|
'c',
|
|
'c#',
|
|
'c++',
|
|
'cadence',
|
|
'cdc',
|
|
'clarity',
|
|
'clj',
|
|
'clojure',
|
|
'closure-templates',
|
|
'cmake',
|
|
'cmd',
|
|
'cobol',
|
|
'codeowners',
|
|
'codeql',
|
|
'coffee',
|
|
'coffeescript',
|
|
'common-lisp',
|
|
'console',
|
|
'cpp',
|
|
'cql',
|
|
'crystal',
|
|
'cs',
|
|
'csharp',
|
|
'css',
|
|
'csv',
|
|
'cue',
|
|
'cypher',
|
|
'd',
|
|
'dart',
|
|
'dax',
|
|
'desktop',
|
|
'diff',
|
|
'docker',
|
|
'dockerfile',
|
|
'dream-maker',
|
|
'elisp',
|
|
'elixir',
|
|
'elm',
|
|
'emacs-lisp',
|
|
'erb',
|
|
'erl',
|
|
'erlang',
|
|
'f',
|
|
'f#',
|
|
'f03',
|
|
'f08',
|
|
'f18',
|
|
'f77',
|
|
'f90',
|
|
'f95',
|
|
'fennel',
|
|
'fish',
|
|
'fluent',
|
|
'for',
|
|
'fortran-fixed-form',
|
|
'fortran-free-form',
|
|
'fs',
|
|
'fsharp',
|
|
'fsl',
|
|
'ftl',
|
|
'gdresource',
|
|
'gdscript',
|
|
'gdshader',
|
|
'genie',
|
|
'gherkin',
|
|
'git-commit',
|
|
'git-rebase',
|
|
'gjs',
|
|
'gleam',
|
|
'glimmer-js',
|
|
'glimmer-ts',
|
|
'glsl',
|
|
'gnuplot',
|
|
'go',
|
|
'gql',
|
|
'graphql',
|
|
'groovy',
|
|
'gts',
|
|
'hack',
|
|
'haml',
|
|
'handlebars',
|
|
'haskell',
|
|
'haxe',
|
|
'hbs',
|
|
'hcl',
|
|
'hjson',
|
|
'hlsl',
|
|
'hs',
|
|
'html',
|
|
'html-derivative',
|
|
'http',
|
|
'hxml',
|
|
'hy',
|
|
'imba',
|
|
'ini',
|
|
'jade',
|
|
'java',
|
|
'javascript',
|
|
'jinja',
|
|
'jison',
|
|
'jl',
|
|
'js',
|
|
'json',
|
|
'json5',
|
|
'jsonc',
|
|
'jsonl',
|
|
'jsonnet',
|
|
'jssm',
|
|
'jsx',
|
|
'julia',
|
|
'kotlin',
|
|
'kql',
|
|
'kt',
|
|
'kts',
|
|
'kusto',
|
|
'latex',
|
|
'less',
|
|
'liquid',
|
|
'lisp',
|
|
'log',
|
|
'logo',
|
|
'lua',
|
|
'make',
|
|
'makefile',
|
|
'markdown',
|
|
'marko',
|
|
'matlab',
|
|
'md',
|
|
'mdc',
|
|
'mdx',
|
|
'mediawiki',
|
|
'mermaid',
|
|
'mojo',
|
|
'move',
|
|
'nar',
|
|
'narrat',
|
|
'nextflow',
|
|
'nf',
|
|
'nginx',
|
|
'nim',
|
|
'nix',
|
|
'nu',
|
|
'nushell',
|
|
'objc',
|
|
'objective-c',
|
|
'objective-cpp',
|
|
'ocaml',
|
|
'pascal',
|
|
'perl',
|
|
'perl6',
|
|
'php',
|
|
'plsql',
|
|
'po',
|
|
'postcss',
|
|
'pot',
|
|
'potx',
|
|
'powerquery',
|
|
'powershell',
|
|
'prisma',
|
|
'prolog',
|
|
'properties',
|
|
'proto',
|
|
'ps',
|
|
'ps1',
|
|
'pug',
|
|
'puppet',
|
|
'purescript',
|
|
'py',
|
|
'python',
|
|
'ql',
|
|
'qml',
|
|
'qmldir',
|
|
'qss',
|
|
'r',
|
|
'racket',
|
|
'raku',
|
|
'razor',
|
|
'rb',
|
|
'reg',
|
|
'regex',
|
|
'regexp',
|
|
'rel',
|
|
'riscv',
|
|
'rs',
|
|
'rst',
|
|
'ruby',
|
|
'rust',
|
|
'sas',
|
|
'sass',
|
|
'scala',
|
|
'scheme',
|
|
'scss',
|
|
'sh',
|
|
'shader',
|
|
'shaderlab',
|
|
'shell',
|
|
'shellscript',
|
|
'shellsession',
|
|
'smalltalk',
|
|
'solidity',
|
|
'soy',
|
|
'sparql',
|
|
'spl',
|
|
'splunk',
|
|
'sql',
|
|
'ssh-config',
|
|
'stata',
|
|
'styl',
|
|
'stylus',
|
|
'svelte',
|
|
'swift',
|
|
'system-verilog',
|
|
'systemd',
|
|
'tasl',
|
|
'tcl',
|
|
'terraform',
|
|
'tex',
|
|
'tf',
|
|
'tfvars',
|
|
'toml',
|
|
'ts',
|
|
'tsp',
|
|
'tsv',
|
|
'tsx',
|
|
'turtle',
|
|
'twig',
|
|
'typ',
|
|
'typescript',
|
|
'typespec',
|
|
'typst',
|
|
'v',
|
|
'vala',
|
|
'vb',
|
|
'verilog',
|
|
'vhdl',
|
|
'vim',
|
|
'viml',
|
|
'vimscript',
|
|
'vue',
|
|
'vue-html',
|
|
'vy',
|
|
'vyper',
|
|
'wasm',
|
|
'wenyan',
|
|
'wgsl',
|
|
'wiki',
|
|
'wikitext',
|
|
'wl',
|
|
'wolfram',
|
|
'xml',
|
|
'xsl',
|
|
'yaml',
|
|
'yml',
|
|
'zenscript',
|
|
'zig',
|
|
'zsh',
|
|
'文言',
|
|
]
|