@ryusei/light
Version:
<div align="center"> <a href="https://light.ryuseijs.com"> <img alt="RyuseiLight" src="https://light.ryuseijs.com/images/svg/logo.svg" width="70"> </a>
377 lines (303 loc) • 8.61 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TypeScript</title>
<link href="../../../../dist/css/themes/ryuseilight-ryusei.min.css" rel="stylesheet">
</head>
<body>
<h3>Practical Example</h3>
<pre class="ryuseilight">
import { LINE_BREAK } from '../../constants/characters';
import { Options, LanguageInfo, Token, Component } from '../../types';
import { EventBus } from '../../event/EventBus';
import { PROJECT_CODE_SHORT } from '../../constants/project';
import { BODY, CODE, CONTAINER, LINE, ROOT, TOKEN } from '../../constants/classes';
import { forOwn, escapeHtml } from '../../utils';
/**
* Stores all Component functions.
*/
const Components: Record<string, Component> = {};
/**
* The class for highlighting code via provided tokens.
*
* @since 0.0.1
*/
export class Renderer {
/**
* Adds components.
*
* @param components - An object literal with Component functions.
*/
static compose( components: Record<string, Component> ): void {
forOwn( components, ( Component, name ) => {
Components[ name ] = Component;
} );
}
/**
* Holds lines with tokens.
*/
readonly lines = [];
/**
* Holds the language info.
*/
readonly info: LanguageInfo;
/**
* Holds the root element if provided.
*/
readonly root: HTMLElement | undefined;
/**
* Holds options.
*/
readonly options: Options;
/**
* Holds the EventBus instance.
*/
readonly event: EventBus = new EventBus();
/**
* The Renderer constructor.
*
* @param lines - Lines with tokens to render.
* @param info - The language info object.
* @param root - Optional. A root element to highlight.
* @param options - Options.
*/
constructor( lines: Token[][], info: LanguageInfo, root?: HTMLElement, options: Options = {} ) {
this.lines = lines;
this.info = info;
this.root = root;
this.options = options;
this.init();
}
/**
* Initializes the instance.
*/
protected init(): void {
const { lines } = this;
if ( lines.length ) {
const tokens = lines[ lines.length - 1 ];
if ( ! tokens.length || ( tokens.length === 1 && ! tokens[ 0 ][ 1 ].trim() ) ) {
// Removes the last empty line.
lines.pop();
}
}
forOwn( Components, Component => {
Component( this );
} );
this.event.emit( 'mounted' );
}
/**
* Renders lines as HTML.
*
* @param append - A function to add fragments to the HTML string.
*
* @return A rendered HTML string.
*/
protected renderLines( append: ( fragment: string ) => void ): void {
const event = this.event;
const tag = this.options.span ? 'span' : 'code';
for ( let i = 0; i < this.lines.length; i++ ) {
const tokens = this.lines[ i ];
const classes = [ LINE ];
event.emit( 'line:open', append, classes, i );
append( `<div class="${ classes.join( ' ' ) }">` );
if ( tokens.length ) {
for ( let j = 0; j < tokens.length; j++ ) {
const token = tokens[ j ];
const classes = [ `${ TOKEN } ${ PROJECT_CODE_SHORT }__${ token[ 0 ] }` ];
event.emit( 'token', token, classes );
append( `<${ tag } class="${ classes.join( ' ' ) }">${ escapeHtml( token[ 1 ] ) }</${ tag }>` );
}
} else {
append( LINE_BREAK );
}
append( '</div>' );
event.emit( 'line:closed', append, i );
}
}
/**
* Returns all lines and wrapper elements.
*
* @param pre - Whether to wrap elements by `pre` or not.
*
* @return An HTML string.
*/
html( pre: boolean ): string {
const event = this.event;
let html = '';
const append = ( fragment: string ) => { html += fragment };
if ( pre ) {
html += `<pre class="${ ROOT } ${ ROOT }--${ this.info.id }">`;
}
const containerClasses = [ CONTAINER ];
event.emit( 'open', append, containerClasses );
html += `<div class="${ containerClasses.join( ' ' ) }">`;
event.emit( 'opened', append );
const bodyClasses = [ `${ BODY }${ this.options.wrap ? ` ${ BODY }--wrap` : '' }` ];
event.emit( 'body:open', append, bodyClasses );
html += `<div class="${ bodyClasses.join( ' ' ) }">`;
event.emit( 'body:opened', append );
html += `<div class="${ CODE }">`;
this.renderLines( append );
html += `</div>`; // code
event.emit( 'body:close', append );
html += `</div>`; // body
event.emit( 'close', append );
html += `</div>`; // container
event.emit( 'closed', append );
if ( pre ) {
html += `</pre>`;
}
return html;
}
/**
* Destroys the instance.
*/
destroy(): void {
this.event.emit( 'destroy' );
this.event.destroy();
}
}
</pre>
<h3>Comments/Strings</h3>
<pre>
/**
* Multiline comment
* 'Should not be a string'
* "Should not be a string"
*/
/* Multiline comment in a single line */
// Single line comment
// 'Should not be a string'
// "Should not be a string"
'Single quote'
'Single \'quote\' with escape'
'Single 'quote' with single quote'
'Single "quote" with double quote'
'Single `quote` with back quote'
"Double quote"
"Double \"quote\" with escape"
"Double "quote" with double quote"
"Double 'quote' with single quote"
"Double `quote` with back quote"
`Back quote`
'Back \`quote\` with escape'
'Back `quote` with back quote'
'Back 'quote' with single quote'
'Back "quote" with double quote'
'/* Should not be a comment */'
'// Should not be a comment'
"/* Should not be a comment */"
"// Should not be a comment"
`/* Should not be a comment */`
`// Should not be a comment`
</pre>
<h3>RegExp</h3>
<pre>
/^.*?[\n\s]/gmsi
</pre>
<h3>Template Literal</h3>
<pre>
`Multiline
template
literal`
`The result will be ${ ( a + b ) * 3 }`
// Nested template literal
`container ${
isMobile()
// ${ comment }
// `
? 'is-mobile'
: `container--${ page.isFront() ? 'front' : 'page' }`
}`;
</pre>
<h3>Functions/Generics</h3>
<pre>
// Function
function apply<T extends object>( value: T ) {}
// Anonymous function
const a = function <T extends object>( value: T ) {}
// Arrow function
<T extends object> ( value: T ) => {}
// Method
{
apply<T extends object>( value: T ) {}
}
type Assign<T, U> = Omit<T, keyof U> & U;
export function assign<T extends object, U extends object[]>( object: T, ...sources: U ): Assign<T, U> {
const keys: Array<string | symbol> = getKeys( source );
if ( a < 1 && b > d ) {
console.log( a );
}
for ( let i = 0; i < keys.length; i++ ) {
}
}
return object;
}
</pre>
<h3>Typing</h3>
<pre>
declare var process: any;
type Token = [ string, number, ...RegExp[] ];
interface CustomDivElement extends HTMLDivElement {
selectionStart: number,
selectionEnd: number,
setSelection( number, number ): void;
}
namespace Lexer {
export interface Grammar {
main: Tokenizers[];
[ key: string ]: Tokenizers[];
}
}
function isArray<T>( subject: T[] ): subject is T[] {
return Array.isArray( subject );
}
</pre>
<h3>Keywords</h3>
<pre>
declare, keyof, namespace, readonly, type, string,
number, boolean, bigint, symbol, any, never, unknown
</pre>
<h3>Classes</h3>
<pre>
Object.keys( object );
class Component {
constructor() {
}
}
const component = new Component();
</pre>
<h3>Booleans</h3>
<pre>
true, false
</pre>
<h3>Numbers</h3>
<pre>
0 1 1.23 .23 +1.23 -1.23
1e10 1e+10 1e-10 1E10 1E+10 1E-10
1.2e10 1.2e+10 1.2e-10 1.2E10 1.2E+10 1.2E-10
</pre>
<h3>Operators</h3>
<pre>
+ - ~ ! / * % ** < > <= >= == != === !==
<< >> >>> & | ^ && || ?? ?
= *= **= /= %= += -= <<= >>= >>>= &= ^= |= &&= ||= ??= :
</pre>
<h3>Brackets</h3>
<pre>
{} () []
</pre>
<h3>Delimiters</h3>
<pre>
; . ,
;;;; .... ,,,,
</pre>
<script src="../../../../dist/js/ryuseilight.min.js"></script>
<script src="../../../../dist/js/languages/typescript.min.js"></script>
<script>
const ryuseilight = new RyuseiLight();
ryuseilight.apply( 'pre', { language: 'typescript' } );
</script>
</body>
</html>