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 }
To use this extension, you have to register it to the RyuseiLight
via the compose()
:
import { MyExtension } from './MyExtension'; RyuseiLight.compose( { MyExtension } );
Now we're ready to implement a custom extension!
Properties
Before creating functionality, you should know what is available inside the function.
Properties | Description |
---|---|
lines | An array that contains lines with tokens |
info | An object that contains the language information |
root | A root element to highlight. This can be empty. |
options | A object for options. |
event | An EventBus object. |
We can access them through a renderer instance like this:
export function MyExtension( { lines } ) { console.log( lines ); }
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' ); } ); }
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:
Event | Timing | Parameters |
---|---|---|
open | Before a container is opened | append , containerClasses |
opened | After a container is opened | append |
body:open | Before a body is opened | append , bodyClasses |
body:opened | After a body is opened | append |
line:open | Before each line is opened | append , lineClasses , lineIndex |
token | Before each token is created | token , tokenClasses |
line:closed | After each line is closed | append , lineIndex |
body:close | Before a body is closed | append |
close | Before a container is closed | append |
closed | After a container is closed | append |
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>' ); } ); }
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>` ); } ); }
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>' ); } ); }
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 ); } );
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 ); } ); } ); }
Note that the 'destroy'
is triggered when the RyuseiLight instance is destroyed.