CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-showdown

A Markdown to HTML converter written in Javascript

Pending
Overview
Eval results
Files

event-system.mddocs/

Event System

Event listener system for hooking into the conversion process at various stages, enabling custom processing and monitoring.

Capabilities

listen

Adds an event listener to a Converter instance.

/**
 * Listen to an event
 * @param name - Event name
 * @param callback - Event callback function
 * @returns Converter instance for chaining
 */
converter.listen(name: string, callback: EventCallback): showdown.Converter

Where EventCallback has the signature:

type EventCallback = (
  evtName: string,
  text: string,
  converter: showdown.Converter,
  options: ConverterOptions,
  globals: any
) => string | void

Usage Examples:

const converter = new showdown.Converter();

// Listen to conversion events
converter.listen('conversion.start', function(evtName, text, converter, options, globals) {
  console.log('Starting conversion of', text.length, 'characters');
});

converter.listen('conversion.end', function(evtName, html, converter, options, globals) {
  console.log('Generated', html.length, 'characters of HTML');
});

// Listen and modify content
converter.listen('headers.before', function(evtName, text, converter, options, globals) {
  // Add custom processing before header parsing
  return text.replace(/^# (.+)/gm, '# 📝 $1');
});

// Chaining listeners
converter
  .listen('event1', callback1)
  .listen('event2', callback2);

Event Types

Events are dispatched at various stages of the conversion process:

Conversion Lifecycle Events

// Conversion start/end
converter.listen('conversion.start', function(evtName, text) {
  console.log('Conversion started');
});

converter.listen('conversion.end', function(evtName, html) {
  console.log('Conversion completed');
});

Parsing Stage Events

Events fired during different parsing stages:

// Before/after specific parsing stages
converter.listen('headers.before', function(evtName, text) {
  console.log('About to process headers');
  return text; // Return modified text or undefined to keep original
});

converter.listen('headers.after', function(evtName, text) {
  console.log('Headers processed');
});

converter.listen('links.before', function(evtName, text) {
  console.log('About to process links');
});

converter.listen('codeBlocks.before', function(evtName, text) {
  console.log('About to process code blocks');
});

Event Callback Details

Parameters

  • evtName: The name of the event being fired
  • text: The current text being processed (may be markdown or HTML)
  • converter: The Converter instance firing the event
  • options: Current converter options
  • globals: Internal conversion state and utilities

Return Values

Event callbacks can:

  • Return undefined or nothing: Keep original text unchanged
  • Return a string: Replace the text with the returned string
  • Modify globals object: Affect internal conversion state
converter.listen('custom.event', function(evtName, text, converter, options, globals) {
  // Log event details
  console.log('Event:', evtName);
  console.log('Text length:', text.length);
  console.log('Options:', options);
  
  // Modify text
  const modifiedText = text.replace(/\[CUSTOM\]/g, '<span class="custom">CUSTOM</span>');
  
  // Return modified text
  return modifiedText;
});

Custom Event Dispatching

While primarily used internally, events can be manually dispatched:

// Access internal dispatch method (not recommended for external use)
const result = converter._dispatch('custom.event', 'some text', options, globals);

Event-Based Extensions

Events are commonly used in listener-type extensions:

showdown.extension('analytics', {
  type: 'listener',
  listeners: {
    'conversion.start': function(evtName, text, converter) {
      analytics.track('markdown.conversion.start', {
        textLength: text.length,
        options: converter.getOptions()
      });
    },
    
    'conversion.end': function(evtName, html, converter) {
      analytics.track('markdown.conversion.end', {
        htmlLength: html.length
      });
    },
    
    'headers.before': function(evtName, text) {
      // Count headers before processing
      const headerCount = (text.match(/^#+\s/gm) || []).length;
      analytics.track('markdown.headers.count', { count: headerCount });
    }
  }
});

// Use extension
const converter = new showdown.Converter({
  extensions: ['analytics']
});

Practical Use Cases

Content Monitoring

const converter = new showdown.Converter();

// Monitor conversion performance
const stats = { conversions: 0, totalTime: 0 };

converter.listen('conversion.start', function() {
  this.startTime = Date.now();
});

converter.listen('conversion.end', function() {
  const duration = Date.now() - this.startTime;
  stats.conversions++;
  stats.totalTime += duration;
  console.log(`Conversion ${stats.conversions} took ${duration}ms`);
});

Content Modification

const converter = new showdown.Converter();

// Add custom processing
converter.listen('headers.before', function(evtName, text) {
  // Add emoji to headers
  return text.replace(/^(#+)\s*(.+)/gm, '$1 🎯 $2');
});

converter.listen('links.after', function(evtName, text) {
  // Add target="_blank" to external links
  return text.replace(
    /<a href="(https?:\/\/[^"]+)"/g,
    '<a href="$1" target="_blank" rel="noopener"'
  );
});

Debugging and Development

const converter = new showdown.Converter();

// Debug event system
const debugEvents = ['conversion.start', 'conversion.end', 'headers.before', 'links.before'];

debugEvents.forEach(eventName => {
  converter.listen(eventName, function(evtName, text) {
    console.log(`[DEBUG] Event: ${evtName}, Text length: ${text.length}`);
    if (text.length < 100) {
      console.log(`[DEBUG] Text: ${text}`);
    }
  });
});

Content Validation

const converter = new showdown.Converter();

// Validate content during conversion
converter.listen('conversion.start', function(evtName, text) {
  if (text.length > 100000) {
    console.warn('Large document detected:', text.length, 'characters');
  }
  
  const linkCount = (text.match(/\[([^\]]+)\]\([^)]+\)/g) || []).length;
  if (linkCount > 50) {
    console.warn('Document contains many links:', linkCount);
  }
});

Error Handling in Event Callbacks

converter.listen('custom.event', function(evtName, text) {
  try {
    // Potentially risky operation
    return processText(text);
  } catch (error) {
    console.error('Event callback error:', error);
    // Return original text on error
    return text;
  }
});

Multiple Listeners

Multiple listeners can be registered for the same event:

const converter = new showdown.Converter();

// First listener
converter.listen('conversion.start', function() {
  console.log('Listener 1: Conversion starting');
});

// Second listener
converter.listen('conversion.start', function() {
  console.log('Listener 2: Conversion starting');
});

// Both will be called in registration order

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