or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collaboration.mdcommands-and-editing.mdcursors-and-enhancements.mdhistory.mdindex.mdinput-and-keymaps.mdmarkdown.mdmenus-and-ui.mdmodel-and-schema.mdschema-definitions.mdstate-management.mdtables.mdtransformations.mdview-and-rendering.md

schema-definitions.mddocs/

0

# Schema Definitions

1

2

Pre-built schema definitions provide common node and mark types for rich text editing. The basic schema covers fundamental text formatting, while the list schema adds structured list support.

3

4

## Capabilities

5

6

### Basic Schema

7

8

The basic schema provides essential node and mark types for general text editing.

9

10

```typescript { .api }

11

/**

12

* Complete basic schema with nodes and marks

13

*/

14

const schema: Schema;

15

```

16

17

### Basic Node Types

18

19

Core node types for document structure.

20

21

```typescript { .api }

22

/**

23

* Document root node - contains all other content

24

*/

25

const doc: NodeSpec;

26

27

/**

28

* Paragraph node - basic text container

29

*/

30

const paragraph: NodeSpec;

31

32

/**

33

* Block quote node - indented quoted content

34

*/

35

const blockquote: NodeSpec;

36

37

/**

38

* Horizontal rule node - visual separator

39

*/

40

const horizontal_rule: NodeSpec;

41

42

/**

43

* Heading node with level attribute (1-6)

44

*/

45

const heading: NodeSpec;

46

47

/**

48

* Code block node - preformatted code

49

*/

50

const code_block: NodeSpec;

51

52

/**

53

* Text node - leaf node containing actual text

54

*/

55

const text: NodeSpec;

56

57

/**

58

* Image node - embedded images with src and optional alt/title

59

*/

60

const image: NodeSpec;

61

62

/**

63

* Hard break node - forced line break

64

*/

65

const hard_break: NodeSpec;

66

```

67

68

### Basic Mark Types

69

70

Inline formatting marks for text styling.

71

72

```typescript { .api }

73

/**

74

* Link mark - hyperlinks with href attribute

75

*/

76

const link: MarkSpec;

77

78

/**

79

* Emphasis mark - italic text styling

80

*/

81

const em: MarkSpec;

82

83

/**

84

* Strong mark - bold text styling

85

*/

86

const strong: MarkSpec;

87

88

/**

89

* Code mark - inline code formatting

90

*/

91

const code: MarkSpec;

92

```

93

94

### Node and Mark Collections

95

96

Organized collections of specifications.

97

98

```typescript { .api }

99

/**

100

* All basic node specifications

101

*/

102

const nodes: {

103

doc: NodeSpec;

104

paragraph: NodeSpec;

105

blockquote: NodeSpec;

106

horizontal_rule: NodeSpec;

107

heading: NodeSpec;

108

code_block: NodeSpec;

109

text: NodeSpec;

110

image: NodeSpec;

111

hard_break: NodeSpec;

112

};

113

114

/**

115

* All basic mark specifications

116

*/

117

const marks: {

118

link: MarkSpec;

119

em: MarkSpec;

120

strong: MarkSpec;

121

code: MarkSpec;

122

};

123

```

124

125

### List Schema Extensions

126

127

The list schema provides structured list node types and commands.

128

129

```typescript { .api }

130

/**

131

* Ordered list node specification

132

*/

133

const orderedList: NodeSpec;

134

135

/**

136

* Bullet list node specification

137

*/

138

const bulletList: NodeSpec;

139

140

/**

141

* List item node specification

142

*/

143

const listItem: NodeSpec;

144

```

145

146

### List Integration

147

148

Functions to integrate list nodes into existing schemas.

149

150

```typescript { .api }

151

/**

152

* Add list nodes to an existing node collection

153

*/

154

function addListNodes(

155

nodes: { [name: string]: NodeSpec },

156

itemContent: string,

157

listGroup?: string

158

): { [name: string]: NodeSpec };

159

```

160

161

### List Commands

162

163

Commands for manipulating list structures.

164

165

