CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-showdown

A Markdown to HTML converter written in Javascript

Pending
Overview
Eval results
Files

extension-system.mddocs/

Extension System

Plugin architecture for adding custom parsing behavior and output modifications to Showdown.

Capabilities

extension

Registers or retrieves extensions globally.

/**
 * Gets or registers an extension
 * @param name - Extension name
 * @param ext - Extension definition, array of extensions, or factory function
 * @returns Extension array when getting, void when setting
 */
showdown.extension(name: string, ext?: Extension | Extension[] | Function): Extension[] | void

Usage Examples:

// Register a simple extension
showdown.extension('highlight', {
  type: 'output',
  regex: /<pre><code\s+class="([^"]+)">([\s\S]*?)<\/code><\/pre>/g,
  replace: '<pre class="$1"><code>$2</code></pre>'
});

// Register extension from function
showdown.extension('custom', function() {
  return {
    type: 'lang',
    filter: function(text) {
      return text.replace(/\[TODO\]/g, '<span class="todo">TODO</span>');
    }
  };
});

// Get registered extension
const highlightExt = showdown.extension('highlight');
console.log(highlightExt);

getAllExtensions

Gets all registered extensions.

/**
 * Gets all extensions registered
 * @returns Object with extension names as keys and extension arrays as values
 */
showdown.getAllExtensions(): { [key: string]: Extension[] }

Usage Examples:

// Get all registered extensions
const allExtensions = showdown.getAllExtensions();
console.log('Available extensions:', Object.keys(allExtensions));

// Check if specific extension exists
const extensions = showdown.getAllExtensions();
if ('highlight' in extensions) {
  console.log('Highlight extension is available');
}

removeExtension

Removes a registered extension.

/**
 * Remove an extension
 * @param name - Extension name to remove
 */
showdown.removeExtension(name: string): void

Usage Examples:

// Register an extension
showdown.extension('temp', {
  type: 'lang',
  filter: text => text
});

// Remove it
showdown.removeExtension('temp');

// Verify removal
const extensions = showdown.getAllExtensions();
console.log('temp' in extensions); // false

resetExtensions

Removes all registered extensions.

/**
 * Removes all extensions
 */
showdown.resetExtensions(): void

Usage Examples:

// Register several extensions
showdown.extension('ext1', { type: 'lang', filter: text => text });
showdown.extension('ext2', { type: 'output', filter: text => text });

// Clear all extensions
showdown.resetExtensions();

// Verify cleanup
const extensions = showdown.getAllExtensions();
console.log(Object.keys(extensions).length); // 0

validateExtension

Validates extension format and structure.

/**
 * Validate extension format
 * @param ext - Extension object to validate
 * @returns True if valid, false otherwise
 */
showdown.validateExtension(ext: Extension): boolean

Usage Examples:

// Valid extension
const validExt = {
  type: 'lang',
  filter: function(text) { return text; }
};
console.log(showdown.validateExtension(validExt)); // true

// Invalid extension (missing filter/regex)
const invalidExt = {
  type: 'lang'
};
console.log(showdown.validateExtension(invalidExt)); // false

Extension Types

Language Extensions ('lang')

Modify the Markdown parsing process before HTML generation.

interface LanguageExtension {
  type: 'lang';
  filter?: (text: string, converter: showdown.Converter, options: ConverterOptions) => string;
  regex?: RegExp | string;
  replace?: string | Function;
}

Usage Examples:

// Filter-based language extension
showdown.extension('alerts', {
  type: 'lang',
  filter: function(text) {
    return text.replace(/\[!(INFO|WARNING|ERROR)\]\s*(.+)/g, function(match, type, content) {
      return `<div class="alert alert-${type.toLowerCase()}">${content}</div>`;
    });
  }
});

// Regex-based language extension
showdown.extension('mentions', {
  type: 'lang',
  regex: /@([a-zA-Z0-9_]+)/g,
  replace: '<a href="/users/$1">@$1</a>'
});

Output Extensions ('output')

Modify the HTML output after Markdown processing.

interface OutputExtension {
  type: 'output';
  filter?: (html: string, converter: showdown.Converter, options: ConverterOptions) => string;
  regex?: RegExp | string;
  replace?: string | Function;
}

Usage Examples:

// Add syntax highlighting classes
showdown.extension('syntax-highlight', {
  type: 'output',
  regex: /<pre><code\s+class="language-([^"]+)">/g,
  replace: '<pre><code class="language-$1 hljs">'
});

