or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

data-models.mddocument-system.mdextension-system.mdicon-system.mdindex.mdrenderer-system.mdtranslation-system.mdutility-services.md
tile.json

utility-services.mddocs/

0

# Utility Services

1

2

Supporting interfaces for HTML sanitization, URL resolution, LaTeX typesetting, and Markdown parsing.

3

4

## Capabilities

5

6

### HTML Sanitization

7

8

#### ISanitizer Interface

9

10

Handles HTML sanitization to ensure safe rendering of untrusted content.

11

12

```typescript { .api }

13

/**

14

* An object that handles html sanitization

15

*/

16

interface ISanitizer {

17

/** Whether to replace URLs by HTML anchors */

18

getAutolink?(): boolean;

19

/**

20

* Sanitize an HTML string

21

* @param dirty - The dirty text

22

* @param options - The optional sanitization options

23

* @returns The sanitized string

24

*/

25

sanitize(dirty: string, options?: ISanitizerOptions): string;

26

/** Whether to allow name and id properties */

27

readonly allowNamedProperties?: boolean;

28

}

29

```

30

31

#### ISanitizerOptions Interface

32

33

Configuration options for HTML sanitization.

34

35

```typescript { .api }

36

/**

37

* The options used to sanitize

38

*/

39

interface ISanitizerOptions {

40

/** The allowed tags */

41

allowedTags?: string[];

42

/** The allowed attributes for a given tag */

43

allowedAttributes?: { [key: string]: string[] };

44

/** The allowed style values for a given tag */

45

allowedStyles?: { [key: string]: { [key: string]: RegExp[] } };

46

}

47

```

48

49

**Usage Example:**

50

51

```typescript

52

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

53

54

class CustomRenderer implements IRenderMime.IRenderer {

55

constructor(private options: IRenderMime.IRendererOptions) {}

56

57

async renderModel(model: IRenderMime.IMimeModel): Promise<void> {

58

const htmlContent = model.data['text/html'] as string;

59

60

// Custom sanitization options for this renderer

61

const sanitizeOptions: IRenderMime.ISanitizerOptions = {

62

allowedTags: ['div', 'span', 'p', 'strong', 'em', 'a', 'img'],

63

allowedAttributes: {

64

'a': ['href', 'title'],

65

'img': ['src', 'alt', 'width', 'height'],

66

'div': ['class', 'style']

67

},

68

allowedStyles: {

69

'div': {

70

'color': [/^#[0-9a-f]{6}$/i],

71

'background-color': [/^#[0-9a-f]{6}$/i],

72

'font-size': [/^\d+px$/]

73

}

74

}

75

};

76

77

// Sanitize the HTML

78

const sanitizedHtml = this.options.sanitizer.sanitize(htmlContent, sanitizeOptions);

79

80

// Check if autolink is enabled

81

const hasAutolink = this.options.sanitizer.getAutolink?.() ?? false;

82

if (hasAutolink) {

83

console.log('URLs will be automatically converted to links');

84

}

85

86

this.node.innerHTML = sanitizedHtml;

87

}

88

}

89

```

90

91

### URL Resolution

92

93

#### IResolver Interface

94

95

Resolves relative URLs and handles file path resolution within JupyterLab.

96

97

```typescript { .api }

98

/**

99

* An object that resolves relative URLs

100

*/

101

interface IResolver {

102

/** Resolve a relative url to an absolute url path */

103

resolveUrl(url: string): Promise<string>;

104

/**

105

* Get the download url for a given absolute url path.

106

* This URL may include a query parameter.

107

*/

108

getDownloadUrl(url: string): Promise<string>;

109

/**

110

* Whether the URL should be handled by the resolver or not.

111

* This is similar to the `isLocal` check in `URLExt`,

112

* but can also perform additional checks on whether the

113

* resolver should handle a given URL.

114

* @param allowRoot - Whether the paths starting at Unix-style filesystem root (`/`) are permitted

115

*/

116

isLocal?(url: string, allowRoot?: boolean): boolean;

117

/**

118

* Resolve a path from Jupyter kernel to a path:

119

* - relative to `root_dir` (preferably) this is in jupyter-server scope,

120

* - path understood and known by kernel (if such a path exists).

121

* Returns `null` if there is no file matching provided path in neither

122

* kernel nor jupyter-server contents manager.

123

*/

124

resolvePath?(path: string): Promise<IResolvedLocation | null>;

125

}

126

```

