'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.Formats = undefined; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); var _createClass = function () { 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _escapeStringRegexp = require('escape-string-regexp'); var _escapeStringRegexp2 = _interopRequireDefault(_escapeStringRegexp); var _formats = require('./formats'); var _formats2 = _interopRequireDefault(_formats); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * TextareaEditor class. * * @param {HTMLElement} el - the textarea element to wrap around */ var TextareaEditor = function () { function TextareaEditor(el) { _classCallCheck(this, TextareaEditor); this.el = el; } /** * Set or get selection range. * * @param {Array} [range] * @return {Array|TextareaEditor} */ _createClass(TextareaEditor, [{ key: 'range', value: function range(_range) { var el = this.el; if (_range == null) { return [el.selectionStart || 0, el.selectionEnd || 0]; } this.focus(); var _range2 = _slicedToArray(_range, 2); el.selectionStart = _range2[0]; el.selectionEnd = _range2[1]; return this; } /** * Insert given text at the current cursor position. * * @param {String} text - text to insert * @return {TextareaEditor} */ }, { key: 'insert', value: function insert(text) { var inserted = true; this.el.contentEditable = true; this.focus(); try { document.execCommand('insertText', false, text); } catch (e) { inserted = false; } this.el.contentEditable = false; if (inserted) return this; try { document.execCommand('ms-beginUndoUnit'); } catch (e) {} var _selection = this.selection(), before = _selection.before, after = _selection.after; this.el.value = before + text + after; try { document.execCommand('ms-endUndoUnit'); } catch (e) {} var event = document.createEvent('Event'); event.initEvent('input', true, true); this.el.dispatchEvent(event); return this; } /** * Set foucs on the TextareaEditor's element. * * @return {TextareaEditor} */ }, { key: 'focus', value: function focus() { if (document.activeElement !== this.el) this.el.focus(); return this; } /** * Get selected text. * * @return {Object} * @private */ }, { key: 'selection', value: function selection() { var _range3 = this.range(), _range4 = _slicedToArray(_range3, 2), start = _range4[0], end = _range4[1]; var value = normalizeNewlines(this.el.value); return { before: value.slice(0, start), content: value.slice(start, end), after: value.slice(end) }; } /** * Get format by name. * * @param {String|Object} format * @return {Object} * @private */ }, { key: 'getFormat', value: function getFormat(format) { if ((typeof format === 'undefined' ? 'undefined' : _typeof(format)) == 'object') { return normalizeFormat(format); } if (!_formats2.default.hasOwnProperty(format)) { throw new Error('Invalid format ' + format); } return normalizeFormat(_formats2.default[format]); } /** * Toggle given `format` on current selection. * Any additional arguments are passed on to `.format()`. * * @param {String|Object} format - name of format or an object * @return {TextareaEditor} */ }, { key: 'toggle', value: function toggle(format) { if (this.hasFormat(format)) return this.unformat(format); for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } return this.format.apply(this, [format].concat(args)); } /** * Format current selcetion with given `format`. * * @param {String|Object} name - name of format or an object * @return {TextareaEditor} */ }, { key: 'format', value: function format(name) { for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { args[_key2 - 1] = arguments[_key2]; } var format = this.getFormat(name); var prefix = format.prefix, suffix = format.suffix, multiline = format.multiline; var _selection2 = this.selection(), before = _selection2.before, content = _selection2.content, after = _selection2.after; var lines = multiline ? content.split('\n') : [content]; var _range5 = this.range(), _range6 = _slicedToArray(_range5, 2), start = _range6[0], end = _range6[1]; // format lines lines = lines.map(function (line, i) { var pval = maybeCall.apply(undefined, [prefix.value, line, i + 1].concat(args)); var sval = maybeCall.apply(undefined, [suffix.value, line, i + 1].concat(args)); if (!multiline || !content.length) { start += pval.length; end += pval.length; } else { end += pval.length + sval.length; } return pval + line + sval; }); var insert = lines.join('\n'); // newlines before and after block if (format.block) { var nlb = matchLength(before, /\n+$/); var nla = matchLength(after, /^\n+/); if (before) { while (nlb < 2) { insert = '\n' + insert; start++; end++; nlb++; } } if (after) { while (nla < 2) { insert = insert + '\n'; nla++; } } } this.insert(insert); this.range([start, end]); return this; } /** * Remove given `format` from current selection. * * @param {String|Object} name - name of format or an object * @return {TextareaEditor} */ }, { key: 'unformat', value: function unformat(name) { if (!this.hasFormat(name)) return this; var format = this.getFormat(name); var prefix = format.prefix, suffix = format.suffix, multiline = format.multiline; var _selection3 = this.selection(), before = _selection3.before, content = _selection3.content, after = _selection3.after; var lines = multiline ? content.split('\n') : [content]; var _range7 = this.range(), _range8 = _slicedToArray(_range7, 2), start = _range8[0], end = _range8[1]; // If this is not a multiline format, include prefixes and suffixes just // outside the selection. if ((!multiline || lines.length == 1) && hasSuffix(before, prefix) && hasPrefix(after, suffix)) { start -= suffixLength(before, prefix); end += prefixLength(after, suffix); this.range([start, end]); lines = [this.selection().content]; } // remove formatting from lines lines = lines.map(function (line) { var plen = prefixLength(line, prefix); var slen = suffixLength(line, suffix); return line.slice(plen, line.length - slen); }); // insert and set selection var insert = lines.join('\n'); this.insert(insert); this.range([start, start + insert.length]); return this; } /** * Check if current seletion has given format. * * @param {String|Object} name - name of format or an object * @return {Boolean} */ }, { key: 'hasFormat', value: function hasFormat(name) { var format = this.getFormat(name); var prefix = format.prefix, suffix = format.suffix, multiline = format.multiline; var _selection4 = this.selection(), before = _selection4.before, content = _selection4.content, after = _selection4.after; var lines = content.split('\n'); // prefix and suffix outside selection if (!multiline || lines.length == 1) { return hasSuffix(before, prefix) && hasPrefix(after, suffix) || hasPrefix(content, prefix) && hasSuffix(content, suffix); } // check which line(s) are formatted var formatted = lines.filter(function (line) { return hasPrefix(line, prefix) && hasSuffix(line, suffix); }); return formatted.length === lines.length; } }]); return TextareaEditor; }(); // Expose formats exports.default = TextareaEditor; exports.Formats = _formats2.default; /** * Check if given prefix is present. * @private */ function hasPrefix(text, prefix) { var exp = new RegExp('^' + prefix.pattern); var result = exp.test(text); if (prefix.antipattern) { var _exp = new RegExp('^' + prefix.antipattern); result = result && !_exp.test(text); } return result; } /** * Check if given suffix is present. * @private */ function hasSuffix(text, suffix) { var exp = new RegExp(suffix.pattern + '$'); var result = exp.test(text); if (suffix.antipattern) { var _exp2 = new RegExp(suffix.antipattern + '$'); result = result && !_exp2.test(text); } return result; } /** * Get length of match. * @private */ function matchLength(text, exp) { var match = text.match(exp); return match ? match[0].length : 0; } /** * Get prefix length. * @private */ function prefixLength(text, prefix) { var exp = new RegExp('^' + prefix.pattern); return matchLength(text, exp); } /** * Get suffix length. * @private */ function suffixLength(text, suffix) { var exp = new RegExp(suffix.pattern + '$'); return matchLength(text, exp); } /** * Normalize newlines. * @private */ function normalizeNewlines(str) { return str.replace('\r\n', '\n'); } /** * Normalize format. * @private */ function normalizeFormat(format) { var clone = Object.assign({}, format); clone.prefix = normalizePrefixSuffix(format.prefix); clone.suffix = normalizePrefixSuffix(format.suffix); return clone; } /** * Normalize prefixes and suffixes. * @private */ function normalizePrefixSuffix() { var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) == 'object') return value; return { value: value, pattern: (0, _escapeStringRegexp2.default)(value) }; } /** * Call if function. * @private */ function maybeCall(value) { for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) { args[_key3 - 1] = arguments[_key3]; } return typeof value == 'function' ? value.apply(undefined, args) : value; }