Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply basic style to non editable elements in TinyMce

Context:

TinyMce has a noneditable plugin that allows to make an element non editable. If an element has the mceNonEditable class, then TinyMce will make the element non editable.

Problem:

I want to be able to wrap this non editable element with basic styling tags.

For example if I have :

Hello <span class="mceNonEditable">user_name</span> how are you today ?

If I click on user_name to select the non editable span and click on the TinyMce Blod button.

enter image description here

I would like the result to be :

Hello <b><span class="mceNonEditable">user_name</span></b> how are you today ? 

But instead of this, nothing happens. When I click the TinyMce Blod button, the code doesn't change.

I created a small jsFiddle to demonstrate this : https://jsfiddle.net/timotheejeannin/2hhpenm5/

What I tried:

  • Configure the noneditable plugin differently (https://www.tinymce.com/docs/plugins/noneditable/)
  • Use the data-mce-contenteditable attribute to override the non editable behavior when a button is clicked. (See usage in TinyMce source code in DOMUtils.js line 1739)
  • Go around the content editable detection. (See in TinyMce source code in Formatter.js line 609)
  • Build my own plugin (looks like it's not possible to solve the issue with a plugin)

I really hope you can help!

like image 584
Timothée Jeannin Avatar asked Nov 30 '16 10:11

Timothée Jeannin


2 Answers

I figured out a "slightly" less hacky way to do this. Essentially, I register a "BeforeExecCommand" event that, for certain events, removes the contenteditable attribute, allows the command to run, and readds the contenteditable false attribute in the "ExecCommand" event. Doing so allowed me to avoid having to custom handle the various possible events that I have seen in other proposed solutions. I have forked your original example to demonstrate the solution (and added a couple of additional formatting options and a "Variables" feature) which can be found here: https://jsfiddle.net/hunterae/8fsnv3h6/40/

Here are the most relevant pieces of the solution:

tinymce.init({
  // Additional options here
  setup: function (editor) {
    var $ = tinymce.dom.DomQuery;
    var nonEditableClass = editor.getParam('noneditable_noneditable_class', 'mceNonEditable');
    // Register a event before certain commands run that will turn contenteditable off temporarilly on noneditable fields
    editor.on('BeforeExecCommand', function (e) {
      // The commands we want to permit formatting noneditable items for
      var textFormatCommands = [
        'mceToggleFormat',
        'mceApplyTextcolor',
        'mceRemoveTextcolor'
      ];
      if (textFormatCommands.indexOf(e.command) !== -1) {
        // Find all elements in the editor body that have the noneditable class on them
        //  and turn contenteditable off  
        $(editor.getBody()).find('.' + nonEditableClass).attr('contenteditable', null);
      }
    });
    // Turn the contenteditable attribute back to false after the command has executed
    editor.on('ExecCommand', function (e) {
        // Find all elements in the editor body that have the noneditable class on them
      //  and turn contenteditable back to false
      $(editor.getBody()).find('.' + nonEditableClass).attr('contenteditable', false);
    });
  },
  init_instance_callback: function (editor) {

    /* 
        The following two hacks fix some weirdness with the way the textcolor
      plugin works - namely, it was attemping to apply color and background-color
      directly on the element that had the noneditable css class on it instead of putting
      a span around it as underline does.
    */
    editor.formatter.get('forecolor')[0].exact = true;
    editor.formatter.get('hilitecolor')[0].exact = true;


  }
});
like image 81
Andrew H Avatar answered Oct 20 '22 17:10

Andrew H


That is my workaround for that. Might be glitchy though.

tinyMCE.init({
  /*your initializer settings*/
  setup: function (ed) {
    ed.on('ExecCommand', function(e) {
      var selection = tinyMCE.activeEditor.selection.getContent();
      var el = document.createElement( 'html' );
      el.innerHTML = "<head></head><body>"+selection+"</body>";
      var datapoints =  Array.from(el.getElementsByClassName('mceNonEditable'));
      if (datapoints.length>0) {
        var styleToggle = function(key, value) {
          var criteria = (datapoints.map(function(datapoint){
            return (datapoint.style[key] == value);
          }).reduce(function(a,b) {
            return a&&b;
          }));
          if (criteria) {
            datapoints.forEach(function(datapoint){
              datapoint.style[key] = "";
              datapoint.contentEditable = false;
        });
      } else {
        datapoints.forEach(function(datapoint){
          datapoint.style[key] = value;
          datapoint.contentEditable = false;
        });
      };
    }
    switch (e.command) {
       case 'mceToggleFormat':
        switch (e.value) {
          case 'bold':
            styleToggle("font-weight", "bold");
            break;
          case 'italic':
            styleToggle ("font-style", "italic");
            break;
          case 'strikethrough':
            styleToggle ("text-decoration", "line-through");
            break;
          case 'underline':
            styleToggle ("text-decoration", "underline");
        };
        tinyMCE.activeEditor.selection.setContent(el.children[1].innerHTML);
        break;
       case ("mceApplyTextcolor"):
         styleToggle ("color", e.value);
         tinyMCE.activeEditor.selection.setContent(el.children[1].innerHTML);
         break;
       case ("FontName"):
         styleToggle ("font-family", e.value);
         tinyMCE.activeEditor.selection.setContent(el.children[1].innerHTML);
         break;
       case ("FontSize"):
         styleToggle ("font-size", e.value);
         tinyMCE.activeEditor.selection.setContent(el.children[1].innerHTML);
         break;
       case ("RemoveFormat"):
         datapoints.forEach(function(datapoint){
           ["font-weight", "font-style", "text-decoration",
           "text-decoration", "color", "font-family", "font-size"].forEach(function(key){
             datapoint.style[key]="";
           })
           datapoint.contentEditable = false;
         });
         tinyMCE.activeEditor.selection.setContent(el.children[1].innerHTML);
         break;
     };
   }
});
/*more stuff*/
  }
});
like image 41
lestrade Avatar answered Oct 20 '22 15:10

lestrade