UNPKG

@ryusei/code

Version:

<div align="center"> <a href="https://code.ryuseijs.com"> <img alt="RyuseiCode" src="https://code.ryuseijs.com/images/svg/logo.svg" width="70"> </a>

2,065 lines (1,777 loc) 378 kB
/*! * RyuseiCode.js * Version : 0.1.17 * License : MIT * Copyright: 2021 Naotoshi Fujita */ 'use strict'; function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } Object.defineProperty(exports, '__esModule', { value: true }); /** * The collection of i18n strings. * * @since 0.1.0 */ var I18N$4 = { copy: 'Copy', cut: 'Cut', paste: 'Paste', selectAll: 'Select All', close: 'Close', confirm: 'OK', activate: 'Activate', notice: 'Notice', cancel: 'Cancel', failedToCopy: 'Can not copy on your environment.', scrollbar: 'Drag to Scroll', inputLabel: 'Edit contents', location: 'Line: %s, Column: %s' }; /** * Icon settings as [ path, stroke?, linecap? ]. * * @since 0.1.0 */ var ICONS$1 = { arrowUp: ['m18.6 10.3c-6.59-6.85-6.59-6.85-6.59-6.85m-6.59 6.85 6.59-6.85m0 17v-17', 3], arrowDown: ['m5.41 13.7 6.59 6.85m6.59-6.85c-6.59 6.85-6.59 6.85-6.59 6.85m0-17v17', 3], close: ['m19 18-14-13m0 13 14-13', 3] }; /** * The map for kay bindings (`[ key, ctrl, shift, alt ]`). * * @since 0.1.0 */ var KEYMAP$6 = { selectAll: ['A', true] }; /** * The collection of modifier keys. * * @since 0.1.0 */ var MODIFIER_KEYS = { "default": ['Ctrl', 'Shift', 'Alt'], mac: ['⌘', '⇧', '⌥'] }; /** * Default values for the editor options. * * @since 0.1.0 */ var DEFAULT_OPTIONS$6 = { language: 'javascript', placeholder: 'Enter code here…', minWidth: '200px', maxWidth: '100%', minHeight: '16em', maxHeight: '40em', indent: ' ', tabSize: 2, tabIndex: 0, keymap: KEYMAP$6, maxInitialLines: 200, icons: ICONS$1, i18n: I18N$4 }; /** * Checks if the array includes the value or not. * `Array#includes` is not supported by IE. * * @param array - An array. * @param value - A value to search for. * * @return `true` if the array includes the value, or otherwise `false`. */ function includes(array, value) { return array.indexOf(value) > -1; } /** * 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$1(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 function or not. * * @param subject - A subject to check. * * @return `true` if the subject is a function, or otherwise `false`. */ function isFunction(subject) { return typeof subject === 'function'; } /** * 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$1(subject) { return typeof subject === 'undefined'; } /** * Checks if the given subject is a Text node or not. * * @param subject - A subject to check. * * @return `true` if the subject is a Text node, or otherwise `false`. */ function isText(subject) { return subject instanceof Text; } /** * Checks if the given subject is a HTMLElement instance or not. * * @param subject - A subject to check. * * @return `true` if the subject is a HTMLElement instance, or otherwise `false`. */ function isHTMLElement(subject) { return subject instanceof HTMLElement; } /** * Checks if the given subject is a BR element or not. * * @param subject - A subject to check. * * @return `true` if the subject is a BR element, or otherwise `false`. */ function isBr(subject) { return subject instanceof HTMLBRElement; } /** * Push the provided value to an array if the value is not an array. * * @param value - A value to push. * @param nest - Optional. Whether to push the value to an array if the value is already an array. * * @return An array containing the value, or the value itself if it is already an array. * If the `nest` is `true` and the first child of the array is not an array, * this returns an array with the provided array. */ function toArray(value, nest) { if (nest === void 0) { nest = false; } if (isArray(value)) { if (nest && !isArray(value[0])) { return [value]; } return value; } return [value]; } var arrayProto = Array.prototype; /** * The slice method for an array-like object. * * @param arrayLike - An array-like object. * @param start - Optional. A start index. * @param end - Optional. A end index. * * @return An array with sliced elements. */ function slice(arrayLike, start, end) { return arrayProto.slice.call(arrayLike, start, end); } /** * The splice method for an array-like object. * * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice * * @param arrayLike - An array-like object. * @param start - A start index. * @param deleteCount - Optional. A number of elements to remove from the `start` index. * @param args - Optional. Any number of items to add. * * @return An array with deleted items. */ function _splice(arrayLike, start, deleteCount) { var _arrayProto$splice; for (var _len2 = arguments.length, args = new Array(_len2 > 3 ? _len2 - 3 : 0), _key2 = 3; _key2 < _len2; _key2++) { args[_key2 - 3] = arguments[_key2]; } return (_arrayProto$splice = arrayProto.splice).call.apply(_arrayProto$splice, [arrayLike, start, deleteCount].concat(args)); } /** * Returns the active element. * This is just an alias of `document.activeElement`. * * @return An active element. */ function activeElement() { return document.activeElement; } /** * Toggles the provided class or classes by following the `add` boolean. * * @param elm - An element whose classes are toggled. * @param classes - A class or class names. * @param add - Whether to add or remove a class. */ function _toggleClass(elm, classes, add) { if (elm) { toArray(classes).forEach(function (name) { if (name) { elm.classList[add ? 'add' : 'remove'](name); } }); } } /** * Adds classes to the element. * * @param elm - An element to add classes to. * @param classes - Classes to add. */ function addClass(elm, classes) { _toggleClass(elm, classes, true); } /** * 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) { toArray(children).forEach(parent.appendChild.bind(parent)); } /** * 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$1(object, iteratee) { if (object) { var keys = Object.keys(object); for (var i = 0; i < keys.length; i++) { if (iteratee(object[keys[i]], keys[i]) === false) { break; } } } return object; } /** * 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$1(object) { for (var _len3 = arguments.length, sources = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { sources[_key3 - 1] = arguments[_key3]; } sources.forEach(function (source) { if (isObject$1(source)) { forOwn$1(source, function (value, key) { if (!isUndefined$1(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 (elm) { if (isString(attrs)) { return elm.getAttribute(attrs) || ''; } if (isObject$1(attrs)) { forOwn$1(attrs, function (value, key) { if (value === null) { elm.removeAttribute(key); } else { elm.setAttribute(key, String(value)); } }); } } } /** * Inserts a node or nodes before the specified reference node. * * @param nodes - A node or nodes to insert. * @param ref - A reference node. */ function before$1(nodes, ref) { toArray(nodes).forEach(function (node) { if (node) { var parent = node.parentNode || ref && ref.parentNode; if (parent) { parent.insertBefore(node, ref); } } }); } /** * Checks if the element matches the provided selector, or passes the predicate function. * * @since 0.1.0 * * @param elm - An element to test. * @param selector - A selector string to match. * * @return `true` if the element matches the selector. */ function matches(elm, selector) { Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector; return elm.matches(selector); } /** * With starting at the given element, * finds the closest parent element that matches the selector. * * @since 0.1.0 * * @param elm - A start element. * @param selector - A selector to search for. * * @return The closest element if found, or `null` if not. * * @throws TypeError */ function closest(elm, selector) { if (isFunction(elm.closest)) { return elm.closest(selector); } while (elm) { if (matches(elm, selector)) { return elm; } elm = elm.parentElement; } return null; } /** * Creates a HTML element. * * @param tag - A tag name. * @param attrs - Optional. An object with attributes to apply the created element to, or a string with classes. * @param parent - Optional. A parent element where the created element is appended. */ function _create(tag, attrs, parent) { var elm = document.createElement(tag); if (attrs) { if (isString(attrs) || isArray(attrs)) { addClass(elm, attrs); } else { attr(elm, attrs); } } if (parent) { _append(parent, elm); } return elm; } /** * The `create` function whose tag argument is fixed to `div`. * * @param attrs - Optional. An object with attributes to apply the created element to, or a string with classes. * @param parent - Optional. A parent element where the created element is appended. */ function div(attrs, parent) { return _create('div', attrs, parent); } /** * Focuses the provided element without scrolling the ascendant element. * * @param elm - An element to focus. */ function _focus5(elm) { if (isFunction(elm['setActive'])) { elm['setActive'](); } else { elm.focus({ preventScroll: true }); } } /** * Checks if the element contains the specified class or not. * * @param elm - An element to check. * @param className - A class name that may be contained by the element. * * @return `true` if the element contains the class, or otherwise `false`. */ function hasClass(elm, className) { return elm && elm.classList.contains(className); } /** * Returns client height of the element. * * @param elm - An element to get height. */ function height(elm) { return elm.clientHeight; } /** * Sets or gets HTML of the provided element. * * @param elm - A element to get or set HTML. * @param html - Optional. HTML to set. */ function html$2(elm, html) { if (elm) { if (isUndefined$1(html)) { return elm.innerHTML; } if (elm.innerHTML !== html) { elm.innerHTML = html; } } } /** * Checks if the default action of the event is prevented or not. * * @param e - An Event object. * * @return `true` if the default action is prevented, or otherwise `false`. */ function isPrevented(e) { return e && e.defaultPrevented; } /** * Joins the provided object as a single line for DOM attributes. * * @param attrs - An object literal for attributes. * * @return A single string containing all attributes. */ function joinAttrs(attrs) { var result = ''; forOwn$1(attrs, function (value, prop) { if (prop && (value || value === false || value === 0)) { result += " " + prop + "=\"" + value + "\""; } }); return result.trim(); } /** * Stores registered handlers which has a key. * * @since 0.1.0 */ var handlerMap = new WeakMap(); /** * Attaches a handler to the event. * * @param elm - An element, a window or a document. * @param events - An event name or names. * @param callback - A handler to attach. * @param key - Optional. The key for identifying the registered handler. */ function on(elm, events, callback, key) { events.split(' ').forEach(function (event) { elm.addEventListener(event, callback); if (key) { var handlers = handlerMap.get(key) || []; handlers.push({ elm: elm, events: events, callback: callback }); handlerMap.set(key, handlers); } }); } /** * Detaches a handler from the event or events. * * @param elm - An element where events are removed. * @param events - Optional. An event name or names. * @param callbackOrKey - Optional. A handler to remove or an object key. */ function off(elm, events, callbackOrKey) { if (isFunction(callbackOrKey)) { events.split(' ').forEach(function (event) { elm.removeEventListener(event, callbackOrKey); }); } else { var handlers = handlerMap.get(callbackOrKey); if (handlers) { handlers.forEach(function (handler) { off(handler.elm, handler.events, handler.callback); }); handlerMap["delete"](callbackOrKey); } } } /** * Prepends children to the specified parent node. * * @param parent - A parent node. * @param children - A child or children to prepend to the parent. */ function prepend(parent, children) { toArray(children).forEach(function (child) { parent.insertBefore(child, parent.firstChild); }); } /** * Call the `preventDefault()` of the provided event. * * @param e - An Event object. * @param stopPropagation - Optional. Whether to stop the event propergation or not. */ function prevent(e, stopPropagation) { if (e) { if (e.cancelable) { e.preventDefault(); } if (stopPropagation) { e.stopPropagation(); } } } /** * Returns an element that matches the provided selector. * * @param parent - A parent element to start searching from. * @param selector - A selector to query. * * @return A found element or `null`. */ function query(parent, selector) { return parent.querySelector(selector); } /** * Returns elements that match the provided selector. * * @param parent - A parent element to start searching from. * @param selector - A selector to query. * * @return The NodeList object that contains matched elements. */ function queryAll(parent, selector) { return parent.querySelectorAll(selector); } /** * Returns a DOMRect object of the provided element or the selection range. * * @param target - An element or a range instance. */ function rect(target) { return target.getBoundingClientRect(); } /** * Removes the provided node from its parent. * * @param nodes - A node or nodes to remove. */ function _remove(nodes) { toArray(nodes).forEach(function (node) { if (node && node.parentNode) { node.parentNode.removeChild(node); } }); } /** * Removes classes from the element. * * @param elm - An element to remove classes from. * @param classes - Classes to remove. */ function removeClass(elm, classes) { _toggleClass(elm, classes, false); } /** * 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) { if (isString(styles)) { return getComputedStyle(elm)[styles]; } forOwn$1(styles, function (value, key) { if (!isUndefined$1(value)) { elm.style[key] = String(value); } }); } /** * Returns an open tag with provided classes. * * @param classes - Classes. * @param attrs - Optional. An object with attributes. * @param tag - Optional. A tag name. */ function tag(classes, attrs, tag) { if (attrs === void 0) { attrs = {}; } return "<" + (tag || 'div') + " " + joinAttrs(assign$1(attrs, { "class": toArray(classes).filter(Boolean).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 (node) { if (isUndefined$1(text)) { return node.textContent; } node.textContent = text; } } /** * Appends `px` to the value. * If the value is already string, just returns it. * * @param value - A value to append `px` to. */ function unit(value) { return isString(value) ? value : value + "px"; } /** * Checks if the client is Android or not. * * @return `true` if the client is Android, or otherwise `false`. */ function isAndroid() { return /android/i.test(navigator.userAgent); } /** * Checks is the browser is based on the Gecko engine or not. * * @return `true` if the browser is the browser is based on the Gecko (Firefox), or otherwise `false`. */ function isGecko() { return !!window['InstallTrigger']; } /** * Checks if the client is iOS or not. * * @return `true` if the client is iOS, or otherwise `false`. */ function isIOS() { var _navigator = navigator, userAgent = _navigator.userAgent; return /iPad|iPhone|iPod/.test(userAgent) || userAgent.indexOf('Mac') > -1 && navigator.maxTouchPoints > 1; } /** * Checks is the browser is IE or not. * * @return `true` if the browser is IE, or otherwise `false`. */ function isIE() { return ( /*@cc_on!@*/ !!document['documentMode'] ); } /** * Checks is the platform is Mac or not. * * @return `true` if the platform is Mac, or otherwise `false`. */ function isMac() { return /Mac/i.test(navigator.platform); } /** * Checks if the device is likely mobile or not. * * @return `true` if the device is likely mobile, or otherwise `false`. */ function isMobile() { return isAndroid() || isIOS(); } /** * The project code. * * @since 0.1.0 */ var PROJECT_CODE = 'ryuseicode'; /** * The abbreviated project code. * * @since 0.1.0 */ var PROJECT_CODE_SHORT = 'rc'; /** * Throws an error if the provided condition is falsy. * * @param condition - If falsy, an error is thrown. * @param message - Optional. A message to display. */ function assert$1(condition, message) { if (message === void 0) { message = ''; } if (!condition) { throw new Error("[" + PROJECT_CODE + "] " + message); } } /** * Returns a function that invokes the provided function at most once in the specified duration. * * @since 0.1.0 * * @param func - A function to throttle. * @param interval - A throttle duration in milliseconds. * @param initialCall - Optional. Determines whether to call the function initially. * @param debounce - Optional. If `true`, the function returns a debounced function instead of throttled one. * @param raf - Optional. Determines whether to use the `requestAnimationFrame` or not. * * @return A throttled function. */ function throttle(func, interval, initialCall, debounce, raf) { var id; var invoker; function throttled() { if (debounce) { cancel(); } for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { args[_key4] = arguments[_key4]; } invoker = invoke.bind.apply(invoke, [this].concat(args)); if (!id) { if (isUndefined$1(id) && initialCall) { invoker(); } else { id = raf ? requestAnimationFrame(invoker) : setTimeout(invoker, interval); } } } function invoke() { for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { args[_key5] = arguments[_key5]; } func.apply(this, args); cancel(); } function cancel() { raf ? cancelAnimationFrame(id) : clearTimeout(id); id = null; } throttled.cancel = cancel; throttled.invoke = function () { if (id) { invoker(); } }; return throttled; } /** * Returns a debounced function that invokes the provided function only after the internal timer expires. * The timer is reset whenever the debounced function is called. * * @param func - A callback function. * @param duration - Debounce duration in milliseconds. * * @return A debounced function. */ function debounce(func, duration) { return throttle(func, duration, false, true); } /** * Fires the provided function on the next tick. * * @param func - A function to call. */ function nextTick(func) { setTimeout(func); } /** * Implements the `throttle` function via requestAnimationFrame. * * @param func - A function to throttle. * @param initialCall - Optional. Determines whether to call the function initially. * * @return A throttled function. */ function rafThrottle(func, initialCall) { return throttle(func, 0, initialCall, false, true); } /** * The collection of forward arrow keys. * * @private * @since 0.1.0 */ var ARROW_FORWARD = ['ArrowDown', 'ArrowRight']; /** * The collection of backward arrow keys. * * @private * @since 0.1.0 */ var ARROW_BACKWARD = ['ArrowUp', 'ArrowLeft']; /** * The collection of all arrow keys. * * @private * @since 0.1.0 */ var ARROW_KEYS = [].concat(ARROW_FORWARD, ARROW_BACKWARD); /** * The map for normalizing differences of keys in browsers. * * @link https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values * * @private * @since 0.1.0 */ var NORMALIZATION_MAP = { Up: 'ArrowUp', Down: 'ArrowDown', Right: 'ArrowRight', Left: 'ArrowLeft', Del: 'Delete', Esc: 'Escape', Spacebar: ' ' }; /** * Normalizes the provided key for different browsers. * * @param key - A key to normalize. */ function normalizeKey(key) { return NORMALIZATION_MAP[key] || key; } /** * Checks if the keyboard event matches the provided matcher or not. * * @param e - A KeyboardEvent object. * @param matchers - A KeyMatcher tuple or an array with matchers. * * @return `true` if the keyboard event satisfies the matcher, or otherwise `false`. */ function matchesKey(e, matchers) { var key = normalizeKey(e.key).toUpperCase(); return matchers && toArray(matchers, true).some(function (matcher) { return key === matcher[0].toUpperCase() && !matcher[1] === !e.ctrlKey && !matcher[2] === !e.shiftKey && !matcher[3] === !e.altKey; }); } /** * Checks if the subject number is between `minOrMax` and `maxOrMin`. * * @param number - A subject number to check. * @param minOrMax - A min or max number. * @param maxOrMin - A max or min number. * @param exclusive - Optional. Whether to exclude `x` or `y`. */ function between(number, minOrMax, maxOrMin, exclusive) { var min = Math.min(minOrMax, maxOrMin); var max = Math.max(minOrMax, maxOrMin); return exclusive ? min < number && number < max : min <= number && number <= max; } var max$1 = Math.max, min$1 = Math.min; /** * Clamps a number. * * @param number - A subject number to check. * @param x - A min or max number. * @param y - A min or max number. */ function clamp(number, x, y) { var minimum = min$1(x, y); var maximum = max$1(x, y); return min$1(max$1(minimum, number), maximum); } var min = Math.min, max = Math.max, floor = Math.floor, ceil = Math.ceil, abs = Math.abs, round = Math.round; /** * Compares the provided 2 positions. * * @return If the `position1` is preceding, returns a negative number, * or if it is following, returns a positive one. If they are same, returns `0`. */ function compare(position1, position2) { return position1[0] - position2[0] || position1[1] - position2[1]; } /** * The alias of document.createRange. * The Range constructor is not supported by IE. * * @since 0.1.0 * * @return A Range instance. */ function createRange() { return document.createRange(); } /** * The alias of window.getSelection. * * @since 0.1.0 * * @return A Selection instance. */ function getSelection() { return window.getSelection(); } /** * Finds a node that the offset number belongs to. * * @param elm - An element to find in. * @param offset - An offset index. * * @return An object that contains a found node and a offset number. */ function findSelectionBoundary(elm, offset) { var children = elm.childNodes; if (!children.length && !offset) { return { node: elm, offset: 0 }; } if (offset <= elm.textContent.length) { for (var i = 0; i < children.length; i++) { var node = children[i]; var length = node.textContent.length; if (isText(node)) { if (offset <= length) { return { node: node, offset: offset }; } } else if (node instanceof Element) { var found = findSelectionBoundary(node, offset); if (found) { return found; } } offset -= length; } } return null; } /** * Sets a selection by an anchor and a focus object. * Note that the Range constructor does not supported by IE. * * @param anchor - An anchor boundary object. * @param focus - A focus boundary object. */ function setSelection(anchor, focus) { if (anchor && focus) { var selection = getSelection(); if (selection.setBaseAndExtent) { selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset); } else { var range = selection.rangeCount > 0 ? selection.getRangeAt(0) : document.createRange(); range.setStart(anchor.node, anchor.offset); range.setEnd(focus.node, focus.offset); selection.removeAllRanges(); selection.addRange(range); } } } /** * Converts the provided string in the camel case to the kebab case. * * @param string - A string to convert. */ function camelToKebab(string) { return string.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase(); } /** * Counts the search string occurrence in the provided sting. * * @param string - A string to search in. * @param search - A string to search for. * @param from - An index to search from. * @param to - An index to search to. * * @return A number of occurrence. */ function count(string, search, from, to) { if (from === void 0) { from = 0; } if (to === void 0) { to = string.length; } if (from || to !== string.length) { string = string.slice(from, to); } return (string.match(new RegExp(search, 'g')) || []).length; } /** * Checks if the string ends with the `search` string or not. * * @param string - A string to check. * @param search - A string to search. * * @return `true` if the string ends with the `search`, or otherwise `false`. */ function endsWith(string, search) { return string.slice(-search.length) === search; } /** * 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); } /** * Escapes string for the RegExp source. * * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions * * @param string - A string to escape. */ function escapeRegExp(string) { return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); } /** * Formats a string. * * @param string - A string to format. * @param replacements - A replacement or replacements. * * @return A formatted string. */ function format(string) { for (var i = 0; i < (arguments.length <= 1 ? 0 : arguments.length - 1); i++) { string = string.replace('%s', String(i + 1 < 1 || arguments.length <= i + 1 ? undefined : arguments[i + 1])); } return string; } /** * Returns the index within the provided string of the nth occurrence. * The optional `from` index determines the start position to search the target from. * * @param string - A string to search in. * @param search - A string to search startsWith * @param nth - A number of the occurrence. * @param from - Optional. A start index to search from. * * @return An index if the nth occurrence of the `search` string is found, or `-1` if not. */ function nthIndexOf(string, search, nth, from) { if (from === void 0) { from = 0; } var index = from - 1; var count = nth; while ((index !== -1 || nth === count) && count--) { index = string.indexOf(search, index + 1); } return index; } /** * Returns a new string filled with a number of copies of the provided string. * * @param string - A string to repeat. * @param count - An integer for determining the number of repeats. * * @return A new string containing copies of the provided string. */ function repeat(string, count) { if (!String.prototype.repeat) { var result = ''; while (count > 0) { if (count % 2) { result += string; } count = floor(count / 2); string += string; } return result; } return string.repeat(count); } /** * Checks if the string starts with the `search` string or not. * * @param string - A string to check. * @param search - A string to search. * * @return `true` if the string starts with the `search`, or otherwise `false`. */ function startsWith$1(string, search) { return string.slice(0, search.length) === search; } var ids = {}; /** * Returns a sequential unique ID as "{ prefix }-{ number }". * * @param prefix - A prefix for the ID. */ function uniqueId(prefix) { var number = (ids[prefix] || 0) + 1; var idNumber = number < 10 ? "0" + number : number; ids[prefix] = number; return "" + prefix + idNumber; } /** * The base class for a component. * * @since 0.1.0 */ var Component = /*#__PURE__*/function () { /** * The Component constructor. * * @param Editor - An Editor instance. */ function Component(Editor) { this.Editor = Editor; this.event = Editor.event; this.options = Editor.options; this.language = Editor.language; } /** * Called when the component is mounted. * * @param elements - A collection of editor elements. */ var _proto2 = Component.prototype; _proto2.mount = function mount(elements) { var _this2 = this; this.elements = elements; forOwn$1(this.Editor.Components, function (_Component, key) { _this2[key] = _Component; }); } /** * Called when the editor is destroyed. * * @internal */ ; _proto2.destroy = function destroy() { off(null, '', this); } /** * Attaches an event handler to an event or events with passing this instance as a key. * They can only be detached by the `off()` member method. * * @param events - An event name, names split by spaces, or an array with names. * @param callback - A callback function. * @param thisArg - Optional. Specifies the `this` parameter of the callback function. * @param priority - Optional. A priority number for the order in which the callbacks are invoked. */ ; _proto2.on = function on(events, callback, thisArg, priority) { this.event.on(events, thisArg ? callback.bind(thisArg) : callback, this, priority); } /** * Detaches handlers registered by `on()` without removing handlers attached by other components. * * @param events - An event name, names split by spaces, or an array with names. */ ; _proto2.off = function off(events) { this.event.off(events, this); } /** * Triggers handlers attached to the event. * * @param event - An event name. * @param args - Optional. Any number of arguments to pass to callback functions. */ ; _proto2.emit = function emit(event) { var _this$event; for (var _len6 = arguments.length, args = new Array(_len6 > 1 ? _len6 - 1 : 0), _key6 = 1; _key6 < _len6; _key6++) { args[_key6 - 1] = arguments[_key6]; } (_this$event = this.event).emit.apply(_this$event, [event].concat(args)); } /** * Listens to native events. * This method stores all listeners and automatically removes them on destruction. * * @param elm - A document, a window or an element. * @param events - An event name or names split by spaces. * @param callback - A callback function. * @param thisArg - Optional. Specifies the `this` parameter of the callback function. */ ; _proto2.bind = function bind(elm, events, callback, thisArg) { on(elm, events, thisArg ? callback.bind(thisArg) : callback, this); } /** * Returns a Language or LanguageConfig object at the focus or specified position. * This method can return different objects depending on the position * if the language allows to embed other languages, such as HTML and PHP. * * @param position - Optional. Specifies the position to get the language at. * * @return A main Language object or sub language config object. */ ; _proto2.getLanguage = function getLanguage(position) { position = position || this.Selection.focus; var language = this.language; var info = this.lines.getInfoAt(position); if (info && info.language && language.use && language.use[info.language]) { return language.use[info.language].config; } return language; } /** * Attempts to invoke the public method of the specified extension. * In terms of the "loose coupling", you'd better try not to use this method. * Using events is enough in most cases. * * @example * ```ts * // Attempts to show the "search" toolbar. * Editor.invoke( 'Toolbar', 'show', 'search' ); * ``` * * @param name - A name of the extension. * @param method - A method name to invoke. * @param args - Optional. Arguments for the method. * * @return The return value of the method. */ ; _proto2.invoke = function invoke(name, method) { var _this$Editor; for (var _len7 = arguments.length, args = new Array(_len7 > 2 ? _len7 - 2 : 0), _key7 = 2; _key7 < _len7; _key7++) { args[_key7 - 2] = arguments[_key7]; } return (_this$Editor = this.Editor).invoke.apply(_this$Editor, [name, method].concat(args)); } /** * Returns the specified extension. * In terms of the "loose coupling", you'd better try not to use this method. * Using events is enough in most cases. * * @param name - A name of an extension. * * @return An extension if found, or otherwise `undefined`. */ ; _proto2.require = function require(name) { return this.Editor.require(name); } /** * Adds default icon strings. They can be still overridden by options. * The IconSettings is a tuple as `[ string, number?, string? ]` corresponding with `[ path, stroke?, linecap? ]`. * * @example * ```ts * this.addIcons( { * myIcon: [ * 'm19 18-14-13m0 13 14-13', * 3, * ], * } ); * ``` * * @param icons - Icon settings to add. */ ; _proto2.addIcons = function addIcons(icons) { var options = this.options; options.icons = assign$1({}, icons, options.icons); } /** * Adds default i18n strings. They can be still overridden by options. * * @example * ```ts * this.addI18n( { * myMessage: 'Hello!', * } ); * ``` * * @param i18n - Additional i18n strings. */ ; _proto2.addI18n = function addI18n(i18n) { var options = this.options; options.i18n = assign$1({}, i18n, options.i18n); } /** * Adds default shortcuts to the keymap object. They can be still overridden by options. * Call this method before RyuseiCode mounts components so that the Keymap component recognizes shortcuts. * * @example * ```js * class MyExtension extends Component { * constructor( Editor ) { * super( Editor ); * * this.addKeyBindings( { * myShortcut: [ 'P', true, true ], * } ); * } * } * ``` * * @param shortcuts - Additional shortcuts. */ ; _proto2.addKeyBindings = function addKeyBindings(shortcuts) { var options = this.options; options.keymap = assign$1({}, shortcuts, options.keymap); } /** * Returns options for each extension, merging provided default values. * * @example * ```js * class MyExtension extends Component { * constructor( Editor ) { * super( Editor ); * * const extensionOptions = this.getOptions( 'myExtension', { option1: true } ); * } * } * ``` * * @param name - An option name. * @param defaults - Default values. * * @return A merged options, or `null`. */ ; _proto2.getOptions = function getOptions(name, defaults) { var options = this.options[name]; if (isUndefined$1(options) || options === true) { return defaults || {}; } if (isObject$1(options)) { return assign$1({}, defaults, options); } assert$1(false); } /** * Returns the latest Lines instance. * This is an alias of `Code#Lines`. * * @return The Lines instance. */ ; _createClass(Component, [{ key: "lines", get: function get() { return this.Code.Lines; } /** * Returns the i18n collection. * This is an alias of `this.options.i18n`. * * @return The object with i18n strings. */ }, { key: "i18n", get: function get() { return this.options.i18n; } }]); return Component; }(); var CLASS_ROOT = PROJECT_CODE; var CLASS_VIEW = PROJECT_CODE + "__view"; var CLASS_BODY = PROJECT_CODE + "__body"; var CLASS_SCROLLER = PROJECT_CODE + "__scroller"; var CLASS_CONTAINER = PROJECT_CODE + "__container"; var CLASS_EDITOR = PROJECT_CODE + "__editor"; var CLASS_SCROLLBARS = PROJECT_CODE + "__scrollbars"; var CLASS_SCROLLBAR = PROJECT_CODE + "__scrollbar"; var CLASS_LINES = PROJECT_CODE + "__lines"; var CLASS_LINE = PROJECT_CODE + "__line"; var CLASS_SOURCE = PROJECT_CODE + "__source"; var CLASS_BACKGROUND = PROJECT_CODE + "__background"; var CLASS_CARETS = PROJECT_CODE + "__carets"; var CLASS_CARET = PROJECT_CODE + "__caret"; var CLASS_MARKERS = PROJECT_CODE + "__markers"; var CLASS_MARKER = PROJECT_CODE + "__marker"; var CLASS_OVERLAY = PROJECT_CODE + "__overlay"; var CLASS_CONTEXT_MENU = PROJECT_CODE + "__context-menu"; var CLASS_CONTEXT_MENU_GROUP = CLASS_CONTEXT_MENU + "__group"; var CLASS_CONTEXT_MENU_LIST = CLASS_CONTEXT_MENU + "__list"; var CLASS_CONTEXT_MENU_ITEM = CLASS_CONTEXT_MENU + "__item"; var CLASS_CONTEXT_MENU_BUTTON = CLASS_CONTEXT_MENU + "__button"; var CLASS_CONTEXT_MENU_LABEL = CLASS_CONTEXT_MENU_BUTTON + "__label"; var CLASS_CONTEXT_MENU_SHORTCUT = CLASS_CONTEXT_MENU_BUTTON + "__shortcut"; var CLASS_TOKEN = PROJECT_CODE_SHORT + "__token"; var CLASS_INPUT = PROJECT_CODE + "__input"; var CLASS_BUTTON = PROJECT_CODE + "__button"; var CLASS_ICON = PROJECT_CODE + "__icon"; var CLASS_PLACEHOLDER = PROJECT_CODE + "__placeholder"; var CLASS_ACTIVE = 'is-active'; var CLASS_RENDERED = 'is-rendered'; var CLASS_INITIALIZED = 'is-initialized'; var CLASS_ANCHOR = 'is-anchor'; var CLASS_FOCUS = 'is-focus'; var CLASS_PRESERVED = 'is-preserved'; var CLASS_FOCUSED = 'is-focused'; var CLASS_READONLY = 'is-readonly'; var CLASS_DRAGGING = 'is-dragging'; var CLASS_EMPTY = 'is-empty'; var CLASS_MOBILE = 'is-mobile'; var EVENT_MOUNT = 'mount'; var EVENT_MOUNTED = 'mounted'; var EVENT_FOCUS = 'focus'; var EVENT_BLUR = 'blur'; var EVENT_READONLY = 'readOnly'; var EVENT_KEYDOWN = 'keydown'; var EVENT_INPUT = 'input'; var EVENT_NEWLINE = 'newline'; var EVENT_CHANGE = 'change'; var EVENT_CHANGED = 'changed'; var EVENT_COMPOSITION_START = 'compositionStart'; var EVENT_COMPOSITION_UPDATE = 'compositionUpdate'; var EVENT_COMPOSITION_END = 'compositionEnd'; var EVENT_ANCHOR_LINE_CHANGED = 'anchorLineChanged'; var EVENT_FOCUS_LINE_CHANGED = 'focusLineChanged'; var EVENT_COPY = 'copy'; var EVENT_CUT = 'cut'; var EVENT_PASTE = 'paste'; var EVENT_KEYMAP = 'keymap'; var EVENT_CHUNK_MOVED = 'chunkMoved'; var EVENT_CHUNK_SUPPLIED = 'chunkSupplied'; var EVENT_SELECTING = 'selecting'; var EVENT_SELECTED = 'selected'; var EVENT_SELECTION_CHANGE = 'selectionChanged'; var EVENT_SCROLL = 'scroll'; var EVENT_SCROLLED = 'scrolled'; var EVENT_SCROLLER_SCROLL = 'scrollerScroll'; var EVENT_WINDOW_SCROLL = 'windowScroll'; var EVENT_RESIZE = 'resize'; var EVENT_SCROLL_WIDTH_CHANGED = 'scrollWidthChanged'; var EVENT_SCROLL_HEIGHT_CHANGED = 'scrollHeightChanged'; var EVENT_SYNCED = 'synced'; var EVENT_CONTEXT_MENU_OPENED = 'contextMenuOpened'; var EVENT_CONTEXT_MENU_CLOSED = 'contextMenuClosed'; var EVENT_CONTEXT_MENU_CLICKED = 'contextMenuClicked'; var EVENT_RESET = 'reset'; var EVENT_INIT_STYLE = 'initStyle'; var EVENT_FONT_LOADED = 'fontLoaded'; var EVENT_DESTROYED = 'destroyed'; /** * The editor is not active. */ var IDLE = 0; /** * The selection is collapsed. */ var COLLAPSED = 1; /** * The selection will change soon. The native selection has not been updated at this timing. */ var START = 2; /** * The selection has just changed after the `START` state. The native selection has been updated. */ var CHANGED = 3; /** * The selection has been programmatically updated. */ var UPDATE = 4; /** * An user is selecting a document. */ var SELECTING = 5; /** * The existing selection is being extended. */ var EXTEND = 6; /** * User finishes the selection. The native selection has not been updated at this timing (in Gecko). */ var END = 7; /** * The selection is settled and it is not collapsed. */ var SELECTED = 8; /** * All contents are selected. */ var SELECTED_ALL = 9; /** * The selection is right-clicked. */ var CLICKED_RIGHT = 10; var STATES = /*#__PURE__*/Object.freeze({ __proto__: null, IDLE: IDLE, COLLAPSED: COLLAPSED, START: START, CHANGED: CHANGED, UPDATE: UPDATE, SELECTING: SELECTING, EXTEND: EXTEND, END: END, SELECTED: SELECTED, SELECTED_ALL: SELECTED_ALL, CLICKED_RIGHT: CLICKED_RIGHT }); /** * The offset amount for the horizontal position of the caret. * * @since 0.1.0 */ var HORIZONTAL_OFFSET = -1; /** * The debounce duration for the `blink` method. * * @since 0.1.0 */ var BLINK_DEBOUNCE_DURATION = 30; /** * The class for creating and controlling the caret element. * * @since 0.1.0 */ var CustomCaret = /*#__PURE__*/function () { /** * The Caret constructor. * * @param Editor - An Editor instance. * @param id - An ID for the caret. * @param parent - A parent element where the caret is appended. */ function CustomCaret(Editor, id, parent) { var _this3 = this; this.Editor = Editor; this.caret = div([CLASS_CARET, CLASS_CARET + "--" + id], parent); this.blink = debounce(this.blink.bind(this), BLINK_DEBOUNCE_DURATION); Editor.event.on(EVENT_RESIZE, function () { if (_this3.position) { _this3.move(_this3.position); } }); } /** * Moves the caret to the specified position. * * @param position - A position to set as [ row, col ]. */ var _proto3 = CustomCaret.prototype; _proto3.move = function move(position) { var Measure = this.Editor.Components.Measure; var rect = Measure.getOffset(position); styles(this.caret, { top: unit(rect.top), left: unit(rect.left + HORIZONTAL_OFFSET), animation: 'none' }); this.blink(); this.position = position; } /** * Displays the caret. */ ; _proto3.show = function show() { addClass(this.caret, CLASS_ACTIVE); } /** * Hides the caret. */ ; _proto3.hide = function hide() { removeClass(this.caret, CLASS_ACTIVE); } /** * Starts the blink animation by removing the `none` value from the `animation`. */ ; _proto3.blink = function blink() { styles(this.caret, { animation: '' }); }; return CustomCaret; }(); /** * The ID of the primary caret. * * @since 0.1.0 */ var PRIMARY_CARET_ID = 'primary'; /** * The component for generating and handling carets. * * @since 0.1.0 */ var Caret = /*#__PURE__*/function (_Component2) { _inheritsLoose(Caret, _Component2); function Caret() { var _this4; _this4 = _Component2.apply(this, arguments) || this; /** * Stores the all registered Caret instances. */ _this4.carets = {}; return _this4; } /** * Mounts the component. * Uses the native caret on IE and mobile devices. * * @internal * * @param elements - A collection of essential editor elements. */ var _proto4 = Caret.prototype; _proto4.mount = function mount(elements) { _Component2.prototype.mount.call(this, elements); this.create(); if (!isIE() && !isMobile()) { this.register(PRIMARY_CARET_ID); this.primary = this.get(PRIMARY_CARET_ID); this.listen(); } } /** * Creates a wrapper element that contains carets. */ ; _proto4.create = function create() { this.wrapper = div({ "class": CLASS_CARETS, role: 'presentation', 'aria-hidden': true }, this.elements.editor); } /** * Listens to some events. */ ; _proto4.listen = function listen() { var _this5 = this; var editable = this.elements.editable; var primary = this.primary, Editor = this.Editor; this.bind(editable, 'focus', function () { if (!Editor.readOnly) { primary.show(); } }); this.bind(editable, 'blur', function () { primary.hide(); }); this.update = rafThrottle(this.update.bind(this), true); this.on(EVENT_READONLY, function (e, readOnly) { if (readOnly) { primary.hide(); } else { if (Editor.isFocused()) { _this5.update(); primary.show(); } } }); this.on(EVENT_SELECTED, this.onSelected, this); this.on(EVENT_SELECTING, this.update); } /** * Called when the selection state is changed. * * @param e - An EventBusEvent object. * @param Selection - A Selection instance. */ ; _proto4.onSelected = function onSelected(e, Selection) { if (!this.Editor.readOnly) { if (Selection.is(CHANGED, COLLAPSED, SELECTED)) { this.update(); } } } /** * Updates the primary caret position on the animation frame. */ ; _proto4.update = function update() { this.primary.move(this.Selection.get(false).end); } /** * Registers a new caret. * * @param id - The ID for the caret to register. * * @return The registered CustomCaret instance. */ ; _proto4.register = function register(id) { var carets = this.carets; assert$1(!carets[id]); var caret = new CustomCaret(this.Editor, id, this.wrapper); carets[id] = caret; return caret; } /** * Returns the primary or the specific CustomCaret instance. * * @param id - Optional. A caret ID. * * @return A CustomCaret instance if available, or otherwise `undefined`. */ ; _proto4.get = function get(id) { if (id === void 0) { id = PRIMARY_CARET_ID; } return this.carets[id]; } /** * Returns the DOMRect object of the primary caret. *