tiny-markdown-editor 0.1.5 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/TinyMDE.js CHANGED
@@ -1,56 +1,13 @@
1
1
  "use strict";
2
2
 
3
- require("core-js/modules/es.array.slice.js");
4
- require("core-js/modules/es.object.to-string.js");
5
- require("core-js/modules/es.array.from.js");
6
- require("core-js/modules/es.string.iterator.js");
7
- require("core-js/modules/es.symbol.js");
8
- require("core-js/modules/es.symbol.description.js");
9
- require("core-js/modules/es.symbol.iterator.js");
10
- require("core-js/modules/es.array.iterator.js");
11
- require("core-js/modules/web.dom-collections.iterator.js");
12
- require("core-js/modules/es.object.set-prototype-of.js");
13
- require("core-js/modules/es.regexp.constructor.js");
14
- require("core-js/modules/es.regexp.to-string.js");
15
- require("core-js/modules/es.weak-map.js");
16
- require("core-js/modules/es.array.reduce.js");
17
- require("core-js/modules/es.object.keys.js");
18
- require("core-js/modules/es.symbol.replace.js");
19
3
  Object.defineProperty(exports, "__esModule", {
20
4
  value: true
21
5
  });
22
6
  exports.default = void 0;
23
- require("core-js/modules/es.regexp.exec.js");
24
- require("core-js/modules/es.string.split.js");
25
- require("core-js/modules/es.string.replace.js");
26
- require("core-js/modules/es.string.match.js");
27
- require("core-js/modules/es.string.trim.js");
28
- require("core-js/modules/es.array.concat.js");
29
- require("core-js/modules/es.parse-int.js");
30
- require("core-js/modules/es.array.splice.js");
31
- require("core-js/modules/es.array.includes.js");
32
- require("core-js/modules/es.string.includes.js");
33
- require("core-js/modules/es.object.assign.js");
34
- require("core-js/modules/es.object.define-property.js");
35
7
  var _grammar = require("./grammar");
