or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdparser.mdserializer.mdspecifications.mdutilities.md

specifications.mddocs/

0

# Specification System

1

2

The specification system provides interfaces and types for defining custom transformation behavior between markdown and ProseMirror elements. It enables extensible parsing and serialization through match functions and runner implementations.

3

4

## Capabilities

5

6

### Node Schema Extensions

7

8

Extended schema specifications that include both ProseMirror schema information and transformation rules.

9

10

```typescript { .api }

11

/**

12

* Schema spec for node. It is a super set of NodeSpec.

13

*/

14

interface NodeSchema extends NodeSpec {

15

/**

16

* To markdown serializer spec.

17

*/

18

readonly toMarkdown: NodeSerializerSpec;

19

20

/**

21

* Parse markdown serializer spec.

22

*/

23

readonly parseMarkdown: NodeParserSpec;

24

25

/**

26

* The priority of the node, by default it's 50.

27

*/

28

readonly priority?: number;

29

}

30

31

/**

32

* Schema spec for mark. It is a super set of MarkSpec.

33

*/

34

interface MarkSchema extends MarkSpec {

35

/**

36

* To markdown serializer spec.

37

*/

38

readonly toMarkdown: MarkSerializerSpec;

39

40

/**

41

* Parse markdown serializer spec.

42

*/

43

readonly parseMarkdown: MarkParserSpec;

44

}

45

```

46

47

**Usage Examples:**

48

49

```typescript

50

import { NodeSchema, MarkSchema } from "@milkdown/transformer";

51

52

// Define a heading node schema

53

const headingSchema: NodeSchema = {

54

attrs: { level: { default: 1 } },

55

content: "inline*",

56

group: "block",

57

defining: true,

58

59

parseMarkdown: {

60

match: (node) => node.type === 'heading',

61

runner: (state, node, type) => {

62

state

63

.openNode(type, { level: node.depth })

64

.next(node.children)

65

.closeNode();

66

}

67

},

68

69

toMarkdown: {

70

match: (node) => node.type.name === 'heading',

71

runner: (state, node) => {

72

state

73

.openNode('heading', undefined, { depth: node.attrs.level })

74

.next(node.content)

75

.closeNode();

76

}

77

},

78

79

priority: 100

80

};

81

82

// Define an emphasis mark schema

83

const emphasisSchema: MarkSchema = {

84

parseMarkdown: {

85

match: (node) => node.type === 'emphasis',

86

runner: (state, node, type) => {

87

state

88

.openMark(type)

89

.next(node.children)

90

.closeMark(type);

91

}

92

},

93

94

toMarkdown: {

95

match: (mark) => mark.type.name === 'emphasis',

96

runner: (state, mark, node) => {

97

state.withMark(mark, 'emphasis');

98

}

99

}

100

};

101

```

102

103

### Parser Specifications

104

105

Specifications for transforming markdown AST nodes into ProseMirror structures.

106

107

```typescript { .api }

108

/**

109

* The spec for node parser in schema.

110

*/

111

interface NodeParserSpec {

112

/**

113

* The match function to check if the node is the target node.

114

* For example: match: (node) => node.type === 'paragraph'

115

* @param node - Markdown AST node to evaluate

116

* @returns true if this spec should handle the node

117

*/

118

match: (node: MarkdownNode) => boolean;

119

120

/**

121

* The runner function to transform the node into prosemirror node.

122

* Generally, you should call methods in state to add node to state.

123

* @param state - Parser state for building ProseMirror structure

124

* @param node - Markdown AST node to transform

125

* @param proseType - Target ProseMirror node type

126

*/

127

runner: (state: ParserState, node: MarkdownNode, proseType: NodeType) => void;

128

}

129

130

/**

131

* The spec for mark parser in schema.

132

*/

133

interface MarkParserSpec {

134

/**

135

* The match function to check if the node is the target mark.

136

* For example: match: (mark) => mark.type === 'emphasis'

137

* @param node - Markdown AST node to evaluate

138

* @returns true if this spec should handle the node

139

*/

140

match: (node: MarkdownNode) => boolean;

141

142

/**

143

* The runner function to transform the node into prosemirror mark.

144

* Generally, you should call methods in state to add mark to state.

145

* @param state - Parser state for building ProseMirror structure

146

* @param node - Markdown AST node to transform

147

* @param proseType - Target ProseMirror mark type

148

*/

149

runner: (state: ParserState, node: MarkdownNode, proseType: MarkType) => void;

150

}

151

```