127

128

#### IResolvedLocation Interface

129

130

Represents a resolved file location with scope information.

131

132

```typescript { .api }

133

interface IResolvedLocation {

134

/** Location scope */

135

scope: 'kernel' | 'server';

136

/** Resolved path */

137

path: string;

138

}

139

```

140

141

**Usage Example:**

142

143

```typescript

144

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

145

146

class ImageRenderer implements IRenderMime.IRenderer {

147

constructor(private options: IRenderMime.IRendererOptions) {}

148

149

async renderModel(model: IRenderMime.IMimeModel): Promise<void> {

150

const imagePath = model.data['image/path'] as string;

151

const resolver = this.options.resolver;

152

153

if (resolver) {

154

try {

155

// Check if this is a local path we should handle

156

const isLocal = resolver.isLocal?.(imagePath) ?? true;

157

158

if (isLocal) {

159

// Resolve relative path to absolute

160

const absoluteUrl = await resolver.resolveUrl(imagePath);

161

162

// Get download URL for the image

163

const downloadUrl = await resolver.getDownloadUrl(absoluteUrl);

164

165

// Resolve kernel path if needed

166

const resolved = await resolver.resolvePath?.(imagePath);

167

if (resolved) {

168

console.log(`Image resolved to ${resolved.scope}: ${resolved.path}`);

169

}

170

171

// Create image element with resolved URL

172

const img = document.createElement('img');

173

img.src = downloadUrl;

174

img.alt = 'Resolved image';

175

this.node.appendChild(img);

176

}

177

} catch (error) {

178

console.error('Failed to resolve image path:', error);

179

this.node.textContent = `Failed to load image: ${imagePath}`;

180

}

181

}

182

}

183

}

184

```

185

186

### Link Handling

187

188

#### ILinkHandler Interface

189

190

Handles click events on links within rendered content.

191

192

```typescript { .api }

193

/**

194

* An object that handles links on a node

195

*/

196

interface ILinkHandler {

197

/**

198

* Add the link handler to the node

199

* @param node the anchor node for which to handle the link

200

* @param path the path to open when the link is clicked

201

* @param id an optional element id to scroll to when the path is opened

202

*/

203

handleLink(node: HTMLElement, path: string, id?: string): void;

204

/**

205

* Add the path handler to the node

206

* @param node the anchor node for which to handle the link

207

* @param path the path to open when the link is clicked

208

* @param scope the scope to which the path is bound

209

* @param id an optional element id to scroll to when the path is opened

210

*/

211

handlePath?(node: HTMLElement, path: string, scope: 'kernel' | 'server', id?: string): void;

212

}

213

```

214

215

**Usage Example:**

216

217

```typescript

218

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

219

220

class LinkEnabledRenderer implements IRenderMime.IRenderer {

221

constructor(private options: IRenderMime.IRendererOptions) {}

222

223

async renderModel(model: IRenderMime.IMimeModel): Promise<void> {

224

const htmlContent = model.data['text/html'] as string;

225

const sanitized = this.options.sanitizer.sanitize(htmlContent);

226

this.node.innerHTML = sanitized;

227

228

// Setup link handling

229

if (this.options.linkHandler) {

230

this.setupLinkHandling();

231

}

232

}

233

234

private setupLinkHandling(): void {

235

const links = this.node.querySelectorAll('a[href]');

236

237

links.forEach(link => {

238

const href = link.getAttribute('href')!;

239

const linkElement = link as HTMLElement;

240

241

// Handle different types of links

242

if (href.startsWith('#')) {

243

// Fragment link - scroll to element

244

const elementId = href.substring(1);

245

this.options.linkHandler!.handleLink(linkElement, '', elementId);

246

} else if (href.startsWith('/')) {

247

// Absolute path - specify scope

248

this.options.linkHandler!.handlePath?.(linkElement, href, 'server');

249

} else if (!href.startsWith('http')) {

250

// Relative path

251

this.options.linkHandler!.handleLink(linkElement, href);

252

}

253

// External links (http/https) are handled by default browser behavior

254

});

255

}

256

}

257

```

