editor.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. 'use strict';
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.Formats = undefined;
  6. 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; };
  7. 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"); } }; }();
  8. 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; }; }();
  9. var _escapeStringRegexp = require('escape-string-regexp');
  10. var _escapeStringRegexp2 = _interopRequireDefault(_escapeStringRegexp);
  11. var _formats = require('./formats');
  12. var _formats2 = _interopRequireDefault(_formats);
  13. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  14. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  15. /**
  16. * TextareaEditor class.
  17. *
  18. * @param {HTMLElement} el - the textarea element to wrap around
  19. */
  20. var TextareaEditor = function () {
  21. function TextareaEditor(el) {
  22. _classCallCheck(this, TextareaEditor);
  23. this.el = el;
  24. }
  25. /**
  26. * Set or get selection range.
  27. *
  28. * @param {Array} [range]
  29. * @return {Array|TextareaEditor}
  30. */
  31. _createClass(TextareaEditor, [{
  32. key: 'range',
  33. value: function range(_range) {
  34. var el = this.el;
  35. if (_range == null) {
  36. return [el.selectionStart || 0, el.selectionEnd || 0];
  37. }
  38. this.focus();
  39. var _range2 = _slicedToArray(_range, 2);
  40. el.selectionStart = _range2[0];
  41. el.selectionEnd = _range2[1];
  42. return this;
  43. }
  44. /**
  45. * Insert given text at the current cursor position.
  46. *
  47. * @param {String} text - text to insert
  48. * @return {TextareaEditor}
  49. */
  50. }, {
  51. key: 'insert',
  52. value: function insert(text) {
  53. var inserted = true;
  54. this.el.contentEditable = true;
  55. this.focus();
  56. try {
  57. document.execCommand('insertText', false, text);
  58. } catch (e) {
  59. inserted = false;
  60. }
  61. this.el.contentEditable = false;
  62. if (inserted) return this;
  63. try {
  64. document.execCommand('ms-beginUndoUnit');
  65. } catch (e) {}
  66. var _selection = this.selection(),
  67. before = _selection.before,
  68. after = _selection.after;
  69. this.el.value = before + text + after;
  70. try {
  71. document.execCommand('ms-endUndoUnit');
  72. } catch (e) {}
  73. var event = document.createEvent('Event');
  74. event.initEvent('input', true, true);
  75. this.el.dispatchEvent(event);
  76. return this;
  77. }
  78. /**
  79. * Set foucs on the TextareaEditor's element.
  80. *
  81. * @return {TextareaEditor}
  82. */
  83. }, {
  84. key: 'focus',
  85. value: function focus() {
  86. if (document.activeElement !== this.el) this.el.focus();
  87. return this;
  88. }
  89. /**
  90. * Get selected text.
  91. *
  92. * @return {Object}
  93. * @private
  94. */
  95. }, {
  96. key: 'selection',
  97. value: function selection() {
  98. var _range3 = this.range(),
  99. _range4 = _slicedToArray(_range3, 2),
  100. start = _range4[0],
  101. end = _range4[1];
  102. var value = normalizeNewlines(this.el.value);
  103. return {
  104. before: value.slice(0, start),
  105. content: value.slice(start, end),
  106. after: value.slice(end)
  107. };
  108. }
  109. /**
  110. * Get format by name.
  111. *
  112. * @param {String|Object} format
  113. * @return {Object}
  114. * @private
  115. */
  116. }, {
  117. key: 'getFormat',
  118. value: function getFormat(format) {
  119. if ((typeof format === 'undefined' ? 'undefined' : _typeof(format)) == 'object') {
  120. return normalizeFormat(format);
  121. }
  122. if (!_formats2.default.hasOwnProperty(format)) {
  123. throw new Error('Invalid format ' + format);
  124. }
  125. return normalizeFormat(_formats2.default[format]);
  126. }
  127. /**
  128. * Toggle given `format` on current selection.
  129. * Any additional arguments are passed on to `.format()`.
  130. *
  131. * @param {String|Object} format - name of format or an object
  132. * @return {TextareaEditor}
  133. */
  134. }, {
  135. key: 'toggle',
  136. value: function toggle(format) {
  137. if (this.hasFormat(format)) return this.unformat(format);
  138. for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  139. args[_key - 1] = arguments[_key];
  140. }
  141. return this.format.apply(this, [format].concat(args));
  142. }
  143. /**
  144. * Format current selcetion with given `format`.
  145. *
  146. * @param {String|Object} name - name of format or an object
  147. * @return {TextareaEditor}
  148. */
  149. }, {
  150. key: 'format',
  151. value: function format(name) {
  152. for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
  153. args[_key2 - 1] = arguments[_key2];
  154. }
  155. var format = this.getFormat(name);
  156. var prefix = format.prefix,
  157. suffix = format.suffix,
  158. multiline = format.multiline;
  159. var _selection2 = this.selection(),
  160. before = _selection2.before,
  161. content = _selection2.content,
  162. after = _selection2.after;
  163. var lines = multiline ? content.split('\n') : [content];
  164. var _range5 = this.range(),
  165. _range6 = _slicedToArray(_range5, 2),
  166. start = _range6[0],
  167. end = _range6[1];
  168. // format lines
  169. lines = lines.map(function (line, i) {
  170. var pval = maybeCall.apply(undefined, [prefix.value, line, i + 1].concat(args));
  171. var sval = maybeCall.apply(undefined, [suffix.value, line, i + 1].concat(args));
  172. if (!multiline || !content.length) {
  173. start += pval.length;
  174. end += pval.length;
  175. } else {
  176. end += pval.length + sval.length;
  177. }
  178. return pval + line + sval;
  179. });
  180. var insert = lines.join('\n');
  181. // newlines before and after block
  182. if (format.block) {
  183. var nlb = matchLength(before, /\n+$/);
  184. var nla = matchLength(after, /^\n+/);
  185. if (before) {
  186. while (nlb < 2) {
  187. insert = '\n' + insert;
  188. start++;
  189. end++;
  190. nlb++;
  191. }
  192. }
  193. if (after) {
  194. while (nla < 2) {
  195. insert = insert + '\n';
  196. nla++;
  197. }
  198. }
  199. }
  200. this.insert(insert);
  201. this.range([start, end]);
  202. return this;
  203. }
  204. /**
  205. * Remove given `format` from current selection.
  206. *
  207. * @param {String|Object} name - name of format or an object
  208. * @return {TextareaEditor}
  209. */
  210. }, {
  211. key: 'unformat',
  212. value: function unformat(name) {
  213. if (!this.hasFormat(name)) return this;
  214. var format = this.getFormat(name);
  215. var prefix = format.prefix,
  216. suffix = format.suffix,
  217. multiline = format.multiline;
  218. var _selection3 = this.selection(),
  219. before = _selection3.before,
  220. content = _selection3.content,
  221. after = _selection3.after;
  222. var lines = multiline ? content.split('\n') : [content];
  223. var _range7 = this.range(),
  224. _range8 = _slicedToArray(_range7, 2),
  225. start = _range8[0],
  226. end = _range8[1];
  227. // If this is not a multiline format, include prefixes and suffixes just
  228. // outside the selection.
  229. if ((!multiline || lines.length == 1) && hasSuffix(before, prefix) && hasPrefix(after, suffix)) {
  230. start -= suffixLength(before, prefix);
  231. end += prefixLength(after, suffix);
  232. this.range([start, end]);
  233. lines = [this.selection().content];
  234. }
  235. // remove formatting from lines
  236. lines = lines.map(function (line) {
  237. var plen = prefixLength(line, prefix);
  238. var slen = suffixLength(line, suffix);
  239. return line.slice(plen, line.length - slen);
  240. });
  241. // insert and set selection
  242. var insert = lines.join('\n');
  243. this.insert(insert);
  244. this.range([start, start + insert.length]);
  245. return this;
  246. }
  247. /**
  248. * Check if current seletion has given format.
  249. *
  250. * @param {String|Object} name - name of format or an object
  251. * @return {Boolean}
  252. */
  253. }, {
  254. key: 'hasFormat',
  255. value: function hasFormat(name) {
  256. var format = this.getFormat(name);
  257. var prefix = format.prefix,
  258. suffix = format.suffix,
  259. multiline = format.multiline;
  260. var _selection4 = this.selection(),
  261. before = _selection4.before,
  262. content = _selection4.content,
  263. after = _selection4.after;
  264. var lines = content.split('\n');
  265. // prefix and suffix outside selection
  266. if (!multiline || lines.length == 1) {
  267. return hasSuffix(before, prefix) && hasPrefix(after, suffix) || hasPrefix(content, prefix) && hasSuffix(content, suffix);
  268. }
  269. // check which line(s) are formatted
  270. var formatted = lines.filter(function (line) {
  271. return hasPrefix(line, prefix) && hasSuffix(line, suffix);
  272. });
  273. return formatted.length === lines.length;
  274. }
  275. }]);
  276. return TextareaEditor;
  277. }();
  278. // Expose formats
  279. exports.default = TextareaEditor;
  280. exports.Formats = _formats2.default;
  281. /**
  282. * Check if given prefix is present.
  283. * @private
  284. */
  285. function hasPrefix(text, prefix) {
  286. var exp = new RegExp('^' + prefix.pattern);
  287. var result = exp.test(text);
  288. if (prefix.antipattern) {
  289. var _exp = new RegExp('^' + prefix.antipattern);
  290. result = result && !_exp.test(text);
  291. }
  292. return result;
  293. }
  294. /**
  295. * Check if given suffix is present.
  296. * @private
  297. */
  298. function hasSuffix(text, suffix) {
  299. var exp = new RegExp(suffix.pattern + '$');
  300. var result = exp.test(text);
  301. if (suffix.antipattern) {
  302. var _exp2 = new RegExp(suffix.antipattern + '$');
  303. result = result && !_exp2.test(text);
  304. }
  305. return result;
  306. }
  307. /**
  308. * Get length of match.
  309. * @private
  310. */
  311. function matchLength(text, exp) {
  312. var match = text.match(exp);
  313. return match ? match[0].length : 0;
  314. }
  315. /**
  316. * Get prefix length.
  317. * @private
  318. */
  319. function prefixLength(text, prefix) {
  320. var exp = new RegExp('^' + prefix.pattern);
  321. return matchLength(text, exp);
  322. }
  323. /**
  324. * Get suffix length.
  325. * @private
  326. */
  327. function suffixLength(text, suffix) {
  328. var exp = new RegExp(suffix.pattern + '$');
  329. return matchLength(text, exp);
  330. }
  331. /**
  332. * Normalize newlines.
  333. * @private
  334. */
  335. function normalizeNewlines(str) {
  336. return str.replace('\r\n', '\n');
  337. }
  338. /**
  339. * Normalize format.
  340. * @private
  341. */
  342. function normalizeFormat(format) {
  343. var clone = Object.assign({}, format);
  344. clone.prefix = normalizePrefixSuffix(format.prefix);
  345. clone.suffix = normalizePrefixSuffix(format.suffix);
  346. return clone;
  347. }
  348. /**
  349. * Normalize prefixes and suffixes.
  350. * @private
  351. */
  352. function normalizePrefixSuffix() {
  353. var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
  354. if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) == 'object') return value;
  355. return {
  356. value: value,
  357. pattern: (0, _escapeStringRegexp2.default)(value)
  358. };
  359. }
  360. /**
  361. * Call if function.
  362. * @private
  363. */
  364. function maybeCall(value) {
  365. for (var _len3 = arguments.length, args = Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
  366. args[_key3 - 1] = arguments[_key3];
  367. }
  368. return typeof value == 'function' ? value.apply(undefined, args) : value;
  369. }