```typescript { .api }

166

/**

167

* Wrap selection in a list of the given type

168

*/

169

function wrapInList(listType: NodeType, attrs?: Attrs): Command;

170

171

/**

172

* Split the current list item

173

*/

174

function splitListItem(itemType: NodeType): Command;

175

176

/**

177

* Split list item while preserving marks

178

*/

179

function splitListItemKeepMarks(itemType: NodeType): Command;

180

181

/**

182

* Lift the current list item out of its list

183

*/

184

function liftListItem(itemType: NodeType): Command;

185

186

/**

187

* Sink the current list item down into a nested list

188

*/

189

function sinkListItem(itemType: NodeType): Command;

190

```

191

192

**Usage Examples:**

193

194

```typescript

195

import { schema as basicSchema, nodes, marks } from "@tiptap/pm/schema-basic";

196

import {

197

addListNodes,

198

wrapInList,

199

splitListItem,

200

liftListItem,

201

sinkListItem

202

} from "@tiptap/pm/schema-list";

203

import { Schema } from "@tiptap/pm/model";

204

205

// Use basic schema directly

206

const simpleEditor = EditorState.create({

207

schema: basicSchema,

208

doc: basicSchema.nodeFromJSON({

209

type: "doc",

210

content: [{

211

type: "paragraph",

212

content: [{ type: "text", text: "Hello world!" }]

213

}]

214

})

215

});

216

217

// Extend basic schema with lists

218

const extendedNodes = addListNodes(nodes, "paragraph block*", "block");

219

const mySchema = new Schema({

220

nodes: extendedNodes,

221

marks: marks

222

});

223

224

// Create list commands

225

const toggleOrderedList = wrapInList(mySchema.nodes.orderedList);

226

const toggleBulletList = wrapInList(mySchema.nodes.bulletList);

227

const splitItem = splitListItem(mySchema.nodes.listItem);

228

const liftItem = liftListItem(mySchema.nodes.listItem);

229

const sinkItem = sinkListItem(mySchema.nodes.listItem);

230

231

// Use in keymap

232

const listKeymap = keymap({

233

"Mod-Shift-7": toggleOrderedList,

234

"Mod-Shift-8": toggleBulletList,

235

"Enter": splitItem,

236

"Mod-[": liftItem,

237

"Mod-]": sinkItem,

238

"Tab": sinkItem,

239

"Shift-Tab": liftItem

240

});

241

242

// Custom schema with additional nodes

243

const customSchema = new Schema({

244

nodes: {

245

...nodes,

246

// Add custom nodes

247

callout: {

248

content: "block+",

249

group: "block",

250

defining: true,

251

attrs: { type: { default: "info" } },

252

parseDOM: [{

253

tag: "div.callout",

254

getAttrs: (dom) => ({ type: dom.getAttribute("data-type") })

255

}],

256

toDOM: (node) => ["div", {

257

class: "callout",

258

"data-type": node.attrs.type

259

}, 0]

260

},

261

262

// Add list nodes

263

...addListNodes(nodes, "paragraph block*", "block")

264

},

265

266

marks: {

267

...marks,

268

// Add custom marks

269

highlight: {

270

attrs: { color: { default: "yellow" } },

271

parseDOM: [{

272

tag: "mark",

273

getAttrs: (dom) => ({ color: dom.style.backgroundColor })

274

}],

275

toDOM: (mark) => ["mark", {

276

style: `background-color: ${mark.attrs.color}`

277

}, 0]

278

}

279

}

280

});

281

```

282

283

## Advanced Schema Customization

284

285

### Custom Node Definitions

286

287

Extend or modify existing node types for specific needs.

288

289

