tiny-markdown-editor 0.1.13 → 0.1.14

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
@@ -32,18 +32,18 @@ class Editor {
32
32
  element = document.getElementById(props.element);
33
33
  }
34
34
  if (!element) {
35
- element = document.getElementsByTagName('body')[0];
35
+ element = document.getElementsByTagName("body")[0];
36
36
  }
37
- if (element.tagName == 'TEXTAREA') {
37
+ if (element.tagName == "TEXTAREA") {
38
38
  this.textarea = element;
39
39
  element = this.textarea.parentNode;
40
40
  }
41
41
  if (this.textarea) {
42
- this.textarea.style.display = 'none';
42
+ this.textarea.style.display = "none";
43
43
  }
44
44
  this.createEditorElement(element);
45
45
  // TODO Placeholder for empty content
46
- this.setContent(props.content || (this.textarea ? this.textarea.value : false) || '# Hello TinyMDE!\nEdit **here**');
46
+ this.setContent(props.content || (this.textarea ? this.textarea.value : "# Hello TinyMDE!\nEdit **here**"));
47
47
  }
48
48
 
49
49
  /**
@@ -51,24 +51,22 @@ class Editor {
51
51
  * @param element The target element of the DOM tree
52
52
  */
53
53
  createEditorElement(element) {
54
- this.e = document.createElement('div');
55
- this.e.className = 'TinyMDE';
54
+ this.e = document.createElement("div");
55
+ this.e.className = "TinyMDE";
56
56
  this.e.contentEditable = true;
57
57
  // The following is important for formatting purposes, but also since otherwise the browser replaces subsequent spaces with    
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';
58
+ // That breaks a lot of stuff, so we do this here and not in CSS—therefore, you don't have to remember to put this in the CSS file
59
+ this.e.style.whiteSpace = "pre-wrap";
60
60
  // Avoid formatting (B / I / U) popping up on iOS
61
- this.e.style.webkitUserModify = 'read-write-plaintext-only';
61
+ this.e.style.webkitUserModify = "read-write-plaintext-only";
62
62
  if (this.textarea && this.textarea.parentNode == element && this.textarea.nextSibling) {
63
63
  element.insertBefore(this.e, this.textarea.nextSibling);
64
64
  } else {
65
65
  element.appendChild(this.e);
66
66
  }
67
67
  this.e.addEventListener("input", e => this.handleInputEvent(e));
68
- // this.e.addEventListener("keydown", (e) => this.handleKeydownEvent(e));
69
68
  document.addEventListener("selectionchange", e => this.handleSelectionChangeEvent(e));
70
69
  this.e.addEventListener("paste", e => this.handlePaste(e));
71
- // this.e.addEventListener('keydown', (e) => this.handleKeyDown(e));
72
70
  this.lineElements = this.e.childNodes; // this will automatically update
73
71
  }
74
72
 
