代码注释

您可以通过使用注释处理器来扩展 Pre 组件渲染代码的方式。

例如,这里我们定义了两个组件:一个为代码块添加红色边框,另一个添加深色背景。

my-code.tsx
import type { AnnotationHandler, RawCode } from "codehike/code"
import { Pre, highlight } from "codehike/code"
export async function MyCode({ codeblock: RawCode }) {
const highlighted = await highlight(codeblock, "github-dark")
return <Pre code={highlighted} handlers={[borderHandler, bgHandler]} />
}
const borderHandler: AnnotationHandler = {
name: "border",
Block: ({ annotation, children }) => (
<div style={{ border: "1px solid red" }}>{children}</div>
),
}
const bgHandler: AnnotationHandler = {
name: "bg",
Inline: ({ annotation, children }) => (
<span style={{ background: "#2d26" }}>{children}</span>
),
}

我们可以在代码中使用这些处理器的 name 作为注释来使用组件:

content.md
```js
// !border(1:2)
const lorem = ipsum == null ? 0 : 1
dolor = lorem - sit(dolor)
// !bg[5:16]
let amet = lorem ? consectetur(ipsum) : 3
```
const lorem = ipsum == null ? 0 : 1
dolor = lorem - sit(dolor)
let amet = lorem ? consectetur(ipsum) : 3

注释语法

我们使用注释来标注代码块。注释语法取决于语言。例如,在 JavaScript 中我们使用 // !name(1:5),但在 Python 中我们使用 # !name(1:5)。对于 JSON(不支持注释),建议使用支持注释的 jsoncjson5

在前面的示例中,我们可以看到两种类型的注释:

  • 块注释应用于一块行。它们使用圆括号 () 来定义行的范围。数字是相对于放置注释的行。
  • 内联注释应用于行内的一组标记。它们使用方括号 [] 来定义注释中包含的列范围。

注释查询

注释中的任何额外内容都会作为 query 传递给注释组件。

例如,我们可以修改前面示例中的组件,使用查询来定义边框和背景的颜色:

my-code.tsx
const borderHandler: AnnotationHandler = {
name: "border",
Block: ({ annotation, children }) => {
const borderColor = annotation.query || "red"
return <div style={{ border: "1px solid", borderColor }}>{children}</div>
},
}
const bgHandler: AnnotationHandler = {
name: "bg",
Inline: ({ annotation, children }) => {
const background = annotation.query || "#2d26"
return <span style={{ background }}>{children}</span>
},
}
content.md
```js
// !border(1:2) purple
const lorem = ipsum == null ? 0 : 1
dolor = lorem - sit(dolor)
// !bg[5:16] darkblue
let amet = lorem ? consectetur(ipsum) : 3
```
const lorem = ipsum == null ? 0 : 1
dolor = lorem - sit(dolor)
let amet = lorem ? consectetur(ipsum) : 3

自定义行和标记组件

有时您想要自定义每一行或标记的渲染,而不仅仅是被注释的那些。您可以通过定义 LineToken 组件来实现:

my-code.tsx
import { InnerLine } from "codehike/code"
const myHandler: AnnotationHandler = {
name: "uglyLineNumbers",
Line: (props) => {
const { lineNumber, totalLines, indentation } = props
return (
<div>
{lineNumber} |
<InnerLine merge={props} className="inline-block" />
</div>
)
},
}

什么是 InnerLine?由于同一行可能被多个注释处理器针对,我们需要使组件可组合。因此 InnerLine 会链接并合并来自不同处理器的所有属性。

例如,如果我们有这两个处理器:

my-code.tsx
const bgHandler: AnnotationHandler = {
Line: (props) => (
<InnerLine
merge={props}
className="bg-red-200"
data-line={props.lineNumber}
/>
),
}
const paddingHandler: AnnotationHandler = {
Line: (props) => <InnerLine merge={props} className="px-2" />,
}

最终渲染结果将是:

