@innovastudio/contentbuilder 1.4.124 → 1.4.126

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.
@@ -1439,7 +1439,7 @@ class Util {
1439
1439
  const selection = this.builder.win.getSelection();
1440
1440
  const container = this.builder.dom.getElm();
1441
1441
  if (!container) return;
1442
- const sameSelection = container && container.innerText === selection.toString();
1442
+ const sameSelection = container && container.innerText === selection.toString().trim();
1443
1443
  if (sameSelection || selection.toString().trim() === '') {
1444
1444
  this.builder.selectionElm = container;
1445
1445
 
@@ -3395,7 +3395,7 @@ class Dom {
3395
3395
  if (callback) callback(false);
3396
3396
  return;
3397
3397
  }
3398
- const sameSelection = container && container.innerText === selection.toString();
3398
+ const sameSelection = container && container.innerText === selection.toString().trim();
3399
3399
  let newElement;
3400
3400
  if (sameSelection || selection.toString().trim() === '') {
3401
3401
  newElement = this.updateSelection(action, value, container);
@@ -3519,7 +3519,7 @@ class Dom {
3519
3519
  const selection = this.builder.win.getSelection();
3520
3520
  let container = this.getElm();
3521
3521
  if (!container) return;
3522
- const sameSelection = container && container.innerText === selection.toString();
3522
+ const sameSelection = container && container.innerText === selection.toString().trim();
3523
3523
  if (currentElement) {
3524
3524
  if (mode === 'block') if (this.getStyle(container, 'display') === 'inline') {
3525
3525
  container = this.getParentBlock(container);
@@ -3592,7 +3592,7 @@ class Dom {
3592
3592
  }
3593
3593
  }
3594
3594
  let blocks = [];
3595
- const blockElms = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'pre'];
3595
+ const blockElms = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'pre', 'td', 'th'];
3596
3596
  elements.forEach(item => {
3597
3597
  const tagName = item.tagName.toLowerCase();
3598
3598
  if (blockElms.includes(tagName)) {
@@ -3600,6 +3600,21 @@ class Dom {
3600
3600
  blocks.push(item);
3601
3601
  }
3602
3602
  });
3603
+
3604
+ /*
3605
+ If a block is double clicked to select the block, the selection goes to the next block as well.
3606
+ This seems a default behavior (tested with simple contentEditable div). So the blocks contains 2 blocks.
3607
+ To fix this, perform an extra test here:
3608
+ */
3609
+ if (blocks.length === 2) {
3610
+ // console.log(blocks[0].innerText.trim());
3611
+ // console.log(selection.toString().trim());
3612
+ if (blocks[0].innerText.trim() === selection.toString().trim()) {
3613
+ blocks.pop();
3614
+ // console.log('remove last')
3615
+ }
3616
+ }
3617
+
3603
3618
  if (multiSelectBlocks) {
3604
3619
  return blocks;
3605
3620
  } else {
@@ -3733,7 +3748,7 @@ class Dom {
3733
3748
  const selection = this.builder.win.getSelection();
3734
3749
  const container = this.getElm();
3735
3750
  if (!container) return;
3736
- const sameSelection = container && container.innerText === selection.toString();
3751
+ const sameSelection = container && container.innerText === selection.toString().trim();
3737
3752
  if (sameSelection || selection.toString().trim() === '') {
3738
3753
  this.updateSelectionToggle(action, value, config, container);
3739
3754
  } else {
@@ -4155,7 +4170,7 @@ class Dom {
4155
4170
  const anchorNode = selection.anchorNode;
4156
4171
  if (!anchorNode) return;
4157
4172
  const container = anchorNode.nodeType !== Node.TEXT_NODE && anchorNode.nodeType !== Node.COMMENT_NODE ? anchorNode : anchorNode.parentElement;
4158
- const sameSelection = container && container.innerText === selection.toString();
4173
+ const sameSelection = container && container.innerText === selection.toString().trim();
4159
4174
  if (sameSelection || selection.toString().trim() === '') {
4160
4175
  this.cleanElement(container);
4161
4176
  } else {
@@ -66285,7 +66300,7 @@ class Rte {
66285
66300
  } else if (num === '+' || num === '-') {
66286
66301
  for (let i = 0; i < Object.keys(classes).length; i++) {
66287
66302
  let className = Object.values(classes)[i];
66288
- if (dom.hasClass(container, className)) {
66303
+ if (container.closest('.' + className) && !container.classList.contains('is-builder')) {
66289
66304
  if (num === '+') {
66290
66305
  if (i + 1 === Object.keys(classes).length) return;
66291
66306
  newClassName = Object.values(classes)[i + 1];
@@ -76867,8 +76882,10 @@ class ContentBuilder {
76867
76882
  // ON PASTE
76868
76883
  // col.addEventListener('paste', this.handleCellPaste.bind(this));
76869
76884
  col.addEventListener('paste', e => {
76870
- const clipboardData = (e.clipboardData || window.clipboardData).getData('text');
76871
- this.handleCellPaste(clipboardData);
76885
+ e.preventDefault();
76886
+ const clipboardDataText = (e.clipboardData || window.clipboardData).getData('text');
76887
+ const clipboardDataHtml = (e.clipboardData || window.clipboardData).getData('text/html');
76888
+ this.handleCellPaste(clipboardDataText, clipboardDataHtml);
76872
76889
  });
76873
76890
  col.setAttribute('data-click', true);
76874
76891
  }
@@ -79046,6 +79063,7 @@ class ContentBuilder {
79046
79063
  // CMD-B
79047
79064
 
79048
79065
  if (this.opts.useCssClasses) {
79066
+ this.uo.saveForUndo();
79049
79067
  this.dom.execCommandToggle('fontWeight', '', this.opts.cssClasses);
79050
79068
  this.opts.onChange();
79051
79069
  e.preventDefault();
@@ -79056,6 +79074,7 @@ class ContentBuilder {
79056
79074
  // CMD-I
79057
79075
 
79058
79076
  if (this.opts.useCssClasses) {
79077
+ this.uo.saveForUndo();
79059
79078
  this.dom.execCommandToggle('fontStyle', '', this.opts.cssClasses);
79060
79079
  this.opts.onChange();
79061
79080
  e.preventDefault();
@@ -79066,6 +79085,7 @@ class ContentBuilder {
79066
79085
  // CMD-U
79067
79086
 
79068
79087
  if (this.opts.useCssClasses) {
79088
+ this.uo.saveForUndo();
79069
79089
  this.dom.execCommandToggle('textUnderline', '', this.opts.cssClasses);
79070
79090
  this.opts.onChange();
79071
79091
  e.preventDefault();
@@ -79076,6 +79096,7 @@ class ContentBuilder {
79076
79096
  // CMD-S
79077
79097
 
79078
79098
  if (this.opts.useCssClasses) {
79099
+ this.uo.saveForUndo();
79079
79100
  this.dom.execCommandToggle('textLinethrough', '', this.opts.cssClasses);
79080
79101
  this.opts.onChange();
79081
79102
  e.preventDefault();
@@ -79083,6 +79104,8 @@ class ContentBuilder {
79083
79104
  }
79084
79105
  }
79085
79106
  if (isCmd && e.which === 221) {
79107
+ this.uo.saveForUndo();
79108
+
79086
79109
  // CMD-]
79087
79110
  e.preventDefault();
79088
79111
  document.execCommand('indent', false, null);
@@ -79091,6 +79114,8 @@ class ContentBuilder {
79091
79114
  markSpan();
79092
79115
  }
79093
79116
  if (isCmd && e.which === 219) {
79117
+ this.uo.saveForUndo();
79118
+
79094
79119
  // CMD-]
79095
79120
  e.preventDefault();
79096
79121
  document.execCommand('outdent', false, null);
@@ -79322,307 +79347,326 @@ class ContentBuilder {
79322
79347
  this.opts.onChange();
79323
79348
  }, 2000);
79324
79349
  }
79325
- handleCellPaste(clipboardData) {
79350
+ handleCellPaste(clipboardData, clipboardDataHtml) {
79326
79351
  this.uo.saveForUndo();
79327
79352
  const util = this.util;
79328
79353
  util.saveSelection(); //required. Without this, CTRL-A (select element) & CTRL-V won't replace the element, but will paste at the end of the element.
79329
79354
 
79330
- let contentword = this.doc.querySelector('#idContentWord');
79331
- if (contentword) contentword.parentNode.removeChild(contentword);
79332
- var el;
79333
- var curr;
79334
- if (this.win.getSelection) {
79335
- curr = this.win.getSelection().getRangeAt(0).commonAncestorContainer;
79336
- if (curr.nodeType === 3) {
79337
- //ini text node
79338
- el = curr.parentNode;
79339
- } else {
79340
- el = curr;
79341
- }
79342
- } else if (this.doc.selection) {
79343
- curr = this.doc.selection.createRange();
79344
- el = this.doc.selection.createRange().parentElement();
79345
- }
79346
- var tmptop = el.getBoundingClientRect().top + this.win.pageYOffset;
79347
- const html = '<div style="position:absolute;z-index:-1000;top:' + tmptop + 'px;left:-1000px;width:100px;height:100px;overflow:auto;" name="idContentWord" id="idContentWord" contenteditable="true"></div>';
79348
- if (!this.iframe) {
79349
- this.dom.appendHtml(this.builderStuff, html);
79350
- } else {
79351
- this.dom.appendHtml(this.contentStuff, html);
79352
- }
79353
- contentword = this.doc.querySelector('#idContentWord');
79354
- contentword.focus();
79355
- setTimeout(() => {
79356
- try {
79357
- var sPastedText = '';
79358
- let contentword = this.doc.querySelector('#idContentWord');
79359
-
79360
- //Check video embed
79361
- var bPasteObject = false;
79362
- var src = contentword.innerText;
79363
- if (!this.opts.disableAutoEmbedVideo) {
79364
- //var youRegex = /^http[s]?:\/\/(((www.youtube.com\/watch\?(feature=player_detailpage&)?)v=)|(youtu.be\/))([^#\&\?]*)/;
79365
- var youRegex = /^http[s]?:\/\/(((www.youtube.com\/watch\?(feature=player_detailpage&)?)v=)|(youtu.be\/))([^#&?]*)/;
79366
- var vimeoRegex = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/)|(video\/))?([0-9]+)\/?/;
79367
- var youRegexMatches = youRegex.exec(src);
79368
- var vimeoRegexMatches = vimeoRegex.exec(src);
79369
- if (youRegexMatches !== null || vimeoRegexMatches !== null) {
79370
- if (youRegexMatches !== null && youRegexMatches.length >= 7) {
79371
- var youMatch = youRegexMatches[6];
79372
- src = 'https://www.youtube.com/embed/' + youMatch + '?rel=0';
79373
- }
79374
- if (vimeoRegexMatches !== null && vimeoRegexMatches.length >= 7) {
79375
- var vimeoMatch = vimeoRegexMatches[6];
79376
- src = 'https://player.vimeo.com/video/' + vimeoMatch;
79377
- }
79378
- sPastedText = '<div class="embed-responsive embed-responsive-16by9"><iframe tabindex="0" width="560" height="315" src="' + src + '" frameborder="0" allowfullscreen=""></iframe></div>';
79379
- bPasteObject = true;
79355
+ // Create a temporary div to hold the pasted HTML
79356
+ let contentword = document.createElement('div');
79357
+ contentword.innerHTML = clipboardDataHtml;
79358
+ try {
79359
+ var sPastedText = '';
79360
+
79361
+ //Check video embed
79362
+ var bPasteObject = false;
79363
+ var src = contentword.innerText;
79364
+ if (!this.opts.disableAutoEmbedVideo) {
79365
+ //var youRegex = /^http[s]?:\/\/(((www.youtube.com\/watch\?(feature=player_detailpage&)?)v=)|(youtu.be\/))([^#\&\?]*)/;
79366
+ var youRegex = /^http[s]?:\/\/(((www.youtube.com\/watch\?(feature=player_detailpage&)?)v=)|(youtu.be\/))([^#&?]*)/;
79367
+ var vimeoRegex = /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/)|(video\/))?([0-9]+)\/?/;
79368
+ var youRegexMatches = youRegex.exec(src);
79369
+ var vimeoRegexMatches = vimeoRegex.exec(src);
79370
+ if (youRegexMatches !== null || vimeoRegexMatches !== null) {
79371
+ if (youRegexMatches !== null && youRegexMatches.length >= 7) {
79372
+ var youMatch = youRegexMatches[6];
79373
+ src = 'https://www.youtube.com/embed/' + youMatch + '?rel=0';
79374
+ }
79375
+ if (vimeoRegexMatches !== null && vimeoRegexMatches.length >= 7) {
79376
+ var vimeoMatch = vimeoRegexMatches[6];
79377
+ src = 'https://player.vimeo.com/video/' + vimeoMatch;
79380
79378
  }
79379
+ sPastedText = '<div class="embed-responsive embed-responsive-16by9"><iframe tabindex="0" width="560" height="315" src="' + src + '" frameborder="0" allowfullscreen=""></iframe></div>';
79380
+ bPasteObject = true;
79381
79381
  }
79382
- if (!bPasteObject) {
79383
- if (this.opts.paste === 'text') {
79384
- /*
79385
- let elms = contentword.querySelectorAll('p,h1,h2,h3,h4,h5,h6');
79386
- Array.prototype.forEach.call(elms, (elm) => {
79387
- elm.innerHTML = elm.innerHTML + ' '; //add space (&nbsp;)
79388
- });
79389
- // sPastedText = contentword.innerText;
79390
- sPastedText = contentword.innerHTML;
79391
- sPastedText = sPastedText.replace(/(<([^>]+)>)/ig,'<br>');
79392
- sPastedText = sPastedText.replace(/(<br\s*\/?>){3,}/gi, '<br>');
79393
- if(sPastedText.indexOf('<br>')===0) {
79394
- sPastedText = sPastedText.substring(4);
79395
- }
79396
- if(sPastedText.substring(sPastedText.length-4)==='<br>'){
79397
- sPastedText = sPastedText.substring(0, sPastedText.length-4);
79398
- }
79399
- sPastedText = sPastedText.trim();
79400
- */
79382
+ }
79383
+ if (!bPasteObject) {
79384
+ if (this.opts.paste === 'text') {
79385
+ /*
79386
+ let elms = contentword.querySelectorAll('p,h1,h2,h3,h4,h5,h6');
79387
+ Array.prototype.forEach.call(elms, (elm) => {
79388
+ elm.innerHTML = elm.innerHTML + ' '; //add space (&nbsp;)
79389
+ });
79390
+ // sPastedText = contentword.innerText;
79391
+ sPastedText = contentword.innerHTML;
79392
+ sPastedText = sPastedText.replace(/(<([^>]+)>)/ig,'<br>');
79393
+ sPastedText = sPastedText.replace(/(<br\s*\/?>){3,}/gi, '<br>');
79394
+ if(sPastedText.indexOf('<br>')===0) {
79395
+ sPastedText = sPastedText.substring(4);
79396
+ }
79397
+ if(sPastedText.substring(sPastedText.length-4)==='<br>'){
79398
+ sPastedText = sPastedText.substring(0, sPastedText.length-4);
79399
+ }
79400
+ sPastedText = sPastedText.trim();
79401
+ */
79401
79402
 
79402
- sPastedText = clipboardData;
79403
- // sPastedText = sPastedText.replace(/(?:\r\n|\r|\n)/g, '<br>');
79403
+ sPastedText = clipboardData;
79404
+ // sPastedText = sPastedText.replace(/(?:\r\n|\r|\n)/g, '<br>');
79405
+ } else {
79406
+ sPastedText = contentword.innerHTML;
79407
+ if (this.opts.paste === 'html') {
79408
+ //with styles
79409
+ sPastedText = util.cleanHTML(sPastedText, false);
79404
79410
  } else {
79405
- sPastedText = contentword.innerHTML;
79406
- if (this.opts.paste === 'html') {
79407
- //with styles
79408
- sPastedText = util.cleanHTML(sPastedText, false);
79409
- } else {
79410
- //html-without-styles (default)
79411
- sPastedText = util.cleanHTML(sPastedText, true);
79412
- }
79413
- contentword.innerHTML = sPastedText;
79414
-
79415
- /*
79416
- // remove attributes
79417
- if(this.opts.paste === 'html'){//with styles
79418
- let elms = contentword.querySelectorAll('*');
79419
- Array.prototype.forEach.call(elms, (elm) => {
79420
- for(let n = 0;n<elm.attributes.length;n++) {
79421
- if(elm.attributes[n].name!=='style') elm.removeAttribute(elm.attributes[n].name);
79422
- }
79423
- });
79424
- } else { //html-without-styles (default)
79425
-
79426
- const removeAttributes = (element) => {
79427
- while (element.attributes.length > 0) {
79428
- element.removeAttribute(element.attributes[0].name);
79429
- }
79430
- };
79431
- let elms = contentword.querySelectorAll('*');
79432
- Array.prototype.forEach.call(elms, (elm) => {
79433
- removeAttributes(elm);
79434
- });
79435
- }
79436
- */
79437
-
79438
- /*
79439
- Additional Cleanup:
79440
- - Remove p inside li
79441
- - Remove li with white-space: pre
79442
- */
79443
- let elms = contentword.querySelectorAll('li');
79444
- Array.prototype.forEach.call(elms, elm => {
79445
- elm.style.whiteSpace = '';
79446
- const childNodes = elm.childNodes;
79447
- let i = childNodes.length;
79448
- while (i--) {
79449
- if (childNodes[i].tagName === 'P') {
79450
- childNodes[i].outerHTML = childNodes[i].innerHTML;
79451
- }
79452
- }
79453
- });
79454
-
79455
- // NOTE: paste <h1><p> jadi nempel
79456
-
79457
- // // Source: https://gist.github.com/sbrin/6801034
79458
- // jQuery('p', $editor).each(function(){
79459
- // var str = jQuery(this).attr('style');
79460
- // var matches = /mso-list:\w+ \w+([0-9]+)/.exec(str);
79461
- // if (matches) {
79462
- // jQuery(this).data('_listLevel', parseInt(matches[1], 10));
79463
- // }
79464
- // });
79465
- // var last_level=0;
79466
- // var pnt = null;
79467
- // jQuery('p', $editor).each(function(){
79468
- // var cur_level = jQuery(this).data('_listLevel');
79469
- // if(cur_level !== undefined){
79470
- // var txt = jQuery(this).text();
79471
- // var list_tag = '<ul></ul>';
79472
- // if (/^\s*\w+\./.test(txt)) {
79473
- // var matches = /([0-9])\./.exec(txt);
79474
- // if (matches) {
79475
- // var start = parseInt(matches[1], 10);
79476
- // list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>';
79477
- // }else{
79478
- // list_tag = '<ol></ol>';
79479
- // }
79480
- // }
79481
-
79482
- // if(cur_level>last_level){
79483
- // if(last_level===0){
79484
- // jQuery(this).before(list_tag);
79485
- // pnt = jQuery(this).prev();
79486
- // }else{
79487
- // pnt = jQuery(list_tag).appendTo(pnt);
79488
- // }
79489
- // }
79490
- // if(cur_level<last_level){
79491
- // for(var i=0; i<last_level-cur_level; i++){
79492
- // pnt = pnt.parent();
79493
- // }
79494
- // }
79495
- // jQuery('span:first', this).remove();
79496
- // pnt.append('<li>' + jQuery(this).html() + '</li>');
79497
- // jQuery(this).remove();
79498
- // last_level = cur_level;
79499
- // }else{
79500
- // last_level = 0;
79501
- // }
79502
- // });
79503
- // //jQuery('[style]', $editor).removeAttr('style'); //done (see cleanHTML)
79504
- // jQuery('[align]', $editor).removeAttr('align');
79505
- // //jQuery('span', $editor).replaceWith(function() {return jQuery(this).contents();}); //done (see cleanHTML)
79506
- // jQuery('span:empty', $editor).remove();
79507
- // //jQuery("[class^='Mso']", $editor).removeAttr('class'); //done (see cleanHTML)
79508
- // jQuery('p:empty', $editor).remove();
79509
-
79510
- sPastedText = contentword.innerHTML;
79411
+ //html-without-styles (default)
79412
+ sPastedText = util.cleanHTML(sPastedText, true);
79511
79413
  }
79512
- }
79513
- contentword = this.doc.querySelector('#idContentWord');
79514
- if (contentword) contentword.parentNode.removeChild(contentword);
79515
- util.restoreSelection();
79516
- var oSel = this.win.getSelection();
79517
- var range = oSel.getRangeAt(0);
79518
- range.extractContents();
79519
- range.collapse(true);
79520
- var docFrag = range.createContextualFragment(sPastedText);
79521
- var lastNode = docFrag.lastChild;
79522
- range.insertNode(docFrag);
79523
- if (this.activeCol) {
79414
+ contentword.innerHTML = sPastedText;
79415
+
79524
79416
  /*
79525
- Additional Cleanup:
79526
- - Remove empty elements (empty p, etc)
79417
+ // remove attributes
79418
+ if(this.opts.paste === 'html'){//with styles
79419
+ let elms = contentword.querySelectorAll('*');
79420
+ Array.prototype.forEach.call(elms, (elm) => {
79421
+ for(let n = 0;n<elm.attributes.length;n++) {
79422
+ if(elm.attributes[n].name!=='style') elm.removeAttribute(elm.attributes[n].name);
79423
+ }
79424
+ });
79425
+ } else { //html-without-styles (default)
79426
+
79427
+ const removeAttributes = (element) => {
79428
+ while (element.attributes.length > 0) {
79429
+ element.removeAttribute(element.attributes[0].name);
79430
+ }
79431
+ };
79432
+ let elms = contentword.querySelectorAll('*');
79433
+ Array.prototype.forEach.call(elms, (elm) => {
79434
+ removeAttributes(elm);
79435
+ });
79436
+ }
79527
79437
  */
79528
- // this.activeCol.find('h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty,p:empty').remove();
79529
- // this.activeCol.querySelectorAll('*:empty').forEach((x)=>{x.remove();}); // Makes <img> removed
79530
- this.activeCol.querySelectorAll('h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty,p:empty').forEach(x => {
79531
- x.remove();
79532
- });
79533
79438
 
79534
79439
  /*
79535
79440
  Additional Cleanup:
79536
- Fix HTML structure. The problem:
79537
- <p class="elm-active">
79538
- ...Sometimes h1, h2, p can be pasted here..
79539
- </p>
79441
+ - Remove p inside li
79442
+ - Remove li with white-space: pre
79540
79443
  */
79541
- let elmActive = this.activeCol.querySelector('p.elm-active,h1.elm-active,h2.elm-active,h3.elm-active,h4.elm-active,h5.elm-active,h6.elm-active');
79542
- if (elmActive) {
79543
- let elms = elmActive.querySelectorAll('p,h1,h2,h3,h4,h5,h6');
79544
- if (elms.length > 0) {
79545
- let elmClosestElement = elmActive.nextElementSibling;
79546
-
79547
- //Fix text that doesn't have paragraph
79548
- let textNodes = Array.from(elmActive.childNodes).filter(node => node.nodeType === 3 && node.textContent.trim().length > 1);
79549
- textNodes.forEach(node => {
79550
- const span = document.createElement('p');
79551
- node.after(span);
79552
- span.appendChild(node);
79553
- });
79554
- if (elmActive.firstElementChild && elmActive.childNodes.length === 1) {
79555
- if (elmActive.firstElementChild.tagName === 'SPAN') {
79556
- // Paste HTML with styles
79444
+ let elms = contentword.querySelectorAll('li');
79445
+ Array.prototype.forEach.call(elms, elm => {
79446
+ elm.style.whiteSpace = '';
79447
+ const childNodes = elm.childNodes;
79448
+ let i = childNodes.length;
79449
+ while (i--) {
79450
+ if (childNodes[i].tagName === 'P') {
79451
+ childNodes[i].outerHTML = childNodes[i].innerHTML;
79452
+ }
79453
+ }
79454
+ });
79557
79455
 
79558
- elmActive.outerHTML = elmActive.firstElementChild.innerHTML; //fix
79456
+ // NOTE: paste <h1><p> jadi nempel
79559
79457
 
79560
- // Re-clean empty elements
79561
- this.activeCol.querySelectorAll('*:empty').forEach(x => {
79562
- x.remove();
79563
- });
79458
+ // // Source: https://gist.github.com/sbrin/6801034
79459
+ // jQuery('p', $editor).each(function(){
79460
+ // var str = jQuery(this).attr('style');
79461
+ // var matches = /mso-list:\w+ \w+([0-9]+)/.exec(str);
79462
+ // if (matches) {
79463
+ // jQuery(this).data('_listLevel', parseInt(matches[1], 10));
79464
+ // }
79465
+ // });
79466
+ // var last_level=0;
79467
+ // var pnt = null;
79468
+ // jQuery('p', $editor).each(function(){
79469
+ // var cur_level = jQuery(this).data('_listLevel');
79470
+ // if(cur_level !== undefined){
79471
+ // var txt = jQuery(this).text();
79472
+ // var list_tag = '<ul></ul>';
79473
+ // if (/^\s*\w+\./.test(txt)) {
79474
+ // var matches = /([0-9])\./.exec(txt);
79475
+ // if (matches) {
79476
+ // var start = parseInt(matches[1], 10);
79477
+ // list_tag = start>1 ? '<ol start="' + start + '"></ol>' : '<ol></ol>';
79478
+ // }else{
79479
+ // list_tag = '<ol></ol>';
79480
+ // }
79481
+ // }
79482
+
79483
+ // if(cur_level>last_level){
79484
+ // if(last_level===0){
79485
+ // jQuery(this).before(list_tag);
79486
+ // pnt = jQuery(this).prev();
79487
+ // }else{
79488
+ // pnt = jQuery(list_tag).appendTo(pnt);
79489
+ // }
79490
+ // }
79491
+ // if(cur_level<last_level){
79492
+ // for(var i=0; i<last_level-cur_level; i++){
79493
+ // pnt = pnt.parent();
79494
+ // }
79495
+ // }
79496
+ // jQuery('span:first', this).remove();
79497
+ // pnt.append('<li>' + jQuery(this).html() + '</li>');
79498
+ // jQuery(this).remove();
79499
+ // last_level = cur_level;
79500
+ // }else{
79501
+ // last_level = 0;
79502
+ // }
79503
+ // });
79504
+ // //jQuery('[style]', $editor).removeAttr('style'); //done (see cleanHTML)
79505
+ // jQuery('[align]', $editor).removeAttr('align');
79506
+ // //jQuery('span', $editor).replaceWith(function() {return jQuery(this).contents();}); //done (see cleanHTML)
79507
+ // jQuery('span:empty', $editor).remove();
79508
+ // //jQuery("[class^='Mso']", $editor).removeAttr('class'); //done (see cleanHTML)
79509
+ // jQuery('p:empty', $editor).remove();
79564
79510
 
79565
- //place cursor
79566
- if (elmClosestElement) this.dom.moveCursorToElement(elmClosestElement.previousElementSibling);else this.dom.moveCursorToElement(this.activeCol);
79567
- let builderActive = this.doc.querySelector('.builder-active');
79568
- if (builderActive) this.applyBehaviorOn(builderActive);
79511
+ sPastedText = contentword.innerHTML;
79512
+ }
79513
+ }
79514
+ contentword = this.doc.querySelector('#idContentWord');
79515
+ if (contentword) contentword.parentNode.removeChild(contentword);
79516
+ util.restoreSelection();
79569
79517
 
79570
- //Trigger Change event
79571
- this.opts.onChange();
79518
+ /*
79519
+ var oSel = this.win.getSelection();
79520
+ var range = oSel.getRangeAt(0);
79521
+ range.extractContents();
79522
+ range.collapse(true);
79523
+ var docFrag = range.createContextualFragment(sPastedText);
79524
+ var lastNode = docFrag.lastChild;
79525
+ range.insertNode(docFrag);
79526
+ */
79572
79527
 
79573
- //Trigger Render event
79574
- this.opts.onRender();
79575
- return;
79576
- }
79577
- }
79528
+ /*
79529
+ When selection is made by double clicking text (to select the entire block),
79530
+ the actual selection goes to the next block as well (default behavior, tested using a simple contentEditable div).
79531
+ To fix this, re-select the contents inside.
79532
+ */
79533
+
79534
+ const blocks = this.dom.getSelectedBlocks();
79535
+ const selection = this.win.getSelection();
79536
+
79537
+ /*
79538
+ Check same selection for the block first (this is more accurate than the sameSelection checking below).
79539
+ In case of double click (to select the entire block), for example, on this element:
79540
+ <h2 class="font-light size-54"><span class="size-24">Heading</span> 2 here</h2>
79541
+ The sameSelection checking below is failed, since the container = this.getElm() will return not the entire h2, but only
79542
+ <span class="size-24">Heading</span>
79543
+ So, blocks checking below is made to fix it.
79544
+ */
79545
+ let blockSelection = false;
79546
+ if (blocks.length === 1) {
79547
+ if (blocks[0].innerText.trim() === selection.toString().trim()) {
79548
+ let range = document.createRange();
79549
+ range.selectNodeContents(blocks[0]);
79550
+ selection.removeAllRanges();
79551
+ selection.addRange(range);
79552
+ blockSelection = true;
79553
+ }
79554
+ }
79555
+ if (!blockSelection) {
79556
+ const container = this.dom.getElm();
79557
+ const sameSelection = container && container.innerText === selection.toString().trim();
79558
+ if (sameSelection || selection.toString().trim() === '') {
79559
+ let range = document.createRange();
79560
+ range.selectNodeContents(container);
79561
+ selection.removeAllRanges();
79562
+ selection.addRange(range);
79563
+ }
79564
+ }
79565
+ document.execCommand('insertHTML', false, sPastedText);
79566
+ if (this.activeCol) {
79567
+ /*
79568
+ Additional Cleanup:
79569
+ - Remove empty elements (empty p, etc)
79570
+ */
79571
+ // this.activeCol.find('h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty,p:empty').remove();
79572
+ // this.activeCol.querySelectorAll('*:empty').forEach((x)=>{x.remove();}); // Makes <img> removed
79573
+ this.activeCol.querySelectorAll('h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty,p:empty').forEach(x => {
79574
+ x.remove();
79575
+ });
79578
79576
 
79579
- // Paste HTML without styles
79580
- elmActive.outerHTML = elmActive.innerHTML; //fix
79577
+ /*
79578
+ Additional Cleanup:
79579
+ Fix HTML structure. The problem:
79580
+ <p class="elm-active">
79581
+ ...Sometimes h1, h2, p can be pasted here..
79582
+ </p>
79583
+ */
79584
+ let elmActive = this.activeCol.querySelector('p.elm-active,h1.elm-active,h2.elm-active,h3.elm-active,h4.elm-active,h5.elm-active,h6.elm-active');
79585
+ if (elmActive) {
79586
+ let elms = elmActive.querySelectorAll('p,h1,h2,h3,h4,h5,h6');
79587
+ if (elms.length > 0) {
79588
+ let elmClosestElement = elmActive.nextElementSibling;
79589
+
79590
+ //Fix text that doesn't have paragraph
79591
+ let textNodes = Array.from(elmActive.childNodes).filter(node => node.nodeType === 3 && node.textContent.trim().length > 1);
79592
+ textNodes.forEach(node => {
79593
+ const span = document.createElement('p');
79594
+ node.after(span);
79595
+ span.appendChild(node);
79596
+ });
79597
+ if (elmActive.firstElementChild && elmActive.childNodes.length === 1) {
79598
+ if (elmActive.firstElementChild.tagName === 'SPAN') {
79599
+ // Paste HTML with styles
79581
79600
 
79582
- // Re-clean empty elements
79583
- // this.activeCol.querySelectorAll('*:empty').forEach((x)=>{x.remove();}); // Makes <img> removed
79584
- this.activeCol.find('h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty,p:empty').remove();
79601
+ elmActive.outerHTML = elmActive.firstElementChild.innerHTML; //fix
79585
79602
 
79586
- //place cursor
79587
- if (elmClosestElement) this.dom.moveCursorToElement(elmClosestElement.previousElementSibling);else this.dom.moveCursorToElement(this.activeCol);
79588
- let builderActive = this.doc.querySelector('.builder-active');
79589
- if (builderActive) this.applyBehaviorOn(builderActive);
79603
+ // Re-clean empty elements
79604
+ this.activeCol.querySelectorAll('*:empty').forEach(x => {
79605
+ x.remove();
79606
+ });
79590
79607
 
79591
- //Trigger Change event
79592
- this.opts.onChange();
79608
+ //place cursor
79609
+ if (elmClosestElement) this.dom.moveCursorToElement(elmClosestElement.previousElementSibling);else this.dom.moveCursorToElement(this.activeCol);
79610
+ let builderActive = this.doc.querySelector('.builder-active');
79611
+ if (builderActive) this.applyBehaviorOn(builderActive);
79593
79612
 
79594
- //Trigger Render event
79595
- this.opts.onRender();
79596
- return;
79613
+ //Trigger Change event
79614
+ this.opts.onChange();
79615
+
79616
+ //Trigger Render event
79617
+ this.opts.onRender();
79618
+ return;
79619
+ }
79597
79620
  }
79621
+
79622
+ // Paste HTML without styles
79623
+ elmActive.outerHTML = elmActive.innerHTML; //fix
79624
+
79625
+ // Re-clean empty elements
79626
+ // this.activeCol.querySelectorAll('*:empty').forEach((x)=>{x.remove();}); // Makes <img> removed
79627
+ this.activeCol.find('h1:empty,h2:empty,h3:empty,h4:empty,h5:empty,h6:empty,p:empty').remove();
79628
+
79629
+ //place cursor
79630
+ if (elmClosestElement) this.dom.moveCursorToElement(elmClosestElement.previousElementSibling);else this.dom.moveCursorToElement(this.activeCol);
79631
+ let builderActive = this.doc.querySelector('.builder-active');
79632
+ if (builderActive) this.applyBehaviorOn(builderActive);
79633
+
79634
+ //Trigger Change event
79635
+ this.opts.onChange();
79636
+
79637
+ //Trigger Render event
79638
+ this.opts.onRender();
79639
+ return;
79598
79640
  }
79599
79641
  }
79600
- range.setStartAfter(lastNode);
79601
- range.setEndAfter(lastNode);
79602
- range.collapse(false);
79603
- var comCon = range.commonAncestorContainer;
79604
- if (comCon && comCon.parentNode) {
79605
- try {
79606
- comCon.parentNode.normalize();
79607
- } catch (e) {
79608
- // Do Nothing
79642
+ }
79643
+
79644
+ /*
79645
+ range.setStartAfter(lastNode);
79646
+ range.setEndAfter(lastNode);
79647
+ range.collapse(false);
79648
+ var comCon = range.commonAncestorContainer;
79649
+ if (comCon && comCon.parentNode) {
79650
+ try { comCon.parentNode.normalize(); } catch (e) {
79651
+ // Do Nothing
79609
79652
  }
79610
- }
79611
- oSel.removeAllRanges();
79612
- oSel.addRange(range);
79613
- let builderActive = this.doc.querySelector('.builder-active');
79614
- if (builderActive) this.applyBehaviorOn(builderActive);
79653
+ }
79654
+ oSel.removeAllRanges();
79655
+ oSel.addRange(range);
79656
+ */
79615
79657
 
79616
- //Trigger Change event
79617
- this.opts.onChange();
79658
+ let builderActive = this.doc.querySelector('.builder-active');
79659
+ if (builderActive) this.applyBehaviorOn(builderActive);
79618
79660
 
79619
- //Trigger Render event
79620
- this.opts.onRender();
79621
- } catch (e) {
79622
- let contentword = this.doc.querySelector('#idContentWord');
79623
- if (contentword) contentword.parentNode.removeChild(contentword);
79624
- }
79625
- }, 800);
79661
+ //Trigger Change event
79662
+ this.opts.onChange();
79663
+
79664
+ //Trigger Render event
79665
+ this.opts.onRender();
79666
+ } catch (e) {
79667
+
79668
+ // Do Nothing
79669
+ }
79626
79670
  }
79627
79671
  cellSelected() {
79628
79672
  const util = this.util;