Skip to content
Advertisement

Email editor with tinymce : how to export a clean html file?

I managed to create an email editor, modeled on this example. At the end of the file I add a download button, so that the user can retrieve the file he edited.

My problem is that tinymce injects a lot of code, tag, class,attributes and id that I would like to remove during export. Is there a function or plugin that can retrieve its file without any reference to tinymce ?

for the moment I delete each element “manually” which does not seem to me at all optimal. There are too many elements (attributes among others everywhere) and I’m sure there is an easier way..

document.getElementById('btnHtml').addEventListener('click', function() {

  let $email = $('.email-container');
  let contentToDelete = document.querySelectorAll("script,div.mce-tinymce,#mceDefaultStyles,.mce-widget,#u0,#u1,button");//
  contentToDelete.forEach((element) => element.remove());//remove all elements and children that are outside tinymce editors

  // Get content from all editors 
  for (var i = 0; i < tinymce.editors.length; i++) {
    let editable = $email.find('.content')[i];
    editable.innerHTML = tinymce.editors[i].getContent();
    editable.removeAttribute('spellcheck');
    // If you remove "contenteditable" then this node will not open TinyMCE when you click on it.
    editable.removeAttribute('data-mce-bogus');
    editable.removeAttribute('data-mce-style');
    editable.removeAttribute('[data-mce-href');
    editable.classList.remove('mce-content-body');
    editable.classList.remove('mce-item-table');
   
  
  }

      var txtboxes = document.querySelectorAll('.content');
      txtboxes.forEach(box => {
      box.replaceWith(...box.childNodes);//remove only  div.content itself not the children
    });
   
       
  let full = new XMLSerializer().serializeToString(document.doctype);//serialize all the document, get the doctype
  let innercontent = document.documentElement.outerHTML;
  let content = full + innercontent; // append doctype to html content
  let blob = new Blob([content], {
    type: 'text/html'
  });

  // Create download link and then download.
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.download = "index.html";
  a.style.display = 'none';
  a.href = url;
  //document.body.appendChild(a);

  // this link will not work here so try it on "codepen.io" or on your computer
  a.click();

  // Release object URL
  window.URL.revokeObjectURL(url);
});
//at the end of my html file
  <button type="button" id="btnHtml" type="button">Download html file</button>

Answer

Is there a function or plugin that can retrieve its file without any reference to tinymce ?

Yes, the function is getContent. I can show you an example using jQuery 3.6.0 and TinyMCE 5.6.0:

// create instances of Tinymce for each .email-editable element.
tinymce.init({
  selector: ".email-editable",
  inline: true,
  plugins: "advlist lists link image",
  toolbar: "styleselect | bold italic forecolor | bullist numlist | link image| removeformat",
  menubar: false,
});

document.getElementById('save').addEventListener('click', function() {

  let $email = $('#email');

  // Get content from all editors 
  for (var i = 0; i < tinymce.editors.length; i++) {
    let editable = $email.find('.email-editable')[i];
    editable.innerHTML = tinymce.editors[i].getContent();
    editable.removeAttribute('spellcheck');

    // If you remove "contenteditable" then this node will not open TinyMCE when you click on it.
    editable.removeAttribute('contenteditable');
    editable.classList.remove('mce-content-body');

    // Note that the "getContent" function omits the TinyMCE metadata. Try it with "console.log". ;-)
    console.log(tinymce.editors[i].getContent());
  }

  // For this example, serialize only the #email element and their children
  let emailContent = new XMLSerializer().serializeToString($('#email')[0]);
  let blob = new Blob([emailContent], {
    type: 'text/html'
  });

  // Create download link and then download.
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.download = "index.html";
  a.style.display = 'none';
  a.href = url;
  //document.body.appendChild(a);

  // this link will not work here so try it on "codepen.io" or on your computer
  a.click();

  // Release object URL
  window.URL.revokeObjectURL(url);
});
#email-header {
  margin-bottom: 10px;
  text-align: center;
  color: rgb(64, 96, 128);
  font-weight: bold;
  font-family: Arial, sans-serif;
  font-size: 30px;
}

#email-footer {
  margin-top: 10px;
  padding: 10px 0;
  color: white;
  background-color: gray;
  text-align: center;
  font-family: Arial, sans-serif;
}

.email-editable {
  font-family: Arial, sans-serif;
}

#save {
  margin-top: 30px;
  padding: 10px 0;
  width: 100%;
}

.special {
  color: #7ae;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.6.0/tinymce.min.js"></script>

<div id="email">
  <div id="email-header">Unmodifiable header :)</div>
  <div class="email-editable">Insert your text here</div>
  <div class="email-editable">
    <ul>
      <li>Some text and more text...</li>
      <li><span class="special">Special item</span> for you.</li>
    </ul>
  </div>
  <div id="email-footer">2022 &copy; Unmodifiable footer :)</div>