output.html
<pre>
<div className="bg-red-200 px-2" data-line="1">...</div>
<div className="bg-red-200 px-2" data-line="2">...</div>
<div className="bg-red-200 px-2" data-line="3">...</div>
</pre>

同样,您可以自定义每个标记的渲染,但这种情况要少见得多:

my-code.tsx
import { InnerToken } from "codehike/code"
const myHandler: AnnotationHandler = {
Token: (props) => {
const { value, style, lineNumber } = props
return <InnerToken merge={props} style={{ display: "inline-block" }} />
},
}

自定义 Pre 组件

您还可以自定义 <pre> 元素本身的渲染:

my-code.tsx
import { InnerPre, getPreRef } from "codehike/code"
const myHandler: AnnotationHandler = {
Pre: (props) => (
<InnerPre merge={props} className="rounded border border-blue-100" />
),
// If you need the ref to the pre element, use PreWithRef:
PreWithRef: (props) => {
const ref = getPreRef(props)
doSomethingWithRef(ref)
return <InnerPre merge={props} />
},
}

AnnotatedLine 和 AnnotatedToken

LineToken 类似,您可以定义 AnnotatedLineAnnotatedToken 来自定义作为注释一部分的单个行和标记的渲染。

有时将 LineAnnotatedLine 组件结合使用以避免重复相同的代码是很有用的。

例如,这里我们为 mark 注释内的行添加 data-mark 属性,然后我们有一个添加边框的 Line 组件,但我们只为具有 data-mark 属性的行添加边框颜色:

my-code.tsx
const mark: AnnotationHandler = {
name: "mark",
AnnotatedLine: ({ annotation, ...props }) => (
<InnerLine merge={props} data-mark={true} />
),
Line: (props) => (
<InnerLine
merge={props}
className="px-2 border-l-4 border-transparent data-[mark]:border-blue-400"
/>
),
}
content.md
```js
const lorem = ipsum == null ? 0 : 1
// !mark(1:2)
dolor = lorem - sit(dolor)
let amet = lorem ? consectetur(ipsum) : 3
```
const lorem = ipsum == null ? 0 : 1
dolor = lorem - sit(dolor)
let amet = lorem ? consectetur(ipsum) : 3

转换注释

您还可以在注释传递给组件之前对其进行转换。这对以下情况很有用:

my-code.tsx
const myHandler: AnnotationHandler = {
transform: (annotation: InlineAnnotation) => {
return {
...annotation,
data: { lorem: "ipsum" },
}
},
...
}

使用正则表达式而不是范围

您可以使用正则表达式来匹配注释的内容,而不是使用行和列范围。

您可以将其用于块注释,但更常见的是用于内联注释:

content.md
```js
// !border[/ipsum/] yellow
const lorem = ipsum == null ? ipsum : 1
// !border[/dolor/g] lime
dolor = lorem - sit(dolor)
let amet = dolor ? consectetur(ipsum) : 3
```
```js
// !border[/ipsum/gm] orange
const lorem = ipsum == null ? ipsum : 1
dolor = lorem - sit(dolor)
let amet = dolor ? consectetur(ipsum) : 3
```
const lorem = ipsum == null ? ipsum : 1
dolor = lorem - sit(dolor)
let amet = dolor ? consectetur(ipsum) : 3
const lorem = ipsum == null ? ipsum : 1
dolor = lorem - sit(dolor)
let amet = dolor ? consectetur(ipsum) : 3

正则表达式还支持标志。最常见的两个是 g(全局)和 m(多行)。

  • m:如果您想要在第一行之后继续搜索,请使用它
  • g:如果您想要匹配多个出现,请使用它

您还可以使用捕获组(参见折叠示例):

content.md
```jsx
// !border[/className="(.*?)"/gm] pink
function Foo() {
return (
<div className="bg-red-200 opacity-50">
<span className="border">hey</span>
</div>
)
}
```
function Foo() {
return (
<div className="bg-red-200 opacity-50">
<span className="border">hey</span>
</div>
)
}