152

153

### Serializer Specifications

154

155

Specifications for transforming ProseMirror structures into markdown AST nodes.

156

157

```typescript { .api }

158

/**

159

* The spec for node serializer in schema.

160

*/

161

interface NodeSerializerSpec {

162

/**

163

* The match function to check if the node is the target node.

164

* For example: match: (node) => node.type.name === 'paragraph'

165

* @param node - ProseMirror node to evaluate

166

* @returns true if this spec should handle the node

167

*/

168

match: (node: Node) => boolean;

169

170

/**

171

* The runner function to transform the node into markdown text.

172

* Generally, you should call methods in state to add node to state.

173

* @param state - Serializer state for building markdown AST

174

* @param node - ProseMirror node to transform

175

*/

176

runner: (state: SerializerState, node: Node) => void;

177

}

178

179

/**

180

* The spec for mark serializer in schema.

181

*/

182

interface MarkSerializerSpec {

183

/**

184

* The match function to check if the mark is the target mark.

185

* For example: match: (mark) => mark.type.name === 'emphasis'

186

* @param mark - ProseMirror mark to evaluate

187

* @returns true if this spec should handle the mark

188

*/

189

match: (mark: Mark) => boolean;

190

191

/**

192

* The runner function to transform the mark into markdown text.

193

* Generally, you should call methods in state to add mark to state.

194

* @param state - Serializer state for building markdown AST

195

* @param mark - ProseMirror mark to transform

196

* @param node - Associated ProseMirror node

197

* @returns Optional boolean to prevent further node processing

198

*/

199

runner: (state: SerializerState, mark: Mark, node: Node) => void | boolean;

200

}

201

```

202

203

**Advanced Specification Examples:**

204

205

```typescript

206

// Complex list item parser with nested content

207

const listItemParserSpec: NodeParserSpec = {

208

match: (node) => node.type === 'listItem',

209

runner: (state, node, type) => {

210

state.openNode(type);

211

212

// Handle paragraph content

213

if (node.children) {

214

node.children.forEach(child => {

215

if (child.type === 'paragraph') {

216

state.next(child.children);

217

} else {

218

state.next(child);

219

}

220

});

221

}

222

223

state.closeNode();

224

}

225

};

226

227

// Code block serializer with language support

228

const codeBlockSerializerSpec: NodeSerializerSpec = {

229

match: (node) => node.type.name === 'code_block',

230

runner: (state, node) => {

231

const lang = node.attrs.language || '';

232

state

233

.openNode('code', node.textContent, {

234

lang: lang,

235

meta: node.attrs.meta || null

236

})

237

.closeNode();

238

}

239

};

240

```

241

242

### Remark Integration Types

243

244

Types for integrating with the remark markdown processing ecosystem.

245

246

```typescript { .api }

247

/**

248

* The universal type of a remark plugin.

249

*/

250

interface RemarkPlugin<T = Record<string, unknown>> {

251

plugin: Plugin<[T], Root>;

252

options: T;

253

}

254

255

/**

256

* The type of remark instance.

257

*/

258

type RemarkParser = ReturnType<typeof remark>;

259

260

/**

261

* Raw plugin type for remark integration.

262

*/

263

type RemarkPluginRaw<T> = Plugin<[T], Root>;

264

```

265

266

**Remark Plugin Integration Examples:**

267

268

```typescript

269

// Custom remark plugin for parsing

270

const customParserPlugin: RemarkPlugin<{ strict: boolean }> = {

271

plugin: (options) => (tree, file) => {

272

// Custom transformation logic

273

if (options.strict) {

274

// Apply strict parsing rules

275

}

276

},

277

options: { strict: true }

278

};

279

280

// Using custom plugins with transformer

281

const customRemark = remark()

282

.use(customParserPlugin.plugin, customParserPlugin.options);

283

284

const parser = ParserState.create(schema, customRemark);

285

```