</div>
<button id="save" type="button">Export to html</button>

Note that I only remove the attributes of containers (i mean .email-editable elements) that TinyMCE has generated for me in my example, so you can remove other attributes too. I don’t use tinymce.editors[i].save(); because it adds the metadata of Tinymce. This metadata is useful for editing the text in the future. For instance, you can store the text in a database and then retrieve it for further editing.

Also note that I use URL.revokeObjectURL. From https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL:

Call this method when you’ve finished using an object URL to let the browser know not to keep the reference to the file any longer.




for the moment I delete each element “manually” which does not seem to me at all optimal. There are too many elements (attributes among others everywhere) and I’m sure there is an easier way..

You are doing right. Another way is by extending the jQuery object to add a function. On the web page https://www.geeksforgeeks.org/how-to-remove-all-attributes-of-an-html-element-using-jquery/ you have an example to add a function that removes all the attributes of a node. Perhaps you can edit that function to add a whitelist (an array of strings) as an input parameter.

The sample code (credits to GeeksForGeeks.org) is:

$.fn.removeAllAttributes = function() {
    return this.each(function() {
        $.each(this.attributes, function() {
            this.ownerElement.removeAttributeNode(this);
        });
    });
};

$('textarea').removeAllAttributes();



UPDATE

if I add a specific class for each editor, in order to give them different options, with this code my logo is repeated 3 times, and my first block of text disappears.. do you know how to correctly retrieve the content with different classes ? I tried with ids, I have the same problem. let editable = $email.find(‘.logo, .banner, .fragment,.content’)[i]; editable.innerHTML = tinymce.editors[i].getContent();

Assign each editor a unique id. The TinyMCE API has the function tinymce.get(id) (see reference) that returns a specific editor, so my new example is…

// create instances of Tinymce for elements #logo, #banner, #fragment and #content.
tinymce.init({
  selector: "#logo",
  inline: true,
  plugins: "advlist lists link image",
  toolbar: "styleselect | bold italic forecolor",
  menubar: false,
});
tinymce.init({
  selector: "#banner",
  inline: true,
  plugins: "advlist lists link image",
  toolbar: "styleselect | bold italic forecolor | bullist numlist | link image| removeformat",
  menubar: false,
});
tinymce.init({
  selector: "#fragment",
  inline: true,
  plugins: "advlist lists link image",
  toolbar: "styleselect | bullist numlist | link image| removeformat",
  menubar: false,
});
tinymce.init({
  selector: "#content",
  inline: true,
  plugins: "advlist lists link image",
  toolbar: "styleselect | bullist numlist | link image| removeformat",
  menubar: false,
});

document.getElementById('saveB').addEventListener('click', function() {
  let $email = $('#email');

  $("#logo").html(tinymce.get("logo").getContent());
  $("#banner").html(tinymce.get("banner").getContent());
  $("#fragment").html(tinymce.get("fragment").getContent());
  $("#content").html(tinymce.get("content").getContent());

  // Clean containers
  $('#logo, #content, #fragment, #banner')
    .removeAttr('spellcheck')
    .removeAttr('contenteditable')
    .removeClass('mce-content-body');

  // For this example, serialize only the #email element and their children
  let emailContent = new XMLSerializer().serializeToString($('#email')[0]);
  let blob = new Blob([emailContent], {
    type: 'text/html'
  });

  // Create download link and then download.
  const url = window.URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.download = "index.html";
  a.style.display = 'none';
  a.href = url;
  //document.body.appendChild(a);

  // this link will not work here so try it on "codepen.io" or on your computer
  a.click();

  // Release object URL
  window.URL.revokeObjectURL(url);
});
#email-header {
  margin-bottom: 10px;
  text-align: center;
  color: rgb(64, 96, 128);
  font-weight: bold;
  font-family: Arial, sans-serif;
  font-size: 30px;
}

#email-footer {
  margin-top: 10px;
  padding: 10px 0;
  color: white;
  background-color: gray;
  text-align: center;
  font-family: Arial, sans-serif;
}

.email-editable {
  font-family: Arial, sans-serif;
}

#saveB {
  margin-top: 30px;
  padding: 10px 0;
  width: 100%;
}

.special {
  color: #7ae;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tinymce/5.6.0/tinymce.min.js"></script>

<div id="email">
  <div id="email-header">Unmodifiable header :)</div>
  <div id="logo">Insert <span style="color:red">your logo</span> here...</div>
  <div id="banner">Add some banner here...</div>
  <div id="fragment">Add <strong>some text</strong> here...</div>
  <div id="content">Insert your content here</div>
  <div id="email-footer">2022 &copy; Unmodifiable footer :)</div>

</div>
<button id="saveB" type="button">Export to html</button>
Advertisement