Skip to content

Commit c05faa6

Browse files
authored
feat: support remark-attributes extension (#104)
1 parent 9926983 commit c05faa6

File tree

13 files changed

+561
-8
lines changed

13 files changed

+561
-8
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@alauda/doom": minor
3+
---
4+
5+
feat: support remark-attributes extension

docs/en/usage/markdown.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
---
22
weight: 3
3-
sourceSHA: 11b29e882a00a517992fb910421ae98617ad987f086fe05f7794e443e5129a82
3+
sourceSHA: cefdfa4ab48a0034a890ff42657bbf4837b19667ee1b8182b8af5e337dbd0f33
44
---
55

66
# Markdown
77

8-
In addition to the standard [gfm](https://github.github.com/gfm) syntax, Doom has some built-in extended Markdown features.
8+
In addition to the standard [gfm](https://github.github.com/gfm) syntax, Doom has built-in some extra Markdown extension features.
99

1010
## Callouts
1111

1212
Source code annotation component
1313

1414
::: note
1515

16-
1. Please use inline code comments according to the actual language, such as `;`, `%`, `#`, `//`, `/** */`, `--`, and `<!-- -->`.
17-
2. If you need to treat it as a code comment, use `[\!code callout]` for escaping.
18-
3. Sometimes, `:::callouts` may display incorrectly due to nested indentation; you can use `<div class="doom-callouts">` or `<Callouts>` component instead.
16+
1. Please use inline code comments according to the actual language, such as `;`, `%`, `#`, `//`, `/** */`, `--`, and `<!-- -->`, etc.
17+
2. If you want to treat them as code comments, please escape with `[\!code callout]`
18+
3. Sometimes, `:::callouts` may display abnormally due to nested indentation; you can use `<div class="doom-callouts">` or the `<Callouts>` component instead
1919

2020
:::
2121

@@ -61,7 +61,7 @@ Memory overhead per virtual machine ≈ (1.002 × requested memory) \
6161

6262
:::
6363

64-
For more source code conversion features, please refer to [Shiki Transformers](https://shiki.style/packages/transformers#transformers).
64+
For more source code transformation features, please refer to [Shiki Transformers](https://shiki.style/packages/transformers#transformers).
6565

6666
## [Mermaid](https://mermaid.js.org)
6767

@@ -85,4 +85,16 @@ graph TD;
8585
C-->D;
8686
```
8787

88-
Combined with [Markdown Preview Mermaid](https://github.com/mjbvz/vscode-markdown-mermaid), you can preview in real-time within VSCode.
88+
With [Markdown Preview Mermaid](https://github.com/mjbvz/vscode-markdown-mermaid), you can preview in real time in VSCode.
89+
90+
## Attribute Extensions {#attributes}
91+
92+
:::warning
93+
Currently only supported in `.md` files, see [related issue](https://github.com/web-infra-dev/rspress/issues/2215#issuecomment-3018371927)
94+
:::
95+
96+
```md
97+
![](/logo.svg){width="100" height="100"}
98+
```
99+
100+
![](/logo.svg){width="100" height="100"}

docs/zh/usage/markdown.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,15 @@ graph TD;
8585
```
8686

8787
配合 [Markdown Preview Mermaid](https://github.com/mjbvz/vscode-markdown-mermaid) 可以在 VSCode 中实时预览。
88+
89+
## 属性扩展 \{#attributes}
90+
91+
:::warning
92+
当前仅支持在 `.md` 文件中使用,参考[相关 issue](https://github.com/web-infra-dev/rspress/issues/2215#issuecomment-3018371927)
93+
:::
94+
95+
```md
96+
![](/logo.svg){width="100" height="100"}
97+
```
98+
99+
![](/logo.svg){width="100" height="100"}

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"eslint-plugin-mdx": "^3.5.0",
8484
"globals": "^16.2.0",
8585
"html-tag-names": "^2.1.0",
86+
"md-attr-parser": "^1.3.0",
8687
"mdast-util-mdx": "^3.0.0",
8788
"mdast-util-mdx-jsx": "^3.2.0",
8889
"mermaid": "^11.6.0",
@@ -108,6 +109,7 @@
108109
"type-fest": "^4.41.0",
109110
"typescript": "^5.8.3",
110111
"typescript-eslint": "^8.34.1",
112+
"unified": "^11.0.5",
111113
"x-fetch": "^0.2.6",
112114
"yaml": "^2.8.0",
113115
"yoctocolors": "^2.1.1"
@@ -120,6 +122,7 @@
120122
"@swc/core": "1.12.2",
121123
"@types/cli-progress": "^3.11.6",
122124
"@types/ejs": "^3.1.5",
125+
"@types/mdast": "^4.0.4",
123126
"@types/node": "^22.15.32",
124127
"@types/picomatch": "^4.0.0",
125128
"@types/react": "^19.1.8",

shim.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,11 @@ declare module 'doom-@permission-functionResourcesMap' {
2121
declare module 'doom-@permission-roleTemplatesMap' {
2222
export default roleTemplatesMap
2323
}
24+
25+
declare module 'md-attr-parser' {
26+
const parseAttrs: (value?: string | null) => {
27+
prop: Record<string, string>
28+
eaten: string
29+
}
30+
export = parseAttrs
31+
}

src/cli/load-config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { difference } from 'es-toolkit'
3131
import { globSync } from 'tinyglobby'
3232
import { cyan } from 'yoctocolors'
3333

34+
import { attributesPlugin } from '../plugins/attributes/index.js'
3435
import {
3536
apiPlugin,
3637
autoSidebarPlugin,
@@ -293,6 +294,7 @@ const getCommonConfig = async ({
293294
apiPlugin({
294295
localBasePath,
295296
}),
297+
attributesPlugin(),
296298
autoSidebarPlugin({ export: export_, ignore }),
297299
autoTocPlugin(),
298300
directivesPlugin(),

src/plugins/attributes/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { Plugin } from '@rspress/core'
2+
3+
import { remarkAttributes } from './remark-attributes/index.js'
4+
5+
export const attributesPlugin = (): Plugin => {
6+
return {
7+
name: 'doom-attributes',
8+
markdown: {
9+
remarkPlugins: [remarkAttributes],
10+
},
11+
}
12+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import parseAttrs from 'md-attr-parser'
2+
import type { Node, Parent, Root } from 'mdast'
3+
import { visit } from 'unist-util-visit'
4+
5+
export interface Attrs extends Node {
6+
type: 'attrs'
7+
value?: string | null
8+
}
9+
10+
declare module 'mdast' {
11+
interface PhrasingContentMap {
12+
attrs: Attrs
13+
}
14+
15+
interface RootContentMap {
16+
attrs: Attrs
17+
}
18+
}
19+
20+
export function attributesTransformer(root: Root): void {
21+
visit(root, 'paragraph', (node, _, parent) => {
22+
if (
23+
'children' in node &&
24+
node.children.length === 1 &&
25+
node.children[0].type === 'attrs'
26+
) {
27+
const children = parent!.children
28+
const index = children.indexOf(node)
29+
children[index] = node.children[0]
30+
}
31+
})
32+
33+
visit(
34+
root,
35+
(node, _, parent) =>
36+
node.type === 'paragraph' && parent?.type === 'listItem',
37+
(node, _, parent) => {
38+
const { children } = node as Parent
39+
40+
const ids = Object.entries(children)
41+
.filter(([, child]) => child.type === 'attrs')
42+
.map(([id, node]) => [Number.parseInt(id, 10), node] as [number, Attrs])
43+
44+
if (ids.length === 0) {
45+
return
46+
}
47+
48+
for (const [index, attrNode] of ids) {
49+
const sibling = children[index - 1]
50+
if (sibling.type === 'text') {
51+
const data = parent!.data
52+
parent!.data = {
53+
...data,
54+
hProperties: {
55+
...data?.hProperties,
56+
...parseAttrs(attrNode.value).prop,
57+
},
58+
}
59+
children.splice(index, 1)
60+
}
61+
}
62+
},
63+
)
64+
65+
visit(root, 'attrs', (node, index, parent) => {
66+
if (index == null || parent == null || parent.children.length <= 1) {
67+
return
68+
}
69+
70+
const { children } = parent
71+
72+
const sibling = children.at(index - 1)
73+
if (!sibling || sibling.type === 'text') {
74+
parent.data = {
75+
...parent.data,
76+
hProperties: {
77+
...parent.data?.hProperties,
78+
...parseAttrs(node.value).prop,
79+
},
80+
}
81+
} else {
82+
sibling.data = {
83+
...sibling.data,
84+
hProperties: {
85+
...sibling.data?.hProperties,
86+
...parseAttrs(node.value).prop,
87+
},
88+
}
89+
}
90+
91+
children.splice(index, 1)
92+
})
93+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { Root } from 'mdast'
2+
import type { Extension as FromMarkdownExtension } from 'mdast-util-from-markdown'
3+
import type { Plugin } from 'unified'
4+
5+
import { attributesTransformer } from './attributes-transformer.js'
6+
import { mdastAttributes } from './mdast-attributes.js'
7+
import { micromarkAttributes } from './micromark-attributes.js'
8+
import type { AttributesExtension } from './types.js'
9+
10+
export interface AttributesData {
11+
micromarkExtensions?: AttributesExtension[]
12+
fromMarkdownExtensions?: FromMarkdownExtension[]
13+
}
14+
15+
export const remarkAttributes: Plugin<[], Root> = function () {
16+
const data = this.data() as AttributesData
17+
18+
function add<K extends keyof AttributesData>(
19+
key: K,
20+
value: NonNullable<AttributesData[K]>[number],
21+
) {
22+
data[key] ||= []
23+
data[key].unshift(value)
24+
}
25+
26+
add('micromarkExtensions', micromarkAttributes())
27+
add('fromMarkdownExtensions', mdastAttributes())
28+
29+
return attributesTransformer
30+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Extension as FromMarkdownExtension } from 'mdast-util-from-markdown'
2+
3+
import type { Attrs } from './attributes-transformer.js'
4+
5+
/**
6+
* Fully-configured extension to add Heading ID nodes to Markdown.
7+
**/
8+
export function mdastAttributes(): FromMarkdownExtension {
9+
return {
10+
enter: {
11+
attrs(token) {
12+
this.enter({ type: 'attrs', value: null }, token)
13+
this.buffer()
14+
},
15+
},
16+
exit: {
17+
attrs(token) {
18+
const attrs = this.resume()
19+
const node = this.stack[this.stack.length - 1] as Attrs
20+
this.exit(token)
21+
node.value = attrs
22+
},
23+
},
24+
}
25+
}

0 commit comments

Comments
 (0)