0
# Extension System
1
2
Plugin architecture for adding custom parsing behavior and output modifications to Showdown.
3
4
## Capabilities
5
6
### extension
7
8
Registers or retrieves extensions globally.
9
10
```javascript { .api }
11
/**
12
* Gets or registers an extension
13
* @param name - Extension name
14
* @param ext - Extension definition, array of extensions, or factory function
15
* @returns Extension array when getting, void when setting
16
*/
17
showdown.extension(name: string, ext?: Extension | Extension[] | Function): Extension[] | void
18
```
19
20
**Usage Examples:**
21
22
```javascript
23
// Register a simple extension
24
showdown.extension('highlight', {
25
type: 'output',
26
regex: /<pre><code\s+class="([^"]+)">([\s\S]*?)<\/code><\/pre>/g,
27
replace: '<pre class="$1"><code>$2</code></pre>'
28
});
29
30
// Register extension from function
31
showdown.extension('custom', function() {
32
return {
33
type: 'lang',
34
filter: function(text) {
35
return text.replace(/\[TODO\]/g, '<span class="todo">TODO</span>');
36
}
37
};
38
});
39
40
// Get registered extension
41
const highlightExt = showdown.extension('highlight');
42
console.log(highlightExt);
43
```
44
45
### getAllExtensions
46
47
Gets all registered extensions.
48
49
```javascript { .api }
50
/**
51
* Gets all extensions registered
52
* @returns Object with extension names as keys and extension arrays as values
53
*/
54
showdown.getAllExtensions(): { [key: string]: Extension[] }
55
```
56
57
**Usage Examples:**
58
59
```javascript
60
// Get all registered extensions
61
const allExtensions = showdown.getAllExtensions();
62
console.log('Available extensions:', Object.keys(allExtensions));
63
64
// Check if specific extension exists
65
const extensions = showdown.getAllExtensions();
66
if ('highlight' in extensions) {
67
console.log('Highlight extension is available');
68
}
69
```
70
71
### removeExtension
72
73
Removes a registered extension.
74
75
```javascript { .api }
76
/**
77
* Remove an extension
78
* @param name - Extension name to remove
79
*/
80
showdown.removeExtension(name: string): void
81
```
82
83
**Usage Examples:**
84
85
```javascript
86
// Register an extension
87
showdown.extension('temp', {
88
type: 'lang',
89
filter: text => text
90
});
91
92
// Remove it
93
showdown.removeExtension('temp');
94
95
// Verify removal
96
const extensions = showdown.getAllExtensions();
97
console.log('temp' in extensions); // false
98
```
99
100
### resetExtensions
101
102
Removes all registered extensions.
103
104
```javascript { .api }
105
/**
106
* Removes all extensions
107
*/
108
showdown.resetExtensions(): void
109
```
110
111
**Usage Examples:**
112
113
```javascript
114
// Register several extensions
115
showdown.extension('ext1', { type: 'lang', filter: text => text });
116
showdown.extension('ext2', { type: 'output', filter: text => text });
117
118
// Clear all extensions
119
showdown.resetExtensions();
120
121
// Verify cleanup
122
const extensions = showdown.getAllExtensions();
123
console.log(Object.keys(extensions).length); // 0
124
```
125
126
### validateExtension
127
128
Validates extension format and structure.
129
130
```javascript { .api }
131
/**
132
* Validate extension format
133
* @param ext - Extension object to validate
134
* @returns True if valid, false otherwise
135
*/
136
showdown.validateExtension(ext: Extension): boolean
137
```
138
139
**Usage Examples:**
140
141
```javascript
142
// Valid extension
143
const validExt = {
144
type: 'lang',
145
filter: function(text) { return text; }
146
};
147
console.log(showdown.validateExtension(validExt)); // true
148
149
// Invalid extension (missing filter/regex)
150
const invalidExt = {
151
type: 'lang'
152
};
153
console.log(showdown.validateExtension(invalidExt)); // false
154
```
155
156
## Extension Types
157
158
### Language Extensions ('lang')
159
160
Modify the Markdown parsing process before HTML generation.
161
162
```javascript { .api }
163
interface LanguageExtension {
164
type: 'lang';
165
filter?: (text: string, converter: showdown.Converter, options: ConverterOptions) => string;
166
regex?: RegExp | string;
167
replace?: string | Function;
168
}
169
```
170
171
**Usage Examples:**
172
173
```javascript
174
// Filter-based language extension
175
showdown.extension('alerts', {
176
type: 'lang',
177
filter: function(text) {
178
return text.replace(/\[!(INFO|WARNING|ERROR)\]\s*(.+)/g, function(match, type, content) {
179
return `<div class="alert alert-${type.toLowerCase()}">${content}</div>`;
180
});
181
}
182
});
183
184
// Regex-based language extension
185
showdown.extension('mentions', {
186
type: 'lang',
187
regex: /@([a-zA-Z0-9_]+)/g,
188
replace: '<a href="/users/$1">@$1</a>'
189
});
190
```
191
192
### Output Extensions ('output')
193
194
Modify the HTML output after Markdown processing.
195
196
```javascript { .api }
197
interface OutputExtension {
198
type: 'output';
199
filter?: (html: string, converter: showdown.Converter, options: ConverterOptions) => string;
200
regex?: RegExp | string;
201
replace?: string | Function;
202
}
203
```
204
205
**Usage Examples:**
206
207
```javascript
208
// Add syntax highlighting classes
209
showdown.extension('syntax-highlight', {
210
type: 'output',
211
regex: /<pre><code\s+class="language-([^"]+)">/g,
212
replace: '<pre><code class="language-$1 hljs">'
213
});
214
215
// Process all links
216
showdown.extension('external-links', {
217
type: 'output',
218
filter: function(html) {
219
return html.replace(/<a href="([^"]*)">/g, function(match, url) {
220
if (url.startsWith('http')) {
221
return `<a href="${url}" target="_blank" rel="noopener">`;
222
}
223
return match;
224
});
225
}
226
});
227
```
228
229
### Listener Extensions ('listener')
230
231
Event-based extensions that respond to conversion events.
232
233
```javascript { .api }
234
interface ListenerExtension {
235
type: 'listener';
236
listeners: {
237
[eventName: string]: (
238
evtName: string,
239
text: string,
240
converter: showdown.Converter,
241
options: ConverterOptions,
242
globals: any
243
) => string | void;
244
};
245
}
246
```
247
248
**Usage Examples:**
249
250
```javascript
251
showdown.extension('stats', {
252
type: 'listener',
253
listeners: {
254
'conversion.start': function(evtName, text, converter) {
255
console.log('Starting conversion of', text.length, 'characters');
256
},
257
'conversion.end': function(evtName, html, converter) {
258
console.log('Generated', html.length, 'characters of HTML');
259
}
260
}
261
});
262
```
263
264
## Extension Loading Patterns
265
266
### File-based Extensions
267
268
```javascript
269
// Load extension from file (Node.js)
270
const myExtension = require('./my-extension');
271
showdown.extension('my-ext', myExtension);
272
273
// Use in converter
274
const converter = new showdown.Converter({
275
extensions: ['my-ext']
276
});
277
```
278
279
### Inline Extensions
280
281
```javascript
282
// Define and register inline
283
showdown.extension('custom', {
284
type: 'lang',
285
regex: /\[\[([^\]]+)\]\]/g,
286
replace: '<span class="wiki-link">$1</span>'
287
});
288
```
289
290
### Factory Function Extensions
291
292
```javascript
293
// Extension factory
294
showdown.extension('configurable', function(options) {
295
options = options || {};
296
const prefix = options.prefix || 'default';
297
298
return {
299
type: 'lang',
300
filter: function(text) {
301
return text.replace(/\[CUSTOM\]/g, `[${prefix}]`);
302
}
303
};
304
});
305
```
306
307
## Using Extensions
308
309
### Global Registration
310
311
```javascript
312
// Register globally
313
showdown.extension('highlight', highlightExtension);
314
315
// All converters can use it
316
const converter1 = new showdown.Converter({ extensions: ['highlight'] });
317
const converter2 = new showdown.Converter({ extensions: ['highlight'] });
318
```
319
320
### Instance-specific Loading
321
322
```javascript
323
// Load extension only for specific converter
324
const converter = new showdown.Converter();
325
converter.addExtension(highlightExtension, 'highlight');
326
```
327
328
### Multiple Extensions
329
330
```javascript
331
// Load multiple extensions
332
const converter = new showdown.Converter({
333
extensions: ['highlight', 'mentions', 'alerts']
334
});
335
336
// Extension order matters - earlier extensions process first
337
```
338
339
## Extension Development
340
341
### Best Practices
342
343
```javascript
344
// Good: Defensive programming
345
showdown.extension('safe-ext', {
346
type: 'lang',
347
filter: function(text, converter, options) {
348
if (!text || typeof text !== 'string') {
349
return text;
350
}
351
352
// Process text safely
353
try {
354
return text.replace(/pattern/g, 'replacement');
355
} catch (error) {
356
console.error('Extension error:', error);
357
return text;
358
}
359
}
360
});
361
362
// Good: Respect options
363
showdown.extension('conditional', {
364
type: 'lang',
365
filter: function(text, converter, options) {
366
if (!options.customFeature) {
367
return text;
368
}
369
return processText(text);
370
}
371
});
372
```
373
374
### Error Handling
375
376
```javascript
377
// Extension with error handling
378
showdown.extension('robust', {
379
type: 'lang',
380
filter: function(text) {
381
try {
382
return complexProcessing(text);
383
} catch (error) {
384
console.warn('Extension failed, returning original text:', error);
385
return text;
386
}
387
}
388
});
389
```
390
391
## SubParser System
392
393
### subParser
394
395
Advanced method for registering or retrieving individual parsing components.
396
397
```javascript { .api }
398
/**
399
* Get or set a subParser
400
* @param name - SubParser name
401
* @param func - SubParser function (optional, for registration)
402
* @returns SubParser function when getting, void when setting
403
*/
404
showdown.subParser(name: string, func?: Function): Function | void
405
```
406
407
**Usage Examples:**
408
409
```javascript
410
// Register a custom subParser
411
showdown.subParser('customBlock', function(text, options, globals) {
412
return text.replace(/\[CUSTOM\]/g, '<div class="custom">Custom Block</div>');
413
});
414
415
// Get existing subParser
416
const headerParser = showdown.subParser('headers');
417
console.log(typeof headerParser); // 'function'
418
419
// Use in extension
420
showdown.extension('enhanced', {
421
type: 'lang',
422
filter: function(text, converter, options) {
423
// Use registered subParser
424
const customParser = showdown.subParser('customBlock');
425
return customParser(text, options, {});
426
}
427
});
428
```
429
430
**Note:** SubParsers are internal parsing components. Modifying them can affect core functionality and should be done with caution.