@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>
1,888 lines (1,629 loc) • 57.1 kB
JavaScript
/*!
* RyuseiLight.js
* Version : 1.2.0
* License : MIT
* Copyright: 2020 Naotoshi Fujita
*/
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
/**
* The line break character.
*
* @private
* @since 0.0.1
*/
var LINE_BREAK = '\n';
var CATEGORY_KEYWORD = 'keyword';
var CATEGORY_CONSTANT = 'constant';
var CATEGORY_COMMENT = 'comment';
var CATEGORY_TAG = 'tag';
var CATEGORY_TAG_CLOSE = 'tag.close';
var CATEGORY_SELECTOR = 'selector';
var CATEGORY_ATRULE = 'atrule';
var CATEGORY_ATTRIBUTE = 'attr';
var CATEGORY_PROPERTY = 'prop';
var CATEGORY_VALUE = 'value';
var CATEGORY_VARIABLE = 'variable';
var CATEGORY_ENTITY = 'entity';
var CATEGORY_CDATA = 'cdata';
var CATEGORY_PROLOG = 'prolog';
var CATEGORY_IDENTIFIER = 'identifier';
var CATEGORY_STRING = 'string';
var CATEGORY_NUMBER = 'number';
var CATEGORY_BOOLEAN = 'boolean';
var CATEGORY_FUNCTION = 'function';
var CATEGORY_CLASS = 'class';
var CATEGORY_DECORATOR = 'decorator';
var CATEGORY_REGEXP = 'regexp';
var CATEGORY_OPERATOR = 'operator';
var CATEGORY_BRACKET = 'bracket';
var CATEGORY_DELIMITER = 'delimiter';
var CATEGORY_SYMBOL = 'symbol';
var CATEGORY_SPACE = 'space';
var CATEGORY_TEXT = 'text'; // Internal use only
var CATEGORY_LINEBREAK = 'lb';
/**
* Checks if the given subject is an object or not.
*
* @param subject - A subject to check.
*
* @return `true` if the subject is an object, or otherwise `false`.
*/
function isObject(subject) {
return subject !== null && typeof subject === 'object';
}
/**
* Checks if the given subject is an array or not.
*
* @param subject - A subject to check.
*
* @return `true` if the subject is an array, or otherwise `false`.
*/
function isArray(subject) {
return Array.isArray(subject);
}
/**
* Checks if the given subject is a string or not.
*
* @param subject - A subject to check.
*
* @return `true` if the subject is a string, or otherwise `false`.
*/
function isString(subject) {
return typeof subject === 'string';
}
/**
* Checks if the given subject is `undefined` or not.
*
* @param subject - A subject to check.
*
* @return `true` if the subject is `undefined`, or otherwise `false`.
*/
function isUndefined(subject) {
return typeof subject === 'undefined';
}
/**
* Checks if the given subject is an HTMLElement instance or not.
*
* @param subject - A subject to check.
*
* @return `true` if the subject is an HTMLElement instance, or otherwise `false`.
*/
function isHTMLElement(subject) {
return subject instanceof HTMLElement;
}
/**
* Push the provided value to an array only if the value is not an array.
*
* @param value - A value to push.
*
* @return An array containing the value, or the value itself if it is already an array.
*/
function toArray(value) {
return isArray(value) ? value : [value];
}
/**
* Adds classes to the element.
*
* @param elm - An element to add classes to.
* @param classes - Classes to add.
*/
function addClass$1(elm, classes) {
toArray(classes).forEach(function (name) {
if (name) {
elm.classList.add(name);
}
});
}
/**
* Appends children to the parent element.
*
* @param parent - A parent element.
* @param children - A child or children to append to the parent.
*/
function append(parent, children) {
children = toArray(children);
for (var i = 0; i < children.length; i++) {
parent.appendChild(children[i]);
}
}
/**
* Iterates over the provided object by own enumerable keys with calling the iteratee function.
*
* @param object - An object to iterate over.
* @param iteratee - An iteratee function that takes the value and key as arguments.
*
* @return A provided object itself.
*/
function forOwn(object, iteratee) {
if (object) {
var keys = Object.keys(object);
for (var i = 0; i < keys.length; i++) {
iteratee(object[keys[i]], keys[i]);
}
}
}
/**
* Assigns all own enumerable properties of all source objects to the provided object.
* `undefined` in source objects will be skipped.
*
* @param object - An object to assign properties to.
* @param sources - Objects to assign properties from.
*
* @return An object assigned properties of the sources to.
*/
function assign(object) {
for (var _len = arguments.length, sources = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
sources[_key - 1] = arguments[_key];
}
sources.forEach(function (source) {
if (isObject(source)) {
forOwn(source, function (value, key) {
if (!isUndefined(source[key])) {
object[key] = source[key];
}
});
}
});
return object;
}
/**
* Sets new attributes to the passed element if the `attrs` is an object literal,
* or gets an attribute value from it if the `attrs` is a string.
*
* @param elm - An element to set or get an attribute.
* @param attrs - An attribute name as a string or new attributes as an object literal.
*/
function attr(elm, attrs) {
if (isString(attrs)) {
return elm.getAttribute(attrs) || '';
}
if (isObject(attrs)) {
forOwn(attrs, function (value, key) {
elm.setAttribute(key, String(value));
});
}
}
/**
* Creates an HTML element.
*
* @param tag - A tag name.
* @param classes - Optional. Classes to add.
* @param parent - Optional. A parent element where the created element is appended.
*/
function create(tag, classes, parent) {
var elm = document.createElement(tag);
if (classes) {
addClass$1(elm, classes);
}
if (parent) {
append(parent, elm);
}
return elm;
}
/**
* Returns an element that matches the provided selector.
*
* @param selector - A selector.
* @param parent - Optional. A parent element to start searching elements from.
*
* @return A found element or `null`.
*/
function query(selector, parent) {
if (parent === void 0) {
parent = document;
}
return parent.querySelector(selector);
}
/**
* Applies inline styles to the provided element by an object literal.
*
* @param elm - An element to apply styles to.
* @param styles - An object literal with styles.
*/
function styles(elm, styles) {
forOwn(styles, function (value, key) {
elm.style[key] = String(value);
});
}
/**
* Returns an open tag with provided classes.
*
* @param classes - Classes.
* @param tag - Optional. A tag name.
*/
function tag(classes, tag) {
return "<" + (tag || 'div') + " class=\"" + classes.join(' ') + "\">";
}
/**
* Sets or gets a text content of the provided node.
*
* @param node - A node to get or set a text.
* @param text - Optional. A text to set.
*/
function text(node, text) {
if (isUndefined(text)) {
return node.textContent;
}
node.textContent = text;
}
/**
* Throws an error if the provided condition is falsy.
*
* @param condition - If falsy, an error is thrown.
* @param message - Optional. A message for the error.
*/
function assert(condition, message) {
if (message === void 0) {
message = '';
}
if (!condition) {
throw new Error(message);
}
}
/**
* The project code name.
*
* @since 0.0.1
*/
var PROJECT_CODE = 'ryuseilight';
/**
* The abbreviated project code.
*
* @since 0.0.1
*/
var PROJECT_CODE_SHORT = 'rl';
/**
* Displays an error message on the console.
*
* @param message - An error message.
*/
function error(message) {
console.error("[" + PROJECT_CODE + "] " + message);
}
/**
* Returns a function that invokes the provided function at most once in the specified duration.
*
* @since 0.0.1
*
* @param callback - A function to throttle.
* @param interval - A throttle duration in milliseconds.
*
* @return A throttled function.
*/
function throttle(callback, interval) {
var timer;
return function () {
if (!timer) {
timer = setTimeout(function () {
callback();
timer = null;
}, interval);
}
};
}
/**
* Finds the provided key from a map and returns its index.
*
* @param map - A map to search in.
* @param key - A key to search for.
*
* @return An index if found, or `-1` otherwise.
*/
function find(map, key) {
for (var i = 0; i < map.length; i++) {
if (map[i][0] === key) {
return i;
}
}
return -1;
}
/**
* Insert entries before the reference entry specified by the `ref`.
* If the reference is not found, a new entry is created.
*
* @param map - A map to insert values to.
* @param ref - A reference key.
* @param entries - entries to insert.
*/
function before(map, ref, entries) {
var index = find(map, ref);
if (index > -1) {
map.splice.apply(map, [index, 0].concat(entries));
} else {
map.push.apply(map, entries);
}
}
/**
* Converts essential HTML special characters to HTML entities.
*
* @param string - A string to escape.
*
* @return An escaped string.
*/
function escapeHtml(string) {
return string.replace(/&/g, '&').replace(/</g, '<');
}
/**
* Checks if the string starts with the `char` or not.
*
* @param string - A string to check.
* @param char - A character.
*
* @return `true` if the string starts with the `char`, or otherwise `false`.
*/
function startsWith(string, _char) {
return string.charAt(0) === _char;
}
/**
* Checks if the RegExp supports the sticky flag or not.
*/
var isStickySupported = !isUndefined(/x/.sticky);
/**
* The class for creating a simple lexer by a Language object.
*
* @since 0.0.1
*/
var Lexer = /*#__PURE__*/function () {
/**
* The Lexer constructor.
*
* @param language - A Language object.
*/
function Lexer(language) {
this.language = language;
this.init(language);
}
/**
* Initializes the language object.
*
* @param language - A Language object to initialize.
*/
var _proto = Lexer.prototype;
_proto.init = function init(language) {
var _this = this;
forOwn(language.grammar, function (tokenizers, key) {
language.grammar[key] = _this.merge(language, tokenizers);
});
forOwn(language.use, this.init.bind(this));
}
/**
* Includes tokenizers required by `#` annotation and flatten them.
*
* @param language - A language object.
* @param tokenizers - Tokenizers.
*
* @return Merged tokenizers.
*/
;
_proto.merge = function merge(language, tokenizers) {
var merged = [];
for (var i = 0; i < tokenizers.length; i++) {
var tokenizer = tokenizers[i];
var _tokenizers$i = tokenizers[i],
category = _tokenizers$i[0],
regexp = _tokenizers$i[1];
if (startsWith(category, '#') && !regexp) {
merged.push.apply(merged, this.merge(language, language.grammar[category.slice(1)]));
} else {
(function () {
var flags = regexp.toString().match(/[gimsy]*$/)[0].replace(/[gy]/g, '');
var source = regexp.source + (isStickySupported ? '' : '|()');
forOwn(language.source, function (replacement, key) {
source = source.replace(new RegExp("%" + key, 'g'), replacement.source);
});
tokenizer[1] = new RegExp(source, (isStickySupported ? 'y' : 'g') + flags);
merged.push(tokenizer);
})();
}
}
return merged;
}
/**
* Parses the text by the provided language and tokenizers.
*
* @param text - A text to tokenize.
* @param language - A Language object.
* @param tokenizers - An array with tokenizers.
* @param state - Optional. The current state name.
*
* @return An index of the text where the handling ends.
*/
;
_proto.parse = function parse(text, language, tokenizers, state) {
var index = 0;
var position = 0;
this.depth++;
main: while (index < text.length && !this.aborted) {
for (var i = 0; i < tokenizers.length; i++) {
var tokenizer = tokenizers[i];
var regexp = tokenizer[1],
action = tokenizer[2];
regexp.lastIndex = index;
var match = regexp.exec(text);
if (!match || !match[0]) {
continue;
}
if (position < index) {
this.push([CATEGORY_TEXT, text.slice(position, index)], language, state);
}
if (action === '@back') {
position = index;
break main;
}
var offset = this.handle(match, language, tokenizer, state);
index += offset || 1;
position = index;
if (action === '@break') {
break main;
}
continue main;
}
index++;
}
if (position < index) {
this.push([CATEGORY_TEXT, text.slice(position)], language, state);
}
this.depth--;
return index;
}
/**
* Pushes the provided token to the lines array.
*
* @param token - A token to push.
* @param language - A Language object.
* @param state - A state name.
*/
;
_proto.push = function push(token, language, state) {
var depth = this.depth;
var category = token[0],
text = token[1];
var start = this.index;
var index = 0;
var from = 0;
while (index > -1 && !this.aborted) {
index = text.indexOf(LINE_BREAK, from);
var line = this.lines[this.index];
var empty = from === index && !line.length;
var code = empty ? LINE_BREAK : text.slice(from, index < 0 ? undefined : index);
var info = {
depth: depth,
language: language.id,
state: state
};
if (code) {
if (category !== CATEGORY_TEXT) {
info.head = index > -1 && !from;
info.tail = index < 0 && !!from;
info.split = index > -1 || !!from;
info.distance = this.index - start;
}
line.push([category === CATEGORY_TEXT && empty ? CATEGORY_LINEBREAK : category, code, info]);
}
if (index > -1) {
this.index++;
this.aborted = this.limit && this.index >= this.limit;
if (!this.aborted) {
from = index + 1;
this.lines[this.index] = [];
}
}
}
}
/**
* Handles the matched text.
*
* @param match - A matched result.
* @param language - A Language object.
* @param tokenizer - A tokenizer that has been matched with the text.
* @param state - A state name.
*
* @return An index of the text where the handling ends.
*/
;
_proto.handle = function handle(match, language, tokenizer, state) {
var category = tokenizer[0];
if (!category) {
return 0;
}
var text = match[0];
if (tokenizer[3] === '@debug') {
// eslint-disable-next-line
console.log(text, tokenizer);
}
if (startsWith(category, '@')) {
assert(language.use);
var lang = language.use[category.slice(1)];
assert(lang);
return this.parse(text, lang, lang.grammar.main, category);
}
if (startsWith(category, '#')) {
var tokenizers = language.grammar[category.slice(1)];
assert(tokenizers);
if (tokenizer[2] === '@rest') {
text = match.input.slice(match.index);
}
return this.parse(text, language, tokenizers, category);
}
this.push([category, text], language, state);
return text.length;
}
/**
* Tokenizes the text by the current language.
*
* @param text - A text to tokenize.
* @param limit - Optional. Limits the number of lines.
*
* @return An array with tokens.
*/
;
_proto.tokenize = function tokenize(text, limit) {
this.lines = [[]];
this.index = 0;
this.depth = -1;
this.limit = limit || 0;
this.aborted = false;
this.parse(text, this.language, this.language.grammar.main, '#main');
return this.lines;
};
return Lexer;
}();
var ROOT = PROJECT_CODE;
var CONTAINER = PROJECT_CODE_SHORT + "__container";
var BODY = PROJECT_CODE_SHORT + "__body";
var CODE = PROJECT_CODE_SHORT + "__code";
var LINE = PROJECT_CODE_SHORT + "__line";
var TOKEN = PROJECT_CODE_SHORT + "__token";
var ACTIVE = 'is-active';
/**
* The class for providing the very simple event bus.
*
* @private
* @since 0.0.1
*/
var EventBus = /*#__PURE__*/function () {
function EventBus() {
/**
* Holds handlers.
*/
this.handlers = {};
}
/**
* Attaches a handler.
*
* @param event - An event name.
* @param callback - A callback function to register.
* @param priority - Optional. A priority number for the order in which the callbacks are invoked.
*/
var _proto2 = EventBus.prototype;
_proto2.on = function on(event, callback, priority) {
if (priority === void 0) {
priority = 10;
}
var handlers = this.handlers[event] = this.handlers[event] || [];
handlers.push({
callback: callback,
priority: priority
});
handlers.sort(function (handler1, handler2) {
return handler1.priority - handler2.priority;
});
}
/**
* Emits an event.
*
* @param event - An event name.
* @param args - Optional. Any number of arguments to pass to callbacks.
*/
;
_proto2.emit = function emit(event) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
(this.handlers[event] || []).forEach(function (handler) {
handler.callback.apply(handler, args);
});
}
/**
* Destroys the event bus.
*/
;
_proto2.destroy = function destroy() {
this.handlers = {};
};
return EventBus;
}();
/**
* Stores all Extensions functions.
*/
var Extensions = {};
/**
* The class for highlighting code via provided tokens.
*
* @since 0.0.1
*/
var Renderer = /*#__PURE__*/function () {
/**
* 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.
*/
function Renderer(lines, info, root, options) {
if (options === void 0) {
options = {};
}
/**
* Holds lines with tokens.
*/
this.lines = [];
/**
* Holds the EventBus instance.
*/
this.event = new EventBus();
this.lines = lines;
this.info = info;
this.root = root;
this.options = options;
this.init();
}
/**
* Adds extensions.
*
* @param extensions - An object literal with Component functions.
*/
Renderer.compose = function compose(extensions) {
forOwn(extensions, function (Component, name) {
Extensions[name] = Component;
});
}
/**
* Initializes the instance.
*/
;
var _proto3 = Renderer.prototype;
_proto3.init = function init() {
var _this2 = this;
var lines = this.lines;
if (lines.length) {
var tokens = lines[lines.length - 1];
if (!tokens.length || tokens.length === 1 && !tokens[0][1].trim()) {
// Removes the last empty line.
lines.pop();
}
}
forOwn(Extensions, function (Component) {
Component(_this2);
});
this.event.emit('mounted');
}
/**
* Renders lines as HTML.
*
* @param append - A function to add fragments to the HTML string.
*
* @return A rendered HTML string.
*/
;
_proto3.renderLines = function renderLines(append) {
var event = this.event;
var tagName = this.options.span ? 'span' : 'code';
for (var i = 0; i < this.lines.length; i++) {
var tokens = this.lines[i];
var classes = [LINE];
event.emit('line:open', append, classes, i);
append(tag(classes));
var _loop = function _loop(j) {
var token = tokens[j];
var categories = token[0].split('.');
var className = PROJECT_CODE_SHORT + "__" + categories[0];
var modifiers = categories.slice(1).map(function (sub) {
return className + "--" + sub;
});
var classes = [TOKEN, className].concat(modifiers);
event.emit('token', token, classes);
append("" + tag(classes, tagName) + escapeHtml(token[1]) + "</" + tagName + ">");
};
for (var j = 0; j < tokens.length; j++) {
_loop(j);
}
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.
*/
;
_proto3.html = function html(pre) {
var event = this.event;
var closeTag = '</div>';
var html = '';
var append = function append(fragment) {
html += fragment;
};
if (pre) {
html += tag([ROOT + " " + ROOT + "--" + this.info.id], 'pre');
}
var containerClasses = [CONTAINER];
event.emit('open', append, containerClasses);
html += tag(containerClasses);
var bodyClasses = ["" + BODY + (this.options.wrap ? " " + BODY + "--wrap" : '')];
event.emit('body:open', append, bodyClasses);
html += tag(bodyClasses);
event.emit('code:open', append);
html += tag([CODE]);
this.renderLines(append);
html += closeTag; // code
event.emit('body:close', append);
html += closeTag; // body
event.emit('close', append);
html += closeTag; // container
event.emit('closed', append);
if (pre) {
html += "</pre>";
}
return html;
}
/**
* Destroys the instance.
*/
;
_proto3.destroy = function destroy() {
this.event.emit('destroy');
this.event.destroy();
};
return Renderer;
}();
/**
* The data attribute name for a language.
*
* @since 0.0.1
*/
var ATTRIBUTE_LANGUAGE = "data-" + PROJECT_CODE_SHORT + "-language";
var REGEXP_NUMBER = /[+-]?(\d+\.?\d*|\d*\.?\d+)([eE][+-]?\d+)?/;
var REGEXP_BOOLEAN = /\b(?:true|false)\b/;
var REGEXP_BRACKET = /[[\]{}()]/;
var REGEXP_SPACE = /[ \t]+/;
var REGEXP_QUOTE = /'(?:\\'|.)*?'/;
var REGEXP_DOUBLE_QUOTE = /"(?:\\"|.)*?"/;
var REGEXP_MULTILINE_COMMENT = /\/\*[\s\S]*?(\*\/|$)/;
var REGEXP_SLASH_COMMENT = /\/\/.*/;
var REGEXP_GENERAL_KEYWORDS = /\b(?:break|catch|class|continue|do|else|extends|finally|for|function|if|implements|in|instanceof|interface|new|null|return|throw|try|while)\b/;
/**
* Returns the CSS language definition.
*
* @return A Language object.
*/
function css() {
return {
id: 'css',
name: 'CSS',
grammar: {
main: [['#common'], // An atrule without a block
['#findSingleAtrule'], // Blocks including atrules
['#findBlock']],
findBlock: [['#block', /(?:(?![\t\n\r ;\{\}])[\s\S])(?:(?![;\{\}])[\s\S])*\{[\s\S]*?\}/, '@rest']],
findSingleAtrule: [['#atrule', /@(?:(?![;\{])[\s\S])+?;/]],
// Finds atrules before { and ;
findAtrule: [['#atrule', /@(?:(?![;\{])[\s\S])*?(?=[;\{])/]],
// May not start with digits
findSelector: [['#selector', /(?:(?![\t\n\r ;\{\}])[\s\S])[\s\S]*?(?=\{)/]],
common: [[CATEGORY_STRING, /(["'])[\s\S]*?(?:(?!\\)[\s\S])\1/], [CATEGORY_COMMENT, REGEXP_MULTILINE_COMMENT], [CATEGORY_SPACE, REGEXP_SPACE]],
block: [['#inner', /{/, '@rest'], [CATEGORY_BRACKET, /}/, '@break'], ['#findAtrule'], ['#findSelector'], [CATEGORY_SPACE, REGEXP_SPACE]],
inner: [[CATEGORY_BRACKET, /{/], ['#common'], ['#findBlock'], ['#props'], ['#findAtrule'], ['', /}/, '@back']],
atrule: [['#common'], ['#url', /\burl\(/, '@rest'], [CATEGORY_SPACE, REGEXP_SPACE], [CATEGORY_ATRULE, /[^\s();]+/], [CATEGORY_DELIMITER, /[:;,]/], ['#paren', /\(/, '@rest']],
paren: [[CATEGORY_BRACKET, /^\(/], ['#common'], ['#paren', /\(/, '@rest'], [CATEGORY_BRACKET, /\)/, '@break'], ['#props']],
selector: [['#common'], [CATEGORY_OPERATOR, /[>+~]/], [CATEGORY_BRACKET, /[[\]()]/], [CATEGORY_DELIMITER, /=/], [CATEGORY_SELECTOR, /::?\S+/], [CATEGORY_SELECTOR, /[\W\d]\S+/], [CATEGORY_TAG, /\b[a-z]+|\*/i], [CATEGORY_SELECTOR, /\S+/]],
url: [['#common'], [CATEGORY_FUNCTION, /^url/], [CATEGORY_BRACKET, /\(/], [CATEGORY_STRING, /[^)]+/], [CATEGORY_BRACKET, /\)/, '@break']],
props: [[CATEGORY_PROPERTY, /[a-z0-9-_\xA0-\uFFFF]+(?=:)/i], ['#url', /\burl\(/, '@rest'], [CATEGORY_FUNCTION, /\b[\w-]+(?=\()\b/], [CATEGORY_KEYWORD, /!important|\b(?:initial|inherit|unset)/], [CATEGORY_PROPERTY, /[a-z0-9-]+(?=:)/], [CATEGORY_NUMBER, /#([0-9a-f]{6}|[0-9a-f]{3})/i], [CATEGORY_NUMBER, /\bU\+[0-9a-f?-]+/i], [CATEGORY_NUMBER, /[+-]?(\d+\.?\d*|\d*\.?\d+)/], [CATEGORY_DELIMITER, /[:;,]/], ['#paren', /\(/, '@rest'], [CATEGORY_BRACKET, /[[\])]/], [CATEGORY_SPACE, REGEXP_SPACE]]
}
};
}
/**
* Returns the JavaScript language definition.
*
* @return A Language object.
*/
function javascript() {
return {
id: 'javascript',
name: 'JavaScript',
alias: ['js'],
source: {
func: /[_$a-z\xA0-\uFFFF][_$a-z0-9\xA0-\uFFFF]*/
},
grammar: {
main: [[CATEGORY_STRING, REGEXP_QUOTE], [CATEGORY_STRING, REGEXP_DOUBLE_QUOTE], ['#backtick', /`/, '@rest'], [CATEGORY_COMMENT, REGEXP_MULTILINE_COMMENT], [CATEGORY_COMMENT, REGEXP_SLASH_COMMENT], [CATEGORY_REGEXP, /\/(\[.*?]|\\\/|.)+?\/[gimsuy]*/], [CATEGORY_KEYWORD, REGEXP_GENERAL_KEYWORDS], [CATEGORY_KEYWORD, /\b(?:as|async|await|case|catch|const|debugger|default|delete|enum|export|from|import|let|package|private|protected|public|super|switch|static|this|typeof|undefined|var|void|with|yield)\b/], [CATEGORY_KEYWORD, /\b((get|set)(?=\s+%func))/i], [CATEGORY_CLASS, /\b[A-Z][\w$]*\b/], [CATEGORY_FUNCTION, /%func(?=\s*\()/i], [CATEGORY_BOOLEAN, REGEXP_BOOLEAN], [CATEGORY_DECORATOR, /@[^\s(@]+/], [CATEGORY_IDENTIFIER, /\b[a-z_$][\w$]*\b/], [CATEGORY_NUMBER, REGEXP_NUMBER], [CATEGORY_OPERATOR, /=>/], [CATEGORY_OPERATOR, /\+[+=]?|-[-=]?|\*\*?=?|[/%^]=?|&&?=?|\|\|?=?|\?\??=?|<<?=?|>>>=?|>>?=?|[!=]=?=?|[~:^]/], [CATEGORY_BRACKET, REGEXP_BRACKET], [CATEGORY_DELIMITER, /[;.,]+/], [CATEGORY_SPACE, REGEXP_SPACE]],
backtick: [[CATEGORY_STRING, /^`/], [CATEGORY_STRING, /(\$[^{]|\\[$`]|[^`$])+/], ['#expression', /\${/, '@rest'], [CATEGORY_STRING, /`/, '@break']],
expression: [[CATEGORY_DELIMITER, /^\${/], [CATEGORY_DELIMITER, /}/, '@break'], ['#main']]
}
};
}
/**
* Returns the HTML language definition.
*
* @param options - Optional. Options.
*
* @return A Language object.
*/
function html(options) {
if (options === void 0) {
options = {};
}
var script = (options.script || javascript)();
var style = (options.style || css)();
var cdata = [CATEGORY_CDATA, /<!\[CDATA\[[\s\S]*\]\]>/i]; // Embedded scripts or styles may contain CDATA sections.
script.grammar.main.unshift(cdata);
style.grammar.main.unshift(cdata);
return {
id: 'html',
alias: ['markup'],
name: 'HTML',
use: {
script: script,
style: style
},
grammar: {
main: [[CATEGORY_COMMENT, /<!\x2D\x2D[\s\S]*?\x2D\x2D>/], [CATEGORY_PROLOG, /<!DOCTYPE[\s\S]*?>/i], cdata, ['#script', /<script[\s\S]*?>[\s\S]*?<\/script>/], ['#style', /<style[\s\S]*?>[\s\S]*?<\/style>/], ['#tag', /<[\s\S]*?>/], [CATEGORY_ENTITY, /&[\da-z]+;|&#\d+;/i], [CATEGORY_SPACE, REGEXP_SPACE]],
script: [['#tag', /^<script[\s\S]*?>/], cdata, ['@script', /[\s\S]+(?=<\/script>)/], ['#tag', /<\/script>/]],
style: [['#tag', /^<style[\s\S]*?>/], ['@style', /[\s\S]+(?=<\/style>)/], ['#tag', /<\/style>/]],
tag: [['#closeTag', /<\/.+>/], ['#tagContent']],
closeTag: [[CATEGORY_TAG_CLOSE, /[^\s/<>"'=]+/], ['#tagContent']],
tagContent: [['#attr', /[\t\n\r ]+[\s\S]+(?=[\t\n\r \/>])/], [CATEGORY_TAG, /[^\s/<>"'=]+/], [CATEGORY_BRACKET, /[<>]/], [CATEGORY_DELIMITER, /[/]/]],
attr: [[CATEGORY_SPACE, REGEXP_SPACE], [CATEGORY_VALUE, /(['"])(\\\1|.)*?\1/], [CATEGORY_DELIMITER, /[/=]/], [CATEGORY_ATTRIBUTE, /[^\s/>"'=]+/]]
}
};
}
/**
* Returns the JSON language definition.
*
* @link https://www.json.org/json-en.html
*
* @return A Language object.
*/
function json() {
return {
id: 'json',
name: 'JSON',
grammar: {
main: [[CATEGORY_PROPERTY, /".*?[^\\]"(?=:)/], [CATEGORY_STRING, REGEXP_DOUBLE_QUOTE], [CATEGORY_KEYWORD, /\bnull\b/], [CATEGORY_NUMBER, /[+-]?(\d+\.?\d*)([eE][+-]?\d+)?/], [CATEGORY_BRACKET, /[{}[\]]/], [CATEGORY_BOOLEAN, REGEXP_BOOLEAN], [CATEGORY_OPERATOR, /:/], [CATEGORY_DELIMITER, /,/], [CATEGORY_SPACE, REGEXP_SPACE]]
}
};
}
/**
* Returns the JSX language definition.
*
* @return A Language object.
*/
function jsx(options) {
if (options === void 0) {
options = {};
}
var language = assign((options.base || javascript)(), {
id: 'jsx',
name: 'JSX',
alias: ['react']
});
var grammar = language.grammar;
before(grammar.main, CATEGORY_CLASS, [['#findPairedTag'], ['#findSelfClosedTag']]);
assign(grammar, {
// This doesn't pick correct paired tags if nested, but they are incrementally searched later.
findPairedTag: [['#pairedTag', /(?:<[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*?([0-9A-Z_a-z]+)[\s\S]*?>[\s\S]*?<\/\1>)|<[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*?>[\s\S]*?<\/>/, '@rest']],
// Should not match the closing delimiter inside `{}`, `''` and `""`.
findSelfClosedTag: [['#selfClosedTag', /<(?:\{[\s\S]*?\}|(["'])[\s\S]*?\1|(?:(?!>)[\s\S]))+?\/>/]],
findBracket: [['#code', /{/, '@rest']],
pairedTag: [['#openTag', /^</, '@rest'], ['#findBracket'], ['#findPairedTag'], ['#findSelfClosedTag'], ['#tagName', /<\/([\w][^\s]*?)?>/, '@break'], [CATEGORY_SPACE, REGEXP_SPACE]],
code: [[CATEGORY_BRACKET, /^{/], [CATEGORY_BRACKET, /}/, '@break'], ['#findBracket'], ['#main']],
selfClosedTag: [['#openTag', /^</, '@rest']],
openTag: [['#tagName', /<\s*[^\s/>"'=]*/], ['#findBracket'], [CATEGORY_ATTRIBUTE, /[^\s/>"'=]+/], [CATEGORY_VALUE, /(['"])(\\\1|.)*?\1/], [CATEGORY_SPACE, REGEXP_SPACE], [CATEGORY_DELIMITER, /[/=]/], [CATEGORY_BRACKET, />/, '@break']],
tagName: [[CATEGORY_BRACKET, /[<>]/], [CATEGORY_SPACE, REGEXP_SPACE], [CATEGORY_DELIMITER, /\//], [CATEGORY_CLASS, /[A-Z][\w$-]*/], [CATEGORY_TAG, /[^\s/>"'=]+/]]
});
return language;
}
/**
* Returns the None language definition.
*
* @return A Language object.
*/
function none() {
return {
id: 'none',
name: '',
grammar: {
main: []
}
};
}
/**
* Returns the SCSS language definition.
*
* @return A Language object.
*/
function scss() {
var language = assign(css(), {
id: 'scss',
name: 'SCSS'
});
var grammar = language.grammar;
assign(grammar, {
findBlock: [
/**
* Include: div {}, .class {}, #id {}, * {}, *{}, #{ $variable } {}, .something__#{ $variable } {}
* Exclude: #{ variable }: value
*/
['#block', /([\*-_a-z]|#\{(?:(?!;)[\s\S])*?\}|((#\{(?:(?!;)[\s\S])*?\}|(?:(?![\t-\r ;\{\}\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])[\s\S]))(#\{(?:(?!;)[\s\S])*?\}|(?:(?![#;\{\}])[\s\S])|#(?:(?!\{)[\s\S]))+?))(?!#)\{[\s\S]*?\}/i, '@rest']],
// May contain #{} interpolation
findSingleAtrule: [['#atrule', /@(#\{|(?:(?![;\{])[\s\S]))+?;/]],
findAtrule: [['#atrule', /@(#\{|(?:(?![;\{])[\s\S]))*?(?=[;\{])/]],
// May contain #{} interpolation
findSelector: [['#selector', /(?:(?!;)[\s\S])*?(?:(?!#)[\s\S])(?=\{)/, '']],
findInterp: [['#interp', /#{/, '@rest']],
common: [['#string'], [CATEGORY_COMMENT, REGEXP_MULTILINE_COMMENT], [CATEGORY_COMMENT, REGEXP_SLASH_COMMENT], [CATEGORY_DELIMITER, /;/], [CATEGORY_SPACE, REGEXP_SPACE]],
string: [['#singleQuote', /'/, '@rest'], ['#doubleQuote', /"/, '@rest']],
singleQuote: [[CATEGORY_STRING, /^'/], ['#findInterp'], [CATEGORY_STRING, /(\\'|#[^{]|[^'#])+/], [CATEGORY_STRING, /'/, '@break']],
doubleQuote: [[CATEGORY_STRING, /^"/], ['#findInterp'], [CATEGORY_STRING, /(\\"|#[^{]|[^"#])+/], [CATEGORY_STRING, /"/, '@break']],
selector: [['#common'], ['#findInterp'], [CATEGORY_OPERATOR, /[>+~]/], [CATEGORY_BRACKET, /[[\]()]/], [CATEGORY_DELIMITER, /=/], [CATEGORY_SELECTOR, /::?\S+(?=#{)/], [CATEGORY_SELECTOR, /[\W\d]\S+(?=#{)/], [CATEGORY_TAG, /\b[a-zA-Z]+\b|\*/], [CATEGORY_SELECTOR, /([^#\s]|#[^{\s])+/]],
url: [['#common'], ['#findInterp'], [CATEGORY_FUNCTION, /^url/], [CATEGORY_BRACKET, /\(/], [CATEGORY_STRING, /[^)]+(?=#{)/], [CATEGORY_STRING, /[^)]+/], [CATEGORY_BRACKET, /\)/, '@break']],
interp: [[CATEGORY_DELIMITER, /#{/], [CATEGORY_DELIMITER, /}/, '@break'], ['#common'], ['#props']]
});
grammar.inner.unshift(['#findInterp']);
before(grammar.atrule, '#url', [['#findInterp']]);
before(grammar.props, CATEGORY_PROPERTY, [['#findInterp'], [CATEGORY_VARIABLE, /\$[\w-_]+/]]);
return language;
}
/**
* Returns the XML language definition.
*
* @return A Language object.
*/
function xml() {
var language = assign(html(), {
id: 'xml',
name: 'XML',
alias: []
});
language.grammar.main.unshift([CATEGORY_PROLOG, /<\?[\s\S]*?\?>/]);
return language;
}
/**
* Returns the XML language definition.
*
* @return A Language object.
*/
function svg() {
return assign(xml(), {
id: 'svg',
name: 'SVG',
alias: []
});
}
/**
* Returns the Typescript language definition.
*
* @return A Language object.
*/
function typescript() {
var language = assign(javascript(), {
id: 'typescript',
name: 'TypeScript',
alias: ['ts']
});
var grammar = language.grammar;
var main = grammar.main;
before(main, CATEGORY_KEYWORD, [[CATEGORY_KEYWORD, /\b(?:declare|keyof|namespace|readonly|type|string|number|boolean|bigint|symbol|object|any|never|unknown|infer|is)\b/]]);
before(main, CATEGORY_FUNCTION, [['#functions', /([_$a-z\xA0-\uFFFF][_$a-z0-9\xA0-\uFFFF]*)?(?:<[^>]+?>)?\s*?\(/]]);
assign(grammar, {
functions: [[CATEGORY_FUNCTION, /^[\w$]+/]].concat(main.filter(function (tokenizer) {
return tokenizer[0] !== '#functions';
}))
});
return language;
}
/**
* Returns the TSX language definition.
*
* @return A Language object.
*/
function tsx() {
return assign(jsx({
base: typescript
}), {
id: 'tsx',
name: 'TSX'
});
}
/**
* Returns the VUE language definition.
*
* @return A Language object.
*/
function vue(options) {
if (options === void 0) {
options = {};
}
var language = assign(html(options), {
id: 'vue',
name: 'Vue',
alias: []
}); // Vue uses Mustache syntax for writing code inside tags.
language.grammar.main.push(['@script', /{{[\s\S]*?}}/]);
return language;
} // export { common } from './common/common';
var index$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
css: css,
html: html,
javascript: javascript,
json: json,
jsx: jsx,
none: none,
scss: scss,
svg: svg,
tsx: tsx,
typescript: typescript,
vue: vue,
xml: xml
});
/**
* Stores all Lexer instances.
*/
var lexers = {};
/**
* The class that tokenizes code for syntax highlighting.
*
* @since 0.0.1
*/
var RyuseiLight = /*#__PURE__*/function () {
/**
* The RyuseiLight constructor.
*
* @param options - Optional. Options.
*/
function RyuseiLight(options) {
/**
* Holds all renderers.
*/
this.renderers = [];
this.options = assign({}, options);
}
/**
* Registers languages.
*
* @param languages - A Language object or objects.
*/
RyuseiLight.register = function register(languages) {
toArray(languages).forEach(function (language) {
var id = language.id;
if (id && !lexers[id]) {
(language.alias || []).concat(id).forEach(function (id) {
lexers[id] = new Lexer(language);
});
}
});
}
/**
* Tokenizes the provided string.
*
* @param code - A string to tokenize.
* @param language - A language ID.
* @param limit - Optional. Limits the (ideal) number of lines.
*
* @return An array of arrays with tokens as [ string, string ].
*/
;
RyuseiLight.tokenize = function tokenize(code, language, limit) {
return RyuseiLight.getLexer(language).tokenize(code, limit);
}
/**
* Checks if the given language has been already registered or not.
*
* @param language - A language to check.
*/
;
RyuseiLight.has = function has(language) {
return !!lexers[language];
}
/**
* Returns a registered Lexer instance.
* If it's not found, the `none` lexer will be returned.
*
* @param language - A language name.
*/
;
RyuseiLight.getLexer = function getLexer(language) {
if (!lexers.none) {
RyuseiLight.register(none());
}
return lexers[language] || lexers.none;
}
/**
* Returns a new Renderer instance.
*
* @param code - A code to highlight.
* @param elm - Optional. An element to highlight.
* @param options - Optional. Options.
*/
;
var _proto4 = RyuseiLight.prototype;
_proto4.getRenderer = function getRenderer(code, elm, options) {
options = assign({}, this.options, options);
var language = options.language;
var _RyuseiLight$getLexer = RyuseiLight.getLexer(language).language,
name = _RyuseiLight$getLexer.name,
id = _RyuseiLight$getLexer.id;
return new Renderer(RyuseiLight.tokenize(code, language), {
name: name,
id: id
}, elm, options);
}
/**
* Applies the highlighter to elements that matches the selector or the provided element.
*
* @param target - A selector or an element.
* @param options - Optional. Options.
*/
;
_proto4.apply = function apply(target, options) {
var elms = isString(target) ? document.querySelectorAll(target) : [target];
for (var i = 0; i < elms.length; i++) {
var elm = elms[i];
if (isHTMLElement(elm)) {
var elmOptions = assign({}, options, {
language: attr(elm, ATTRIBUTE_LANGUAGE) || undefined
});
var renderer = this.getRenderer(text(elm), elm, elmOptions);
var isPre = elm instanceof HTMLPreElement;
if (isPre) {
addClass$1(elm, [ROOT, ROOT + "--" + renderer.info.id]);
}
elm.innerHTML = renderer.html(!isPre);
renderer.event.emit('applied', elm);
this.renderers.push(renderer);
}
}
}
/**
* Returns highlighted HTML by tokenizing the provided code.
*
* @param code - Code to highlight.
* @param options - Optional. Options.
*
* @return Highlighted HTML string.
*/
;
_proto4.html = function html(code, options) {
assert(isString(code), 'Invalid code.');
return this.getRenderer(code, null, options).html(true);
}
/**
* Destroys the instance.
*/
;
_proto4.destroy = function destroy() {
this.renderers.forEach(function (renderer) {
renderer.destroy();
});
};
return RyuseiLight;
}();
/**
* Composes extensions.
*
* @param extensions - An object literal with Extension functions.
*/
RyuseiLight.compose = Renderer.compose;
/**
* The data attribute name for active lines.
* The value must be an array in JSON format, such as "[ 2, [ 5, 10 ] ]"
*
* @private
* @since 0.0.1
*/
var ATTRIBUTE_ACTIVE_LINES = "data-" + PROJECT_CODE_SHORT + "-active-lines";
/**
* The component for highlighting lines.
*
* @since 0.0.1
*/
function ActiveLines(_ref) {
var event = _ref.event,
root = _ref.root,
options = _ref.options;
var lines = root && parseData(root) || options.activeLines;
if (isArray(lines)) {
var activeLines = normalize(lines);
event.on('gutter:row:open', function (html, classes, index) {
if (activeLines[index]) {
classes.push(activeLines[index]);
}
});
event.on('line:open', function (html, classes, index) {
if (activeLines[index]) {
classes.push(activeLines[index]);
}
});
}
}
/**
* Attempts to get definition of active lines from a data attribute.
*
* @param elm - A root element.
*
* @return An array with line numbers if available, or otherwise `undefined`.
*/
function parseData(elm) {
var data = attr(elm, ATTRIBUTE_ACTIVE_LINES);
if (data) {
try {
return JSON.parse(data);
} catch (e) {
error(e.message);
}
}
}
/**
* Normalizes the definition of lines to activate.
*
* @param lines - An array with line numbers.
*
* @return An array with normalized line numbers.
*/
function normalize(lines) {
var numbers = [];
lines.forEach(function (range) {
if (!isArray(range)) {
range = [range, range];
}
var start = (+range[0] || 1) - 1;
var end = (+range[1] || 1) - 1;
for (var i = start; i <= end; i++) {
numbers[i] = ACTIVE;
}
});
return numbers;
}
/**
* The data attribute name for a caption.
*
* @since 0.0.22
*/
var ATTRIBUTE_CAPTION = "data-" + PROJECT_CODE_SHORT + "-caption";
/**
* The component for wrapping a code snipped by a figure tag and appending a figcaption.
*
* @since 0.0.22
*/
function Caption(_ref2) {
var event = _ref2.event,
root = _ref2.root,
options = _ref2.options;
var attrCaption = root && attr(root, ATTRIBUTE_CAPTION);
if (!attrCaption && !options.caption) {
return;
}
var captionOptions = options.caption;
var _assign = assign({}, isObject(captionOptions) ? captionOptions : null),
position = _assign.position,
html = _assign.html;
var caption = attrCaption || html || (isString(captionOptions) ? captionOptions : '');
if (caption) {
var bottom = position === 'bottom';
event.on('open', function (append) {
append("<figure class=\"" + PROJECT_CODE_SHORT + "__figure\">");
if (!bottom) {
appendCaption(append, caption);
}
});
event.on('closed', function (append) {
if (bottom) {
appendCaption(append, caption, true);
}
append('</figure>');
});
}
}
/**
* Appends a figcaption element with a provided caption.
*
* @param append - The append function.
* @param caption - A caption.
* @param bottom - Optional. Set `true` for a bottom caption.
*/
function appendCaption(append, caption, bottom) {
var className = PROJECT_CODE_SHORT + "__figcaption";
append("<figcaption class=\"" + className + " " + (className + (bottom ? '--bottom' : '--top')) + "\">");
append("<span>" + caption + "</span>");
append("</figcaption>");
}
/**
* Default options for the Copy component.
*
* @private
*
* @since 0.0.1
*/
var DEFAULT_OPTIONS$1 = {
html: 'Copy',
activeHtml: 'Done',
duration: 1000,
ariaLabel: 'Copy code to clipboard',
position: 'topRight'
};
/**
* The component for creating a copy button and handling click.
*
* @since 0.0.1
*/
function Copy(_ref3) {
var lines = _ref3.lines,
event = _ref3.event,
options = _ref3.options;
if (options.copy) {
var copyOptions = assign({}, DEFAULT_OPTIONS$1, isObject(options.copy) ? options.copy : {});
var buttonClass = PROJECT_CODE_SHORT + "__copy";
var labelClass = PROJECT_CODE_SHORT + "__button__label";
options.tools = copyOptions.position;
event.on("tools:" + copyOptions.position, function (append) {
append("<button type=\"button\" class=\"rl__button " + buttonClass + "\" aria-label=\"" + copyOptions.ariaLabel + "\">");
append("<span class=\"" + labelClass + " " + labelClass + "--inactive\">" + copyOptions.html + "</span>");
append("<span class=\"" + labelClass + " " + labelClass + "--active\">" + copyOptions.activeHtml + "</span>");
append("</button>");
});
event.on('applied', function (root) {
var button = query("." + buttonClass, root);
var code = lines.map(function (line) {
return line.map(function (token) {
return token[1];
}).join('');
}).join(LINE_BREAK);
if (button) {
var onClick = function onClick() {
copy(code, button, copyOptions.duration);
};
button.addEventListener('click', onClick);
event.on('destroy', function () {
button.removeEventListener('click', onClick);
});
}
});
}
}
/**
* Attempts to copy the provided code by the Clipboard API.
*
* @param code - A code to copy.
* @param button - A button element.
* @param duration - Duration for the button activation.
*/
function copy(code, button, duration) {
var onSuccess = function onSuccess() {
if (duration) {
toggleClass(button, duration);
}
};
if (navigator.clipboard) {
navigator.clipboard.writeText(code).then(onSuccess)["catch"](function () {
return execCopy(code, onSuccess);
});
} else {
execCopy(code, onSuccess);
}
}
/**
* Attempts to copy the provided code by the `document.execCommand()` for old browsers.
* Note that this method is deprecated.
*
* @param code - Code to copy.
* @param onSuccess - Called after the copy is done.
*/
function execCopy(code, onSuccess) {
var textarea = create('textarea');
textarea.textContent = code;
styles(textarea, {
position: 'absolute',
left: '-99999px'
});
append(document.body, textarea);
textarea.focus();
textarea.select();
var failed;
try {
document.execCommand('copy');
} catch (e) {
alert('Failed to copy.');
failed = true;
}
document.body.removeChild(textarea);
if (!failed) {
onSuccess();
}
}
/**
* Toggles the active class of the button.
*
* @param button - A button element.
* @param duration - Duration for the button activation.
*/
function toggleClass(button, duration) {
addClass$1(button, ACTIVE);
var prop = '_rlTimer';
if (button[prop]) {
clearTimeout(button[prop]);
}
button[prop] = setTimeout(function () {
button.classList.remove(ACTIVE);
}, duration);
}
/**
* The class name for added lines.
*
* @private
* @since 0.0.17
*/
var CLASS_ADDED = 'is-added';
/**
* The class name for deleted lines.
*
* @private
* @since 0.0.17
*/
var CLASS_DELETED = 'is-deleted';
/**
* The class name for deleted lines.
*
* @private
* @since 0.0.17
*/
var CLASS_DIFF = PROJECT_CODE_SHORT + "__diff";
/**
* Default options for the Diff component.
*
* @since 0.0.17
*/
var DEFAULT_OPTIONS = {
addedSymbol: '+',
deletedSymbol: '-'
};
/**
* The component for highlighting added/deleted lines.
*
* @since 0.0.17
*/
function Diff(_ref4) {
var event = _ref4.event,
lines = _ref4.lines,
options = _ref4.options;
if (!options.diff) {
return;
}
options.gutter = true;
var diffOptions = assign({}, DEFAULT_OPTIONS, isObject(options.diff) ? options.diff : null);
var added = [];
var deleted = [];
lines.forEach(function (tokens, index) {
if (tokens.length) {
var _text = tokens[0][1];
var processed;
if (startsWith(_text, diffOptions.addedSymbol)) {
added.push(index);
processed = true;
} else if (startsWith(_text, diffOptions.deletedSymbol)) {
deleted.push(index);
processed = true;
}
if (processed) {
convertSymbols(diffOptions.removeSymbols, tokens);
}
}
});
if (!added.length && !deleted.length) {
return;
}
event.on('line:open', function (append, classes, i) {
addClass(added, deleted, i, classes);
});
event.on('gutter:row:open', function (append, classes, i) {
addClass(added, deleted, i, classes);
});
event.on('gutter:row:opened', function (append, i) {
var content = LINE_BREAK;
if (added.indexOf(i) > -1) {
content = diffOptions.addedSymbol;
} else if (deleted.indexOf(i) > -1) {
content = diffOptions.deletedSymbol;
}
append("<span class=\"" + CLASS_DIFF + "\">" + content + "</span>");
}, 20);
event.on('lineNumber:open', function (append, classes, i, data) {
data.skip = deleted.indexOf(i) > -1;
});
}
/**
* Adds a status class according to the added or deleted lines.
*
* @param added - An array with added line indices.
* @param deleted - An array with deleted line indices.
* @param index - A line index.
* @param classes - An array with line classes.
*/
function addClass(added, deleted, index, classes) {
if (added.indexOf(index) > -1) {
classes.push(CLASS_ADDED);
} else if (deleted.indexOf(index) > -1) {
classes.push(CLASS_DELETED);
}
}
/**
* Converts +/- symbols to spaces or removes them.
*
* @param remove - Whether to remove symbols or not.
* @param tokens - Target tokens.
*/
function convertSymbols(remove, tokens) {
var _tokens$ = tokens[0],
category = _tokens$[0],
text = _tokens$[1];
if (remove) {
if (text.length === 1) {
tokens.shift();
} else {
tokens[0] = [category, text.slice(1)];
}
} else {
var spaceToken = [CATEGORY_SPACE, ' '];
if (text.length === 1) {
tokens[0] = spaceToken;
} else {
tokens[0] = [category, text.slice(1)];
tokens.unshift(spaceToken);
}
}
}
/**
* The throttle duration in milliseconds for resizing gutter rows.
*
* @since 0.0.1
*/
var THROTTLE_DURATION = 100;
/**
* The class name for a gutter element.
*
* @since 0.0.1
*/
var GUTTER_CLASS_NAME = PROJECT_CODE_SHORT + "__gutter";
/**
* The class name for row element in a gutter.
*
* @since 0.0.1
*/
var GUTTER_ROW_CLASS_NAME = GUTTER_CLASS_NAME + "__row";
/**
* The component for creating a gutter and its rows.
* This is usually activated by other extensions through the `gutter` option.
*
* @since 0.0.1
*/
function Gutter(_ref5) {
var lines = _ref5.lines,
event = _ref5.event,
root = _ref5.root,
options = _ref5.options;
// Wait for initialization of other extensions.
event.on('mounted', function () {
if (!options.gutter) {
return;
}
event.on('open', function (append, classes) {
classes.push('has-gutter');
});
event