snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
180
apps/docs/components/content/code.tsx
Normal file
180
apps/docs/components/content/code.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
'use client'
|
||||
|
||||
import { assert, getOwnProperty } from '@tldraw/utils'
|
||||
import React, {
|
||||
Fragment,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
createContext,
|
||||
isValidElement,
|
||||
useContext,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { cn } from '@/utils/cn'
|
||||
import { TldrawLink } from '../common/tldraw-link'
|
||||
|
||||
const CodeLinksContext = createContext<Record<string, string>>({})
|
||||
|
||||
export function CodeLinks({
|
||||
children,
|
||||
links,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
links: Record<string, string>
|
||||
}) {
|
||||
return <CodeLinksContext.Provider value={links}>{children}</CodeLinksContext.Provider>
|
||||
}
|
||||
|
||||
const FocusLinesContext = createContext<null | number[]>(null)
|
||||
export function FocusLines({ children, lines }: { children: ReactNode; lines: number[] }) {
|
||||
return <FocusLinesContext.Provider value={lines}>{children}</FocusLinesContext.Provider>
|
||||
}
|
||||
|
||||
const blurredLineClassName = '[&_*]:!opacity-60 [&_*]:!text-white'
|
||||
|
||||
function CodeLink({ children, href }: { children: ReactNode; href: string }) {
|
||||
return (
|
||||
<TldrawLink
|
||||
href={href}
|
||||
className="group-[.not-prose]:underline group-[.not-prose]:hover:no-underline decoration-[#667e8b]"
|
||||
>
|
||||
{children}
|
||||
</TldrawLink>
|
||||
)
|
||||
}
|
||||
|
||||
// does this token look like a JS identifier?
|
||||
function isIdentifier(token: string): boolean {
|
||||
return /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/g.test(token)
|
||||
}
|
||||
|
||||
// split the code into an array of identifiers, and the bits in between them
|
||||
function tokenize(code: string): string[] {
|
||||
const identifierRegex = /[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*/g
|
||||
|
||||
let currentIdx = 0
|
||||
const tokens = []
|
||||
for (const identifierMatch of code.matchAll(identifierRegex)) {
|
||||
const [identifier] = identifierMatch
|
||||
const idx = identifierMatch.index
|
||||
if (idx > currentIdx) {
|
||||
tokens.push(code.slice(currentIdx, idx))
|
||||
}
|
||||
tokens.push(identifier)
|
||||
currentIdx = idx + identifier.length
|
||||
}
|
||||
if (currentIdx < code.length) {
|
||||
tokens.push(code.slice(currentIdx))
|
||||
}
|
||||
|
||||
return tokens
|
||||
}
|
||||
|
||||
// this is for inline code blocks (e.g. `code`)
|
||||
// these don't get highlighted, so the children is just a string
|
||||
function InlineCodeElem({ children, ...props }: React.ComponentProps<'code'>) {
|
||||
const code = children as string
|
||||
assert(typeof code === 'string', 'InlineCodeElem children must be a string')
|
||||
const codeLinks = useContext(CodeLinksContext)
|
||||
return useMemo(
|
||||
() => (
|
||||
<code {...props}>
|
||||
{codeLinks
|
||||
? tokenize(code).map((token, i) => {
|
||||
if (isIdentifier(token)) {
|
||||
const link = getOwnProperty(codeLinks, token)
|
||||
if (link) {
|
||||
return (
|
||||
<CodeLink key={i} href={link}>
|
||||
{token}
|
||||
</CodeLink>
|
||||
)
|
||||
}
|
||||
}
|
||||
return <Fragment key={i}>{token}</Fragment>
|
||||
})
|
||||
: code}
|
||||
</code>
|
||||
),
|
||||
[code, codeLinks, props]
|
||||
)
|
||||
}
|
||||
|
||||
// This is code that has been highlighted by shikijs
|
||||
// Children is an array of spans, each representing a line and containing an array of spans which are tokens
|
||||
// There is sometimes whitespace between the lines
|
||||
function HighlightedCodeElem({ children, ...props }: React.ComponentProps<'code'>) {
|
||||
const codeLinks = useContext(CodeLinksContext)
|
||||
const focusLines = useContext(FocusLinesContext)
|
||||
|
||||
let lineNumber = 0
|
||||
return (
|
||||
<code {...props}>
|
||||
{React.Children.map(children, (line) => {
|
||||
// ignore whitespace
|
||||
if (!line || !isValidElement(line)) return line
|
||||
|
||||
assert(line.type === 'span', 'Invalid code child (not a span)')
|
||||
const lineElement = line as ReactElement<{ className?: string; children?: ReactNode }>
|
||||
assert(lineElement.props.className === 'line', 'Invalid code child (not a line)')
|
||||
|
||||
// apply line lowlights
|
||||
lineNumber++
|
||||
let updatedLine = lineElement
|
||||
if (focusLines && !focusLines?.includes(lineNumber)) {
|
||||
updatedLine = React.cloneElement(lineElement, {
|
||||
...lineElement.props,
|
||||
className: cn(lineElement.props?.className, blurredLineClassName),
|
||||
})
|
||||
}
|
||||
// inject links where needed
|
||||
if (codeLinks) {
|
||||
updatedLine = React.cloneElement(updatedLine, {
|
||||
...updatedLine.props,
|
||||
children: React.Children.map(updatedLine.props.children, (elem) => {
|
||||
if (!isValidElement(elem)) return elem
|
||||
if (elem.type !== 'span') return elem
|
||||
const spanElement = elem as ReactElement<{ children?: ReactNode }>
|
||||
const token = spanElement.props.children
|
||||
if (typeof token !== 'string') return elem
|
||||
const tokens = tokenize(token)
|
||||
return React.cloneElement(spanElement, {
|
||||
...spanElement.props,
|
||||
children: tokens.map((token, i) => {
|
||||
if (!isIdentifier(token)) return token
|
||||
const link = getOwnProperty(codeLinks, token)
|
||||
if (!link) return token
|
||||
return (
|
||||
<CodeLink href={link} key={i}>
|
||||
{token}
|
||||
</CodeLink>
|
||||
)
|
||||
}),
|
||||
})
|
||||
}),
|
||||
})
|
||||
}
|
||||
return updatedLine
|
||||
})}
|
||||
</code>
|
||||
)
|
||||
}
|
||||
|
||||
export function Code(props: React.ComponentProps<'code'>) {
|
||||
if (typeof props.children === 'string') {
|
||||
return <InlineCodeElem {...props} />
|
||||
}
|
||||
if (Array.isArray(props.children) && props.children[0].props?.className === 'line') {
|
||||
return <HighlightedCodeElem {...props} />
|
||||
}
|
||||
if (
|
||||
isValidElement<any>(props.children) &&
|
||||
props.children.type === 'span' &&
|
||||
(props.children.props as { className?: string })?.className === 'line'
|
||||
) {
|
||||
// eslint-disable-next-line react/no-children-prop
|
||||
return <HighlightedCodeElem {...props} children={[props.children]} />
|
||||
}
|
||||
console.warn('Invalid code children', props.children)
|
||||
return <code {...props} />
|
||||
}
|
||||
Reference in New Issue
Block a user