```typescript

290

// Custom heading with ID support

291

const headingWithId = {

292

...heading,

293

attrs: {

294

level: { default: 1 },

295

id: { default: null }

296

},

297

parseDOM: [

298

...heading.parseDOM.map(rule => ({

299

...rule,

300

getAttrs: (dom) => ({

301

level: +dom.tagName.slice(1),

302

id: dom.id || null

303

})

304

}))

305

],

306

toDOM: (node) => {

307

const attrs = { id: node.attrs.id };

308

return [`h${node.attrs.level}`, attrs, 0];

309

}

310

};

311

312

// Code block with language support

313

const codeBlockWithLang = {

314

...code_block,

315

attrs: {

316

language: { default: null },

317

showLineNumbers: { default: false }

318

},

319

parseDOM: [{

320

tag: "pre",

321

preserveWhitespace: "full",

322

getAttrs: (dom) => ({

323

language: dom.getAttribute("data-language"),

324

showLineNumbers: dom.hasAttribute("data-line-numbers")

325

})

326

}],

327

toDOM: (node) => {

328

const attrs = {};

329

if (node.attrs.language) {

330

attrs["data-language"] = node.attrs.language;

331

}

332

if (node.attrs.showLineNumbers) {

333

attrs["data-line-numbers"] = "";

334

}

335

return ["pre", attrs, ["code", 0]];

336

}

337

};

338

```

339

340

### List Customization

341

342

Customize list behavior and styling.

343

344

```typescript

345

// Custom list with additional attributes

346

const customOrderedList = {

347

...orderedList,

348

attrs: {

349

order: { default: 1 },

350

style: { default: "decimal" }

351

},

352

parseDOM: [{

353

tag: "ol",

354

getAttrs: (dom) => ({

355

order: dom.hasAttribute("start") ? +dom.getAttribute("start") : 1,

356

style: dom.style.listStyleType || "decimal"

357

})

358

}],

359

toDOM: (node) => {

360

const attrs = {};

361

if (node.attrs.order !== 1) {

362

attrs.start = node.attrs.order;

363

}

364

if (node.attrs.style !== "decimal") {

365

attrs.style = `list-style-type: ${node.attrs.style}`;

366

}

367

return ["ol", attrs, 0];

368

}

369

};

370

371

// Task list item extension

372

const taskListItem = {

373

content: "paragraph block*",

374

defining: true,

375

attrs: {

376

checked: { default: false },

377

id: { default: null }

378

},

379

parseDOM: [{

380

tag: "li[data-task-item]",

381

getAttrs: (dom) => ({

382

checked: dom.hasAttribute("data-checked"),

383

id: dom.getAttribute("data-id")

384

})

385

}],

386

toDOM: (node) => {

387

const attrs = { "data-task-item": "" };

388

if (node.attrs.checked) {

389

attrs["data-checked"] = "";

390

}

391

if (node.attrs.id) {

392

attrs["data-id"] = node.attrs.id;

393

}

394

return ["li", attrs, 0];

395

}

396

};

397

```

398

399

### Schema Validation

400

401

Ensure schema consistency and proper configuration.

402

403

```typescript

404

// Validate schema configuration

405

function validateCustomSchema(schemaSpec: SchemaSpec): string[] {

406

const errors: string[] = [];

407

408

// Check required nodes

409

const requiredNodes = ["doc", "paragraph", "text"];

410

for (const node of requiredNodes) {

411

if (!schemaSpec.nodes[node]) {

412

errors.push(`Missing required node: ${node}`);

413

}

414

}

415

416

// Validate content expressions

417

for (const [name, spec] of Object.entries(schemaSpec.nodes)) {

418

if (spec.content && !isValidContentExpression(spec.content)) {

419

errors.push(`Invalid content expression for ${name}: ${spec.content}`);

420

}

421

}

422

423

return errors;

424

}

425

426

// Create schema with validation

427

function createValidatedSchema(schemaSpec: SchemaSpec): Schema {

428

const errors = validateCustomSchema(schemaSpec);

429

if (errors.length > 0) {

430

throw new Error(`Schema validation failed:\n${errors.join("\n")}`);

431

}

432

return new Schema(schemaSpec);

433

}

434

```

435

436

## Types

437

438

```typescript { .api }

439

/**

440

* Schema specification for nodes and marks

441

*/

442

interface SchemaSpec {

443

nodes: { [name: string]: NodeSpec };

444

marks?: { [name: string]: MarkSpec };

445

topNode?: string;

446

}

447

448

/**

449

* List integration options

450

*/

451

interface ListOptions {

452

itemContent?: string;

453

listGroup?: string;

454

}

455

456

/**

457

* Custom node attributes

458

*/

459

interface CustomNodeAttrs {

460

[key: string]: AttributeSpec;

461

}

462

```