0
# Event System
1
2
Event listener system for hooking into the conversion process at various stages, enabling custom processing and monitoring.
3
4
## Capabilities
5
6
### listen
7
8
Adds an event listener to a Converter instance.
9
10
```javascript { .api }
11
/**
12
* Listen to an event
13
* @param name - Event name
14
* @param callback - Event callback function
15
* @returns Converter instance for chaining
16
*/
17
converter.listen(name: string, callback: EventCallback): showdown.Converter
18
```
19
20
Where `EventCallback` has the signature:
21
22
```javascript { .api }
23
type EventCallback = (
24
evtName: string,
25
text: string,
26
converter: showdown.Converter,
27
options: ConverterOptions,
28
globals: any
29
) => string | void
30
```
31
32
**Usage Examples:**
33
34
```javascript
35
const converter = new showdown.Converter();
36
37
// Listen to conversion events
38
converter.listen('conversion.start', function(evtName, text, converter, options, globals) {
39
console.log('Starting conversion of', text.length, 'characters');
40
});
41
42
converter.listen('conversion.end', function(evtName, html, converter, options, globals) {
43
console.log('Generated', html.length, 'characters of HTML');
44
});
45
46
// Listen and modify content
47
converter.listen('headers.before', function(evtName, text, converter, options, globals) {
48
// Add custom processing before header parsing
49
return text.replace(/^# (.+)/gm, '# π $1');
50
});
51
52
// Chaining listeners
53
converter
54
.listen('event1', callback1)
55
.listen('event2', callback2);
56
```
57
58
## Event Types
59
60
Events are dispatched at various stages of the conversion process:
61
62
### Conversion Lifecycle Events
63
64
```javascript
65
// Conversion start/end
66
converter.listen('conversion.start', function(evtName, text) {
67
console.log('Conversion started');
68
});
69
70
converter.listen('conversion.end', function(evtName, html) {
71
console.log('Conversion completed');
72
});
73
```
74
75
### Parsing Stage Events
76
77
Events fired during different parsing stages:
78
79
```javascript
80
// Before/after specific parsing stages
81
converter.listen('headers.before', function(evtName, text) {
82
console.log('About to process headers');
83
return text; // Return modified text or undefined to keep original
84
});
85
86
converter.listen('headers.after', function(evtName, text) {
87
console.log('Headers processed');
88
});
89
90
converter.listen('links.before', function(evtName, text) {
91
console.log('About to process links');
92
});
93
94
converter.listen('codeBlocks.before', function(evtName, text) {
95
console.log('About to process code blocks');
96
});
97
```
98
99
## Event Callback Details
100
101
### Parameters
102
103
- **evtName**: The name of the event being fired
104
- **text**: The current text being processed (may be markdown or HTML)
105
- **converter**: The Converter instance firing the event
106
- **options**: Current converter options
107
- **globals**: Internal conversion state and utilities
108
109
### Return Values
110
111
Event callbacks can:
112
- Return `undefined` or nothing: Keep original text unchanged
113
- Return a string: Replace the text with the returned string
114
- Modify globals object: Affect internal conversion state
115
116
```javascript
117
converter.listen('custom.event', function(evtName, text, converter, options, globals) {
118
// Log event details
119
console.log('Event:', evtName);
120
console.log('Text length:', text.length);
121
console.log('Options:', options);
122
123
// Modify text
124
const modifiedText = text.replace(/\[CUSTOM\]/g, '<span class="custom">CUSTOM</span>');
125
126
// Return modified text
127
return modifiedText;
128
});
129
```
130
131
## Custom Event Dispatching
132
133
While primarily used internally, events can be manually dispatched:
134
135
```javascript
136
// Access internal dispatch method (not recommended for external use)
137
const result = converter._dispatch('custom.event', 'some text', options, globals);
138
```
139
140
## Event-Based Extensions
141
142
Events are commonly used in listener-type extensions:
143
144
```javascript
145
showdown.extension('analytics', {
146
type: 'listener',
147
listeners: {
148
'conversion.start': function(evtName, text, converter) {
149
analytics.track('markdown.conversion.start', {
150
textLength: text.length,
151
options: converter.getOptions()
152
});
153
},
154
155
'conversion.end': function(evtName, html, converter) {
156
analytics.track('markdown.conversion.end', {
157
htmlLength: html.length
158
});
159
},
160
161
'headers.before': function(evtName, text) {
162
// Count headers before processing
163
const headerCount = (text.match(/^#+\s/gm) || []).length;
164
analytics.track('markdown.headers.count', { count: headerCount });
165
}
166
}
167
});
168
169
// Use extension
170
const converter = new showdown.Converter({
171
extensions: ['analytics']
172
});
173
```
174
175
## Practical Use Cases
176
177
### Content Monitoring
178
179
```javascript
180
const converter = new showdown.Converter();
181
182
// Monitor conversion performance
183
const stats = { conversions: 0, totalTime: 0 };
184
185
converter.listen('conversion.start', function() {
186
this.startTime = Date.now();
187
});
188
189
converter.listen('conversion.end', function() {
190
const duration = Date.now() - this.startTime;
191
stats.conversions++;
192
stats.totalTime += duration;
193
console.log(`Conversion ${stats.conversions} took ${duration}ms`);
194
});
195
```
196
197
### Content Modification
198
199
```javascript
200
const converter = new showdown.Converter();
201
202
// Add custom processing
203
converter.listen('headers.before', function(evtName, text) {
204
// Add emoji to headers
205
return text.replace(/^(#+)\s*(.+)/gm, '$1 π― $2');
206
});
207
208
converter.listen('links.after', function(evtName, text) {
209
// Add target="_blank" to external links
210
return text.replace(
211
/<a href="(https?:\/\/[^"]+)"/g,
212
'<a href="$1" target="_blank" rel="noopener"'
213
);
214
});
215
```
216
217
### Debugging and Development
218
219
```javascript
220
const converter = new showdown.Converter();
221
222
// Debug event system
223
const debugEvents = ['conversion.start', 'conversion.end', 'headers.before', 'links.before'];
224
225
debugEvents.forEach(eventName => {
226
converter.listen(eventName, function(evtName, text) {
227
console.log(`[DEBUG] Event: ${evtName}, Text length: ${text.length}`);
228
if (text.length < 100) {
229
console.log(`[DEBUG] Text: ${text}`);
230
}
231
});
232
});
233
```
234
235
### Content Validation
236
237
```javascript
238
const converter = new showdown.Converter();
239
240
// Validate content during conversion
241
converter.listen('conversion.start', function(evtName, text) {
242
if (text.length > 100000) {
243
console.warn('Large document detected:', text.length, 'characters');
244
}
245
246
const linkCount = (text.match(/\[([^\]]+)\]\([^)]+\)/g) || []).length;
247
if (linkCount > 50) {
248
console.warn('Document contains many links:', linkCount);
249
}
250
});
251
```
252
253
## Error Handling in Event Callbacks
254
255
```javascript
256
converter.listen('custom.event', function(evtName, text) {
257
try {
258
// Potentially risky operation
259
return processText(text);
260
} catch (error) {
261
console.error('Event callback error:', error);
262
// Return original text on error
263
return text;
264
}
265
});
266
```
267
268
## Multiple Listeners
269
270
Multiple listeners can be registered for the same event:
271
272
```javascript
273
const converter = new showdown.Converter();
274
275
// First listener
276
converter.listen('conversion.start', function() {
277
console.log('Listener 1: Conversion starting');
278
});
279
280
// Second listener
281
converter.listen('conversion.start', function() {
282
console.log('Listener 2: Conversion starting');
283
});
284
285
// Both will be called in registration order
286
```