or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mdcore-setup.mddirectives-nodes.mdheader-buttons.mdindex.mdpage-enhancement.mdtransforms.md

transforms.mddocs/

0

# Content Transforms

1

2

Post-processing transforms that modify document structure after parsing. The primary transform converts footnotes to margin/side notes for enhanced book-style presentation.

3

4

## Capabilities

5

6

### Footnote to Sidenote Transform

7

8

The main transform class that converts standard footnotes into interactive margin notes.

9

10

```python { .api }

11

class HandleFootnoteTransform(SphinxPostTransform):

12

"""

13

Transform footnotes into side/margin notes for book-style presentation.

14

15

Converts standard reStructuredText footnotes into interactive

16

margin content that appears beside the main text on desktop

17

and below on mobile devices.

18

"""

19

20

default_priority: int = 1 # Transform priority

21

formats: tuple = ("html",) # Only applies to HTML output

22

23

def run(self, **kwargs: Any) -> None:

24

"""

25

Execute the footnote transformation.

26

27

Process:

28

1. Check if sidenotes are enabled in theme options

29

2. Find all footnote references in the document

30

3. Locate corresponding footnote definitions

31

4. Convert footnotes to SideNoteNode instances

32

5. Handle marginnotes (starting with {-}) differently from sidenotes

33

6. Position notes appropriately in document structure

34

7. Remove original footnote definitions

35

36

Parameters:

37

- kwargs: Additional keyword arguments from Sphinx

38

"""

39

```

40

41

### Compatibility Utilities

42

43

Utility functions for cross-version compatibility with docutils.

44

45

```python { .api }

46

def findall(node: Element, *args, **kwargs) -> Iterator[Element]:

47

"""

48

Compatibility wrapper for docutils node traversal.

49

50

Uses findall() in newer docutils versions, falls back to traverse()

51

in older versions.

52

53

Parameters:

54

- node: Document node to search

55

- args: Arguments passed to findall/traverse

56

- kwargs: Keyword arguments passed to findall/traverse

57

58

Returns:

59

Iterator over matching nodes

60

"""

61

```

62

63

## Usage Examples

64

65

### Enabling Sidenote Transform

66

67

```python

68

# In Sphinx conf.py

69

html_theme_options = {

70

"use_sidenotes": True # Enable footnote to sidenote conversion

71

}

72

```

73

74

### Standard Footnote Usage

75

76

In reStructuredText source:

77

78

```rst

79

This is regular text with a footnote reference [#note1]_.

80

81

This is text with a margin note [#margin1]_.

82

83

.. [#note1] This will become a sidenote with a number.

84

85

.. [#margin1] {-} This will become a marginnote without a number.

86

```

87

88

### Transform Registration

89

90

```python

91

from sphinx.application import Sphinx

92

from sphinx_book_theme._transforms import HandleFootnoteTransform

93

94

def setup(app: Sphinx):

95

# Register the post-transform

96

app.add_post_transform(HandleFootnoteTransform)

97

98

return {"parallel_read_safe": True}

99

```

100

101

### Custom Transform Implementation

102

103

```python

104

from sphinx_book_theme._transforms import HandleFootnoteTransform

105

from sphinx_book_theme._compat import findall

106

from docutils import nodes as docutil_nodes

107

108

class CustomFootnoteTransform(HandleFootnoteTransform):

109

"""Custom footnote transform with additional processing."""

110

111

def run(self, **kwargs):

112

# Get theme options

113

theme_options = get_theme_options_dict(self.app)

114

115

if not theme_options.get("use_sidenotes", False):

116

return None

117

118

# Use compatibility function for node traversal

119

for ref_node in findall(self.document, docutil_nodes.footnote_reference):

120

# Custom processing logic here

121

pass

122

```

123

124

### Manual Node Traversal

125

126

```python

127

from sphinx_book_theme._compat import findall

128

from docutils import nodes

129

130

# Find all emphasis nodes in a document

131

for emphasis in findall(document, nodes.emphasis):

132

print(f"Emphasized text: {emphasis.astext()}")

133

134

# Find nodes with specific attributes

135

for node in findall(document, nodes.paragraph):

136

if 'highlight' in node.attributes.get('classes', []):

137

print(f"Highlighted paragraph: {node.astext()}")

138

```

139

140

## Transform Behavior

141

142

### Sidenote Processing

143

144

1. **Detection**: Finds footnote references like `[#note1]_`

145

2. **Matching**: Locates corresponding footnote definitions `.. [#note1] content`

146

3. **Classification**:

147

- Regular sidenotes: Show with number

148

- Marginnotes: Content starts with `{-}`, no number shown

149

4. **Positioning**: Places notes adjacent to references in document structure

150

5. **Nested Handling**: Handles footnotes inside containers (admonitions, etc.)

151

152

### HTML Output Structure

153

154

#### Sidenote Output

155

```html

156

<label for='sidenote-role-1' class='margin-toggle'>

157

<span class='sidenote'>

158

<sup>1</sup>

159

Sidenote content here

160

</span>

161

</label>

162

<input type='checkbox' id='sidenote-role-1' class='margin-toggle'>

163

```

164

165

#### Marginnote Output

166

```html

167

<label for='marginnote-role-1' class='margin-toggle marginnote-label'>

168

</label>

169

<input type='checkbox' id='marginnote-role-1' class='margin-toggle'>

170

```

171

172

### Configuration Options

173

174

```python

175

# Theme options affecting transform behavior

176

html_theme_options = {

177

"use_sidenotes": True, # Enable/disable the transform

178

}

179

```