// Process all links
showdown.extension('external-links', {
  type: 'output',
  filter: function(html) {
    return html.replace(/<a href="([^"]*)">/g, function(match, url) {
      if (url.startsWith('http')) {
        return `<a href="${url}" target="_blank" rel="noopener">`;
      }
      return match;
    });
  }
});

Listener Extensions ('listener')

Event-based extensions that respond to conversion events.

interface ListenerExtension {
  type: 'listener';
  listeners: {
    [eventName: string]: (
      evtName: string,
      text: string,
      converter: showdown.Converter,
      options: ConverterOptions,
      globals: any
    ) => string | void;
  };
}

Usage Examples:

showdown.extension('stats', {
  type: 'listener',
  listeners: {
    'conversion.start': function(evtName, text, converter) {
      console.log('Starting conversion of', text.length, 'characters');
    },
    'conversion.end': function(evtName, html, converter) {
      console.log('Generated', html.length, 'characters of HTML');
    }
  }
});

Extension Loading Patterns

File-based Extensions

// Load extension from file (Node.js)
const myExtension = require('./my-extension');
showdown.extension('my-ext', myExtension);

// Use in converter
const converter = new showdown.Converter({
  extensions: ['my-ext']
});

Inline Extensions

// Define and register inline
showdown.extension('custom', {
  type: 'lang',
  regex: /\[\[([^\]]+)\]\]/g,
  replace: '<span class="wiki-link">$1</span>'
});

Factory Function Extensions

// Extension factory
showdown.extension('configurable', function(options) {
  options = options || {};
  const prefix = options.prefix || 'default';
  
  return {
    type: 'lang',
    filter: function(text) {
      return text.replace(/\[CUSTOM\]/g, `[${prefix}]`);
    }
  };
});

Using Extensions

Global Registration

// Register globally
showdown.extension('highlight', highlightExtension);

// All converters can use it
const converter1 = new showdown.Converter({ extensions: ['highlight'] });
const converter2 = new showdown.Converter({ extensions: ['highlight'] });

Instance-specific Loading

// Load extension only for specific converter
const converter = new showdown.Converter();
converter.addExtension(highlightExtension, 'highlight');

Multiple Extensions

// Load multiple extensions
const converter = new showdown.Converter({
  extensions: ['highlight', 'mentions', 'alerts']
});

// Extension order matters - earlier extensions process first

Extension Development

Best Practices

// Good: Defensive programming
showdown.extension('safe-ext', {
  type: 'lang',
  filter: function(text, converter, options) {
    if (!text || typeof text !== 'string') {
      return text;
    }
    
    // Process text safely
    try {
      return text.replace(/pattern/g, 'replacement');
    } catch (error) {
      console.error('Extension error:', error);
      return text;
    }
  }
});

// Good: Respect options
showdown.extension('conditional', {
  type: 'lang',
  filter: function(text, converter, options) {
    if (!options.customFeature) {
      return text;
    }
    return processText(text);
  }
});

Error Handling

// Extension with error handling
showdown.extension('robust', {
  type: 'lang',
  filter: function(text) {
    try {
      return complexProcessing(text);
    } catch (error) {
      console.warn('Extension failed, returning original text:', error);
      return text;
    }
  }
});

SubParser System

subParser

Advanced method for registering or retrieving individual parsing components.

/**
 * Get or set a subParser
 * @param name - SubParser name
 * @param func - SubParser function (optional, for registration)
 * @returns SubParser function when getting, void when setting
 */
showdown.subParser(name: string, func?: Function): Function | void

Usage Examples:

// Register a custom subParser
showdown.subParser('customBlock', function(text, options, globals) {
  return text.replace(/\[CUSTOM\]/g, '<div class="custom">Custom Block</div>');
});

// Get existing subParser
const headerParser = showdown.subParser('headers');
console.log(typeof headerParser); // 'function'

// Use in extension
showdown.extension('enhanced', {
  type: 'lang',
  filter: function(text, converter, options) {
    // Use registered subParser
    const customParser = showdown.subParser('customBlock');
    return customParser(text, options, {});
  }
});

Note: SubParsers are internal parsing components. Modifying them can affect core functionality and should be done with caution.

Install with Tessl CLI

npx tessl i tessl/npm-showdown

docs

cli.md

core-conversion.md

event-system.md

extension-system.md

flavor-management.md

global-configuration.md

index.md

instance-configuration.md

tile.json