258

259

### LaTeX Typesetting

260

261

#### ILatexTypesetter Interface

262

263

Handles LaTeX mathematical expression rendering.

264

265

```typescript { .api }

266

/**

267

* The interface for a LaTeX typesetter

268

*/

269

interface ILatexTypesetter {

270

/**

271

* Typeset a DOM element.

272

* The typesetting may happen synchronously or asynchronously.

273

* @param element - the DOM element to typeset

274

*/

275

typeset(element: HTMLElement): void;

276

}

277

```

278

279

**Usage Example:**

280

281

```typescript

282

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

283

284

class MathRenderer implements IRenderMime.IRenderer {

285

constructor(private options: IRenderMime.IRendererOptions) {}

286

287

async renderModel(model: IRenderMime.IMimeModel): Promise<void> {

288

const mathContent = model.data['text/latex'] as string;

289

290

// Create container for math content

291

const mathContainer = document.createElement('div');

292

mathContainer.className = 'math-content';

293

mathContainer.textContent = mathContent;

294

295

this.node.appendChild(mathContainer);

296

297

// Typeset LaTeX if typesetter is available

298

if (this.options.latexTypesetter) {

299

this.options.latexTypesetter.typeset(this.node);

300

}

301

}

302

}

303

```

304

305

### Markdown Parsing

306

307

#### IMarkdownParser Interface

308

309

Converts Markdown text to HTML.

310

311

```typescript { .api }

312

/**

313

* The interface for a Markdown parser

314

*/

315

interface IMarkdownParser {

316

/**

317

* Render a markdown source into unsanitized HTML

318

* @param source - The string to render

319

* @returns A promise of the string containing HTML which may require sanitization

320

*/

321

render(source: string): Promise<string>;

322

}

323

```

324

325

**Usage Example:**

326

327

```typescript

328

import { IRenderMime } from "@jupyterlab/rendermime-interfaces";

329

330

class MarkdownRenderer implements IRenderMime.IRenderer {

331

constructor(private options: IRenderMime.IRendererOptions) {}

332

333

async renderModel(model: IRenderMime.IMimeModel): Promise<void> {

334

const markdownSource = model.data['text/markdown'] as string;

335

336

if (this.options.markdownParser) {

337

try {

338

// Parse markdown to HTML

339

const rawHtml = await this.options.markdownParser.render(markdownSource);

340

341

// Sanitize the resulting HTML

342

const sanitizedHtml = this.options.sanitizer.sanitize(rawHtml);

343

344

this.node.innerHTML = sanitizedHtml;

345

346

// Apply LaTeX typesetting if available

347

if (this.options.latexTypesetter) {

348

this.options.latexTypesetter.typeset(this.node);

349

}

350

351

// Setup link handling if available

352

if (this.options.linkHandler) {

353

this.setupLinks();

354

}

355

356

} catch (error) {

357

console.error('Failed to render markdown:', error);

358

this.node.textContent = 'Failed to render markdown content';

359

}

360

} else {

361

// Fallback: display raw markdown

362

const pre = document.createElement('pre');

363

pre.textContent = markdownSource;

364

this.node.appendChild(pre);

365

}

366

}

367

368

private setupLinks(): void {

369

const links = this.node.querySelectorAll('a[href]');

370

links.forEach(link => {

371

const href = link.getAttribute('href')!;

372

this.options.linkHandler!.handleLink(link as HTMLElement, href);

373

});

374

}

375

}

376

```