Creating Extensions

Extension

The purpose of an extension is to modify an HTML output and/or add custom functionality to a code snippet. An extension is a just a simple function that takes a Renderer instance. this function is called every time when the apply() or html() method is executed.

export function MyExtension( renderer ) {
// do something
}
JavaScript

To use this extension, you have to register it to the RyuseiLight via the compose():

import { MyExtension } from './MyExtension';
RyuseiLight.compose( { MyExtension } );
JavaScript

Now we're ready to implement a custom extension!

Properties

Before creating functionality, you should know what is available inside the function.

PropertiesDescription
linesAn array that contains lines with tokens
infoAn object that contains the language information
rootA root element to highlight. This can be empty.
optionsA object for options.
eventAn EventBus object.

We can access them through a renderer instance like this:

export function MyExtension( { lines } ) {
console.log( lines );
}
JavaScript

Modifying HTML

The renderer emits several events when generating an HTML string. By listening to them, we can add HTML fragments of classes.

export function MyExtension( { event } ) {
event.on( 'open', ( append, containerClasses ) => {
append( '<div class="my-custom-title">Code Snippet</div>' );
containerClasses.push( 'my-container' );
} );
}
JavaScript

The open event is emitted just before a container element is generated. We can inject a custom HTML by using the append function like the example above.

The containerClasses is an array containing class names for a container. If we push a new class, it will be also rendered.

HTML Events

Here is a list of events and parameters related with HTML generation:

EventTimingParameters
openBefore a container is openedappend, containerClasses
openedAfter a container is openedappend
body:openBefore a body is openedappend, bodyClasses
body:openedAfter a body is openedappend
line:openBefore each line is openedappend, lineClasses, lineIndex
tokenBefore each token is createdtoken, tokenClasses
line:closedAfter each line is closedappend, lineIndex
body:closeBefore a body is closedappend
closeBefore a container is closedappend
closedAfter a container is closedappend

By listening events, we can wrap a container with another div like this:

export function MyExtension( { event } ) {
event.on( 'open', append => {
append( '<div>' );
} );
event.on( 'closed', append => {
append( '</div>' );
} );
}
JavaScript

Extension Events

Some extensions, such as Gutter and Overlay, also emit events for other extensions to change HTML. Actually, the LanguageName extension activates the Overlay extension and inserts HTML by the overlay:topRight event.

Let's take a look of a part of the Overlay extension:

if ( overlay.topRight || options.tools ) {
event.on( 'close', append => {
append( `<div class="${ className } ${ className }--top-right">` );
event.emit( 'overlay:topRight', append );
if ( options.tools ) {
append( `<div class="${ PROJECT_CODE_SHORT }__tools">` );
event.emit( 'overlay:tools', append );
append( `</div>` );
}
append( `</div>` );
} );
}
JavaScript

An extension can interact with other extensions only through options and events. This is how to insert a custom HTML into the overlay element:

export function MyExtension( { event, options } ) {
options.overlay = options.overlay || {};
options.overlay.topRight = true;
event.on( 'overlay:topRight', append => {
append( '<span>Hello!</span>' );
} );
}
JavaScript

You can see all extensions here.

Interacting with Elements

We can only interact with generated elements after they are inserted into the DOM tree by the apply() method. When the method is called, the 'applied' event will be emitted:

event.on( 'applied', root => {
console.log( root );
} );
JavaScript

The root argument is an element where the RyuseiLight is applied. Here is a simple example to add a custom button and listen to the click event:

export function MyExtension( { event } ) {
event.on( 'open', append => {
append( '<button class="my-button">Click Me!</button>' );
} );
event.on( 'applied', root => {
const button = root.querySelector( '.my-button' );
const onClick = () => alert( 'clicked!' );
button.addEventListener( 'click', onClick );
event.on( 'destroy', () => {
button.removeEventListener( 'click', onClick );
} );
} );
}
JavaScript

Note that the 'destroy' is triggered when the RyuseiLight instance is destroyed.