36
- function _wrapRegExp() { _wrapRegExp = function _wrapRegExp(re, groups) { return new BabelRegExp(re, void 0, groups); }; var _super = RegExp.prototype, _groups = new WeakMap(); function BabelRegExp(re, flags, groups) { var _this = new RegExp(re, flags); return _groups.set(_this, groups || _groups.get(re)), _setPrototypeOf(_this, BabelRegExp.prototype); } function buildGroups(result, re) { var g = _groups.get(re); return Object.keys(g).reduce(function (groups, name) { var i = g[name]; if ("number" == typeof i) groups[name] = result[i];else { for (var k = 0; void 0 === result[i[k]] && k + 1 < i.length;) { k++; } groups[name] = result[i[k]]; } return groups; }, Object.create(null)); } return _inherits(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (str) { var result = _super.exec.call(this, str); if (result) { result.groups = buildGroups(result, this); var indices = result.indices; indices && (indices.groups = buildGroups(indices, this)); } return result; }, BabelRegExp.prototype[Symbol.replace] = function (str, substitution) { if ("string" == typeof substitution) { var groups = _groups.get(this); return _super[Symbol.replace].call(this, str, substitution.replace(/\$<([^>]+)>/g, function (_, name) { var group = groups[name]; return "$" + (Array.isArray(group) ? group.join("$") : group); })); } if ("function" == typeof substitution) { var _this = this; return _super[Symbol.replace].call(this, str, function () { var args = arguments; return "object" != _typeof(args[args.length - 1]) && (args = [].slice.call(args)).push(buildGroups(args, _this)), substitution.apply(this, args); }); } return _super[Symbol.replace].call(this, str, substitution); }, _wrapRegExp.apply(this, arguments); }
37
- function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
38
- function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
39
- function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
40
- function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
41
- function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
42
- function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
43
- function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
44
- function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
45
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
46
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
47
- function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
48
- 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); } }
49
- function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
50
- var Editor = /*#__PURE__*/function () {
51
- function Editor() {
52
- var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
53
- _classCallCheck(this, Editor);
8
+ class Editor {
9
+ constructor() {
10
+ let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
54
11
  this.e = null;
55
12
  this.textarea = null;
56
13
  this.lines = [];
@@ -65,7 +22,7 @@ var Editor = /*#__PURE__*/function () {
65
22
  change: [],
66
23
  selection: []
67
24
  };
68
- var element = props.element;
25
+ let element = props.element;
69
26
  this.textarea = props.textarea;
70
27
  if (this.textarea && !this.textarea.tagName) {
71
28
  this.textarea = document.getElementById(this.textarea);
@@ -93,1739 +50,1596 @@ var Editor = /*#__PURE__*/function () {
93
50
  * Creates the editor element inside the target element of the DOM tree
94
51
  * @param element The target element of the DOM tree
95
52
  */
96
- _createClass(Editor, [{
97
- key: "createEditorElement",
98
- value: function createEditorElement(element) {
99
- var _this = this;
100
- this.e = document.createElement('div');
101
- this.e.className = 'TinyMDE';
102
- this.e.contentEditable = true;
103
- // The following is important for formatting purposes, but also since otherwise the browser replaces subsequent spaces with &nbsp; &nbsp;
104
- // That breaks a lot of stuff, so we do this here and not in CSS—therefore, you don't have to remember to but this in the CSS file
105
- this.e.style.whiteSpace = 'pre-wrap';
106
- // Avoid formatting (B / I / U) popping up on iOS
107
- this.e.style.webkitUserModify = 'read-write-plaintext-only';
108
- if (this.textarea && this.textarea.parentNode == element && this.textarea.nextSibling) {
109
- element.insertBefore(this.e, this.textarea.nextSibling);
110
- } else {
111
- element.appendChild(this.e);
112
- }
113
- this.e.addEventListener("input", function (e) {
114
- return _this.handleInputEvent(e);
115
- });
116
- // this.e.addEventListener("keydown", (e) => this.handleKeydownEvent(e));
117
- document.addEventListener("selectionchange", function (e) {
118
- return _this.handleSelectionChangeEvent(e);
119
- });
120
- this.e.addEventListener("paste", function (e) {
121
- return _this.handlePaste(e);
122
- });
123
- // this.e.addEventListener('keydown', (e) => this.handleKeyDown(e));
124
- this.lineElements = this.e.childNodes; // this will automatically update
53
+ createEditorElement(element) {
54
+ this.e = document.createElement('div');
55
+ this.e.className = 'TinyMDE';
56
+ this.e.contentEditable = true;
57
+ // The following is important for formatting purposes, but also since otherwise the browser replaces subsequent spaces with &nbsp; &nbsp;
58
+ // That breaks a lot of stuff, so we do this here and not in CSS—therefore, you don't have to remember to but this in the CSS file
59
+ this.e.style.whiteSpace = 'pre-wrap';
60
+ // Avoid formatting (B / I / U) popping up on iOS
61
+ this.e.style.webkitUserModify = 'read-write-plaintext-only';
62
+ if (this.textarea && this.textarea.parentNode == element && this.textarea.nextSibling) {
63
+ element.insertBefore(this.e, this.textarea.nextSibling);
64
+ } else {
65
+ element.appendChild(this.e);
125
66
  }
67
+ this.e.addEventListener("input", e => this.handleInputEvent(e));
68
+ // this.e.addEventListener("keydown", (e) => this.handleKeydownEvent(e));
69
+ document.addEventListener("selectionchange", e => this.handleSelectionChangeEvent(e));
70
+ this.e.addEventListener("paste", e => this.handlePaste(e));
71
+ // this.e.addEventListener('keydown', (e) => this.handleKeyDown(e));
72
+ this.lineElements = this.e.childNodes; // this will automatically update
73
+ }
126
74
 
127
- /**
128
- * Sets the editor content.
129
- * @param {string} content The new Markdown content
130
- */
131
- }, {
132
- key: "setContent",
133
- value: function setContent(content) {
134
- // Delete any existing content
135
- while (this.e.firstChild) {
136
- this.e.removeChild(this.e.firstChild);
137
- }
138
- this.lines = content.split(/(?:\r\n|\r|\n)/);
139
- this.lineDirty = [];
140
- for (var lineNum = 0; lineNum < this.lines.length; lineNum++) {
141
- var le = document.createElement('div');
142
- this.e.appendChild(le);
143
- this.lineDirty.push(true);
144
- }
145
- this.lineTypes = new Array(this.lines.length);
146
- this.updateFormatting();
147
- this.fireChange();
75
+ /**
76
+ * Sets the editor content.
77
+ * @param {string} content The new Markdown content
78
+ */
79
+ setContent(content) {
80
+ // Delete any existing content
81
+ while (this.e.firstChild) {
82
+ this.e.removeChild(this.e.firstChild);
148
83
  }
149
-
150
- /**
151
- * Gets the editor content as a Markdown string.
152
- * @returns {string} The editor content as a markdown string
153
- */
154
- }, {
155
- key: "getContent",
156
- value: function getContent() {
157
- return this.lines.join('\n');
84
+ this.lines = content.split(/(?:\r\n|\r|\n)/);
85
+ this.lineDirty = [];
86
+ for (let lineNum = 0; lineNum < this.lines.length; lineNum++) {
87
+ let le = document.createElement('div');
88
+ this.e.appendChild(le);
89
+ this.lineDirty.push(true);
158
90
  }
91
+ this.lineTypes = new Array(this.lines.length);
92
+ this.updateFormatting();
93
+ this.fireChange();
94
+ }
159
95
 
160
- /**
161
- * This is the main method to update the formatting (from this.lines to HTML output)
162
- */
163
- }, {
164
- key: "updateFormatting",
165
- value: function updateFormatting() {
166
- // First, parse line types. This will update this.lineTypes, this.lineReplacements, and this.lineCaptures
167
- // We don't apply the formatting yet
168
- this.updateLineTypes();
169
- // Collect any valid link labels from link reference definitions—we need that for formatting to determine what's a valid link
170
- this.updateLinkLabels();
171
- // Now, apply the formatting
172
- this.applyLineTypes();
173
- }
96
+ /**
97
+ * Gets the editor content as a Markdown string.
98
+ * @returns {string} The editor content as a markdown string
99
+ */
100
+ getContent() {
101
+ return this.lines.join('\n');
102
+ }
174
103
 
175
- /**
176
- * Updates this.linkLabels: For every link reference definition (line type TMLinkReferenceDefinition), we collect the label
177
- */
178
- }, {
179
- key: "updateLinkLabels",
180
- value: function updateLinkLabels() {
181
- this.linkLabels = [];
182
- for (var l = 0; l < this.lines.length; l++) {
183
- if (this.lineTypes[l] == 'TMLinkReferenceDefinition') {
184
- this.linkLabels.push(this.lineCaptures[l][_grammar.lineGrammar.TMLinkReferenceDefinition.labelPlaceholder]);
185
- }
186
- }
187
- }
104
+ /**
105
+ * This is the main method to update the formatting (from this.lines to HTML output)
106
+ */
107
+ updateFormatting() {
108
+ // First, parse line types. This will update this.lineTypes, this.lineReplacements, and this.lineCaptures
109
+ // We don't apply the formatting yet
110
+ this.updateLineTypes();
111
+ // Collect any valid link labels from link reference definitions—we need that for formatting to determine what's a valid link
112
+ this.updateLinkLabels();
113
+ // Now, apply the formatting
114
+ this.applyLineTypes();
115
+ }
188
116
 
189
- /**
190
- * Helper function to replace placeholders from a RegExp capture. The replacement string can contain regular dollar placeholders (e.g., $1),
191
- * which are interpreted like in String.replace(), but also double dollar placeholders ($$1). In the case of double dollar placeholders,
192
- * Markdown inline grammar is applied on the content of the captured subgroup, i.e., $$1 processes inline Markdown grammar in the content of the
193
- * first captured subgroup, and replaces `$$1` with the result.
194
- *
195
- * @param {string} replacement The replacement string, including placeholders.
196
- * @param capture The result of a RegExp.exec() call
197
- * @returns The replacement string, with placeholders replaced from the capture result.
198
- */
199
- }, {
200
- key: "replace",
201
- value: function replace(replacement, capture) {
202
- var _this2 = this;
203
- return replacement.replace(/\$\$([0-9])/g, function (str, p1) {
204
- return "<span class=\"TMInlineFormatted\">".concat(_this2.processInlineStyles(capture[p1]), "</span>");
205
- }).replace(/\$([0-9])/g, function (str, p1) {
206
- return (0, _grammar.htmlescape)(capture[p1]);
207
- });
117
+ /**
118
+ * Updates this.linkLabels: For every link reference definition (line type TMLinkReferenceDefinition), we collect the label
119
+ */
120
+ updateLinkLabels() {
121
+ this.linkLabels = [];
122
+ for (let l = 0; l < this.lines.length; l++) {
123
+ if (this.lineTypes[l] == 'TMLinkReferenceDefinition') {
124
+ this.linkLabels.push(this.lineCaptures[l][_grammar.lineGrammar.TMLinkReferenceDefinition.labelPlaceholder]);
125
+ }
208
126
  }
127
+ }
209
128
 
210
- /**
211
- * Applies the line types (from this.lineTypes as well as the capture result in this.lineReplacements and this.lineCaptures)
212
- * and processes inline formatting for all lines.
213
- */
214
- }, {
215
- key: "applyLineTypes",
216
- value: function applyLineTypes() {
217
- for (var lineNum = 0; lineNum < this.lines.length; lineNum++) {
218
- if (this.lineDirty[lineNum]) {
219
- var contentHTML = this.replace(this.lineReplacements[lineNum], this.lineCaptures[lineNum]);
220
- // this.lineHTML[lineNum] = (contentHTML == '' ? '<br />' : contentHTML); // Prevent empty elements which can't be selected etc.
221
- this.lineElements[lineNum].className = this.lineTypes[lineNum];
222
- this.lineElements[lineNum].removeAttribute('style');
223
- this.lineElements[lineNum].innerHTML = contentHTML == '' ? '<br />' : contentHTML; // Prevent empty elements which can't be selected etc.
224
- }
129
+ /**
130
+ * Helper function to replace placeholders from a RegExp capture. The replacement string can contain regular dollar placeholders (e.g., $1),
131
+ * which are interpreted like in String.replace(), but also double dollar placeholders ($$1). In the case of double dollar placeholders,
132
+ * Markdown inline grammar is applied on the content of the captured subgroup, i.e., $$1 processes inline Markdown grammar in the content of the
133
+ * first captured subgroup, and replaces `$$1` with the result.
134
+ *
135
+ * @param {string} replacement The replacement string, including placeholders.
136
+ * @param capture The result of a RegExp.exec() call
137
+ * @returns The replacement string, with placeholders replaced from the capture result.
138
+ */
139
+ replace(replacement, capture) {
140
+ return replacement.replace(/(\${1,2})([0-9])/g, (str, p1, p2) => {
141
+ if (p1 == '$') return (0, _grammar.htmlescape)(capture[p2]);else return `<span class="TMInlineFormatted">${this.processInlineStyles(capture[p2])}</span>`;
142
+ });
143
+ }
225
144
 
226
- this.lineElements[lineNum].dataset.lineNum = lineNum;
145
+ /**
146
+ * Applies the line types (from this.lineTypes as well as the capture result in this.lineReplacements and this.lineCaptures)
147
+ * and processes inline formatting for all lines.
148
+ */
149
+ applyLineTypes() {
150
+ for (let lineNum = 0; lineNum < this.lines.length; lineNum++) {
151
+ if (this.lineDirty[lineNum]) {
152
+ let contentHTML = this.replace(this.lineReplacements[lineNum], this.lineCaptures[lineNum]);
153
+ // this.lineHTML[lineNum] = (contentHTML == '' ? '<br />' : contentHTML); // Prevent empty elements which can't be selected etc.
154
+ this.lineElements[lineNum].className = this.lineTypes[lineNum];
155
+ this.lineElements[lineNum].removeAttribute('style');
156
+ this.lineElements[lineNum].innerHTML = contentHTML == '' ? '<br />' : contentHTML; // Prevent empty elements which can't be selected etc.
227
157
  }
158
+
159
+ this.lineElements[lineNum].dataset.lineNum = lineNum;
228
160
  }
161
+ }
229
162
 
230
- /**
231
- * Determines line types for all lines based on the line / block grammar. Captures the results of the respective line
232
- * grammar regular expressions.
233
- * Updates this.lineTypes, this.lineCaptures, and this.lineReplacements.
234
- */
235
- }, {
236
- key: "updateLineTypes",
237
- value: function updateLineTypes() {
238
- var codeBlockType = false;
239
- var codeBlockSeqLength = 0;
240
- var htmlBlock = false;
241
- for (var lineNum = 0; lineNum < this.lines.length; lineNum++) {
242
- var lineType = 'TMPara';
243
- var lineCapture = [this.lines[lineNum]];
244
- var lineReplacement = '$$0'; // Default replacement for paragraph: Inline format the entire line
245
-
246
- // Check ongoing code blocks
247
- // if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceBacktickOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeBacktick')) {
248
- if (codeBlockType == 'TMCodeFenceBacktickOpen') {
249
- // We're in a backtick-fenced code block, check if the current line closes it
250
- var capture = _grammar.lineGrammar.TMCodeFenceBacktickClose.regexp.exec(this.lines[lineNum]);
251
- if (capture && capture.groups['seq'].length >= codeBlockSeqLength) {
252
- lineType = 'TMCodeFenceBacktickClose';
253
- lineReplacement = _grammar.lineGrammar.TMCodeFenceBacktickClose.replacement;
254
- lineCapture = capture;
255
- codeBlockType = false;
256
- } else {
257
- lineType = 'TMFencedCodeBacktick';
258
- lineReplacement = '$0';
259
- lineCapture = [this.lines[lineNum]];
260
- }
163
+ /**
164
+ * Determines line types for all lines based on the line / block grammar. Captures the results of the respective line
165
+ * grammar regular expressions.
166
+ * Updates this.lineTypes, this.lineCaptures, and this.lineReplacements.
167
+ */
168
+ updateLineTypes() {
169
+ let codeBlockType = false;
170
+ let codeBlockSeqLength = 0;
171
+ let htmlBlock = false;
172
+ for (let lineNum = 0; lineNum < this.lines.length; lineNum++) {
173
+ let lineType = 'TMPara';
174
+ let lineCapture = [this.lines[lineNum]];
175
+ let lineReplacement = '$$0'; // Default replacement for paragraph: Inline format the entire line
176
+
177
+ // Check ongoing code blocks
178
+ // if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceBacktickOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeBacktick')) {
179
+ if (codeBlockType == 'TMCodeFenceBacktickOpen') {
180
+ // We're in a backtick-fenced code block, check if the current line closes it
181
+ let capture = _grammar.lineGrammar.TMCodeFenceBacktickClose.regexp.exec(this.lines[lineNum]);
182
+ if (capture && capture.groups['seq'].length >= codeBlockSeqLength) {
183
+ lineType = 'TMCodeFenceBacktickClose';
184
+ lineReplacement = _grammar.lineGrammar.TMCodeFenceBacktickClose.replacement;
185
+ lineCapture = capture;
186
+ codeBlockType = false;
187
+ } else {
188
+ lineType = 'TMFencedCodeBacktick';
189
+ lineReplacement = '$0';
190
+ lineCapture = [this.lines[lineNum]];
261
191
  }
262
- // if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceTildeOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeTilde')) {
263
- else if (codeBlockType == 'TMCodeFenceTildeOpen') {
264
- // We're in a tilde-fenced code block
265
- var _capture = _grammar.lineGrammar.TMCodeFenceTildeClose.regexp.exec(this.lines[lineNum]);
266
- if (_capture && _capture.groups['seq'].length >= codeBlockSeqLength) {
267
- lineType = 'TMCodeFenceTildeClose';
268
- lineReplacement = _grammar.lineGrammar.TMCodeFenceTildeClose.replacement;
269
- lineCapture = _capture;
270
- codeBlockType = false;
271
- } else {
272
- lineType = 'TMFencedCodeTilde';
273
- lineReplacement = '$0';
274
- lineCapture = [this.lines[lineNum]];
275
- }
192
+ }
193
+ // if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceTildeOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeTilde')) {
194
+ else if (codeBlockType == 'TMCodeFenceTildeOpen') {
195
+ // We're in a tilde-fenced code block
196
+ let capture = _grammar.lineGrammar.TMCodeFenceTildeClose.regexp.exec(this.lines[lineNum]);
197
+ if (capture && capture.groups['seq'].length >= codeBlockSeqLength) {
198
+ lineType = 'TMCodeFenceTildeClose';
199
+ lineReplacement = _grammar.lineGrammar.TMCodeFenceTildeClose.replacement;
200
+ lineCapture = capture;
201
+ codeBlockType = false;
202
+ } else {
203
+ lineType = 'TMFencedCodeTilde';
204
+ lineReplacement = '$0';
205
+ lineCapture = [this.lines[lineNum]];
276
206
  }
207
+ }
277
208
 
278
- // Check HTML block types
279
- if (lineType == 'TMPara' && htmlBlock === false) {
280
- var _iterator = _createForOfIteratorHelper(_grammar.htmlBlockGrammar),
281
- _step;
282
- try {
283
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
284
- var htmlBlockType = _step.value;
285
- if (this.lines[lineNum].match(htmlBlockType.start)) {
286
- // Matching start condition. Check if this tag can start here (not all start conditions allow breaking a paragraph).
287
- if (htmlBlockType.paraInterrupt || lineNum == 0 || !(this.lineTypes[lineNum - 1] == 'TMPara' || this.lineTypes[lineNum - 1] == 'TMUL' || this.lineTypes[lineNum - 1] == 'TMOL' || this.lineTypes[lineNum - 1] == 'TMBlockquote')) {
288
- htmlBlock = htmlBlockType;
289
- break;
290
- }
291
- }
209
+ // Check HTML block types
210
+ if (lineType == 'TMPara' && htmlBlock === false) {
211
+ for (let htmlBlockType of _grammar.htmlBlockGrammar) {
212
+ if (this.lines[lineNum].match(htmlBlockType.start)) {
213
+ // Matching start condition. Check if this tag can start here (not all start conditions allow breaking a paragraph).
214
+ if (htmlBlockType.paraInterrupt || lineNum == 0 || !(this.lineTypes[lineNum - 1] == 'TMPara' || this.lineTypes[lineNum - 1] == 'TMUL' || this.lineTypes[lineNum - 1] == 'TMOL' || this.lineTypes[lineNum - 1] == 'TMBlockquote')) {
215
+ htmlBlock = htmlBlockType;
216
+ break;
292
217
  }
293
- } catch (err) {
294
- _iterator.e(err);
295
- } finally {
296
- _iterator.f();
297
218
  }
298
219
  }
299
- if (htmlBlock !== false) {
300
- lineType = 'TMHTMLBlock';
301
- lineReplacement = '$0'; // No formatting in TMHTMLBlock
302
- lineCapture = [this.lines[lineNum]]; // This should already be set but better safe than sorry
303
-
304
- // Check if HTML block should be closed
305
- if (htmlBlock.end) {
306
- // Specific end condition
307
- if (this.lines[lineNum].match(htmlBlock.end)) {
308
- htmlBlock = false;
309
- }
310
- } else {
311
- // No specific end condition, ends with blank line
312
- if (lineNum == this.lines.length - 1 || this.lines[lineNum + 1].match(_grammar.lineGrammar.TMBlankLine.regexp)) {
313
- htmlBlock = false;
314
- }
220
+ }
221
+ if (htmlBlock !== false) {
222
+ lineType = 'TMHTMLBlock';
223
+ lineReplacement = '$0'; // No formatting in TMHTMLBlock
224
+ lineCapture = [this.lines[lineNum]]; // This should already be set but better safe than sorry
225
+
226
+ // Check if HTML block should be closed
227
+ if (htmlBlock.end) {
228
+ // Specific end condition
229
+ if (this.lines[lineNum].match(htmlBlock.end)) {
230
+ htmlBlock = false;
231
+ }
232
+ } else {
233
+ // No specific end condition, ends with blank line
234
+ if (lineNum == this.lines.length - 1 || this.lines[lineNum + 1].match(_grammar.lineGrammar.TMBlankLine.regexp)) {
235
+ htmlBlock = false;
315
236
  }
316
237
  }
238
+ }
317
239
 
318
- // Check all regexps if we haven't applied one of the code block types
319
- if (lineType == 'TMPara') {
320
- for (var type in _grammar.lineGrammar) {
321
- if (_grammar.lineGrammar[type].regexp) {
322
- var _capture2 = _grammar.lineGrammar[type].regexp.exec(this.lines[lineNum]);
323
- if (_capture2) {
324
- lineType = type;
325
- lineReplacement = _grammar.lineGrammar[type].replacement;
326
- lineCapture = _capture2;
327
- break;
328
- }
240
+ // Check all regexps if we haven't applied one of the code block types
241
+ if (lineType == 'TMPara') {
242
+ for (let type in _grammar.lineGrammar) {
243
+ if (_grammar.lineGrammar[type].regexp) {
244
+ let capture = _grammar.lineGrammar[type].regexp.exec(this.lines[lineNum]);
245
+ if (capture) {
246
+ lineType = type;
247
+ lineReplacement = _grammar.lineGrammar[type].replacement;
248
+ lineCapture = capture;
249
+ break;
329
250
  }
330
251
  }
331
252
  }
253
+ }
332
254
 
333
- // If we've opened a code block, remember that
334
- if (lineType == 'TMCodeFenceBacktickOpen' || lineType == 'TMCodeFenceTildeOpen') {
335
- codeBlockType = lineType;
336
- codeBlockSeqLength = lineCapture.groups['seq'].length;
337
- }
255
+ // If we've opened a code block, remember that
256
+ if (lineType == 'TMCodeFenceBacktickOpen' || lineType == 'TMCodeFenceTildeOpen') {
257
+ codeBlockType = lineType;
258
+ codeBlockSeqLength = lineCapture.groups['seq'].length;
259
+ }
338
260
 
339
- // Link reference definition and indented code can't interrupt a paragraph
340
- if ((lineType == 'TMIndentedCode' || lineType == 'TMLinkReferenceDefinition') && lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMPara' || this.lineTypes[lineNum - 1] == 'TMUL' || this.lineTypes[lineNum - 1] == 'TMOL' || this.lineTypes[lineNum - 1] == 'TMBlockquote')) {
341
- // Fall back to TMPara
342
- lineType = 'TMPara';
343
- lineCapture = [this.lines[lineNum]];
344
- lineReplacement = '$$0';
345
- }
261
+ // Link reference definition and indented code can't interrupt a paragraph
262
+ if ((lineType == 'TMIndentedCode' || lineType == 'TMLinkReferenceDefinition') && lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMPara' || this.lineTypes[lineNum - 1] == 'TMUL' || this.lineTypes[lineNum - 1] == 'TMOL' || this.lineTypes[lineNum - 1] == 'TMBlockquote')) {
263
+ // Fall back to TMPara
264
+ lineType = 'TMPara';
265
+ lineCapture = [this.lines[lineNum]];
266
+ lineReplacement = '$$0';
267
+ }
346
268
 
347
- // Setext H2 markers that can also be interpreted as an empty list item should be regarded as such (as per CommonMark spec)
348
- if (lineType == 'TMSetextH2Marker') {
349
- var _capture3 = _grammar.lineGrammar.TMUL.regexp.exec(this.lines[lineNum]);
350
- if (_capture3) {
351
- lineType = 'TMUL';
352
- lineReplacement = _grammar.lineGrammar.TMUL.replacement;
353
- lineCapture = _capture3;
354
- }
269
+ // Setext H2 markers that can also be interpreted as an empty list item should be regarded as such (as per CommonMark spec)
270
+ if (lineType == 'TMSetextH2Marker') {
271
+ let capture = _grammar.lineGrammar.TMUL.regexp.exec(this.lines[lineNum]);
272
+ if (capture) {
273
+ lineType = 'TMUL';
274
+ lineReplacement = _grammar.lineGrammar.TMUL.replacement;
275
+ lineCapture = capture;
355
276
  }
277
+ }
356
278
 
357
- // Setext headings are only valid if preceded by a paragraph (and if so, they change the type of the previous paragraph)
358
- if (lineType == 'TMSetextH1Marker' || lineType == 'TMSetextH2Marker') {
359
- if (lineNum == 0 || this.lineTypes[lineNum - 1] != 'TMPara') {
360
- // Setext marker is invalid. However, a H2 marker might still be a valid HR, so let's check that
361
- var _capture4 = _grammar.lineGrammar.TMHR.regexp.exec(this.lines[lineNum]);
362
- if (_capture4) {
363
- // Valid HR
364
- lineType = 'TMHR';
365
- lineCapture = _capture4;
366
- lineReplacement = _grammar.lineGrammar.TMHR.replacement;
367
- } else {
368
- // Not valid HR, format as TMPara
369
- lineType = 'TMPara';
370
- lineCapture = [this.lines[lineNum]];
371
- lineReplacement = '$$0';
372
- }
279
+ // Setext headings are only valid if preceded by a paragraph (and if so, they change the type of the previous paragraph)
280
+ if (lineType == 'TMSetextH1Marker' || lineType == 'TMSetextH2Marker') {
281
+ if (lineNum == 0 || this.lineTypes[lineNum - 1] != 'TMPara') {
282
+ // Setext marker is invalid. However, a H2 marker might still be a valid HR, so let's check that
283
+ let capture = _grammar.lineGrammar.TMHR.regexp.exec(this.lines[lineNum]);
284
+ if (capture) {
285
+ // Valid HR
286
+ lineType = 'TMHR';
287
+ lineCapture = capture;
288
+ lineReplacement = _grammar.lineGrammar.TMHR.replacement;
373
289
  } else {
374
- // Valid setext marker. Change types of preceding para lines
375
- var headingLine = lineNum - 1;
376
- var headingLineType = lineType == 'TMSetextH1Marker' ? 'TMSetextH1' : 'TMSetextH2';
377
- do {
378
- if (this.lineTypes[headingLineType] != headingLineType) {
379
- this.lineTypes[headingLine] = headingLineType;
380
- this.lineDirty[headingLineType] = true;
381
- }
382
- this.lineReplacements[headingLine] = '$$0';
383
- this.lineCaptures[headingLine] = [this.lines[headingLine]];
384
- headingLine--;
385
- } while (headingLine >= 0 && this.lineTypes[headingLine] == 'TMPara');
290
+ // Not valid HR, format as TMPara
291
+ lineType = 'TMPara';
292
+ lineCapture = [this.lines[lineNum]];
293
+ lineReplacement = '$$0';
386
294
  }
295
+ } else {
296
+ // Valid setext marker. Change types of preceding para lines
297
+ let headingLine = lineNum - 1;
298
+ const headingLineType = lineType == 'TMSetextH1Marker' ? 'TMSetextH1' : 'TMSetextH2';
299
+ do {
300
+ if (this.lineTypes[headingLineType] != headingLineType) {
301
+ this.lineTypes[headingLine] = headingLineType;
302
+ this.lineDirty[headingLineType] = true;
303
+ }
304
+ this.lineReplacements[headingLine] = '$$0';
305
+ this.lineCaptures[headingLine] = [this.lines[headingLine]];
306
+ headingLine--;
307
+ } while (headingLine >= 0 && this.lineTypes[headingLine] == 'TMPara');
387
308
  }
388
- // Lastly, save the line style to be applied later
389
- if (this.lineTypes[lineNum] != lineType) {
390
- this.lineTypes[lineNum] = lineType;
391
- this.lineDirty[lineNum] = true;
392
- }
393
- this.lineReplacements[lineNum] = lineReplacement;
394
- this.lineCaptures[lineNum] = lineCapture;
395
309
  }
310
+ // Lastly, save the line style to be applied later
311
+ if (this.lineTypes[lineNum] != lineType) {
312
+ this.lineTypes[lineNum] = lineType;
313
+ this.lineDirty[lineNum] = true;
314
+ }
315
+ this.lineReplacements[lineNum] = lineReplacement;
316
+ this.lineCaptures[lineNum] = lineCapture;
396
317
  }
318
+ }
397
319
 
398
- /**
399
- * Updates all line contents from the HTML, then re-applies formatting.
400
- */
401
- }, {
402
- key: "updateLineContentsAndFormatting",
403
- value: function updateLineContentsAndFormatting() {
404
- this.clearDirtyFlag();
405
- this.updateLineContents();
406
- this.updateFormatting();
407
- }
408
-
409
- /**
410
- * Attempts to parse a link or image at the current position. This assumes that the opening [ or ![ has already been matched.
411
- * Returns false if this is not a valid link, image. See below for more information
412
- * @param {string} originalString The original string, starting at the opening marker ([ or ![)
413
- * @param {boolean} isImage Whether or not this is an image (opener == ![)
414
- * @returns false if not a valid link / image.
415
- * Otherwise returns an object with two properties: output is the string to be included in the processed output,
416
- * charCount is the number of input characters (from originalString) consumed.
417
- */
418
- }, {
419
- key: "parseLinkOrImage",
420
- value: function parseLinkOrImage(originalString, isImage) {
421
- // Skip the opening bracket
422
- var textOffset = isImage ? 2 : 1;
423
- var opener = originalString.substr(0, textOffset);
424
- var type = isImage ? 'TMImage' : 'TMLink';
425
- var currentOffset = textOffset;
426
- var bracketLevel = 1;
427
- var linkText = false;
428
- var linkRef = false;
429
- var linkLabel = [];
430
- var linkDetails = []; // If matched, this will be an array: [whitespace + link destination delimiter, link destination, link destination delimiter, whitespace, link title delimiter, link title, link title delimiter + whitespace]. All can be empty strings.
431
-
432
- textOuter: while (currentOffset < originalString.length && linkText === false /* empty string is okay */) {
433
- var string = originalString.substr(currentOffset);
434
-
435
- // Capture any escapes and code blocks at current position, they bind more strongly than links
436
- // We don't have to actually process them here, that'll be done later in case the link / image is valid, but we need to skip over them.
437
- for (var _i = 0, _arr = ['escape', 'code', 'autolink', 'html']; _i < _arr.length; _i++) {
438
- var rule = _arr[_i];
439
- var cap = _grammar.inlineGrammar[rule].regexp.exec(string);
440
- if (cap) {
441
- currentOffset += cap[0].length;
442
- continue textOuter;
443
- }
444
- }
320
+ /**
321
+ * Updates all line contents from the HTML, then re-applies formatting.
322
+ */
323
+ updateLineContentsAndFormatting() {
324
+ this.clearDirtyFlag();
325
+ this.updateLineContents();
326
+ this.updateFormatting();
327
+ }
445
328
 
446
- // Check for image. It's okay for an image to be included in a link or image
447
- if (string.match(_grammar.inlineGrammar.imageOpen.regexp)) {
448
- // Opening image. It's okay if this is a matching pair of brackets
449
- bracketLevel++;
450
- currentOffset += 2;
329
+ /**
330
+ * Attempts to parse a link or image at the current position. This assumes that the opening [ or ![ has already been matched.
331
+ * Returns false if this is not a valid link, image. See below for more information
332
+ * @param {string} originalString The original string, starting at the opening marker ([ or ![)
333
+ * @param {boolean} isImage Whether or not this is an image (opener == ![)
334
+ * @returns false if not a valid link / image.
335
+ * Otherwise returns an object with two properties: output is the string to be included in the processed output,
336
+ * charCount is the number of input characters (from originalString) consumed.
337
+ */
338
+ parseLinkOrImage(originalString, isImage) {
339
+ // Skip the opening bracket
340
+ let textOffset = isImage ? 2 : 1;
341
+ let opener = originalString.substr(0, textOffset);
342
+ let type = isImage ? 'TMImage' : 'TMLink';
343
+ let currentOffset = textOffset;
344
+ let bracketLevel = 1;
345
+ let linkText = false;
346
+ let linkRef = false;
347
+ let linkLabel = [];
348
+ let linkDetails = []; // If matched, this will be an array: [whitespace + link destination delimiter, link destination, link destination delimiter, whitespace, link title delimiter, link title, link title delimiter + whitespace]. All can be empty strings.
349
+
350
+ textOuter: while (currentOffset < originalString.length && linkText === false /* empty string is okay */) {
351
+ let string = originalString.substr(currentOffset);
352
+
353
+ // Capture any escapes and code blocks at current position, they bind more strongly than links
354
+ // We don't have to actually process them here, that'll be done later in case the link / image is valid, but we need to skip over them.
355
+ for (let rule of ['escape', 'code', 'autolink', 'html']) {
356
+ let cap = _grammar.inlineGrammar[rule].regexp.exec(string);
357
+ if (cap) {
358
+ currentOffset += cap[0].length;
451
359
  continue textOuter;
452
360
  }
361
+ }
453
362
 
454
- // Check for link (not an image because that would have been captured and skipped over above)
455
- if (string.match(_grammar.inlineGrammar.linkOpen.regexp)) {
456
- // Opening bracket. Two things to do:
457
- // 1) it's okay if this part of a pair of brackets.
458
- // 2) If we are currently trying to parse a link, this nested bracket musn't start a valid link (no nested links allowed)
459
- bracketLevel++;
460
- // if (bracketLevel >= 2) return false; // Nested unescaped brackets, this doesn't qualify as a link / image
461
- if (!isImage) {
462
- if (this.parseLinkOrImage(string, false)) {
463
- // Valid link inside this possible link, which makes this link invalid (inner links beat outer ones)
464
- return false;
465
- }
466
- }
467
- currentOffset += 1;
468
- continue textOuter;
469
- }
363
+ // Check for image. It's okay for an image to be included in a link or image
364
+ if (string.match(_grammar.inlineGrammar.imageOpen.regexp)) {
365
+ // Opening image. It's okay if this is a matching pair of brackets
366
+ bracketLevel++;
367
+ currentOffset += 2;
368
+ continue textOuter;
369
+ }
470
370
 
471
- // Check for closing bracket
472
- if (string.match(/^\]/)) {
473
- bracketLevel--;
474
- if (bracketLevel == 0) {
475
- // Found matching bracket and haven't found anything disqualifying this as link / image.
476
- linkText = originalString.substr(textOffset, currentOffset - textOffset);
477
- currentOffset++;
478
- continue textOuter;
371
+ // Check for link (not an image because that would have been captured and skipped over above)
372
+ if (string.match(_grammar.inlineGrammar.linkOpen.regexp)) {
373
+ // Opening bracket. Two things to do:
374
+ // 1) it's okay if this part of a pair of brackets.
375
+ // 2) If we are currently trying to parse a link, this nested bracket musn't start a valid link (no nested links allowed)
376
+ bracketLevel++;
377
+ // if (bracketLevel >= 2) return false; // Nested unescaped brackets, this doesn't qualify as a link / image
378
+ if (!isImage) {
379
+ if (this.parseLinkOrImage(string, false)) {
380
+ // Valid link inside this possible link, which makes this link invalid (inner links beat outer ones)
381
+ return false;
479
382
  }
480
383
  }
384
+ currentOffset += 1;
385
+ continue textOuter;
386
+ }
481
387
 
482
- // Nothing matches, proceed to next char
483
- currentOffset++;
388
+ // Check for closing bracket
389
+ if (string.match(/^\]/)) {
390
+ bracketLevel--;
391
+ if (bracketLevel == 0) {
392
+ // Found matching bracket and haven't found anything disqualifying this as link / image.
393
+ linkText = originalString.substr(textOffset, currentOffset - textOffset);
394
+ currentOffset++;
395
+ continue textOuter;
396
+ }
484
397
  }
485
398
 
486
- // Did we find a link text (i.e., find a matching closing bracket?)
487
- if (linkText === false) return false; // Nope
488
-
489
- // So far, so good. We've got a valid link text. Let's see what type of link this is
490
- var nextChar = currentOffset < originalString.length ? originalString.substr(currentOffset, 1) : '';
491
-
492
- // REFERENCE LINKS
493
- if (nextChar == '[') {
494
- var _string = originalString.substr(currentOffset);
495
- var _cap = _grammar.inlineGrammar.linkLabel.regexp.exec(_string);
496
- if (_cap) {
497
- // Valid link label
498
- currentOffset += _cap[0].length;
499
- linkLabel.push(_cap[1], _cap[2], _cap[3]);
500
- if (_cap[_grammar.inlineGrammar.linkLabel.labelPlaceholder]) {
501
- // Full reference link
502
- linkRef = _cap[_grammar.inlineGrammar.linkLabel.labelPlaceholder];
503
- } else {
504
- // Collapsed reference link
505
- linkRef = linkText.trim();
506
- }
399
+ // Nothing matches, proceed to next char
400
+ currentOffset++;
401
+ }
402
+
403
+ // Did we find a link text (i.e., find a matching closing bracket?)
404
+ if (linkText === false) return false; // Nope
405
+
406
+ // So far, so good. We've got a valid link text. Let's see what type of link this is
407
+ let nextChar = currentOffset < originalString.length ? originalString.substr(currentOffset, 1) : '';
408
+
409
+ // REFERENCE LINKS
410
+ if (nextChar == '[') {
411
+ let string = originalString.substr(currentOffset);
412
+ let cap = _grammar.inlineGrammar.linkLabel.regexp.exec(string);
413
+ if (cap) {
414
+ // Valid link label
415
+ currentOffset += cap[0].length;
416
+ linkLabel.push(cap[1], cap[2], cap[3]);
417
+ if (cap[_grammar.inlineGrammar.linkLabel.labelPlaceholder]) {
418
+ // Full reference link
419
+ linkRef = cap[_grammar.inlineGrammar.linkLabel.labelPlaceholder];
507
420
  } else {
508
- // Not a valid link label
509
- return false;
421
+ // Collapsed reference link
422
+ linkRef = linkText.trim();
510
423
  }
511
- } else if (nextChar != '(') {
512
- // Shortcut ref link
513
- linkRef = linkText.trim();
514
-
515
- // INLINE LINKS
516
424
  } else {
517
- // nextChar == '('
518
-
519
- // Potential inline link
520
- currentOffset++;
521
- var parenthesisLevel = 1;
522
- inlineOuter: while (currentOffset < originalString.length && parenthesisLevel > 0) {
523
- var _string2 = originalString.substr(currentOffset);
524
-
525
- // Process whitespace
526
- var _cap2 = /^\s+/.exec(_string2);
527
- if (_cap2) {
528
- switch (linkDetails.length) {
529
- case 0:
530
- linkDetails.push(_cap2[0]);
531
- break;
532
- // Opening whitespace
533
- case 1:
534
- linkDetails.push(_cap2[0]);
535
- break;
536
- // Open destination, but not a destination yet; desination opened with <
537
- case 2:
538
- // Open destination with content in it. Whitespace only allowed if opened by angle bracket, otherwise this closes the destination
539
- if (linkDetails[0].match(/</)) {
540
- linkDetails[1] = linkDetails[1].concat(_cap2[0]);
541
- } else {
542
- if (parenthesisLevel != 1) return false; // Unbalanced parenthesis
543
- linkDetails.push(''); // Empty end delimiter for destination
544
- linkDetails.push(_cap2[0]); // Whitespace in between destination and title
545
- }
546
-
547
- break;
548
- case 3:
549
- linkDetails.push(_cap2[0]);
550
- break;
551
- // Whitespace between destination and title
552
- case 4:
553
- return false;
554
- // This should never happen (no opener for title yet, but more whitespace to capture)
555
- case 5:
556
- linkDetails.push('');
557
- // Whitespace at beginning of title, push empty title and continue
558
- case 6:
559
- linkDetails[5] = linkDetails[5].concat(_cap2[0]);
560
- break;
561
- // Whitespace in title
562
- case 7:
563
- linkDetails[6] = linkDetails[6].concat(_cap2[0]);
564
- break;
565
- // Whitespace after closing delimiter
566
- default:
567
- return false;
568
- // We should never get here
569
- }
425
+ // Not a valid link label
426
+ return false;
427
+ }
428
+ } else if (nextChar != '(') {
429
+ // Shortcut ref link
430
+ linkRef = linkText.trim();
431
+
432
+ // INLINE LINKS
433
+ } else {
434
+ // nextChar == '('
435
+
436
+ // Potential inline link
437
+ currentOffset++;
438
+ let parenthesisLevel = 1;
439
+ inlineOuter: while (currentOffset < originalString.length && parenthesisLevel > 0) {
440
+ let string = originalString.substr(currentOffset);
441
+
442
+ // Process whitespace
443
+ let cap = /^\s+/.exec(string);
444
+ if (cap) {
445
+ switch (linkDetails.length) {
446
+ case 0:
447
+ linkDetails.push(cap[0]);
448
+ break;
449
+ // Opening whitespace
450
+ case 1:
451
+ linkDetails.push(cap[0]);
452
+ break;
453
+ // Open destination, but not a destination yet; desination opened with <
454
+ case 2:
455
+ // Open destination with content in it. Whitespace only allowed if opened by angle bracket, otherwise this closes the destination
456
+ if (linkDetails[0].match(/</)) {
457
+ linkDetails[1] = linkDetails[1].concat(cap[0]);
458
+ } else {
459
+ if (parenthesisLevel != 1) return false; // Unbalanced parenthesis
460
+ linkDetails.push(''); // Empty end delimiter for destination
461
+ linkDetails.push(cap[0]); // Whitespace in between destination and title
462
+ }
570
463
 
571
- currentOffset += _cap2[0].length;
572
- continue inlineOuter;
464
+ break;
465
+ case 3:
466
+ linkDetails.push(cap[0]);
467
+ break;
468
+ // Whitespace between destination and title
469
+ case 4:
470
+ return false;
471
+ // This should never happen (no opener for title yet, but more whitespace to capture)
472
+ case 5:
473
+ linkDetails.push('');
474
+ // Whitespace at beginning of title, push empty title and continue
475
+ case 6:
476
+ linkDetails[5] = linkDetails[5].concat(cap[0]);
477
+ break;
478
+ // Whitespace in title
479
+ case 7:
480
+ linkDetails[6] = linkDetails[6].concat(cap[0]);
481
+ break;
482
+ // Whitespace after closing delimiter
483
+ default:
484
+ return false;
485
+ // We should never get here
573
486
  }
574
487
 
575
- // Process backslash escapes
576
- _cap2 = _grammar.inlineGrammar.escape.regexp.exec(_string2);
577
- if (_cap2) {
578
- switch (linkDetails.length) {
579
- case 0:
580
- linkDetails.push('');
581
- // this opens the link destination, add empty opening delimiter and proceed to next case
582
- case 1:
583
- linkDetails.push(_cap2[0]);
584
- break;
585
- // This opens the link destination, append it
586
- case 2:
587
- linkDetails[1] = linkDetails[1].concat(_cap2[0]);
588
- break;
589
- // Part of the link destination
590
- case 3:
591
- return false;
592
- // Lacking opening delimiter for link title
593
- case 4:
594
- return false;
595
- // Lcaking opening delimiter for link title
596
- case 5:
597
- linkDetails.push('');
598
- // This opens the link title
599
- case 6:
600
- linkDetails[5] = linkDetails[5].concat(_cap2[0]);
601
- break;
602
- // Part of the link title
603
- default:
604
- return false;
605
- // After link title was closed, without closing parenthesis
606
- }
488
+ currentOffset += cap[0].length;
489
+ continue inlineOuter;
490
+ }
607
491
 
608
- currentOffset += _cap2[0].length;
609
- continue inlineOuter;
492
+ // Process backslash escapes
493
+ cap = _grammar.inlineGrammar.escape.regexp.exec(string);
494
+ if (cap) {
495
+ switch (linkDetails.length) {
496
+ case 0:
497
+ linkDetails.push('');
498
+ // this opens the link destination, add empty opening delimiter and proceed to next case
499
+ case 1:
500
+ linkDetails.push(cap[0]);
501
+ break;
502
+ // This opens the link destination, append it
503
+ case 2:
504
+ linkDetails[1] = linkDetails[1].concat(cap[0]);
505
+ break;
506
+ // Part of the link destination
507
+ case 3:
508
+ return false;
509
+ // Lacking opening delimiter for link title
510
+ case 4:
511
+ return false;
512
+ // Lcaking opening delimiter for link title
513
+ case 5:
514
+ linkDetails.push('');
515
+ // This opens the link title
516
+ case 6:
517
+ linkDetails[5] = linkDetails[5].concat(cap[0]);
518
+ break;
519
+ // Part of the link title
520
+ default:
521
+ return false;
522
+ // After link title was closed, without closing parenthesis
610
523
  }
611
524
 
612
- // Process opening angle bracket as deilimiter of destination
613
- if (linkDetails.length < 2 && _string2.match(/^</)) {
614
- if (linkDetails.length == 0) linkDetails.push('');
615
- linkDetails[0] = linkDetails[0].concat('<');
616
- currentOffset++;
617
- continue inlineOuter;
618
- }
525
+ currentOffset += cap[0].length;
526
+ continue inlineOuter;
527
+ }
619
528
 
620
- // Process closing angle bracket as delimiter of destination
621
- if ((linkDetails.length == 1 || linkDetails.length == 2) && _string2.match(/^>/)) {
622
- if (linkDetails.length == 1) linkDetails.push(''); // Empty link destination
623
- linkDetails.push('>');
624
- currentOffset++;
625
- continue inlineOuter;
626
- }
529
+ // Process opening angle bracket as deilimiter of destination
530
+ if (linkDetails.length < 2 && string.match(/^</)) {
531
+ if (linkDetails.length == 0) linkDetails.push('');
532
+ linkDetails[0] = linkDetails[0].concat('<');
533
+ currentOffset++;
534
+ continue inlineOuter;
535
+ }
627
536
 
628
- // Process non-parenthesis delimiter for title.
629
- _cap2 = /^["']/.exec(_string2);
630
- // For this to be a valid opener, we have to either have no destination, only whitespace so far,
631
- // or a destination with trailing whitespace.
632
- if (_cap2 && (linkDetails.length == 0 || linkDetails.length == 1 || linkDetails.length == 4)) {
633
- while (linkDetails.length < 4) {
634
- linkDetails.push('');
635
- }
636
- linkDetails.push(_cap2[0]);
637
- currentOffset++;
638
- continue inlineOuter;
639
- }
537
+ // Process closing angle bracket as delimiter of destination
538
+ if ((linkDetails.length == 1 || linkDetails.length == 2) && string.match(/^>/)) {
539
+ if (linkDetails.length == 1) linkDetails.push(''); // Empty link destination
540
+ linkDetails.push('>');
541
+ currentOffset++;
542
+ continue inlineOuter;
543
+ }
640
544
 
641
- // For this to be a valid closer, we have to have an opener and some or no title, and this has to match the opener
642
- if (_cap2 && (linkDetails.length == 5 || linkDetails.length == 6) && linkDetails[4] == _cap2[0]) {
643
- if (linkDetails.length == 5) linkDetails.push(''); // Empty link title
644
- linkDetails.push(_cap2[0]);
645
- currentOffset++;
646
- continue inlineOuter;
647
- }
648
- // Other cases (linkDetails.length == 2, 3, 7) will be handled with the "default" case below.
649
-
650
- // Process opening parenthesis
651
- if (_string2.match(/^\(/)) {
652
- switch (linkDetails.length) {
653
- case 0:
654
- linkDetails.push('');
655
- // this opens the link destination, add empty opening delimiter and proceed to next case
656
- case 1:
657
- linkDetails.push('');
658
- // This opens the link destination
659
- case 2:
660
- // Part of the link destination
661
- linkDetails[1] = linkDetails[1].concat('(');
662
- if (!linkDetails[0].match(/<$/)) parenthesisLevel++;
663
- break;
664
- case 3:
665
- linkDetails.push('');
666
- // opening delimiter for link title
667
- case 4:
668
- linkDetails.push('(');
669
- break;
670
- // opening delimiter for link title
671
- case 5:
672
- linkDetails.push('');
673
- // opens the link title, add empty title content and proceed to next case
674
- case 6:
675
- // Part of the link title. Un-escaped parenthesis only allowed in " or ' delimited title
676
- if (linkDetails[4] == '(') return false;
677
- linkDetails[5] = linkDetails[5].concat('(');
678
- break;
679
- default:
680
- return false;
681
- // After link title was closed, without closing parenthesis
682
- }
545
+ // Process non-parenthesis delimiter for title.
546
+ cap = /^["']/.exec(string);
547
+ // For this to be a valid opener, we have to either have no destination, only whitespace so far,
548
+ // or a destination with trailing whitespace.
549
+ if (cap && (linkDetails.length == 0 || linkDetails.length == 1 || linkDetails.length == 4)) {
550
+ while (linkDetails.length < 4) linkDetails.push('');
551
+ linkDetails.push(cap[0]);
552
+ currentOffset++;
553
+ continue inlineOuter;
554
+ }
683
555
 
684
- currentOffset++;
685
- continue inlineOuter;
556
+ // For this to be a valid closer, we have to have an opener and some or no title, and this has to match the opener
557
+ if (cap && (linkDetails.length == 5 || linkDetails.length == 6) && linkDetails[4] == cap[0]) {
558
+ if (linkDetails.length == 5) linkDetails.push(''); // Empty link title
559
+ linkDetails.push(cap[0]);
560
+ currentOffset++;
561
+ continue inlineOuter;
562
+ }
563
+ // Other cases (linkDetails.length == 2, 3, 7) will be handled with the "default" case below.
564
+
565
+ // Process opening parenthesis
566
+ if (string.match(/^\(/)) {
567
+ switch (linkDetails.length) {
568
+ case 0:
569
+ linkDetails.push('');
570
+ // this opens the link destination, add empty opening delimiter and proceed to next case
571
+ case 1:
572
+ linkDetails.push('');
573
+ // This opens the link destination
574
+ case 2:
575
+ // Part of the link destination
576
+ linkDetails[1] = linkDetails[1].concat('(');
577
+ if (!linkDetails[0].match(/<$/)) parenthesisLevel++;
578
+ break;
579
+ case 3:
580
+ linkDetails.push('');
581
+ // opening delimiter for link title
582
+ case 4:
583
+ linkDetails.push('(');
584
+ break;
585
+ // opening delimiter for link title
586
+ case 5:
587
+ linkDetails.push('');
588
+ // opens the link title, add empty title content and proceed to next case
589
+ case 6:
590
+ // Part of the link title. Un-escaped parenthesis only allowed in " or ' delimited title
591
+ if (linkDetails[4] == '(') return false;
592
+ linkDetails[5] = linkDetails[5].concat('(');
593
+ break;
594
+ default:
595
+ return false;
596
+ // After link title was closed, without closing parenthesis
686
597
  }
687
598
 
688
- // Process closing parenthesis
689
- if (_string2.match(/^\)/)) {
690
- if (linkDetails.length <= 2) {
691
- // We are inside the link destination. Parentheses have to be matched if not in angle brackets
692
- while (linkDetails.length < 2) {
693
- linkDetails.push('');
694
- }
695
- if (!linkDetails[0].match(/<$/)) parenthesisLevel--;
696
- if (parenthesisLevel > 0) {
697
- linkDetails[1] = linkDetails[1].concat(')');
698
- }
699
- } else if (linkDetails.length == 5 || linkDetails.length == 6) {
700
- // We are inside the link title.
701
- if (linkDetails[4] == '(') {
702
- // This closes the link title
703
- if (linkDetails.length == 5) linkDetails.push('');
704
- linkDetails.push(')');
705
- } else {
706
- // Just regular ol' content
707
- if (linkDetails.length == 5) linkDetails.push(')');else linkDetails[5] = linkDetails[5].concat(')');
708
- }
709
- } else {
710
- parenthesisLevel--; // This should decrease it from 1 to 0...
711
- }
599
+ currentOffset++;
600
+ continue inlineOuter;
601
+ }
712
602
 
713
- if (parenthesisLevel == 0) {
714
- // No invalid condition, let's make sure the linkDetails array is complete
715
- while (linkDetails.length < 7) {
716
- linkDetails.push('');
717
- }
603
+ // Process closing parenthesis
604
+ if (string.match(/^\)/)) {
605
+ if (linkDetails.length <= 2) {
606
+ // We are inside the link destination. Parentheses have to be matched if not in angle brackets
607
+ while (linkDetails.length < 2) linkDetails.push('');
608
+ if (!linkDetails[0].match(/<$/)) parenthesisLevel--;
609
+ if (parenthesisLevel > 0) {
610
+ linkDetails[1] = linkDetails[1].concat(')');
718
611
  }
719
- currentOffset++;
720
- continue inlineOuter;
721
- }
722
-
723
- // Any old character
724
- _cap2 = /^./.exec(_string2);
725
- if (_cap2) {
726
- switch (linkDetails.length) {
727
- case 0:
728
- linkDetails.push('');
729
- // this opens the link destination, add empty opening delimiter and proceed to next case
730
- case 1:
731
- linkDetails.push(_cap2[0]);
732
- break;
733
- // This opens the link destination, append it
734
- case 2:
735
- linkDetails[1] = linkDetails[1].concat(_cap2[0]);
736
- break;
737
- // Part of the link destination
738
- case 3:
739
- return false;
740
- // Lacking opening delimiter for link title
741
- case 4:
742
- return false;
743
- // Lcaking opening delimiter for link title
744
- case 5:
745
- linkDetails.push('');
746
- // This opens the link title
747
- case 6:
748
- linkDetails[5] = linkDetails[5].concat(_cap2[0]);
749
- break;
750
- // Part of the link title
751
- default:
752
- return false;
753
- // After link title was closed, without closing parenthesis
612
+ } else if (linkDetails.length == 5 || linkDetails.length == 6) {
613
+ // We are inside the link title.
614
+ if (linkDetails[4] == '(') {
615
+ // This closes the link title
616
+ if (linkDetails.length == 5) linkDetails.push('');
617
+ linkDetails.push(')');
618
+ } else {
619
+ // Just regular ol' content
620
+ if (linkDetails.length == 5) linkDetails.push(')');else linkDetails[5] = linkDetails[5].concat(')');
754
621
  }
622
+ } else {
623
+ parenthesisLevel--; // This should decrease it from 1 to 0...
624
+ }
755
625
 
756
- currentOffset += _cap2[0].length;
757
- continue inlineOuter;
626
+ if (parenthesisLevel == 0) {
627
+ // No invalid condition, let's make sure the linkDetails array is complete
628
+ while (linkDetails.length < 7) linkDetails.push('');
758
629
  }
759
- throw "Infinite loop"; // we should never get here since the last test matches any character
630
+ currentOffset++;
631
+ continue inlineOuter;
760
632
  }
761
633
 
762
- if (parenthesisLevel > 0) return false; // Parenthes(es) not closed
763
- }
764
-
765
- if (linkRef !== false) {
766
- // Ref link; check that linkRef is valid
767
- var valid = false;
768
- var _iterator2 = _createForOfIteratorHelper(this.linkLabels),
769
- _step2;
770
- try {
771
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
772
- var _label = _step2.value;
773
- if (_label == linkRef) {
774
- valid = true;
634
+ // Any old character
635
+ cap = /^./.exec(string);
636
+ if (cap) {
637
+ switch (linkDetails.length) {
638
+ case 0:
639
+ linkDetails.push('');
640
+ // this opens the link destination, add empty opening delimiter and proceed to next case
641
+ case 1:
642
+ linkDetails.push(cap[0]);
775
643
  break;
776
- }
644
+ // This opens the link destination, append it
645
+ case 2:
646
+ linkDetails[1] = linkDetails[1].concat(cap[0]);
647
+ break;
648
+ // Part of the link destination
649
+ case 3:
650
+ return false;
651
+ // Lacking opening delimiter for link title
652
+ case 4:
653
+ return false;
654
+ // Lcaking opening delimiter for link title
655
+ case 5:
656
+ linkDetails.push('');
657
+ // This opens the link title
658
+ case 6:
659
+ linkDetails[5] = linkDetails[5].concat(cap[0]);
660
+ break;
661
+ // Part of the link title
662
+ default:
663
+ return false;
664
+ // After link title was closed, without closing parenthesis
777
665
  }
778
- } catch (err) {
779
- _iterator2.e(err);
780
- } finally {
781
- _iterator2.f();
782
- }
783
- var label = valid ? "TMLinkLabel TMLinkLabel_Valid" : "TMLinkLabel TMLinkLabel_Invalid";
784
- var output = "<span class=\"TMMark TMMark_".concat(type, "\">").concat(opener, "</span><span class=\"").concat(type, " ").concat(linkLabel.length < 3 || !linkLabel[1] ? label : "", "\">").concat(this.processInlineStyles(linkText), "</span><span class=\"TMMark TMMark_").concat(type, "\">]</span>");
785
- if (linkLabel.length >= 3) {
786
- output = output.concat("<span class=\"TMMark TMMark_".concat(type, "\">").concat(linkLabel[0], "</span>"), "<span class=\"".concat(label, "\">").concat(linkLabel[1], "</span>"), "<span class=\"TMMark TMMark_".concat(type, "\">").concat(linkLabel[2], "</span>"));
787
- }
788
- return {
789
- output: output,
790
- charCount: currentOffset
791
- };
792
- } else if (linkDetails) {
793
- // Inline link
794
666
 
795
- // This should never happen, but better safe than sorry.
796
- while (linkDetails.length < 7) {
797
- linkDetails.push('');
667
+ currentOffset += cap[0].length;
668
+ continue inlineOuter;
798
669
  }
799
- return {
800
- output: "<span class=\"TMMark TMMark_".concat(type, "\">").concat(opener, "</span><span class=\"").concat(type, "\">").concat(this.processInlineStyles(linkText), "</span><span class=\"TMMark TMMark_").concat(type, "\">](").concat(linkDetails[0], "</span><span class=\"").concat(type, "Destination\">").concat(linkDetails[1], "</span><span class=\"TMMark TMMark_").concat(type, "\">").concat(linkDetails[2]).concat(linkDetails[3]).concat(linkDetails[4], "</span><span class=\"").concat(type, "Title\">").concat(linkDetails[5], "</span><span class=\"TMMark TMMark_").concat(type, "\">").concat(linkDetails[6], ")</span>"),
801
- charCount: currentOffset
802
- };
670
+ throw "Infinite loop"; // we should never get here since the last test matches any character
803
671
  }
804
- return false;
672
+
673
+ if (parenthesisLevel > 0) return false; // Parenthes(es) not closed
805
674
  }
806
675
 
807
- /**
808
- * Formats a markdown string as HTML, using Markdown inline formatting.
809
- * @param {string} originalString The input (markdown inline formatted) string
810
- * @returns {string} The HTML formatted output
811
- */
812
- }, {
813
- key: "processInlineStyles",
814
- value: function processInlineStyles(originalString) {
815
- var _this3 = this;
816
- var processed = '';
817
- var stack = []; // Stack is an array of objects of the format: {delimiter, delimString, count, output}
818
- var offset = 0;
819
- var string = originalString;
820
- var _loop = function _loop() {
821
- var _loop2 = function _loop2() {
822
- var rule = _arr2[_i2];
823
- var cap = _grammar.inlineGrammar[rule].regexp.exec(string);
824
- if (cap) {
825
- string = string.substr(cap[0].length);
826
- offset += cap[0].length;
827
- processed += _grammar.inlineGrammar[rule].replacement
828
- // .replace(/\$\$([1-9])/g, (str, p1) => processInlineStyles(cap[p1])) // todo recursive calling
829
- .replace(/\$([1-9])/g, function (str, p1) {
830
- return (0, _grammar.htmlescape)(cap[p1]);
831
- });
832
- return {
833
- v: "continue|outer"
834
- };
835
- }
836
- };
837
- // Process simple rules (non-delimiter)
838
- for (var _i2 = 0, _arr2 = ['escape', 'code', 'autolink', 'html']; _i2 < _arr2.length; _i2++) {
839
- var _ret2 = _loop2();
840
- if (_typeof(_ret2) === "object") return _ret2.v;
676
+ if (linkRef !== false) {
677
+ // Ref link; check that linkRef is valid
678
+ let valid = false;
679
+ for (let label of this.linkLabels) {
680
+ if (label == linkRef) {
681
+ valid = true;
682
+ break;
841
683
  }
684
+ }
685
+ let label = valid ? "TMLinkLabel TMLinkLabel_Valid" : "TMLinkLabel TMLinkLabel_Invalid";
686
+ let output = `<span class="TMMark TMMark_${type}">${opener}</span><span class="${type} ${linkLabel.length < 3 || !linkLabel[1] ? label : ""}">${this.processInlineStyles(linkText)}</span><span class="TMMark TMMark_${type}">]</span>`;
687
+ if (linkLabel.length >= 3) {
688
+ output = output.concat(`<span class="TMMark TMMark_${type}">${linkLabel[0]}</span>`, `<span class="${label}">${linkLabel[1]}</span>`, `<span class="TMMark TMMark_${type}">${linkLabel[2]}</span>`);
689
+ }
690
+ return {
691
+ output: output,
692
+ charCount: currentOffset
693
+ };
694
+ } else if (linkDetails) {
695
+ // Inline link
842
696
 
843
- // Check for links / images
844
- var potentialLink = string.match(_grammar.inlineGrammar.linkOpen.regexp);
845
- var potentialImage = string.match(_grammar.inlineGrammar.imageOpen.regexp);
846
- if (potentialImage || potentialLink) {
847
- var result = _this3.parseLinkOrImage(string, potentialImage);
848
- if (result) {
849
- processed = "".concat(processed).concat(result.output);
850
- string = string.substr(result.charCount);
851
- offset += result.charCount;
852
- return "continue|outer";
853
- }
854
- }
697
+ // This should never happen, but better safe than sorry.
698
+ while (linkDetails.length < 7) {
699
+ linkDetails.push('');
700
+ }
701
+ return {
702
+ output: `<span class="TMMark TMMark_${type}">${opener}</span><span class="${type}">${this.processInlineStyles(linkText)}</span><span class="TMMark TMMark_${type}">](${linkDetails[0]}</span><span class="${type}Destination">${linkDetails[1]}</span><span class="TMMark TMMark_${type}">${linkDetails[2]}${linkDetails[3]}${linkDetails[4]}</span><span class="${type}Title">${linkDetails[5]}</span><span class="TMMark TMMark_${type}">${linkDetails[6]})</span>`,
703
+ charCount: currentOffset
704
+ };
705
+ }
706
+ return false;
707
+ }
855
708
 
856
- // Check for em / strong delimiters
857
- var cap = /(^\*+)|(^_+)/.exec(string);
709
+ /**
710
+ * Formats a markdown string as HTML, using Markdown inline formatting.
711
+ * @param {string} originalString The input (markdown inline formatted) string
712
+ * @returns {string} The HTML formatted output
713
+ */
714
+ processInlineStyles(originalString) {
715
+ let processed = '';
716
+ let stack = []; // Stack is an array of objects of the format: {delimiter, delimString, count, output}
717
+ let offset = 0;
718
+ let string = originalString;
719
+ outer: while (string) {
720
+ // Process simple rules (non-delimiter)
721
+ for (let rule of ['escape', 'code', 'autolink', 'html']) {
722
+ let cap = _grammar.inlineGrammar[rule].regexp.exec(string);
858
723
  if (cap) {
859
- var delimCount = cap[0].length;
860
- var delimString = cap[0];
861
- var currentDelimiter = cap[0][0]; // This should be * or _
862
-
863
724
  string = string.substr(cap[0].length);
725
+ offset += cap[0].length;
726
+ processed += _grammar.inlineGrammar[rule].replacement
727
+ // .replace(/\$\$([1-9])/g, (str, p1) => processInlineStyles(cap[p1])) // todo recursive calling
728
+ .replace(/\$([1-9])/g, (str, p1) => (0, _grammar.htmlescape)(cap[p1]));
729
+ continue outer;
730
+ }
731
+ }
864
732
 
865
- // We have a delimiter run. Let's check if it can open or close an emphasis.
733
+ // Check for links / images
734
+ let potentialLink = string.match(_grammar.inlineGrammar.linkOpen.regexp);
735
+ let potentialImage = string.match(_grammar.inlineGrammar.imageOpen.regexp);
736
+ if (potentialImage || potentialLink) {
737
+ let result = this.parseLinkOrImage(string, potentialImage);
738
+ if (result) {
739
+ processed = `${processed}${result.output}`;
740
+ string = string.substr(result.charCount);
741
+ offset += result.charCount;
742
+ continue outer;
743
+ }
744
+ }
866
745
 
867
- var preceding = offset > 0 ? originalString.substr(0, offset) : ' '; // beginning and end of line count as whitespace
868
- var following = offset + cap[0].length < originalString.length ? string : ' ';
869
- var punctuationFollows = following.match(_grammar.punctuationLeading);
870
- var punctuationPrecedes = preceding.match(_grammar.punctuationTrailing);
871
- var whitespaceFollows = following.match(/^\s/);
872
- var whitespacePrecedes = preceding.match(/\s$/);
746
+ // Check for em / strong delimiters
747
+ let cap = /(^\*+)|(^_+)/.exec(string);
748
+ if (cap) {
749
+ let delimCount = cap[0].length;
750
+ const delimString = cap[0];
751
+ const currentDelimiter = cap[0][0]; // This should be * or _
873
752
 
874
- // These are the rules for right-flanking and left-flanking delimiter runs as per CommonMark spec
875
- var canOpen = !whitespaceFollows && (!punctuationFollows || !!whitespacePrecedes || !!punctuationPrecedes);
876
- var canClose = !whitespacePrecedes && (!punctuationPrecedes || !!whitespaceFollows || !!punctuationFollows);
753
+ string = string.substr(cap[0].length);
877
754
 
878
- // Underscores have more detailed rules than just being part of left- or right-flanking run:
879
- if (currentDelimiter == '_' && canOpen && canClose) {
880
- canOpen = punctuationPrecedes;
881
- canClose = punctuationFollows;
882
- }
755
+ // We have a delimiter run. Let's check if it can open or close an emphasis.
883
756
 
884
- // If the delimiter can close, check the stack if there's something it can close
885
- if (canClose) {
886
- var stackPointer = stack.length - 1;
887
- // See if we can find a matching opening delimiter, move down through the stack
888
- while (delimCount && stackPointer >= 0) {
889
- if (stack[stackPointer].delimiter == currentDelimiter) {
890
- // We found a matching delimiter, let's construct the formatted string
891
-
892
- // Firstly, if we skipped any stack levels, pop them immediately (non-matching delimiters)
893
- while (stackPointer < stack.length - 1) {
894
- var _entry = stack.pop();
895
- processed = "".concat(_entry.output).concat(_entry.delimString.substr(0, _entry.count)).concat(processed);
896
- }
897
-
898
- // Then, format the string
899
- if (delimCount >= 2 && stack[stackPointer].count >= 2) {
900
- // Strong
901
- processed = "<span class=\"TMMark\">".concat(currentDelimiter).concat(currentDelimiter, "</span><strong class=\"TMStrong\">").concat(processed, "</strong><span class=\"TMMark\">").concat(currentDelimiter).concat(currentDelimiter, "</span>");
902
- delimCount -= 2;
903
- stack[stackPointer].count -= 2;
904
- } else {
905
- // Em
906
- processed = "<span class=\"TMMark\">".concat(currentDelimiter, "</span><em class=\"TMEm\">").concat(processed, "</em><span class=\"TMMark\">").concat(currentDelimiter, "</span>");
907
- delimCount -= 1;
908
- stack[stackPointer].count -= 1;
909
- }
910
-
911
- // If that stack level is empty now, pop it
912
- if (stack[stackPointer].count == 0) {
913
- var _entry2 = stack.pop();
914
- processed = "".concat(_entry2.output).concat(processed);
915
- stackPointer--;
916
- }
917
- } else {
918
- // This stack level's delimiter type doesn't match the current delimiter type
919
- // Go down one level in the stack
920
- stackPointer--;
921
- }
922
- }
923
- }
924
- // If there are still delimiters left, and the delimiter run can open, push it on the stack
925
- if (delimCount && canOpen) {
926
- stack.push({
927
- delimiter: currentDelimiter,
928
- delimString: delimString,
929
- count: delimCount,
930
- output: processed
931
- });
932
- processed = ''; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
933
- delimCount = 0;
934
- }
757
+ const preceding = offset > 0 ? originalString.substr(0, offset) : ' '; // beginning and end of line count as whitespace
758
+ const following = offset + cap[0].length < originalString.length ? string : ' ';
759
+ const punctuationFollows = following.match(_grammar.punctuationLeading);
760
+ const punctuationPrecedes = preceding.match(_grammar.punctuationTrailing);
761
+ const whitespaceFollows = following.match(/^\s/);
762
+ const whitespacePrecedes = preceding.match(/\s$/);
935
763
 
936
- // Any delimiters that are left (closing unmatched) are appended to the output.
937
- if (delimCount) {
938
- processed = "".concat(processed).concat(delimString.substr(0, delimCount));
939
- }
940
- offset += cap[0].length;
941
- return "continue|outer";
764
+ // These are the rules for right-flanking and left-flanking delimiter runs as per CommonMark spec
765
+ let canOpen = !whitespaceFollows && (!punctuationFollows || !!whitespacePrecedes || !!punctuationPrecedes);
766
+ let canClose = !whitespacePrecedes && (!punctuationPrecedes || !!whitespaceFollows || !!punctuationFollows);
767
+
768
+ // Underscores have more detailed rules than just being part of left- or right-flanking run:
769
+ if (currentDelimiter == '_' && canOpen && canClose) {
770
+ canOpen = punctuationPrecedes;
771
+ canClose = punctuationFollows;
942
772
  }
943
773
 
944
- // Check for strikethrough delimiter
945
- cap = /^~~/.exec(string);
946
- if (cap) {
947
- var consumed = false;
948
- var _stackPointer = stack.length - 1;
774
+ // If the delimiter can close, check the stack if there's something it can close
775
+ if (canClose) {
776
+ let stackPointer = stack.length - 1;
949
777
  // See if we can find a matching opening delimiter, move down through the stack
950
- while (!consumed && _stackPointer >= 0) {
951
- if (stack[_stackPointer].delimiter == '~') {
778
+ while (delimCount && stackPointer >= 0) {
779
+ if (stack[stackPointer].delimiter == currentDelimiter) {
952
780
  // We found a matching delimiter, let's construct the formatted string
953
781
 
954
782
  // Firstly, if we skipped any stack levels, pop them immediately (non-matching delimiters)
955
- while (_stackPointer < stack.length - 1) {
956
- var _entry4 = stack.pop();
957
- processed = "".concat(_entry4.output).concat(_entry4.delimString.substr(0, _entry4.count)).concat(processed);
783
+ while (stackPointer < stack.length - 1) {
784
+ const entry = stack.pop();
785
+ processed = `${entry.output}${entry.delimString.substr(0, entry.count)}${processed}`;
958
786
  }
959
787
 
960
788
  // Then, format the string
961
- processed = "<span class=\"TMMark\">~~</span><del class=\"TMStrikethrough\">".concat(processed, "</del><span class=\"TMMark\">~~</span>");
962
- var _entry3 = stack.pop();
963
- processed = "".concat(_entry3.output).concat(processed);
964
- consumed = true;
789
+ if (delimCount >= 2 && stack[stackPointer].count >= 2) {
790
+ // Strong
791
+ processed = `<span class="TMMark">${currentDelimiter}${currentDelimiter}</span><strong class="TMStrong">${processed}</strong><span class="TMMark">${currentDelimiter}${currentDelimiter}</span>`;
792
+ delimCount -= 2;
793
+ stack[stackPointer].count -= 2;
794
+ } else {
795
+ // Em
796
+ processed = `<span class="TMMark">${currentDelimiter}</span><em class="TMEm">${processed}</em><span class="TMMark">${currentDelimiter}</span>`;
797
+ delimCount -= 1;
798
+ stack[stackPointer].count -= 1;
799
+ }
800
+
801
+ // If that stack level is empty now, pop it
802
+ if (stack[stackPointer].count == 0) {
803
+ let entry = stack.pop();
804
+ processed = `${entry.output}${processed}`;
805
+ stackPointer--;
806
+ }
965
807
  } else {
966
808
  // This stack level's delimiter type doesn't match the current delimiter type
967
809
  // Go down one level in the stack
968
- _stackPointer--;
810
+ stackPointer--;
969
811
  }
970
812
  }
813
+ }
814
+ // If there are still delimiters left, and the delimiter run can open, push it on the stack
815
+ if (delimCount && canOpen) {
816
+ stack.push({
817
+ delimiter: currentDelimiter,
818
+ delimString: delimString,
819
+ count: delimCount,
820
+ output: processed
821
+ });
822
+ processed = ''; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
823
+ delimCount = 0;
824
+ }
971
825
 
972
- // If there are still delimiters left, and the delimiter run can open, push it on the stack
973
- if (!consumed) {
974
- stack.push({
975
- delimiter: '~',
976
- delimString: '~~',
977
- count: 2,
978
- output: processed
979
- });
980
- processed = ''; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
981
- }
826
+ // Any delimiters that are left (closing unmatched) are appended to the output.
827
+ if (delimCount) {
828
+ processed = `${processed}${delimString.substr(0, delimCount)}`;
829
+ }
830
+ offset += cap[0].length;
831
+ continue outer;
832
+ }
982
833
 
983
- offset += cap[0].length;
984
- string = string.substr(cap[0].length);
985
- return "continue|outer";
834
+ // Check for strikethrough delimiter
835
+ cap = /^~~/.exec(string);
836
+ if (cap) {
837
+ let consumed = false;
838
+ let stackPointer = stack.length - 1;
839
+ // See if we can find a matching opening delimiter, move down through the stack
840
+ while (!consumed && stackPointer >= 0) {
841
+ if (stack[stackPointer].delimiter == '~') {
842
+ // We found a matching delimiter, let's construct the formatted string
843
+
844
+ // Firstly, if we skipped any stack levels, pop them immediately (non-matching delimiters)
845
+ while (stackPointer < stack.length - 1) {
846
+ const entry = stack.pop();
847
+ processed = `${entry.output}${entry.delimString.substr(0, entry.count)}${processed}`;
848
+ }
849
+
850
+ // Then, format the string
851
+ processed = `<span class="TMMark">~~</span><del class="TMStrikethrough">${processed}</del><span class="TMMark">~~</span>`;
852
+ let entry = stack.pop();
853
+ processed = `${entry.output}${processed}`;
854
+ consumed = true;
855
+ } else {
856
+ // This stack level's delimiter type doesn't match the current delimiter type
857
+ // Go down one level in the stack
858
+ stackPointer--;
859
+ }
986
860
  }
987
861
 
988
- // Process 'default' rule
989
- cap = _grammar.inlineGrammar.default.regexp.exec(string);
990
- if (cap) {
991
- string = string.substr(cap[0].length);
992
- offset += cap[0].length;
993
- processed += _grammar.inlineGrammar.default.replacement.replace(/\$([1-9])/g, function (str, p1) {
994
- return (0, _grammar.htmlescape)(cap[p1]);
862
+ // If there are still delimiters left, and the delimiter run can open, push it on the stack
863
+ if (!consumed) {
864
+ stack.push({
865
+ delimiter: '~',
866
+ delimString: '~~',
867
+ count: 2,
868
+ output: processed
995
869
  });
996
- return "continue|outer";
870
+ processed = ''; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
997
871
  }
998
- throw 'Infinite loop!';
999
- };
1000
- outer: while (string) {
1001
- var _ret = _loop();
1002
- if (_ret === "continue|outer") continue outer;
872
+
873
+ offset += cap[0].length;
874
+ string = string.substr(cap[0].length);
875
+ continue outer;
1003
876
  }
1004
877
 
1005
- // Empty the stack, any opening delimiters are unused
1006
- while (stack.length) {
1007
- var entry = stack.pop();
1008
- processed = "".concat(entry.output).concat(entry.delimString.substr(0, entry.count)).concat(processed);
878
+ // Process 'default' rule
879
+ cap = _grammar.inlineGrammar.default.regexp.exec(string);
880
+ if (cap) {
881
+ string = string.substr(cap[0].length);
882
+ offset += cap[0].length;
883
+ processed += _grammar.inlineGrammar.default.replacement.replace(/\$([1-9])/g, (str, p1) => (0, _grammar.htmlescape)(cap[p1]));
884
+ continue outer;
1009
885
  }
1010
- return processed;
886
+ throw 'Infinite loop!';
1011
887
  }
1012
888
 
1013
- /**
1014
- * Clears the line dirty flag (resets it to an array of false)
1015
- */
1016
- }, {
1017
- key: "clearDirtyFlag",
1018
- value: function clearDirtyFlag() {
1019
- this.lineDirty = new Array(this.lines.length);
1020
- for (var i = 0; i < this.lineDirty.length; i++) {
1021
- this.lineDirty[i] = false;
1022
- }
889
+ // Empty the stack, any opening delimiters are unused
890
+ while (stack.length) {
891
+ const entry = stack.pop();
892
+ processed = `${entry.output}${entry.delimString.substr(0, entry.count)}${processed}`;
1023
893
  }
894
+ return processed;
895
+ }
1024
896
 
1025
- /**
1026
- * Updates the class properties (lines, lineElements) from the DOM.
1027
- * @returns true if contents changed
1028
- */
1029
- }, {
1030
- key: "updateLineContents",
1031
- value: function updateLineContents() {
1032
- // this.lineDirty = [];
1033
- // Check if we have changed anything about the number of lines (inserted or deleted a paragraph)
1034
- // < 0 means line(s) removed; > 0 means line(s) added
1035
- var lineDelta = this.e.childElementCount - this.lines.length;
1036
- if (lineDelta) {
1037
- // yup. Let's try how much we can salvage (find out which lines from beginning and end were unchanged)
1038
- // Find lines from the beginning that haven't changed...
1039
- var firstChangedLine = 0;
1040
- while (firstChangedLine <= this.lines.length && firstChangedLine <= this.lineElements.length && this.lineElements[firstChangedLine] // Check that the line element hasn't been deleted
1041
- && this.lines[firstChangedLine] == this.lineElements[firstChangedLine].textContent) {
1042
- firstChangedLine++;
1043
- }
897
+ /**
898
+ * Clears the line dirty flag (resets it to an array of false)
899
+ */
900
+ clearDirtyFlag() {
901
+ this.lineDirty = new Array(this.lines.length);
902
+ for (let i = 0; i < this.lineDirty.length; i++) {
903
+ this.lineDirty[i] = false;
904
+ }
905
+ }
1044
906
 
1045
- // End also from the end
1046
- var lastChangedLine = -1;
1047
- while (-lastChangedLine < this.lines.length && -lastChangedLine < this.lineElements.length && this.lines[this.lines.length + lastChangedLine] == this.lineElements[this.lineElements.length + lastChangedLine].textContent) {
1048
- lastChangedLine--;
1049
- }
1050
- var linesToDelete = this.lines.length + lastChangedLine + 1 - firstChangedLine;
1051
- if (linesToDelete < -lineDelta) linesToDelete = -lineDelta;
1052
- if (linesToDelete < 0) linesToDelete = 0;
1053
- var linesToAdd = [];
1054
- for (var l = 0; l < linesToDelete + lineDelta; l++) {
1055
- linesToAdd.push(this.lineElements[firstChangedLine + l].textContent);
1056
- }
1057
- this.spliceLines(firstChangedLine, linesToDelete, linesToAdd, false);
1058
- } else {
1059
- // No lines added or removed
1060
- for (var line = 0; line < this.lineElements.length; line++) {
1061
- var e = this.lineElements[line];
1062
- var ct = e.textContent;
1063
- if (this.lines[line] !== ct) {
1064
- // Line changed, update it
1065
- this.lines[line] = ct;
1066
- this.lineDirty[line] = true;
1067
- }
1068
- }
907
+ /**
908
+ * Updates the class properties (lines, lineElements) from the DOM.
909
+ * @returns true if contents changed
910
+ */
911
+ updateLineContents() {
912
+ // this.lineDirty = [];
913
+ // Check if we have changed anything about the number of lines (inserted or deleted a paragraph)
914
+ // < 0 means line(s) removed; > 0 means line(s) added
915
+ let lineDelta = this.e.childElementCount - this.lines.length;
916
+ if (lineDelta) {
917
+ // yup. Let's try how much we can salvage (find out which lines from beginning and end were unchanged)
918
+ // Find lines from the beginning that haven't changed...
919
+ let firstChangedLine = 0;
920
+ while (firstChangedLine <= this.lines.length && firstChangedLine <= this.lineElements.length && this.lineElements[firstChangedLine] // Check that the line element hasn't been deleted
921
+ && this.lines[firstChangedLine] == this.lineElements[firstChangedLine].textContent) {
922
+ firstChangedLine++;
1069
923
  }
1070
- }
1071
924
 
1072
- /**
1073
- * Processes a new paragraph.
1074
- * @param sel The current selection
1075
- */
1076
- }, {
1077
- key: "processNewParagraph",
1078
- value: function processNewParagraph(sel) {
1079
- if (!sel) return;
1080
-
1081
- // Update lines from content
1082
- this.updateLineContents();
1083
- var continuableType = false;
1084
- // Let's see if we need to continue a list
1085
-
1086
- var checkLine = sel.col > 0 ? sel.row : sel.row - 1;
1087
- switch (this.lineTypes[checkLine]) {
1088
- case 'TMUL':
1089
- continuableType = 'TMUL';
1090
- break;
1091
- case 'TMOL':
1092
- continuableType = 'TMOL';
1093
- break;
1094
- case 'TMIndentedCode':
1095
- continuableType = 'TMIndentedCode';
1096
- break;
925
+ // End also from the end
926
+ let lastChangedLine = -1;
927
+ while (-lastChangedLine < this.lines.length && -lastChangedLine < this.lineElements.length && this.lines[this.lines.length + lastChangedLine] == this.lineElements[this.lineElements.length + lastChangedLine].textContent) {
928
+ lastChangedLine--;
1097
929
  }
1098
- var lines = this.lines[sel.row].replace(/\n\n$/, '\n').split(/(?:\r\n|\n|\r)/);
1099
- if (lines.length == 1) {
1100
- // No new line
1101
- this.updateFormatting();
1102
- return;
930
+ let linesToDelete = this.lines.length + lastChangedLine + 1 - firstChangedLine;
931
+ if (linesToDelete < -lineDelta) linesToDelete = -lineDelta;
932
+ if (linesToDelete < 0) linesToDelete = 0;
933
+ let linesToAdd = [];
934
+ for (let l = 0; l < linesToDelete + lineDelta; l++) {
935
+ linesToAdd.push(this.lineElements[firstChangedLine + l].textContent);
1103
936
  }
1104
- this.spliceLines(sel.row, 1, lines, true);
1105
- sel.row++;
1106
- sel.col = 0;
1107
- if (continuableType) {
1108
- // Check if the previous line was non-empty
1109
- var capture = _grammar.lineGrammar[continuableType].regexp.exec(this.lines[sel.row - 1]);
1110
- if (capture) {
1111
- // Convention: capture[1] is the line type marker, capture[2] is the content
1112
- if (capture[2]) {
1113
- // Previous line has content, continue the continuable type
1114
-
1115
- // Hack for OL: increment number
1116
- if (continuableType == 'TMOL') {
1117
- capture[1] = capture[1].replace(/\d{1,9}/, function (result) {
1118
- return parseInt(result[0]) + 1;
1119
- });
1120
- }
1121
- this.lines[sel.row] = "".concat(capture[1]).concat(this.lines[sel.row]);
1122
- this.lineDirty[sel.row] = true;
1123
- sel.col = capture[1].length;
1124
- } else {
1125
- // Previous line has no content, remove the continuable type from the previous row
1126
- this.lines[sel.row - 1] = '';
1127
- this.lineDirty[sel.row - 1] = true;
1128
- }
937
+ this.spliceLines(firstChangedLine, linesToDelete, linesToAdd, false);
938
+ } else {
939
+ // No lines added or removed
940
+ for (let line = 0; line < this.lineElements.length; line++) {
941
+ let e = this.lineElements[line];
942
+ let ct = e.textContent;
943
+ if (this.lines[line] !== ct) {
944
+ // Line changed, update it
945
+ this.lines[line] = ct;
946
+ this.lineDirty[line] = true;
1129
947
  }
1130
948
  }
1131
- this.updateFormatting();
1132
949
  }
950
+ }
1133
951
 
1134
- // /**
1135
- // * Processes a "delete" input action.
1136
- // * @param {object} focus The selection
1137
- // * @param {boolean} forward If true, performs a forward delete, otherwise performs a backward delete
1138
- // */
1139
- // processDelete(focus, forward) {
1140
- // if (!focus) return;
1141
- // let anchor = this.getSelection(true);
1142
- // // Do we have a non-empty selection?
1143
- // if (focus.col != anchor.col || focus.row != anchor.row) {
1144
- // // non-empty. direction doesn't matter.
1145
- // this.paste('', anchor, focus);
1146
- // } else {
1147
- // if (forward) {
1148
- // if (focus.col < this.lines[focus.row].length) this.paste('', {row: focus.row, col: focus.col + 1}, focus);
1149
- // else if (focus.col < this.lines.length) this.paste('', {row: focus.row + 1, col: 0}, focus);
1150
- // // Otherwise, we're at the very end and can't delete forward
1151
- // } else {
1152
- // if (focus.col > 0) this.paste('', {row: focus.row, col: focus.col - 1}, focus);
1153
- // else if (focus.row > 0) this.paste('', {row: focus.row - 1, col: this.lines[focus.row - 1].length - 1}, focus);
1154
- // // Otherwise, we're at the very beginning and can't delete backwards
1155
- // }
1156
- // }
1157
-
1158
- // }
1159
-
1160
- /**
1161
- * Gets the current position of the selection counted by row and column of the editor Markdown content (as opposed to the position in the DOM).
1162
- *
1163
- * @param {boolean} getAnchor if set to true, gets the selection anchor (start point of the selection), otherwise gets the focus (end point).
1164
- * @return {object} An object representing the selection, with properties col and row.
1165
- */
1166
- }, {
1167
- key: "getSelection",
1168
- value: function getSelection() {
1169
- var getAnchor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
1170
- var selection = window.getSelection();
1171
- var startNode = getAnchor ? selection.anchorNode : selection.focusNode;
1172
- if (!startNode) return null;
1173
- var offset = startNode.nodeType === Node.TEXT_NODE ? getAnchor ? selection.anchorOffset : selection.focusOffset : 0;
1174
- if (startNode == this.e) {
1175
- return {
1176
- row: 0,
1177
- col: offset
1178
- };
1179
- }
1180
- var col = this.computeColumn(startNode, offset);
1181
- if (col === null) return null; // We are outside of the editor
1182
-
1183
- // Find the row node
1184
- var node = startNode;
1185
- while (node.parentElement != this.e) {
1186
- node = node.parentElement;
1187
- }
1188
- var row = 0;
1189
- // Check if we can read a line number from the data-line-num attribute.
1190
- // The last condition is a security measure since inserting a new paragraph copies the previous rows' line number
1191
- if (node.dataset && node.dataset.lineNum && (!node.previousSibling || node.previousSibling.dataset.lineNum != node.dataset.lineNum)) {
1192
- row = parseInt(node.dataset.lineNum);
1193
- } else {
1194
- while (node.previousSibling) {
1195
- row++;
1196
- node = node.previousSibling;
952
+ /**
953
+ * Processes a new paragraph.
954
+ * @param sel The current selection
955
+ */
956
+ processNewParagraph(sel) {
957
+ if (!sel) return;
958
+
959
+ // Update lines from content
960
+ this.updateLineContents();
961
+ let continuableType = false;
962
+ // Let's see if we need to continue a list
963
+
964
+ let checkLine = sel.col > 0 ? sel.row : sel.row - 1;
965
+ switch (this.lineTypes[checkLine]) {
966
+ case 'TMUL':
967
+ continuableType = 'TMUL';
968
+ break;
969
+ case 'TMOL':
970
+ continuableType = 'TMOL';
971
+ break;
972
+ case 'TMIndentedCode':
973
+ continuableType = 'TMIndentedCode';
974
+ break;
975
+ }
976
+ let lines = this.lines[sel.row].replace(/\n\n$/, '\n').split(/(?:\r\n|\n|\r)/);
977
+ if (lines.length == 1) {
978
+ // No new line
979
+ this.updateFormatting();
980
+ return;
981
+ }
982
+ this.spliceLines(sel.row, 1, lines, true);
983
+ sel.row++;
984
+ sel.col = 0;
985
+ if (continuableType) {
986
+ // Check if the previous line was non-empty
987
+ let capture = _grammar.lineGrammar[continuableType].regexp.exec(this.lines[sel.row - 1]);
988
+ if (capture) {
989
+ // Convention: capture[1] is the line type marker, capture[2] is the content
990
+ if (capture[2]) {
991
+ // Previous line has content, continue the continuable type
992
+
993
+ // Hack for OL: increment number
994
+ if (continuableType == 'TMOL') {
995
+ capture[1] = capture[1].replace(/\d{1,9}/, result => {
996
+ return parseInt(result[0]) + 1;
997
+ });
998
+ }
999
+ this.lines[sel.row] = `${capture[1]}${this.lines[sel.row]}`;
1000
+ this.lineDirty[sel.row] = true;
1001
+ sel.col = capture[1].length;
1002
+ } else {
1003
+ // Previous line has no content, remove the continuable type from the previous row
1004
+ this.lines[sel.row - 1] = '';
1005
+ this.lineDirty[sel.row - 1] = true;
1197
1006
  }
1198
1007
  }
1008
+ }
1009
+ this.updateFormatting();
1010
+ }
1011
+
1012
+ // /**
1013
+ // * Processes a "delete" input action.
1014
+ // * @param {object} focus The selection
1015
+ // * @param {boolean} forward If true, performs a forward delete, otherwise performs a backward delete
1016
+ // */
1017
+ // processDelete(focus, forward) {
1018
+ // if (!focus) return;
1019
+ // let anchor = this.getSelection(true);
1020
+ // // Do we have a non-empty selection?
1021
+ // if (focus.col != anchor.col || focus.row != anchor.row) {
1022
+ // // non-empty. direction doesn't matter.
1023
+ // this.paste('', anchor, focus);
1024
+ // } else {
1025
+ // if (forward) {
1026
+ // if (focus.col < this.lines[focus.row].length) this.paste('', {row: focus.row, col: focus.col + 1}, focus);
1027
+ // else if (focus.col < this.lines.length) this.paste('', {row: focus.row + 1, col: 0}, focus);
1028
+ // // Otherwise, we're at the very end and can't delete forward
1029
+ // } else {
1030
+ // if (focus.col > 0) this.paste('', {row: focus.row, col: focus.col - 1}, focus);
1031
+ // else if (focus.row > 0) this.paste('', {row: focus.row - 1, col: this.lines[focus.row - 1].length - 1}, focus);
1032
+ // // Otherwise, we're at the very beginning and can't delete backwards
1033
+ // }
1034
+ // }
1035
+
1036
+ // }
1037
+
1038
+ /**
1039
+ * Gets the current position of the selection counted by row and column of the editor Markdown content (as opposed to the position in the DOM).
1040
+ *
1041
+ * @param {boolean} getAnchor if set to true, gets the selection anchor (start point of the selection), otherwise gets the focus (end point).
1042
+ * @return {object} An object representing the selection, with properties col and row.
1043
+ */
1044
+ getSelection() {
1045
+ let getAnchor = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
1046
+ const selection = window.getSelection();
1047
+ let startNode = getAnchor ? selection.anchorNode : selection.focusNode;
1048
+ if (!startNode) return null;
1049
+ let offset = startNode.nodeType === Node.TEXT_NODE ? getAnchor ? selection.anchorOffset : selection.focusOffset : 0;
1050
+ if (startNode == this.e) {
1199
1051
  return {
1200
- row: row,
1201
- col: col,
1202
- node: startNode
1052
+ row: 0,
1053
+ col: offset
1203
1054
  };
1204
1055
  }
1056
+ let col = this.computeColumn(startNode, offset);
1057
+ if (col === null) return null; // We are outside of the editor
1205
1058
 
1206
- /**
1207
- * Computes a column within an editor line from a node and offset within that node.
1208
- * @param {Node} startNode The node
1209
- * @param {int} offset THe selection
1210
- * @returns {int} the column, or null if the node is not inside the editor
1211
- */
1212
- }, {
1213
- key: "computeColumn",
1214
- value: function computeColumn(startNode, offset) {
1215
- var node = startNode;
1216
- var col = offset;
1217
- // First, make sure we're actually in the editor.
1218
- while (node && node.parentNode != this.e) {
1219
- node = node.parentNode;
1220
- }
1221
- if (node == null) return null;
1222
- node = startNode;
1223
- while (node.parentNode != this.e) {
1224
- if (node.previousSibling) {
1225
- node = node.previousSibling;
1226
- col += node.textContent.length;
1227
- } else {
1228
- node = node.parentNode;
1229
- }
1059
+ // Find the row node
1060
+ let node = startNode;
1061
+ while (node.parentElement != this.e) {
1062
+ node = node.parentElement;
1063
+ }
1064
+ let row = 0;
1065
+ // Check if we can read a line number from the data-line-num attribute.
1066
+ // The last condition is a security measure since inserting a new paragraph copies the previous rows' line number
1067
+ if (node.dataset && node.dataset.lineNum && (!node.previousSibling || node.previousSibling.dataset.lineNum != node.dataset.lineNum)) {
1068
+ row = parseInt(node.dataset.lineNum);
1069
+ } else {
1070
+ while (node.previousSibling) {
1071
+ row++;
1072
+ node = node.previousSibling;
1230
1073
  }
1231
- return col;
1232
1074
  }
1075
+ return {
1076
+ row: row,
1077
+ col: col,
1078
+ node: startNode
1079
+ };
1080
+ }
1233
1081
 
1234
- /**
1235
- * Computes DOM node and offset within that node from a position expressed as row and column.
1236
- * @param {int} row Row (line number)
1237
- * @param {int} col Column
1238
- * @returns An object with two properties: node and offset. offset may be null;
1239
- */
1240
- }, {
1241
- key: "computeNodeAndOffset",
1242
- value: function computeNodeAndOffset(row, col) {
1243
- var bindRight = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1244
- if (row >= this.lineElements.length) {
1245
- // Selection past the end of text, set selection to end of text
1246
- row = this.lineElements.length - 1;
1247
- col = this.lines[row].length;
1248
- }
1249
- if (col > this.lines[row].length) {
1250
- col = this.lines[row].length;
1082
+ /**
1083
+ * Computes a column within an editor line from a node and offset within that node.
1084
+ * @param {Node} startNode The node
1085
+ * @param {int} offset THe selection
1086
+ * @returns {int} the column, or null if the node is not inside the editor
1087
+ */
1088
+ computeColumn(startNode, offset) {
1089
+ let node = startNode;
1090
+ let col = offset;
1091
+ // First, make sure we're actually in the editor.
1092
+ while (node && node.parentNode != this.e) {
1093
+ node = node.parentNode;
1094
+ }
1095
+ if (node == null) return null;
1096
+ node = startNode;
1097
+ while (node.parentNode != this.e) {
1098
+ if (node.previousSibling) {
1099
+ node = node.previousSibling;
1100
+ col += node.textContent.length;
1101
+ } else {
1102
+ node = node.parentNode;
1251
1103
  }
1252
- var parentNode = this.lineElements[row];
1253
- var node = parentNode.firstChild;
1254
- var childrenComplete = false;
1255
- // default return value
1256
- var rv = {
1257
- node: parentNode.firstChild ? parentNode.firstChild : parentNode,
1258
- offset: 0
1259
- };
1260
- while (node != parentNode) {
1261
- if (!childrenComplete && node.nodeType === Node.TEXT_NODE) {
1262
- if (node.nodeValue.length >= col) {
1263
- if (bindRight && node.nodeValue.length == col) {
1264
- // Selection is at the end of this text node, but we are binding right (prefer an offset of 0 in the next text node)
1265
- // Remember return value in case we don't find another text node
1266
- rv = {
1267
- node: node,
1268
- offset: col
1269
- };
1270
- col = 0;
1271
- } else {
1272
- return {
1273
- node: node,
1274
- offset: col
1275
- };
1276
- }
1104
+ }
1105
+ return col;
1106
+ }
1107
+
1108
+ /**
1109
+ * Computes DOM node and offset within that node from a position expressed as row and column.
1110
+ * @param {int} row Row (line number)
1111
+ * @param {int} col Column
1112
+ * @returns An object with two properties: node and offset. offset may be null;
1113
+ */
1114
+ computeNodeAndOffset(row, col) {
1115
+ let bindRight = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
1116
+ if (row >= this.lineElements.length) {
1117
+ // Selection past the end of text, set selection to end of text
1118
+ row = this.lineElements.length - 1;
1119
+ col = this.lines[row].length;
1120
+ }
1121
+ if (col > this.lines[row].length) {
1122
+ col = this.lines[row].length;
1123
+ }
1124
+ const parentNode = this.lineElements[row];
1125
+ let node = parentNode.firstChild;
1126
+ let childrenComplete = false;
1127
+ // default return value
1128
+ let rv = {
1129
+ node: parentNode.firstChild ? parentNode.firstChild : parentNode,
1130
+ offset: 0
1131
+ };
1132
+ while (node != parentNode) {
1133
+ if (!childrenComplete && node.nodeType === Node.TEXT_NODE) {
1134
+ if (node.nodeValue.length >= col) {
1135
+ if (bindRight && node.nodeValue.length == col) {
1136
+ // Selection is at the end of this text node, but we are binding right (prefer an offset of 0 in the next text node)
1137
+ // Remember return value in case we don't find another text node
1138
+ rv = {
1139
+ node: node,
1140
+ offset: col
1141
+ };
1142
+ col = 0;
1277
1143
  } else {
1278
- col -= node.nodeValue.length;
1144
+ return {
1145
+ node: node,
1146
+ offset: col
1147
+ };
1279
1148
  }
1280
- }
1281
- if (!childrenComplete && node.firstChild) {
1282
- node = node.firstChild;
1283
- } else if (node.nextSibling) {
1284
- childrenComplete = false;
1285
- node = node.nextSibling;
1286
1149
  } else {
1287
- childrenComplete = true;
1288
- node = node.parentNode;
1150
+ col -= node.nodeValue.length;
1289
1151
  }
1290
1152
  }
1291
-
1292
- // Either, the position was invalid and we just return the default return value
1293
- // Or we are binding right and the selection is at the end of the line
1294
- return rv;
1153
+ if (!childrenComplete && node.firstChild) {
1154
+ node = node.firstChild;
1155
+ } else if (node.nextSibling) {
1156
+ childrenComplete = false;
1157
+ node = node.nextSibling;
1158
+ } else {
1159
+ childrenComplete = true;
1160
+ node = node.parentNode;
1161
+ }
1295
1162
  }
1296
1163
 
1297
- /**
1298
- * Sets the selection based on rows and columns within the editor Markdown content.
1299
- * @param {object} focus Object representing the selection, needs to have properties row and col.
1300
- */
1301
- }, {
1302
- key: "setSelection",
1303
- value: function setSelection(focus) {
1304
- var anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1305
- if (!focus) return;
1306
- var range = document.createRange();
1307
- var _this$computeNodeAndO = this.computeNodeAndOffset(focus.row, focus.col, anchor && anchor.row == focus.row && anchor.col > focus.col),
1308
- focusNode = _this$computeNodeAndO.node,
1309
- focusOffset = _this$computeNodeAndO.offset; // Bind selection right if anchor is in the same row and behind the focus
1310
- var anchorNode = null,
1311
- anchorOffset = null;
1312
- if (anchor && (anchor.row != focus.row || anchor.col != focus.col)) {
1313
- var _this$computeNodeAndO2 = this.computeNodeAndOffset(anchor.row, anchor.col, focus.row == anchor.row && focus.col > anchor.col),
1314
- node = _this$computeNodeAndO2.node,
1315
- offset = _this$computeNodeAndO2.offset;
1316
- anchorNode = node;
1317
- anchorOffset = offset;
1318
- }
1319
- if (anchorNode) range.setStart(anchorNode, anchorOffset);else range.setStart(focusNode, focusOffset);
1320
- range.setEnd(focusNode, focusOffset);
1321
- var windowSelection = window.getSelection();
1322
- windowSelection.removeAllRanges();
1323
- windowSelection.addRange(range);
1164
+ // Either, the position was invalid and we just return the default return value
1165
+ // Or we are binding right and the selection is at the end of the line
1166
+ return rv;
1167
+ }
1168
+
1169
+ /**
1170
+ * Sets the selection based on rows and columns within the editor Markdown content.
1171
+ * @param {object} focus Object representing the selection, needs to have properties row and col.
1172
+ */
1173
+ setSelection(focus) {
1174
+ let anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1175
+ if (!focus) return;
1176
+ let range = document.createRange();
1177
+ let {
1178
+ node: focusNode,
1179
+ offset: focusOffset
1180
+ } = this.computeNodeAndOffset(focus.row, focus.col, anchor && anchor.row == focus.row && anchor.col > focus.col); // Bind selection right if anchor is in the same row and behind the focus
1181
+ let anchorNode = null,
1182
+ anchorOffset = null;
1183
+ if (anchor && (anchor.row != focus.row || anchor.col != focus.col)) {
1184
+ let {
1185
+ node,
1186
+ offset
1187
+ } = this.computeNodeAndOffset(anchor.row, anchor.col, focus.row == anchor.row && focus.col > anchor.col);
1188
+ anchorNode = node;
1189
+ anchorOffset = offset;
1324
1190
  }
1191
+ if (anchorNode) range.setStart(anchorNode, anchorOffset);else range.setStart(focusNode, focusOffset);
1192
+ range.setEnd(focusNode, focusOffset);
1193
+ let windowSelection = window.getSelection();
1194
+ windowSelection.removeAllRanges();
1195
+ windowSelection.addRange(range);
1196
+ }
1325
1197
 
1326
- /**
1327
- * Event handler for input events
1328
- */
1329
- }, {
1330
- key: "handleInputEvent",
1331
- value: function handleInputEvent(event) {
1332
- var focus = this.getSelection();
1333
- if ((event.inputType == 'insertParagraph' || event.inputType == 'insertLineBreak') && focus) {
1334
- this.clearDirtyFlag();
1335
- this.processNewParagraph(focus);
1198
+ /**
1199
+ * Event handler for input events
1200
+ */
1201
+ handleInputEvent(event) {
1202
+ let focus = this.getSelection();
1203
+ if ((event.inputType == 'insertParagraph' || event.inputType == 'insertLineBreak') && focus) {
1204
+ this.clearDirtyFlag();
1205
+ this.processNewParagraph(focus);
1206
+ } else {
1207
+ if (!this.e.firstChild) {
1208
+ this.e.innerHTML = '<div class="TMBlankLine"><br></div>';
1336
1209
  } else {
1337
- if (!this.e.firstChild) {
1338
- this.e.innerHTML = '<div class="TMBlankLine"><br></div>';
1339
- } else {
1340
- for (var childNode = this.e.firstChild; childNode; childNode = childNode.nextSibling) {
1341
- if (childNode.nodeType != 3 || childNode.tagName != 'DIV') {
1342
- // Found a child node that's either not an element or not a div. Wrap it in a div.
1343
- var divWrapper = document.createElement('div');
1344
- this.e.insertBefore(divWrapper, childNode);
1345
- this.e.removeChild(childNode);
1346
- divWrapper.appendChild(childNode);
1347
- }
1210
+ for (let childNode = this.e.firstChild; childNode; childNode = childNode.nextSibling) {
1211
+ if (childNode.nodeType != 3 || childNode.tagName != 'DIV') {
1212
+ // Found a child node that's either not an element or not a div. Wrap it in a div.
1213
+ let divWrapper = document.createElement('div');
1214
+ this.e.insertBefore(divWrapper, childNode);
1215
+ this.e.removeChild(childNode);
1216
+ divWrapper.appendChild(childNode);
1348
1217
  }
1349
1218
  }
1350
- this.updateLineContentsAndFormatting();
1351
1219
  }
1352
- if (focus) this.setSelection(focus);
1353
- this.fireChange();
1220
+ this.updateLineContentsAndFormatting();
1354
1221
  }
1222
+ if (focus) this.setSelection(focus);
1223
+ this.fireChange();
1224
+ }
1355
1225
 
1356
- /**
1357
- * Event handler for "selectionchange" events.
1358
- */
1359
- }, {
1360
- key: "handleSelectionChangeEvent",
1361
- value: function handleSelectionChangeEvent() {
1362
- this.fireSelection();
1363
- }
1226
+ /**
1227
+ * Event handler for "selectionchange" events.
1228
+ */
1229
+ handleSelectionChangeEvent() {
1230
+ this.fireSelection();
1231
+ }
1364
1232
 
1365
- /**
1366
- * Convenience function to "splice" new lines into the arrays this.lines, this.lineDirty, this.lineTypes, and the DOM elements
1367
- * underneath the editor element.
1368
- * This method is essentially Array.splice, only that the third parameter takes an un-spread array (and the forth parameter)
1369
- * determines whether the DOM should also be adjusted.
1370
- *
1371
- * @param {int} startLine Position at which to start changing the array of lines
1372
- * @param {int} linesToDelete Number of lines to delete
1373
- * @param {array} linesToInsert Array of strings representing the lines to be inserted
1374
- * @param {boolean} adjustLineElements If true, then <div> elements are also inserted in the DOM at the respective position
1375
- */
1376
- }, {
1377
- key: "spliceLines",
1378
- value: function spliceLines(startLine) {
1379
- var _this$lines, _this$lineTypes, _this$lineDirty;
1380
- var linesToDelete = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1381
- var linesToInsert = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
1382
- var adjustLineElements = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
1383
- if (adjustLineElements) {
1384
- for (var i = 0; i < linesToDelete; i++) {
1385
- this.e.removeChild(this.e.childNodes[startLine]);
1386
- }
1233
+ /**
1234
+ * Convenience function to "splice" new lines into the arrays this.lines, this.lineDirty, this.lineTypes, and the DOM elements
1235
+ * underneath the editor element.
1236
+ * This method is essentially Array.splice, only that the third parameter takes an un-spread array (and the forth parameter)
1237
+ * determines whether the DOM should also be adjusted.
1238
+ *
1239
+ * @param {int} startLine Position at which to start changing the array of lines
1240
+ * @param {int} linesToDelete Number of lines to delete
1241
+ * @param {array} linesToInsert Array of strings representing the lines to be inserted
1242
+ * @param {boolean} adjustLineElements If true, then <div> elements are also inserted in the DOM at the respective position
1243
+ */
1244
+ spliceLines(startLine) {
1245
+ let linesToDelete = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
1246
+ let linesToInsert = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
1247
+ let adjustLineElements = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
1248
+ if (adjustLineElements) {
1249
+ for (let i = 0; i < linesToDelete; i++) {
1250
+ this.e.removeChild(this.e.childNodes[startLine]);
1387
1251
  }
1388
- var insertedBlank = [];
1389
- var insertedDirty = [];
1390
- for (var _i3 = 0; _i3 < linesToInsert.length; _i3++) {
1391
- insertedBlank.push('');
1392
- insertedDirty.push(true);
1393
- if (adjustLineElements) {
1394
- if (this.e.childNodes[startLine]) this.e.insertBefore(document.createElement('div'), this.e.childNodes[startLine]);else this.e.appendChild(document.createElement('div'));
1395
- }
1252
+ }
1253
+ let insertedBlank = [];
1254
+ let insertedDirty = [];
1255
+ for (let i = 0; i < linesToInsert.length; i++) {
1256
+ insertedBlank.push('');
1257
+ insertedDirty.push(true);
1258
+ if (adjustLineElements) {
1259
+ if (this.e.childNodes[startLine]) this.e.insertBefore(document.createElement('div'), this.e.childNodes[startLine]);else this.e.appendChild(document.createElement('div'));
1396
1260
  }
1397
- (_this$lines = this.lines).splice.apply(_this$lines, [startLine, linesToDelete].concat(_toConsumableArray(linesToInsert)));
1398
- (_this$lineTypes = this.lineTypes).splice.apply(_this$lineTypes, [startLine, linesToDelete].concat(insertedBlank));
1399
- (_this$lineDirty = this.lineDirty).splice.apply(_this$lineDirty, [startLine, linesToDelete].concat(insertedDirty));
1400
1261
  }
1262
+ this.lines.splice(startLine, linesToDelete, ...linesToInsert);
1263
+ this.lineTypes.splice(startLine, linesToDelete, ...insertedBlank);
1264
+ this.lineDirty.splice(startLine, linesToDelete, ...insertedDirty);
1265
+ }
1401
1266
 
1402
- /**
1403
- * Event handler for the "paste" event
1404
- */
1405
- }, {
1406
- key: "handlePaste",
1407
- value: function handlePaste(event) {
1408
- event.preventDefault();
1409
-
1410
- // get text representation of clipboard
1411
- var text = (event.originalEvent || event).clipboardData.getData('text/plain');
1267
+ /**
1268
+ * Event handler for the "paste" event
1269
+ */
1270
+ handlePaste(event) {
1271
+ event.preventDefault();
1412
1272
 
1413
- // insert text manually
1414
- this.paste(text);
1415
- }
1273
+ // get text representation of clipboard
1274
+ let text = (event.originalEvent || event).clipboardData.getData('text/plain');
1416
1275
 
1417
- /**
1418
- * Pastes the text at the current selection (or at the end, if no current selection)
1419
- * @param {string} text
1420
- */
1421
- }, {
1422
- key: "paste",
1423
- value: function paste(text) {
1424
- var anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1425
- var focus = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1426
- if (!anchor) anchor = this.getSelection(true);
1427
- if (!focus) focus = this.getSelection(false);
1428
- var beginning, end;
1429
- if (!focus) {
1430
- focus = {
1431
- row: this.lines.length - 1,
1432
- col: this.lines[this.lines.length - 1].length
1433
- }; // Insert at end
1434
- }
1276
+ // insert text manually
1277
+ this.paste(text);
1278
+ }
1435
1279
 
1436
- if (!anchor) {
1437
- anchor = focus;
1438
- }
1439
- if (anchor.row < focus.row || anchor.row == focus.row && anchor.col <= focus.col) {
1440
- beginning = anchor;
1441
- end = focus;
1442
- } else {
1443
- beginning = focus;
1444
- end = anchor;
1445
- }
1446
- var insertedLines = text.split(/(?:\r\n|\r|\n)/);
1447
- var lineBefore = this.lines[beginning.row].substr(0, beginning.col);
1448
- var lineEnd = this.lines[end.row].substr(end.col);
1449
- insertedLines[0] = lineBefore.concat(insertedLines[0]);
1450
- var endColPos = insertedLines[insertedLines.length - 1].length;
1451
- insertedLines[insertedLines.length - 1] = insertedLines[insertedLines.length - 1].concat(lineEnd);
1452
- this.spliceLines(beginning.row, 1 + end.row - beginning.row, insertedLines);
1453
- focus.row = beginning.row + insertedLines.length - 1;
1454
- focus.col = endColPos;
1455
- this.updateFormatting();
1456
- this.setSelection(focus);
1457
- this.fireChange();
1280
+ /**
1281
+ * Pastes the text at the current selection (or at the end, if no current selection)
1282
+ * @param {string} text
1283
+ */
1284
+ paste(text) {
1285
+ let anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1286
+ let focus = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1287
+ if (!anchor) anchor = this.getSelection(true);
1288
+ if (!focus) focus = this.getSelection(false);
1289
+ let beginning, end;
1290
+ if (!focus) {
1291
+ focus = {
1292
+ row: this.lines.length - 1,
1293
+ col: this.lines[this.lines.length - 1].length
1294
+ }; // Insert at end
1458
1295
  }
1459
1296
 
1460
- /**
1461
- * Computes the (lowest in the DOM tree) common ancestor of two DOM nodes.
1462
- * @param {Node} node1 the first node
1463
- * @param {Node} node2 the second node
1464
- * @returns {Node} The commen ancestor node, or null if there is no common ancestor
1465
- */
1466
- }, {
1467
- key: "computeCommonAncestor",
1468
- value: function computeCommonAncestor(node1, node2) {
1469
- if (!node1 || !node2) return null;
1470
- if (node1 == node2) return node1;
1471
- var ancestry = function ancestry(node) {
1472
- var ancestry = [];
1473
- while (node) {
1474
- ancestry.unshift(node);
1475
- node = node.parentNode;
1476
- }
1477
- return ancestry;
1478
- };
1479
- var ancestry1 = ancestry(node1);
1480
- var ancestry2 = ancestry(node2);
1481
- if (ancestry1[0] != ancestry2[0]) return null;
1482
- var i;
1483
- for (i = 0; ancestry1[i] == ancestry2[i]; i++) {
1484
- ;
1485
- }
1486
- return ancestry1[i - 1];
1297
+ if (!anchor) {
1298
+ anchor = focus;
1299
+ }
1300
+ if (anchor.row < focus.row || anchor.row == focus.row && anchor.col <= focus.col) {
1301
+ beginning = anchor;
1302
+ end = focus;
1303
+ } else {
1304
+ beginning = focus;
1305
+ end = anchor;
1487
1306
  }
1307
+ let insertedLines = text.split(/(?:\r\n|\r|\n)/);
1308
+ let lineBefore = this.lines[beginning.row].substr(0, beginning.col);
1309
+ let lineEnd = this.lines[end.row].substr(end.col);
1310
+ insertedLines[0] = lineBefore.concat(insertedLines[0]);
1311
+ let endColPos = insertedLines[insertedLines.length - 1].length;
1312
+ insertedLines[insertedLines.length - 1] = insertedLines[insertedLines.length - 1].concat(lineEnd);
1313
+ this.spliceLines(beginning.row, 1 + end.row - beginning.row, insertedLines);
1314
+ focus.row = beginning.row + insertedLines.length - 1;
1315
+ focus.col = endColPos;
1316
+ this.updateFormatting();
1317
+ this.setSelection(focus);
1318
+ this.fireChange();
1319
+ }
1488
1320
 
1489
- /**
1490
- * Finds the (lowest in the DOM tree) enclosing DOM node with a given class.
1491
- * @param {object} focus The focus selection object
1492
- * @param {object} anchor The anchor selection object
1493
- * @param {string} className The class name to find
1494
- * @returns {Node} The enclosing DOM node with the respective class (inside the editor), if there is one; null otherwise.
1495
- */
1496
- }, {
1497
- key: "computeEnclosingMarkupNode",
1498
- value: function computeEnclosingMarkupNode(focus, anchor, className) {
1499
- var node = null;
1500
- if (!focus) return null;
1501
- if (!anchor) {
1502
- node = focus.node;
1503
- } else {
1504
- if (focus.row != anchor.row) return null;
1505
- node = this.computeCommonAncestor(focus.node, anchor.node);
1506
- }
1507
- if (!node) return null;
1508
- while (node != this.e) {
1509
- if (node.className && node.className.includes(className)) return node;
1321
+ /**
1322
+ * Computes the (lowest in the DOM tree) common ancestor of two DOM nodes.
1323
+ * @param {Node} node1 the first node
1324
+ * @param {Node} node2 the second node
1325
+ * @returns {Node} The commen ancestor node, or null if there is no common ancestor
1326
+ */
1327
+ computeCommonAncestor(node1, node2) {
1328
+ if (!node1 || !node2) return null;
1329
+ if (node1 == node2) return node1;
1330
+ const ancestry = node => {
1331
+ let ancestry = [];
1332
+ while (node) {
1333
+ ancestry.unshift(node);
1510
1334
  node = node.parentNode;
1511
1335
  }
1512
- // Ascended all the way to the editor element
1513
- return null;
1336
+ return ancestry;
1337
+ };
1338
+ const ancestry1 = ancestry(node1);
1339
+ const ancestry2 = ancestry(node2);
1340
+ if (ancestry1[0] != ancestry2[0]) return null;
1341
+ let i;
1342
+ for (i = 0; ancestry1[i] == ancestry2[i]; i++);
1343
+ return ancestry1[i - 1];
1344
+ }
1345
+
1346
+ /**
1347
+ * Finds the (lowest in the DOM tree) enclosing DOM node with a given class.
1348
+ * @param {object} focus The focus selection object
1349
+ * @param {object} anchor The anchor selection object
1350
+ * @param {string} className The class name to find
1351
+ * @returns {Node} The enclosing DOM node with the respective class (inside the editor), if there is one; null otherwise.
1352
+ */
1353
+ computeEnclosingMarkupNode(focus, anchor, className) {
1354
+ let node = null;
1355
+ if (!focus) return null;
1356
+ if (!anchor) {
1357
+ node = focus.node;
1358
+ } else {
1359
+ if (focus.row != anchor.row) return null;
1360
+ node = this.computeCommonAncestor(focus.node, anchor.node);
1514
1361
  }
1362
+ if (!node) return null;
1363
+ while (node != this.e) {
1364
+ if (node.className && node.className.includes(className)) return node;
1365
+ node = node.parentNode;
1366
+ }
1367
+ // Ascended all the way to the editor element
1368
+ return null;
1369
+ }
1515
1370
 
1516
- /**
1517
- * Returns the state (true / false) of all commands.
1518
- * @param focus Focus of the selection. If not given, assumes the current focus.
1519
- */
1520
- }, {
1521
- key: "getCommandState",
1522
- value: function getCommandState() {
1523
- var focus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1524
- var anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1525
- var commandState = {};
1526
- if (!focus) focus = this.getSelection(false);
1527
- if (!anchor) anchor = this.getSelection(true);
1528
- if (!focus) {
1529
- for (var cmd in _grammar.commands) {
1530
- commandState[cmd] = null;
1531
- }
1532
- return commandState;
1533
- }
1534
- if (!anchor) anchor = focus;
1535
- var start, end;
1536
- if (anchor.row < focus.row || anchor.row == focus.row && anchor.col < focus.col) {
1537
- start = anchor;
1538
- end = focus;
1539
- } else {
1540
- start = focus;
1541
- end = anchor;
1542
- }
1543
- if (end.row > start.row && end.col == 0) {
1544
- end.row--;
1545
- end.col = this.lines[end.row].length; // Selection to beginning of next line is said to end at the beginning of the last line
1371
+ /**
1372
+ * Returns the state (true / false) of all commands.
1373
+ * @param focus Focus of the selection. If not given, assumes the current focus.
1374
+ */
1375
+ getCommandState() {
1376
+ let focus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
1377
+ let anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
1378
+ let commandState = {};
1379
+ if (!focus) focus = this.getSelection(false);
1380
+ if (!anchor) anchor = this.getSelection(true);
1381
+ if (!focus) {
1382
+ for (let cmd in _grammar.commands) {
1383
+ commandState[cmd] = null;
1546
1384
  }
1385
+ return commandState;
1386
+ }
1387
+ if (!anchor) anchor = focus;
1388
+ let start, end;
1389
+ if (anchor.row < focus.row || anchor.row == focus.row && anchor.col < focus.col) {
1390
+ start = anchor;
1391
+ end = focus;
1392
+ } else {
1393
+ start = focus;
1394
+ end = anchor;
1395
+ }
1396
+ if (end.row > start.row && end.col == 0) {
1397
+ end.row--;
1398
+ end.col = this.lines[end.row].length; // Selection to beginning of next line is said to end at the beginning of the last line
1399
+ }
1547
1400
 
1548
- for (var _cmd in _grammar.commands) {
1549
- if (_grammar.commands[_cmd].type == 'inline') {
1550
- if (!focus || focus.row != anchor.row || !this.isInlineFormattingAllowed(focus, anchor)) {
1551
- commandState[_cmd] = null;
1552
- } else {
1553
- // The command state is true if there is a respective enclosing markup node (e.g., the selection is enclosed in a <b>..</b>) ...
1554
- commandState[_cmd] = !!this.computeEnclosingMarkupNode(focus, anchor, _grammar.commands[_cmd].className) ||
1555
- // ... or if it's an empty string preceded by and followed by formatting markers, e.g. **|** where | is the cursor
1401
+ for (let cmd in _grammar.commands) {
1402
+ if (_grammar.commands[cmd].type == 'inline') {
1403
+ if (!focus || focus.row != anchor.row || !this.isInlineFormattingAllowed(focus, anchor)) {
1404
+ commandState[cmd] = null;
1405
+ } else {
1406
+ // The command state is true if there is a respective enclosing markup node (e.g., the selection is enclosed in a <b>..</b>) ...
1407
+ commandState[cmd] = !!this.computeEnclosingMarkupNode(focus, anchor, _grammar.commands[cmd].className) ||
1408
+ // ... or if it's an empty string preceded by and followed by formatting markers, e.g. **|** where | is the cursor
1556
1409
 
1557
- focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(_grammar.commands[_cmd].unset.prePattern) && !!this.lines[focus.row].substr(focus.col).match(_grammar.commands[_cmd].unset.postPattern);
1558
- }
1410
+ focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(_grammar.commands[cmd].unset.prePattern) && !!this.lines[focus.row].substr(focus.col).match(_grammar.commands[cmd].unset.postPattern);
1559
1411
  }
1560
- if (_grammar.commands[_cmd].type == 'line') {
1561
- if (!focus) {
1562
- commandState[_cmd] = null;
1563
- } else {
1564
- var state = this.lineTypes[start.row] == _grammar.commands[_cmd].className;
1565
- for (var line = start.row; line <= end.row; line++) {
1566
- if (this.lineTypes[line] == _grammar.commands[_cmd].className != state) {
1567
- state = null;
1568
- break;
1569
- }
1412
+ }
1413
+ if (_grammar.commands[cmd].type == 'line') {
1414
+ if (!focus) {
1415
+ commandState[cmd] = null;
1416
+ } else {
1417
+ let state = this.lineTypes[start.row] == _grammar.commands[cmd].className;
1418
+ for (let line = start.row; line <= end.row; line++) {
1419
+ if (this.lineTypes[line] == _grammar.commands[cmd].className != state) {
1420
+ state = null;
1421
+ break;
1570
1422
  }
1571
- commandState[_cmd] = state;
1572
1423
  }
1424
+ commandState[cmd] = state;
1573
1425
  }
1574
1426
  }
1575
- return commandState;
1576
1427
  }
1428
+ return commandState;
1429
+ }
1577
1430
 
1578
- /**
1579
- * Sets a command state
1580
- * @param {string} command
1581
- * @param {boolean} state
1582
- */
1583
- }, {
1584
- key: "setCommandState",
1585
- value: function setCommandState(command, state) {
1586
- if (_grammar.commands[command].type == 'inline') {
1587
- var anchor = this.getSelection(true);
1588
- var focus = this.getSelection(false);
1589
- if (!anchor) anchor = focus;
1590
- if (!anchor) return;
1591
- if (anchor.row != focus.row) return;
1592
- if (!this.isInlineFormattingAllowed(focus, anchor)) return;
1593
- var markupNode = this.computeEnclosingMarkupNode(focus, anchor, _grammar.commands[command].className);
1594
- this.clearDirtyFlag();
1595
-
1596
- // First case: There's an enclosing markup node, remove the markers around that markup node
1597
- if (markupNode) {
1598
- this.lineDirty[focus.row] = true;
1599
- var startCol = this.computeColumn(markupNode, 0);
1600
- var len = markupNode.textContent.length;
1601
- var left = this.lines[focus.row].substr(0, startCol).replace(_grammar.commands[command].unset.prePattern, '');
1602
- var mid = this.lines[focus.row].substr(startCol, len);
1603
- var right = this.lines[focus.row].substr(startCol + len).replace(_grammar.commands[command].unset.postPattern, '');
1604
- this.lines[focus.row] = left.concat(mid, right);
1605
- anchor.col = left.length;
1606
- focus.col = anchor.col + len;
1607
- this.updateFormatting();
1608
- this.setSelection(focus, anchor);
1609
-
1610
- // Second case: Empty selection with surrounding formatting markers, remove those
1611
- } else if (focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(_grammar.commands[command].unset.prePattern) && !!this.lines[focus.row].substr(focus.col).match(_grammar.commands[command].unset.postPattern)) {
1612
- this.lineDirty[focus.row] = true;
1613
- var _left = this.lines[focus.row].substr(0, focus.col).replace(_grammar.commands[command].unset.prePattern, '');
1614
- var _right = this.lines[focus.row].substr(focus.col).replace(_grammar.commands[command].unset.postPattern, '');
1615
- this.lines[focus.row] = _left.concat(_right);
1616
- focus.col = anchor.col = _left.length;
1617
- this.updateFormatting();
1618
- this.setSelection(focus, anchor);
1619
-
1620
- // Not currently formatted, insert formatting markers
1621
- } else {
1622
- // Trim any spaces from the selection
1623
- var _ref = focus.col < anchor.col ? {
1624
- startCol: focus.col,
1625
- endCol: anchor.col
1626
- } : {
1627
- startCol: anchor.col,
1628
- endCol: focus.col
1629
- },
1630
- _startCol = _ref.startCol,
1631
- endCol = _ref.endCol;
1632
- var match = this.lines[focus.row].substr(_startCol, endCol - _startCol).match( /*#__PURE__*/_wrapRegExp(/^(\s*).*\S(\s*)$/, {
1633
- leading: 1,
1634
- trailing: 2
1635
- }));
1636
- if (match) {
1637
- _startCol += match.groups.leading.length;
1638
- endCol -= match.groups.trailing.length;
1639
- }
1640
- focus.col = _startCol;
1641
- anchor.col = endCol;
1431
+ /**
1432
+ * Sets a command state
1433
+ * @param {string} command
1434
+ * @param {boolean} state
1435
+ */
1436
+ setCommandState(command, state) {
1437
+ if (_grammar.commands[command].type == 'inline') {
1438
+ let anchor = this.getSelection(true);
1439
+ let focus = this.getSelection(false);
1440
+ if (!anchor) anchor = focus;
1441
+ if (!anchor) return;
1442
+ if (anchor.row != focus.row) return;
1443
+ if (!this.isInlineFormattingAllowed(focus, anchor)) return;
1444
+ let markupNode = this.computeEnclosingMarkupNode(focus, anchor, _grammar.commands[command].className);
1445
+ this.clearDirtyFlag();
1446
+
1447
+ // First case: There's an enclosing markup node, remove the markers around that markup node
1448
+ if (markupNode) {
1449
+ this.lineDirty[focus.row] = true;
1450
+ const startCol = this.computeColumn(markupNode, 0);
1451
+ const len = markupNode.textContent.length;
1452
+ const left = this.lines[focus.row].substr(0, startCol).replace(_grammar.commands[command].unset.prePattern, '');
1453
+ const mid = this.lines[focus.row].substr(startCol, len);
1454
+ const right = this.lines[focus.row].substr(startCol + len).replace(_grammar.commands[command].unset.postPattern, '');
1455
+ this.lines[focus.row] = left.concat(mid, right);
1456
+ anchor.col = left.length;
1457
+ focus.col = anchor.col + len;
1458
+ this.updateFormatting();
1459
+ this.setSelection(focus, anchor);
1460
+
1461
+ // Second case: Empty selection with surrounding formatting markers, remove those
1462
+ } else if (focus.col == anchor.col && !!this.lines[focus.row].substr(0, focus.col).match(_grammar.commands[command].unset.prePattern) && !!this.lines[focus.row].substr(focus.col).match(_grammar.commands[command].unset.postPattern)) {
1463
+ this.lineDirty[focus.row] = true;
1464
+ const left = this.lines[focus.row].substr(0, focus.col).replace(_grammar.commands[command].unset.prePattern, '');
1465
+ const right = this.lines[focus.row].substr(focus.col).replace(_grammar.commands[command].unset.postPattern, '');
1466
+ this.lines[focus.row] = left.concat(right);
1467
+ focus.col = anchor.col = left.length;
1468
+ this.updateFormatting();
1469
+ this.setSelection(focus, anchor);
1642
1470
 
1643
- // Just insert markup before and after and hope for the best.
1644
- this.wrapSelection(_grammar.commands[command].set.pre, _grammar.commands[command].set.post, focus, anchor);
1645
- // TODO clean this up so that markup remains properly nested
1471
+ // Not currently formatted, insert formatting markers
1472
+ } else {
1473
+ // Trim any spaces from the selection
1474
+ let {
1475
+ startCol,
1476
+ endCol
1477
+ } = focus.col < anchor.col ? {
1478
+ startCol: focus.col,
1479
+ endCol: anchor.col
1480
+ } : {
1481
+ startCol: anchor.col,
1482
+ endCol: focus.col
1483
+ };
1484
+ let match = this.lines[focus.row].substr(startCol, endCol - startCol).match(/^(?<leading>\s*).*\S(?<trailing>\s*)$/);
1485
+ if (match) {
1486
+ startCol += match.groups.leading.length;
1487
+ endCol -= match.groups.trailing.length;
1646
1488
  }
1647
- } else if (_grammar.commands[command].type == 'line') {
1648
- var _anchor = this.getSelection(true);
1649
- var _focus = this.getSelection(false);
1650
- if (!_anchor) _anchor = _focus;
1651
- if (!_focus) return;
1652
- this.clearDirtyFlag();
1653
- var start = _anchor.row > _focus.row ? _focus : _anchor;
1654
- var end = _anchor.row > _focus.row ? _anchor : _focus;
1655
- if (end.row > start.row && end.col == 0) {
1656
- end.row--;
1489
+ focus.col = startCol;
1490
+ anchor.col = endCol;
1491
+
1492
+ // Just insert markup before and after and hope for the best.
1493
+ this.wrapSelection(_grammar.commands[command].set.pre, _grammar.commands[command].set.post, focus, anchor);
1494
+ // TODO clean this up so that markup remains properly nested
1495
+ }
1496
+ } else if (_grammar.commands[command].type == 'line') {
1497
+ let anchor = this.getSelection(true);
1498
+ let focus = this.getSelection(false);
1499
+ if (!anchor) anchor = focus;
1500
+ if (!focus) return;
1501
+ this.clearDirtyFlag();
1502
+ let start = anchor.row > focus.row ? focus : anchor;
1503
+ let end = anchor.row > focus.row ? anchor : focus;
1504
+ if (end.row > start.row && end.col == 0) {
1505
+ end.row--;
1506
+ }
1507
+ for (let line = start.row; line <= end.row; line++) {
1508
+ if (state && this.lineTypes[line] != _grammar.commands[command].className) {
1509
+ this.lines[line] = this.lines[line].replace(_grammar.commands[command].set.pattern, _grammar.commands[command].set.replacement.replace('$#', line - start.row + 1));
1510
+ this.lineDirty[line] = true;
1657
1511
  }
1658
- for (var line = start.row; line <= end.row; line++) {
1659
- if (state && this.lineTypes[line] != _grammar.commands[command].className) {
1660
- this.lines[line] = this.lines[line].replace(_grammar.commands[command].set.pattern, _grammar.commands[command].set.replacement.replace('$#', line - start.row + 1));
1661
- this.lineDirty[line] = true;
1662
- }
1663
- if (!state && this.lineTypes[line] == _grammar.commands[command].className) {
1664
- this.lines[line] = this.lines[line].replace(_grammar.commands[command].unset.pattern, _grammar.commands[command].unset.replacement);
1665
- this.lineDirty[line] = true;
1666
- }
1512
+ if (!state && this.lineTypes[line] == _grammar.commands[command].className) {
1513
+ this.lines[line] = this.lines[line].replace(_grammar.commands[command].unset.pattern, _grammar.commands[command].unset.replacement);
1514
+ this.lineDirty[line] = true;
1667
1515
  }
1668
- this.updateFormatting();
1669
- this.setSelection({
1670
- row: end.row,
1671
- col: this.lines[end.row].length
1672
- }, {
1673
- row: start.row,
1674
- col: 0
1675
- });
1676
1516
  }
1517
+ this.updateFormatting();
1518
+ this.setSelection({
1519
+ row: end.row,
1520
+ col: this.lines[end.row].length
1521
+ }, {
1522
+ row: start.row,
1523
+ col: 0
1524
+ });
1677
1525
  }
1526
+ }
1678
1527
 
1679
- /**
1680
- * Returns whether or not inline formatting is allowed at the current focus
1681
- * @param {object} focus The current focus
1682
- */
1683
- }, {
1684
- key: "isInlineFormattingAllowed",
1685
- value: function isInlineFormattingAllowed() {
1686
- // TODO Remove parameters from all calls
1687
- var sel = window.getSelection();
1688
- if (!sel) return false;
1689
-
1690
- // Check if we can find a common ancestor with the class `TMInlineFormatted`
1691
-
1692
- // Special case: Empty selection right before `TMInlineFormatted`
1693
- if (sel.isCollapsed && sel.focusNode.nodeType == 3 && sel.focusOffset == sel.focusNode.nodeValue.length) {
1694
- var node;
1695
- for (node = sel.focusNode; node && node.nextSibling == null; node = node.parentNode) {
1696
- ;
1697
- }
1698
- if (node && node.nextSibling.className && node.nextSibling.className.includes('TMInlineFormatted')) return true;
1699
- }
1528
+ /**
1529
+ * Returns whether or not inline formatting is allowed at the current focus
1530
+ * @param {object} focus The current focus
1531
+ */
1532
+ isInlineFormattingAllowed() {
1533
+ // TODO Remove parameters from all calls
1534
+ const sel = window.getSelection();
1535
+ if (!sel) return false;
1536
+
1537
+ // Check if we can find a common ancestor with the class `TMInlineFormatted`
1538
+
1539
+ // Special case: Empty selection right before `TMInlineFormatted`
1540
+ if (sel.isCollapsed && sel.focusNode.nodeType == 3 && sel.focusOffset == sel.focusNode.nodeValue.length) {
1541
+ let node;
1542
+ for (node = sel.focusNode; node && node.nextSibling == null; node = node.parentNode);
1543
+ if (node && node.nextSibling.className && node.nextSibling.className.includes('TMInlineFormatted')) return true;
1544
+ }
1700
1545
 
1701
- // Look for a common ancestor
1702
- var ancestor = this.computeCommonAncestor(sel.focusNode, sel.anchorNode);
1703
- if (!ancestor) return false;
1546
+ // Look for a common ancestor
1547
+ let ancestor = this.computeCommonAncestor(sel.focusNode, sel.anchorNode);
1548
+ if (!ancestor) return false;
1704
1549
 
1705
- // Check if there's an ancestor of class 'TMInlineFormatted' or 'TMBlankLine'
1706
- while (ancestor && ancestor != this.e) {
1707
- if (ancestor.className && (ancestor.className.includes('TMInlineFormatted') || ancestor.className.includes('TMBlankLine'))) return true;
1708
- ancestor = ancestor.parentNode;
1709
- }
1710
- return false;
1550
+ // Check if there's an ancestor of class 'TMInlineFormatted' or 'TMBlankLine'
1551
+ while (ancestor && ancestor != this.e) {
1552
+ if (ancestor.className && (ancestor.className.includes('TMInlineFormatted') || ancestor.className.includes('TMBlankLine'))) return true;
1553
+ ancestor = ancestor.parentNode;
1711
1554
  }
1555
+ return false;
1556
+ }
1712
1557
 
1713
- /**
1714
- * Wraps the current selection in the strings pre and post. If the selection is not on one line, returns.
1715
- * @param {string} pre The string to insert before the selection.
1716
- * @param {string} post The string to insert after the selection.
1717
- * @param {object} focus The current selection focus. If null, selection will be computed.
1718
- * @param {object} anchor The current selection focus. If null, selection will be computed.
1719
- */
1720
- }, {
1721
- key: "wrapSelection",
1722
- value: function wrapSelection(pre, post) {
1723
- var focus = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1724
- var anchor = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
1725
- if (!focus) focus = this.getSelection(false);
1726
- if (!anchor) anchor = this.getSelection(true);
1727
- if (!focus || !anchor || focus.row != anchor.row) return;
1728
- this.lineDirty[focus.row] = true;
1729
- var startCol = focus.col < anchor.col ? focus.col : anchor.col;
1730
- var endCol = focus.col < anchor.col ? anchor.col : focus.col;
1731
- var left = this.lines[focus.row].substr(0, startCol).concat(pre);
1732
- var mid = endCol == startCol ? '' : this.lines[focus.row].substr(startCol, endCol - startCol);
1733
- var right = post.concat(this.lines[focus.row].substr(endCol));
1734
- this.lines[focus.row] = left.concat(mid, right);
1735
- anchor.col = left.length;
1736
- focus.col = anchor.col + mid.length;
1737
- this.updateFormatting();
1738
- this.setSelection(focus, anchor);
1739
- }
1558
+ /**
1559
+ * Wraps the current selection in the strings pre and post. If the selection is not on one line, returns.
1560
+ * @param {string} pre The string to insert before the selection.
1561
+ * @param {string} post The string to insert after the selection.
1562
+ * @param {object} focus The current selection focus. If null, selection will be computed.
1563
+ * @param {object} anchor The current selection focus. If null, selection will be computed.
1564
+ */
1565
+ wrapSelection(pre, post) {
1566
+ let focus = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
1567
+ let anchor = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
1568
+ if (!focus) focus = this.getSelection(false);
1569
+ if (!anchor) anchor = this.getSelection(true);
1570
+ if (!focus || !anchor || focus.row != anchor.row) return;
1571
+ this.lineDirty[focus.row] = true;
1572
+ const startCol = focus.col < anchor.col ? focus.col : anchor.col;
1573
+ const endCol = focus.col < anchor.col ? anchor.col : focus.col;
1574
+ const left = this.lines[focus.row].substr(0, startCol).concat(pre);
1575
+ const mid = endCol == startCol ? '' : this.lines[focus.row].substr(startCol, endCol - startCol);
1576
+ const right = post.concat(this.lines[focus.row].substr(endCol));
1577
+ this.lines[focus.row] = left.concat(mid, right);
1578
+ anchor.col = left.length;
1579
+ focus.col = anchor.col + mid.length;
1580
+ this.updateFormatting();
1581
+ this.setSelection(focus, anchor);
1582
+ }
1740
1583
 
1741
- /**
1742
- * Toggles the command state for a command (true <-> false)
1743
- * @param {string} command The editor command
1744
- */
1745
- }, {
1746
- key: "toggleCommandState",
1747
- value: function toggleCommandState(command) {
1748
- if (!this.lastCommandState) this.lastCommandState = this.getCommandState();
1749
- this.setCommandState(command, !this.lastCommandState[command]);
1750
- }
1584
+ /**
1585
+ * Toggles the command state for a command (true <-> false)
1586
+ * @param {string} command The editor command
1587
+ */
1588
+ toggleCommandState(command) {
1589
+ if (!this.lastCommandState) this.lastCommandState = this.getCommandState();
1590
+ this.setCommandState(command, !this.lastCommandState[command]);
1591
+ }
1751
1592
 
1752
- /**
1753
- * Fires a change event. Updates the linked textarea and notifies any event listeners.
1754
- */
1755
- }, {
1756
- key: "fireChange",
1757
- value: function fireChange() {
1758
- if (!this.textarea && !this.listeners.change.length) return;
1759
- var content = this.getContent();
1760
- if (this.textarea) this.textarea.value = content;
1761
- var _iterator3 = _createForOfIteratorHelper(this.listeners.change),
1762
- _step3;
1763
- try {
1764
- for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
1765
- var listener = _step3.value;
1766
- listener({
1767
- content: content,
1768
- linesDirty: this.linesDirty
1769
- });
1770
- }
1771
- } catch (err) {
1772
- _iterator3.e(err);
1773
- } finally {
1774
- _iterator3.f();
1775
- }
1593
+ /**
1594
+ * Fires a change event. Updates the linked textarea and notifies any event listeners.
1595
+ */
1596
+ fireChange() {
1597
+ if (!this.textarea && !this.listeners.change.length) return;
1598
+ const content = this.getContent();
1599
+ if (this.textarea) this.textarea.value = content;
1600
+ for (let listener of this.listeners.change) {
1601
+ listener({
1602
+ content: content,
1603
+ linesDirty: this.linesDirty
1604
+ });
1776
1605
  }
1606
+ }
1777
1607
 
1778
- /**
1779
- * Fires a "selection changed" event.
1780
- */
1781
- }, {
1782
- key: "fireSelection",
1783
- value: function fireSelection() {
1784
- if (this.listeners.selection && this.listeners.selection.length) {
1785
- var focus = this.getSelection(false);
1786
- var anchor = this.getSelection(true);
1787
- var commandState = this.getCommandState(focus, anchor);
1788
- if (this.lastCommandState) {
1789
- Object.assign(this.lastCommandState, commandState);
1790
- } else {
1791
- this.lastCommandState = Object.assign({}, commandState);
1792
- }
1793
- var _iterator4 = _createForOfIteratorHelper(this.listeners.selection),
1794
- _step4;
1795
- try {
1796
- for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
1797
- var listener = _step4.value;
1798
- listener({
1799
- focus: focus,
1800
- anchor: anchor,
1801
- commandState: this.lastCommandState
1802
- });
1803
- }
1804
- } catch (err) {
1805
- _iterator4.e(err);
1806
- } finally {
1807
- _iterator4.f();
1808
- }
1608
+ /**
1609
+ * Fires a "selection changed" event.
1610
+ */
1611
+ fireSelection() {
1612
+ if (this.listeners.selection && this.listeners.selection.length) {
1613
+ let focus = this.getSelection(false);
1614
+ let anchor = this.getSelection(true);
1615
+ let commandState = this.getCommandState(focus, anchor);
1616
+ if (this.lastCommandState) {
1617
+ Object.assign(this.lastCommandState, commandState);
1618
+ } else {
1619
+ this.lastCommandState = Object.assign({}, commandState);
1620
+ }
1621
+ for (let listener of this.listeners.selection) {
1622
+ listener({
1623
+ focus: focus,
1624
+ anchor: anchor,
1625
+ commandState: this.lastCommandState
1626
+ });
1809
1627
  }
1810
1628
  }
1629
+ }
1811
1630
 
1812
- /**
1813
- * Adds an event listener.
1814
- * @param {string} type The type of event to listen to. Can be 'change' or 'selection'
1815
- * @param {*} listener Function of the type (event) => {} to be called when the event occurs.
1816
- */
1817
- }, {
1818
- key: "addEventListener",
1819
- value: function addEventListener(type, listener) {
1820
- if (type.match(/^(?:change|input)$/i)) {
1821
- this.listeners.change.push(listener);
1822
- }
1823
- if (type.match(/^(?:selection|selectionchange)$/i)) {
1824
- this.listeners.selection.push(listener);
1825
- }
1631
+ /**
1632
+ * Adds an event listener.
1633
+ * @param {string} type The type of event to listen to. Can be 'change' or 'selection'
1634
+ * @param {*} listener Function of the type (event) => {} to be called when the event occurs.
1635
+ */
1636
+ addEventListener(type, listener) {
1637
+ if (type.match(/^(?:change|input)$/i)) {
1638
+ this.listeners.change.push(listener);
1639
+ }
1640
+ if (type.match(/^(?:selection|selectionchange)$/i)) {
1641
+ this.listeners.selection.push(listener);
1826
1642
  }
1827
- }]);
1828
- return Editor;
1829
- }();
1830
- var _default = Editor;
1831
- exports.default = _default;
1643
+ }
1644
+ }
1645
+ var _default = exports.default = Editor;