@@ -84,7 +82,7 @@ class Editor {
84
82
  this.lines = content.split(/(?:\r\n|\r|\n)/);
85
83
  this.lineDirty = [];
86
84
  for (let lineNum = 0; lineNum < this.lines.length; lineNum++) {
87
- let le = document.createElement('div');
85
+ let le = document.createElement("div");
88
86
  this.e.appendChild(le);
89
87
  this.lineDirty.push(true);
90
88
  }
@@ -98,7 +96,7 @@ class Editor {
98
96
  * @returns {string} The editor content as a markdown string
99
97
  */
100
98
  getContent() {
101
- return this.lines.join('\n');
99
+ return this.lines.join("\n");
102
100
  }
103
101
 
104
102
  /**
@@ -120,7 +118,7 @@ class Editor {
120
118
  updateLinkLabels() {
121
119
  this.linkLabels = [];
122
120
  for (let l = 0; l < this.lines.length; l++) {
123
- if (this.lineTypes[l] == 'TMLinkReferenceDefinition') {
121
+ if (this.lineTypes[l] == "TMLinkReferenceDefinition") {
124
122
  this.linkLabels.push(this.lineCaptures[l][_grammar.lineGrammar.TMLinkReferenceDefinition.labelPlaceholder]);
125
123
  }
126
124
  }
@@ -128,22 +126,22 @@ class Editor {
128
126
 
129
127
  /**
130
128
  * 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,
129
+ * which are interpreted like in String.replace(), but also double dollar placeholders ($$1). In the case of double dollar placeholders,
132
130
  * 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
131
  * first captured subgroup, and replaces `$$1` with the result.
134
- *
132
+ *
135
133
  * @param {string} replacement The replacement string, including placeholders.
136
134
  * @param capture The result of a RegExp.exec() call
137
135
  * @returns The replacement string, with placeholders replaced from the capture result.
138
136
  */
139
137
  replace(replacement, capture) {
140
138
  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>`;
139
+ if (p1 == "$") return (0, _grammar.htmlescape)(capture[p2]);else return `<span class="TMInlineFormatted">${this.processInlineStyles(capture[p2])}</span>`;
142
140
  });
143
141
  }
144
142
 
145
143
  /**
146
- * Applies the line types (from this.lineTypes as well as the capture result in this.lineReplacements and this.lineCaptures)
144
+ * Applies the line types (from this.lineTypes as well as the capture result in this.lineReplacements and this.lineCaptures)
147
145
  * and processes inline formatting for all lines.
148
146
  */
149
147
  applyLineTypes() {
@@ -152,8 +150,8 @@ class Editor {
152
150
  let contentHTML = this.replace(this.lineReplacements[lineNum], this.lineCaptures[lineNum]);
153
151
  // this.lineHTML[lineNum] = (contentHTML == '' ? '<br />' : contentHTML); // Prevent empty elements which can't be selected etc.
154
152
  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.
153
+ this.lineElements[lineNum].removeAttribute("style");
154
+ this.lineElements[lineNum].innerHTML = contentHTML == "" ? "<br />" : contentHTML; // Prevent empty elements which can't be selected etc.
157
155
  }
158
156
 
159
157
  this.lineElements[lineNum].dataset.lineNum = lineNum;
@@ -170,48 +168,48 @@ class Editor {
170
168
  let codeBlockSeqLength = 0;
171
169
  let htmlBlock = false;
172
170
  for (let lineNum = 0; lineNum < this.lines.length; lineNum++) {
173
- let lineType = 'TMPara';
171
+ let lineType = "TMPara";
174
172
  let lineCapture = [this.lines[lineNum]];
175
- let lineReplacement = '$$0'; // Default replacement for paragraph: Inline format the entire line
173
+ let lineReplacement = "$$0"; // Default replacement for paragraph: Inline format the entire line
176
174
 
177
175
  // Check ongoing code blocks
178
176
  // if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceBacktickOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeBacktick')) {
179
- if (codeBlockType == 'TMCodeFenceBacktickOpen') {
177
+ if (codeBlockType == "TMCodeFenceBacktickOpen") {
180
178
  // We're in a backtick-fenced code block, check if the current line closes it
181
179
  let capture = _grammar.lineGrammar.TMCodeFenceBacktickClose.regexp.exec(this.lines[lineNum]);
182
- if (capture && capture.groups['seq'].length >= codeBlockSeqLength) {
183
- lineType = 'TMCodeFenceBacktickClose';
180
+ if (capture && capture.groups["seq"].length >= codeBlockSeqLength) {
181
+ lineType = "TMCodeFenceBacktickClose";
184
182
  lineReplacement = _grammar.lineGrammar.TMCodeFenceBacktickClose.replacement;
185
183
  lineCapture = capture;
186
184
  codeBlockType = false;
187
185
  } else {
188
- lineType = 'TMFencedCodeBacktick';
189
- lineReplacement = '$0';
186
+ lineType = "TMFencedCodeBacktick";
187
+ lineReplacement = "$0";
190
188
  lineCapture = [this.lines[lineNum]];
191
189
  }
192
190
  }
193
191
  // if (lineNum > 0 && (this.lineTypes[lineNum - 1] == 'TMCodeFenceTildeOpen' || this.lineTypes[lineNum - 1] == 'TMFencedCodeTilde')) {
194
- else if (codeBlockType == 'TMCodeFenceTildeOpen') {
192
+ else if (codeBlockType == "TMCodeFenceTildeOpen") {
195
193
  // We're in a tilde-fenced code block
196
194
  let capture = _grammar.lineGrammar.TMCodeFenceTildeClose.regexp.exec(this.lines[lineNum]);
197
- if (capture && capture.groups['seq'].length >= codeBlockSeqLength) {
198
- lineType = 'TMCodeFenceTildeClose';
195
+ if (capture && capture.groups["seq"].length >= codeBlockSeqLength) {
196
+ lineType = "TMCodeFenceTildeClose";
199
197
  lineReplacement = _grammar.lineGrammar.TMCodeFenceTildeClose.replacement;
200
198
  lineCapture = capture;
201
199
  codeBlockType = false;
202
200
  } else {
203
- lineType = 'TMFencedCodeTilde';
204
- lineReplacement = '$0';
201
+ lineType = "TMFencedCodeTilde";
202
+ lineReplacement = "$0";
205
203
  lineCapture = [this.lines[lineNum]];
206
204
  }
207
205
  }
208
206
 
209
207
  // Check HTML block types
210
- if (lineType == 'TMPara' && htmlBlock === false) {
208
+ if (lineType == "TMPara" && htmlBlock === false) {
211
209
  for (let htmlBlockType of _grammar.htmlBlockGrammar) {
212
210
  if (this.lines[lineNum].match(htmlBlockType.start)) {
213
211
  // 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')) {
212
+ 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
213
  htmlBlock = htmlBlockType;
216
214
  break;
217
215
  }
@@ -219,8 +217,8 @@ class Editor {
219
217
  }
220
218
  }
221
219
  if (htmlBlock !== false) {
222
- lineType = 'TMHTMLBlock';
223
- lineReplacement = '$0'; // No formatting in TMHTMLBlock
220
+ lineType = "TMHTMLBlock";
221
+ lineReplacement = "$0"; // No formatting in TMHTMLBlock
224
222
  lineCapture = [this.lines[lineNum]]; // This should already be set but better safe than sorry
225
223
 
226
224
  // Check if HTML block should be closed
@@ -238,7 +236,7 @@ class Editor {
238
236
  }
239
237
 
240
238
  // Check all regexps if we haven't applied one of the code block types
241
- if (lineType == 'TMPara') {
239
+ if (lineType == "TMPara") {
242
240
  for (let type in _grammar.lineGrammar) {
243
241
  if (_grammar.lineGrammar[type].regexp) {
244
242
  let capture = _grammar.lineGrammar[type].regexp.exec(this.lines[lineNum]);
@@ -253,58 +251,58 @@ class Editor {
253
251
  }
254
252
 
255
253
  // If we've opened a code block, remember that
256
- if (lineType == 'TMCodeFenceBacktickOpen' || lineType == 'TMCodeFenceTildeOpen') {
254
+ if (lineType == "TMCodeFenceBacktickOpen" || lineType == "TMCodeFenceTildeOpen") {
257
255
  codeBlockType = lineType;
258
- codeBlockSeqLength = lineCapture.groups['seq'].length;
256
+ codeBlockSeqLength = lineCapture.groups["seq"].length;
259
257
  }
260
258
 
261
259
  // 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')) {
260
+ 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
261
  // Fall back to TMPara
264
- lineType = 'TMPara';
262
+ lineType = "TMPara";
265
263
  lineCapture = [this.lines[lineNum]];
266
- lineReplacement = '$$0';
264
+ lineReplacement = "$$0";
267
265
  }
268
266
 
269
267
  // 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') {
268
+ if (lineType == "TMSetextH2Marker") {
271
269
  let capture = _grammar.lineGrammar.TMUL.regexp.exec(this.lines[lineNum]);
272
270
  if (capture) {
273
- lineType = 'TMUL';
271
+ lineType = "TMUL";
274
272
  lineReplacement = _grammar.lineGrammar.TMUL.replacement;
275
273
  lineCapture = capture;
276
274
  }
277
275
  }
278
276
 
279
277
  // 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') {
278
+ if (lineType == "TMSetextH1Marker" || lineType == "TMSetextH2Marker") {
279
+ if (lineNum == 0 || this.lineTypes[lineNum - 1] != "TMPara") {
282
280
  // Setext marker is invalid. However, a H2 marker might still be a valid HR, so let's check that
283
281
  let capture = _grammar.lineGrammar.TMHR.regexp.exec(this.lines[lineNum]);
284
282
  if (capture) {
285
283
  // Valid HR
286
- lineType = 'TMHR';
284
+ lineType = "TMHR";
287
285
  lineCapture = capture;
288
286
  lineReplacement = _grammar.lineGrammar.TMHR.replacement;
289
287
  } else {
290
288
  // Not valid HR, format as TMPara
291
- lineType = 'TMPara';
289
+ lineType = "TMPara";
292
290
  lineCapture = [this.lines[lineNum]];
293
- lineReplacement = '$$0';
291
+ lineReplacement = "$$0";
294
292
  }
295
293
  } else {
296
294
  // Valid setext marker. Change types of preceding para lines
297
295
  let headingLine = lineNum - 1;
298
- const headingLineType = lineType == 'TMSetextH1Marker' ? 'TMSetextH1' : 'TMSetextH2';
296
+ const headingLineType = lineType == "TMSetextH1Marker" ? "TMSetextH1" : "TMSetextH2";
299
297
  do {
300
298
  if (this.lineTypes[headingLineType] != headingLineType) {
301
299
  this.lineTypes[headingLine] = headingLineType;
302
300
  this.lineDirty[headingLineType] = true;
303
301
  }
304
- this.lineReplacements[headingLine] = '$$0';
302
+ this.lineReplacements[headingLine] = "$$0";
305
303
  this.lineCaptures[headingLine] = [this.lines[headingLine]];
306
304
  headingLine--;
307
- } while (headingLine >= 0 && this.lineTypes[headingLine] == 'TMPara');
305
+ } while (headingLine >= 0 && this.lineTypes[headingLine] == "TMPara");
308
306
  }
309
307
  }
310
308
  // Lastly, save the line style to be applied later
@@ -327,19 +325,19 @@ class Editor {
327
325
  }
328
326
 
329
327
  /**
330
- * Attempts to parse a link or image at the current position. This assumes that the opening [ or ![ has already been matched.
328
+ * Attempts to parse a link or image at the current position. This assumes that the opening [ or ![ has already been matched.
331
329
  * Returns false if this is not a valid link, image. See below for more information
332
330
  * @param {string} originalString The original string, starting at the opening marker ([ or ![)
333
331
  * @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,
332
+ * @returns false if not a valid link / image.
333
+ * Otherwise returns an object with two properties: output is the string to be included in the processed output,
336
334
  * charCount is the number of input characters (from originalString) consumed.
337
335
  */
338
336
  parseLinkOrImage(originalString, isImage) {
339
337
  // Skip the opening bracket
340
338
  let textOffset = isImage ? 2 : 1;
341
339
  let opener = originalString.substr(0, textOffset);
342
- let type = isImage ? 'TMImage' : 'TMLink';
340
+ let type = isImage ? "TMImage" : "TMLink";
343
341
  let currentOffset = textOffset;
344
342
  let bracketLevel = 1;
345
343
  let linkText = false;
@@ -352,7 +350,7 @@ class Editor {
352
350
 
353
351
  // Capture any escapes and code blocks at current position, they bind more strongly than links
354
352
  // 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']) {
353
+ for (let rule of ["escape", "code", "autolink", "html"]) {
356
354
  let cap = _grammar.inlineGrammar[rule].regexp.exec(string);
357
355
  if (cap) {
358
356
  currentOffset += cap[0].length;
@@ -404,10 +402,10 @@ class Editor {
404
402
  if (linkText === false) return false; // Nope
405
403
 
406
404
  // 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) : '';
405
+ let nextChar = currentOffset < originalString.length ? originalString.substr(currentOffset, 1) : "";
408
406
 
409
407
  // REFERENCE LINKS
410
- if (nextChar == '[') {
408
+ if (nextChar == "[") {
411
409
  let string = originalString.substr(currentOffset);
412
410
  let cap = _grammar.inlineGrammar.linkLabel.regexp.exec(string);
413
411
  if (cap) {
@@ -425,7 +423,7 @@ class Editor {
425
423
  // Not a valid link label
426
424
  return false;
427
425
  }
428
- } else if (nextChar != '(') {
426
+ } else if (nextChar != "(") {
429
427
  // Shortcut ref link
430
428
  linkRef = linkText.trim();
431
429
 
@@ -457,7 +455,7 @@ class Editor {
457
455
  linkDetails[1] = linkDetails[1].concat(cap[0]);
458
456
  } else {
459
457
  if (parenthesisLevel != 1) return false; // Unbalanced parenthesis
460
- linkDetails.push(''); // Empty end delimiter for destination
458
+ linkDetails.push(""); // Empty end delimiter for destination
461
459
  linkDetails.push(cap[0]); // Whitespace in between destination and title
462
460
  }
463
461
 
@@ -470,7 +468,7 @@ class Editor {
470
468
  return false;
471
469
  // This should never happen (no opener for title yet, but more whitespace to capture)
472
470
  case 5:
473
- linkDetails.push('');
471
+ linkDetails.push("");
474
472
  // Whitespace at beginning of title, push empty title and continue
475
473
  case 6:
476
474
  linkDetails[5] = linkDetails[5].concat(cap[0]);
@@ -494,7 +492,7 @@ class Editor {
494
492
  if (cap) {
495
493
  switch (linkDetails.length) {
496
494
  case 0:
497
- linkDetails.push('');
495
+ linkDetails.push("");
498
496
  // this opens the link destination, add empty opening delimiter and proceed to next case
499
497
  case 1:
500
498
  linkDetails.push(cap[0]);
@@ -511,7 +509,7 @@ class Editor {
511
509
  return false;
512
510
  // Lcaking opening delimiter for link title
513
511
  case 5:
514
- linkDetails.push('');
512
+ linkDetails.push("");
515
513
  // This opens the link title
516
514
  case 6:
517
515
  linkDetails[5] = linkDetails[5].concat(cap[0]);
@@ -528,26 +526,26 @@ class Editor {
528
526
 
529
527
  // Process opening angle bracket as deilimiter of destination
530
528
  if (linkDetails.length < 2 && string.match(/^</)) {
531
- if (linkDetails.length == 0) linkDetails.push('');
532
- linkDetails[0] = linkDetails[0].concat('<');
529
+ if (linkDetails.length == 0) linkDetails.push("");
530
+ linkDetails[0] = linkDetails[0].concat("<");
533
531
  currentOffset++;
534
532
  continue inlineOuter;
535
533
  }
536
534
 
537
535
  // Process closing angle bracket as delimiter of destination
538
536
  if ((linkDetails.length == 1 || linkDetails.length == 2) && string.match(/^>/)) {
539
- if (linkDetails.length == 1) linkDetails.push(''); // Empty link destination
540
- linkDetails.push('>');
537
+ if (linkDetails.length == 1) linkDetails.push(""); // Empty link destination
538
+ linkDetails.push(">");
541
539
  currentOffset++;
542
540
  continue inlineOuter;
543
541
  }
544
542
 
545
- // Process non-parenthesis delimiter for title.
543
+ // Process non-parenthesis delimiter for title.
546
544
  cap = /^["']/.exec(string);
547
545
  // For this to be a valid opener, we have to either have no destination, only whitespace so far,
548
546
  // or a destination with trailing whitespace.
549
547
  if (cap && (linkDetails.length == 0 || linkDetails.length == 1 || linkDetails.length == 4)) {
550
- while (linkDetails.length < 4) linkDetails.push('');
548
+ while (linkDetails.length < 4) linkDetails.push("");
551
549
  linkDetails.push(cap[0]);
552
550
  currentOffset++;
553
551
  continue inlineOuter;
@@ -555,7 +553,7 @@ class Editor {
555
553
 
556
554
  // 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
555
  if (cap && (linkDetails.length == 5 || linkDetails.length == 6) && linkDetails[4] == cap[0]) {
558
- if (linkDetails.length == 5) linkDetails.push(''); // Empty link title
556
+ if (linkDetails.length == 5) linkDetails.push(""); // Empty link title
559
557
  linkDetails.push(cap[0]);
560
558
  currentOffset++;
561
559
  continue inlineOuter;
@@ -566,30 +564,30 @@ class Editor {
566
564
  if (string.match(/^\(/)) {
567
565
  switch (linkDetails.length) {
568
566
  case 0:
569
- linkDetails.push('');
567
+ linkDetails.push("");
570
568
  // this opens the link destination, add empty opening delimiter and proceed to next case
571
569
  case 1:
572
- linkDetails.push('');
570
+ linkDetails.push("");
573
571
  // This opens the link destination
574
572
  case 2:
575
573
  // Part of the link destination
576
- linkDetails[1] = linkDetails[1].concat('(');
574
+ linkDetails[1] = linkDetails[1].concat("(");
577
575
  if (!linkDetails[0].match(/<$/)) parenthesisLevel++;
578
576
  break;
579
577
  case 3:
580
- linkDetails.push('');
578
+ linkDetails.push("");
581
579
  // opening delimiter for link title
582
580
  case 4:
583
- linkDetails.push('(');
581
+ linkDetails.push("(");
584
582
  break;
585
583
  // opening delimiter for link title
586
584
  case 5:
587
- linkDetails.push('');
588
- // opens the link title, add empty title content and proceed to next case
585
+ linkDetails.push("");
586
+ // opens the link title, add empty title content and proceed to next case
589
587
  case 6:
590
588
  // 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('(');
589
+ if (linkDetails[4] == "(") return false;
590
+ linkDetails[5] = linkDetails[5].concat("(");
593
591
  break;
594
592
  default:
595
593
  return false;
@@ -604,20 +602,20 @@ class Editor {
604
602
  if (string.match(/^\)/)) {
605
603
  if (linkDetails.length <= 2) {
606
604
  // We are inside the link destination. Parentheses have to be matched if not in angle brackets
607
- while (linkDetails.length < 2) linkDetails.push('');
605
+ while (linkDetails.length < 2) linkDetails.push("");
608
606
  if (!linkDetails[0].match(/<$/)) parenthesisLevel--;
609
607
  if (parenthesisLevel > 0) {
610
- linkDetails[1] = linkDetails[1].concat(')');
608
+ linkDetails[1] = linkDetails[1].concat(")");
611
609
  }
612
610
  } else if (linkDetails.length == 5 || linkDetails.length == 6) {
613
- // We are inside the link title.
614
- if (linkDetails[4] == '(') {
611
+ // We are inside the link title.
612
+ if (linkDetails[4] == "(") {
615
613
  // This closes the link title
616
- if (linkDetails.length == 5) linkDetails.push('');
617
- linkDetails.push(')');
614
+ if (linkDetails.length == 5) linkDetails.push("");
615
+ linkDetails.push(")");
618
616
  } else {
619
617
  // Just regular ol' content
620
- if (linkDetails.length == 5) linkDetails.push(')');else linkDetails[5] = linkDetails[5].concat(')');
618
+ if (linkDetails.length == 5) linkDetails.push(")");else linkDetails[5] = linkDetails[5].concat(")");
621
619
  }
622
620
  } else {
623
621
  parenthesisLevel--; // This should decrease it from 1 to 0...
@@ -625,7 +623,7 @@ class Editor {
625
623
 
626
624
  if (parenthesisLevel == 0) {
627
625
  // No invalid condition, let's make sure the linkDetails array is complete
628
- while (linkDetails.length < 7) linkDetails.push('');
626
+ while (linkDetails.length < 7) linkDetails.push("");
629
627
  }
630
628
  currentOffset++;
631
629
  continue inlineOuter;
@@ -636,7 +634,7 @@ class Editor {
636
634
  if (cap) {
637
635
  switch (linkDetails.length) {
638
636
  case 0:
639
- linkDetails.push('');
637
+ linkDetails.push("");
640
638
  // this opens the link destination, add empty opening delimiter and proceed to next case
641
639
  case 1:
642
640
  linkDetails.push(cap[0]);
@@ -653,7 +651,7 @@ class Editor {
653
651
  return false;
654
652
  // Lcaking opening delimiter for link title
655
653
  case 5:
656
- linkDetails.push('');
654
+ linkDetails.push("");
657
655
  // This opens the link title
658
656
  case 6:
659
657
  linkDetails[5] = linkDetails[5].concat(cap[0]);
@@ -696,7 +694,7 @@ class Editor {
696
694
 
697
695
  // This should never happen, but better safe than sorry.
698
696
  while (linkDetails.length < 7) {
699
- linkDetails.push('');
697
+ linkDetails.push("");
700
698
  }
701
699
  return {
702
700
  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>`,
@@ -712,13 +710,13 @@ class Editor {
712
710
  * @returns {string} The HTML formatted output
713
711
  */
714
712
  processInlineStyles(originalString) {
715
- let processed = '';
713
+ let processed = "";
716
714
  let stack = []; // Stack is an array of objects of the format: {delimiter, delimString, count, output}
717
715
  let offset = 0;
718
716
  let string = originalString;
719
717
  outer: while (string) {
720
718
  // Process simple rules (non-delimiter)
721
- for (let rule of ['escape', 'code', 'autolink', 'html']) {
719
+ for (let rule of ["escape", "code", "autolink", "html"]) {
722
720
  let cap = _grammar.inlineGrammar[rule].regexp.exec(string);
723
721
  if (cap) {
724
722
  string = string.substr(cap[0].length);
@@ -754,8 +752,8 @@ class Editor {
754
752
 
755
753
  // We have a delimiter run. Let's check if it can open or close an emphasis.
756
754
 
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 : ' ';
755
+ const preceding = offset > 0 ? originalString.substr(0, offset) : " "; // beginning and end of line count as whitespace
756
+ const following = offset + cap[0].length < originalString.length ? string : " ";
759
757
  const punctuationFollows = following.match(_grammar.punctuationLeading);
760
758
  const punctuationPrecedes = preceding.match(_grammar.punctuationTrailing);
761
759
  const whitespaceFollows = following.match(/^\s/);
@@ -766,7 +764,7 @@ class Editor {
766
764
  let canClose = !whitespacePrecedes && (!punctuationPrecedes || !!whitespaceFollows || !!punctuationFollows);
767
765
 
768
766
  // Underscores have more detailed rules than just being part of left- or right-flanking run:
769
- if (currentDelimiter == '_' && canOpen && canClose) {
767
+ if (currentDelimiter == "_" && canOpen && canClose) {
770
768
  canOpen = punctuationPrecedes;
771
769
  canClose = punctuationFollows;
772
770
  }
@@ -819,7 +817,7 @@ class Editor {
819
817
  count: delimCount,
820
818
  output: processed
821
819
  });
822
- processed = ''; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
820
+ processed = ""; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
823
821
  delimCount = 0;
824
822
  }
825
823
 
@@ -838,7 +836,7 @@ class Editor {
838
836
  let stackPointer = stack.length - 1;
839
837
  // See if we can find a matching opening delimiter, move down through the stack
840
838
  while (!consumed && stackPointer >= 0) {
841
- if (stack[stackPointer].delimiter == '~') {
839
+ if (stack[stackPointer].delimiter == "~") {
842
840
  // We found a matching delimiter, let's construct the formatted string
843
841
 
844
842
  // Firstly, if we skipped any stack levels, pop them immediately (non-matching delimiters)
@@ -862,12 +860,12 @@ class Editor {
862
860
  // If there are still delimiters left, and the delimiter run can open, push it on the stack
863
861
  if (!consumed) {
864
862
  stack.push({
865
- delimiter: '~',
866
- delimString: '~~',
863
+ delimiter: "~",
864
+ delimString: "~~",
867
865
  count: 2,
868
866
  output: processed
869
867
  });
870
- processed = ''; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
868
+ processed = ""; // Current formatted output has been pushed on the stack and will be prepended when the stack gets popped
871
869
  }
872
870
 
873
871
  offset += cap[0].length;
@@ -883,7 +881,7 @@ class Editor {
883
881
  processed += _grammar.inlineGrammar.default.replacement.replace(/\$([1-9])/g, (str, p1) => (0, _grammar.htmlescape)(cap[p1]));
884
882
  continue outer;
885
883
  }
886
- throw 'Infinite loop!';
884
+ throw "Infinite loop!";
887
885
  }
888
886
 
889
887
  // Empty the stack, any opening delimiters are unused
@@ -894,7 +892,7 @@ class Editor {
894
892
  return processed;
895
893
  }
896
894
 
897
- /**
895
+ /**
898
896
  * Clears the line dirty flag (resets it to an array of false)
899
897
  */
900
898
  clearDirtyFlag() {
@@ -909,7 +907,7 @@ class Editor {
909
907
  * @returns true if contents changed
910
908
  */
911
909
  updateLineContents() {
912
- // this.lineDirty = [];
910
+ // this.lineDirty = [];
913
911
  // Check if we have changed anything about the number of lines (inserted or deleted a paragraph)
914
912
  // < 0 means line(s) removed; > 0 means line(s) added
915
913
  let lineDelta = this.e.childElementCount - this.lines.length;
@@ -917,8 +915,9 @@ class Editor {
917
915
  // yup. Let's try how much we can salvage (find out which lines from beginning and end were unchanged)
918
916
  // Find lines from the beginning that haven't changed...
919
917
  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) {
918
+ while (firstChangedLine <= this.lines.length && firstChangedLine <= this.lineElements.length && this.lineElements[firstChangedLine] &&
919
+ // Check that the line element hasn't been deleted
920
+ this.lines[firstChangedLine] == this.lineElements[firstChangedLine].textContent) {
922
921
  firstChangedLine++;
923
922
  }
924
923
 
@@ -963,17 +962,17 @@ class Editor {
963
962
 
964
963
  let checkLine = sel.col > 0 ? sel.row : sel.row - 1;
965
964
  switch (this.lineTypes[checkLine]) {
966
- case 'TMUL':
967
- continuableType = 'TMUL';
965
+ case "TMUL":
966
+ continuableType = "TMUL";
968
967
  break;
969
- case 'TMOL':
970
- continuableType = 'TMOL';
968
+ case "TMOL":
969
+ continuableType = "TMOL";
971
970
  break;
972
- case 'TMIndentedCode':
973
- continuableType = 'TMIndentedCode';
971
+ case "TMIndentedCode":
972
+ continuableType = "TMIndentedCode";
974
973
  break;
975
974
  }
976
- let lines = this.lines[sel.row].replace(/\n\n$/, '\n').split(/(?:\r\n|\n|\r)/);
975
+ let lines = this.lines[sel.row].replace(/\n\n$/, "\n").split(/(?:\r\n|\n|\r)/);
977
976
  if (lines.length == 1) {
978
977
  // No new line
979
978
  this.updateFormatting();
@@ -991,7 +990,7 @@ class Editor {
991
990
  // Previous line has content, continue the continuable type
992
991
 
993
992
  // Hack for OL: increment number
994
- if (continuableType == 'TMOL') {
993
+ if (continuableType == "TMOL") {
995
994
  capture[1] = capture[1].replace(/\d{1,9}/, result => {
996
995
  return parseInt(result[0]) + 1;
997
996
  });
@@ -1001,7 +1000,7 @@ class Editor {
1001
1000
  sel.col = capture[1].length;
1002
1001
  } else {
1003
1002
  // Previous line has no content, remove the continuable type from the previous row
1004
- this.lines[sel.row - 1] = '';
1003
+ this.lines[sel.row - 1] = "";
1005
1004
  this.lineDirty[sel.row - 1] = true;
1006
1005
  }
1007
1006
  }
@@ -1017,7 +1016,7 @@ class Editor {
1017
1016
  // processDelete(focus, forward) {
1018
1017
  // if (!focus) return;
1019
1018
  // let anchor = this.getSelection(true);
1020
- // // Do we have a non-empty selection?
1019
+ // // Do we have a non-empty selection?
1021
1020
  // if (focus.col != anchor.col || focus.row != anchor.row) {
1022
1021
  // // non-empty. direction doesn't matter.
1023
1022
  // this.paste('', anchor, focus);
@@ -1037,7 +1036,7 @@ class Editor {
1037
1036
 
1038
1037
  /**
1039
1038
  * 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
- *
1039
+ *
1041
1040
  * @param {boolean} getAnchor if set to true, gets the selection anchor (start point of the selection), otherwise gets the focus (end point).
1042
1041
  * @return {object} An object representing the selection, with properties col and row.
1043
1042
  */
@@ -1046,7 +1045,7 @@ class Editor {
1046
1045
  const selection = window.getSelection();
1047
1046
  let startNode = getAnchor ? selection.anchorNode : selection.focusNode;
1048
1047
  if (!startNode) return null;
1049
- let offset = startNode.nodeType === Node.TEXT_NODE ? getAnchor ? selection.anchorOffset : selection.focusOffset : 0;
1048
+ let offset = getAnchor ? selection.anchorOffset : selection.focusOffset;
1050
1049
  if (startNode == this.e) {
1051
1050
  return {
1052
1051
  row: 0,
@@ -1087,13 +1086,28 @@ class Editor {
1087
1086
  */
1088
1087
  computeColumn(startNode, offset) {
1089
1088
  let node = startNode;
1090
- let col = offset;
1089
+ let col;
1091
1090
  // First, make sure we're actually in the editor.
1092
1091
  while (node && node.parentNode != this.e) {
1093
1092
  node = node.parentNode;
1094
1093
  }
1095
1094
  if (node == null) return null;
1096
- node = startNode;
1095
+
1096
+ // There are two ways that offset can be defined:
1097
+ // - Either, the node is a text node, in which case it is the offset within the text
1098
+ // - Or, the node is an element with child notes, in which case the offset refers to the
1099
+ // child node after which the selection is located
1100
+ if (startNode.nodeType === Node.TEXT_NODE || offset === 0) {
1101
+ // In the case that the node is non-text node but the offset is 0,
1102
+ // The selection is at the beginning of that element so we
1103
+ // can simply use the same approach as if it were at the beginning
1104
+ // of a text node.
1105
+ col = offset;
1106
+ node = startNode;
1107
+ } else if (offset > 0) {
1108
+ node = startNode.childNodes[offset - 1];
1109
+ col = node.textContent.length;
1110
+ }
1097
1111
  while (node.parentNode != this.e) {
1098
1112
  if (node.previousSibling) {
1099
1113
  node = node.previousSibling;
@@ -1196,27 +1210,19 @@ class Editor {
1196
1210
  windowSelection.addRange(range);
1197
1211
  }
1198
1212
 
1199
- /**
1200
- * Event handler for input events
1213
+ /**
1214
+ * Event handler for input events
1201
1215
  */
1202
1216
  handleInputEvent(event) {
1203
1217
  let focus = this.getSelection();
1204
- if ((event.inputType == 'insertParagraph' || event.inputType == 'insertLineBreak') && focus) {
1218
+ if ((event.inputType == "insertParagraph" || event.inputType == "insertLineBreak") && focus) {
1205
1219
  this.clearDirtyFlag();
1206
1220
  this.processNewParagraph(focus);
1207
1221
  } else {
1208
1222
  if (!this.e.firstChild) {
1209
1223
  this.e.innerHTML = '<div class="TMBlankLine"><br></div>';
1210
1224
  } else {
1211
- for (let childNode = this.e.firstChild; childNode; childNode = childNode.nextSibling) {
1212
- if (childNode.nodeType != 3 || childNode.tagName != 'DIV') {
1213
- // Found a child node that's either not an element or not a div. Wrap it in a div.
1214
- let divWrapper = document.createElement('div');
1215
- this.e.insertBefore(divWrapper, childNode);
1216
- this.e.removeChild(childNode);
1217
- divWrapper.appendChild(childNode);
1218
- }
1219
- }
1225
+ this.fixNodeHierarchy();
1220
1226
  }
1221
1227
  this.updateLineContentsAndFormatting();
1222
1228
  }
@@ -1224,6 +1230,31 @@ class Editor {
1224
1230
  this.fireChange();
1225
1231
  }
1226
1232
 
1233
+ /**
1234
+ * Fixes the node hierarchy – makes sure that each line is in a div, and there are no nested divs
1235
+ */
1236
+ fixNodeHierarchy() {
1237
+ const originalChildren = Array.from(this.e.childNodes);
1238
+ originalChildren.flatMap(child => {
1239
+ child.parentElement.removeChild(child);
1240
+ if (child.nodeType !== Node.ELEMENT_NODE || child.tagName !== "DIV") {
1241
+ // Found a child node that's either not an element or not a div. Wrap it in a div.
1242
+ const divWrapper = document.createElement("div");
1243
+ divWrapper.appendChild(child);
1244
+ return divWrapper;
1245
+ } else {
1246
+ const grandChildren = Array.from(child.childNodes);
1247
+ if (grandChildren.some(grandChild => grandChild.nodeType === Node.ELEMENT_NODE && grandChild.tagName === "DIV")) {
1248
+ return grandChildren;
1249
+ } else {
1250
+ return child;
1251
+ }
1252
+ }
1253
+ }).forEach(child => {
1254
+ this.e.appendChild(child);
1255
+ });
1256
+ }
1257
+
1227
1258
  /**
1228
1259
  * Event handler for "selectionchange" events.
1229
1260
  */
@@ -1232,11 +1263,11 @@ class Editor {
1232
1263
  }
1233
1264
 
1234
1265
  /**
1235
- * Convenience function to "splice" new lines into the arrays this.lines, this.lineDirty, this.lineTypes, and the DOM elements
1266
+ * Convenience function to "splice" new lines into the arrays this.lines, this.lineDirty, this.lineTypes, and the DOM elements
1236
1267
  * underneath the editor element.
1237
1268
  * This method is essentially Array.splice, only that the third parameter takes an un-spread array (and the forth parameter)
1238
1269
  * determines whether the DOM should also be adjusted.
1239
- *
1270
+ *
1240
1271
  * @param {int} startLine Position at which to start changing the array of lines
1241
1272
  * @param {int} linesToDelete Number of lines to delete
1242
1273
  * @param {array} linesToInsert Array of strings representing the lines to be inserted
@@ -1254,10 +1285,10 @@ class Editor {
1254
1285
  let insertedBlank = [];
1255
1286
  let insertedDirty = [];
1256
1287
  for (let i = 0; i < linesToInsert.length; i++) {
1257
- insertedBlank.push('');
1288
+ insertedBlank.push("");
1258
1289
  insertedDirty.push(true);
1259
1290
  if (adjustLineElements) {
1260
- if (this.e.childNodes[startLine]) this.e.insertBefore(document.createElement('div'), this.e.childNodes[startLine]);else this.e.appendChild(document.createElement('div'));
1291
+ if (this.e.childNodes[startLine]) this.e.insertBefore(document.createElement("div"), this.e.childNodes[startLine]);else this.e.appendChild(document.createElement("div"));
1261
1292
  }
1262
1293
  }
1263
1294
  this.lines.splice(startLine, linesToDelete, ...linesToInsert);
@@ -1272,7 +1303,7 @@ class Editor {
1272
1303
  event.preventDefault();
1273
1304
 
1274
1305
  // get text representation of clipboard
1275
- let text = (event.originalEvent || event).clipboardData.getData('text/plain');
1306
+ let text = (event.originalEvent || event).clipboardData.getData("text/plain");
1276
1307
 
1277
1308
  // insert text manually
1278
1309
  this.paste(text);
@@ -1280,7 +1311,7 @@ class Editor {
1280
1311
 
1281
1312
  /**
1282
1313
  * Pastes the text at the current selection (or at the end, if no current selection)
1283
- * @param {string} text
1314
+ * @param {string} text
1284
1315
  */
1285
1316
  paste(text) {
1286
1317
  let anchor = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
@@ -1401,18 +1432,17 @@ class Editor {
1401
1432
  }
1402
1433
 
1403
1434
  for (let cmd in _grammar.commands) {
1404
- if (_grammar.commands[cmd].type == 'inline') {
1435
+ if (_grammar.commands[cmd].type == "inline") {
1405
1436
  if (!focus || focus.row != anchor.row || !this.isInlineFormattingAllowed(focus, anchor)) {
1406
1437
  commandState[cmd] = null;
1407
1438
  } else {
1408
- // The command state is true if there is a respective enclosing markup node (e.g., the selection is enclosed in a <b>..</b>) ...
1439
+ // The command state is true if there is a respective enclosing markup node (e.g., the selection is enclosed in a <b>..</b>) ...
1409
1440
  commandState[cmd] = !!this.computeEnclosingMarkupNode(focus, anchor, _grammar.commands[cmd].className) ||
1410
1441
  // ... or if it's an empty string preceded by and followed by formatting markers, e.g. **|** where | is the cursor
1411
-
1412
1442
  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);
1413
1443
  }
1414
1444
  }
1415
- if (_grammar.commands[cmd].type == 'line') {
1445
+ if (_grammar.commands[cmd].type == "line") {
1416
1446
  if (!focus) {
1417
1447
  commandState[cmd] = null;
1418
1448
  } else {
@@ -1432,11 +1462,11 @@ class Editor {
1432
1462
 
1433
1463
  /**
1434
1464
  * Sets a command state
1435
- * @param {string} command
1436
- * @param {boolean} state
1465
+ * @param {string} command
1466
+ * @param {boolean} state
1437
1467
  */
1438
1468
  setCommandState(command, state) {
1439
- if (_grammar.commands[command].type == 'inline') {
1469
+ if (_grammar.commands[command].type == "inline") {
1440
1470
  let anchor = this.getSelection(true);
1441
1471
  let focus = this.getSelection(false);
1442
1472
  if (!anchor) anchor = focus;
@@ -1451,9 +1481,9 @@ class Editor {
1451
1481
  this.lineDirty[focus.row] = true;
1452
1482
  const startCol = this.computeColumn(markupNode, 0);
1453
1483
  const len = markupNode.textContent.length;
1454
- const left = this.lines[focus.row].substr(0, startCol).replace(_grammar.commands[command].unset.prePattern, '');
1484
+ const left = this.lines[focus.row].substr(0, startCol).replace(_grammar.commands[command].unset.prePattern, "");
1455
1485
  const mid = this.lines[focus.row].substr(startCol, len);
1456
- const right = this.lines[focus.row].substr(startCol + len).replace(_grammar.commands[command].unset.postPattern, '');
1486
+ const right = this.lines[focus.row].substr(startCol + len).replace(_grammar.commands[command].unset.postPattern, "");
1457
1487
  this.lines[focus.row] = left.concat(mid, right);
1458
1488
  anchor.col = left.length;
1459
1489
  focus.col = anchor.col + len;
@@ -1464,8 +1494,8 @@ class Editor {
1464
1494
  // Second case: Empty selection with surrounding formatting markers, remove those
1465
1495
  } 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)) {
1466
1496
  this.lineDirty[focus.row] = true;
1467
- const left = this.lines[focus.row].substr(0, focus.col).replace(_grammar.commands[command].unset.prePattern, '');
1468
- const right = this.lines[focus.row].substr(focus.col).replace(_grammar.commands[command].unset.postPattern, '');
1497
+ const left = this.lines[focus.row].substr(0, focus.col).replace(_grammar.commands[command].unset.prePattern, "");
1498
+ const right = this.lines[focus.row].substr(focus.col).replace(_grammar.commands[command].unset.postPattern, "");
1469
1499
  this.lines[focus.row] = left.concat(right);
1470
1500
  focus.col = anchor.col = left.length;
1471
1501
  this.updateFormatting();
@@ -1493,12 +1523,12 @@ class Editor {
1493
1523
  focus.col = startCol;
1494
1524
  anchor.col = endCol;
1495
1525
 
1496
- // Just insert markup before and after and hope for the best.
1526
+ // Just insert markup before and after and hope for the best.
1497
1527
  this.wrapSelection(_grammar.commands[command].set.pre, _grammar.commands[command].set.post, focus, anchor);
1498
1528
  this.fireChange();
1499
1529
  // TODO clean this up so that markup remains properly nested
1500
1530
  }
1501
- } else if (_grammar.commands[command].type == 'line') {
1531
+ } else if (_grammar.commands[command].type == "line") {
1502
1532
  let anchor = this.getSelection(true);
1503
1533
  let focus = this.getSelection(false);
1504
1534
  if (!anchor) anchor = focus;
@@ -1511,7 +1541,7 @@ class Editor {
1511
1541
  }
1512
1542
  for (let line = start.row; line <= end.row; line++) {
1513
1543
  if (state && this.lineTypes[line] != _grammar.commands[command].className) {
1514
- this.lines[line] = this.lines[line].replace(_grammar.commands[command].set.pattern, _grammar.commands[command].set.replacement.replace('$#', line - start.row + 1));
1544
+ this.lines[line] = this.lines[line].replace(_grammar.commands[command].set.pattern, _grammar.commands[command].set.replacement.replace("$#", line - start.row + 1));
1515
1545
  this.lineDirty[line] = true;
1516
1546
  }
1517
1547
  if (!state && this.lineTypes[line] == _grammar.commands[command].className) {
@@ -1532,7 +1562,7 @@ class Editor {
1532
1562
  }
1533
1563
 
1534
1564
  /**
1535
- * Returns whether or not inline formatting is allowed at the current focus
1565
+ * Returns whether or not inline formatting is allowed at the current focus
1536
1566
  * @param {object} focus The current focus
1537
1567
  */
1538
1568
  isInlineFormattingAllowed() {
@@ -1540,13 +1570,13 @@ class Editor {
1540
1570
  const sel = window.getSelection();
1541
1571
  if (!sel || !sel.focusNode || !sel.anchorNode) return false;
1542
1572
 
1543
- // Check if we can find a common ancestor with the class `TMInlineFormatted`
1573
+ // Check if we can find a common ancestor with the class `TMInlineFormatted`
1544
1574
 
1545
1575
  // Special case: Empty selection right before `TMInlineFormatted`
1546
1576
  if (sel.isCollapsed && sel.focusNode.nodeType == 3 && sel.focusOffset == sel.focusNode.nodeValue.length) {
1547
1577
  let node;
1548
1578
  for (node = sel.focusNode; node && node.nextSibling == null; node = node.parentNode);
1549
- if (node && node.nextSibling.className && node.nextSibling.className.includes('TMInlineFormatted')) return true;
1579
+ if (node && node.nextSibling.className && node.nextSibling.className.includes("TMInlineFormatted")) return true;
1550
1580
  }
1551
1581
 
1552
1582
  // Look for a common ancestor
@@ -1555,7 +1585,7 @@ class Editor {
1555
1585
 
1556
1586
  // Check if there's an ancestor of class 'TMInlineFormatted' or 'TMBlankLine'
1557
1587
  while (ancestor && ancestor != this.e) {
1558
- if (ancestor.className && (ancestor.className.includes('TMInlineFormatted') || ancestor.className.includes('TMBlankLine'))) return true;
1588
+ if (ancestor.className && (ancestor.className.includes("TMInlineFormatted") || ancestor.className.includes("TMBlankLine"))) return true;
1559
1589
  ancestor = ancestor.parentNode;
1560
1590
  }
1561
1591
  return false;
@@ -1578,7 +1608,7 @@ class Editor {
1578
1608
  const startCol = focus.col < anchor.col ? focus.col : anchor.col;
1579
1609
  const endCol = focus.col < anchor.col ? anchor.col : focus.col;
1580
1610
  const left = this.lines[focus.row].substr(0, startCol).concat(pre);
1581
- const mid = endCol == startCol ? '' : this.lines[focus.row].substr(startCol, endCol - startCol);
1611
+ const mid = endCol == startCol ? "" : this.lines[focus.row].substr(startCol, endCol - startCol);
1582
1612
  const right = post.concat(this.lines[focus.row].substr(endCol));
1583
1613
  this.lines[focus.row] = left.concat(mid, right);
1584
1614
  anchor.col = left.length;