将基本样式应用于TinyMce中的不可编辑元素

时间:2016-11-30 10:27:43

标签: javascript html tinymce tinymce-4

上下文:

TinyMce有一个noneditable插件,可以让元素不可编辑。如果一个元素具有mceNonEditable类,那么TinyMce将使该元素不可编辑。

问题:

我希望能够使用基本样式标记包装这个不可编辑的元素。

例如,如果我有:

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

如果我点击user_name选择不可编辑的范围,请点击 TinyMce Blod按钮

enter image description here

我希望结果是:

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

但不是这样,没有任何反应。当我点击 TinyMce Blod按钮时,代码不会改变。

我创建了一个小jsFiddle来演示:https://jsfiddle.net/timotheejeannin/2hhpenm5/

我尝试了什么:

我真的希望你能帮忙!

5 个答案:

答案 0 :(得分:4)

我想出了一种“稍微”少一点hacky的方式来做到这一点。本质上,我注册了一个“ BeforeExecCommand”事件,对于某些事件,该事件将删除contenteditable属性,允许该命令运行,并读取“ ExecCommand”事件中的contenteditable false属性。这样做使我避免了必须自定义处理在其他提议的解决方案中看到的各种可能的事件。我已经分叉了您的原始示例以演示该解决方案(并添加了两个其他的格式设置选项和一个“变量”功能),可以在以下位置找到:https://jsfiddle.net/hunterae/8fsnv3h6/40/

以下是解决方案中最相关的部分:

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;


  }
});

答案 1 :(得分:1)

这是我的解决方法。可能会出现故障。

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*/
  }
});

答案 2 :(得分:0)

我相信您可以使用一个简单的自定义工具栏按钮来执行此操作,您可以将其添加到TinyMCE配置中。

当您单击不可编辑的元素时,您将有效地获得一个DOM节点,该节点是整个不可编辑的元素。然后,您绝对可以使用DOM操作向该元素添加样式或将该元素包装在另一个标记中。例如,请看这个TinyMCE小提琴:

http://fiddle.tinymce.com/sDfaab

当您单击不可编辑的元素并单击Add Style to Node按钮时,您将注意到整个不可编辑的元素将获得一个粗体文本的新内联样式。

答案 3 :(得分:0)

这适用于基本格式。

更新:像建议的那样更改选择根本不可靠,因为TinyMCE会在此事件之前和之后处理此事件,并将其弄乱……最好的方法是为您的ID分配ID不可编辑的对象,并直接在ExecCommand上将它们更改为DOM!因此,在选择中获取对象ID,然后在DOM中更改其样式,而不是在字符串HTML选择中。

这是基于上面答案的旧代码(仅用于启发):

var ed = tinymce.activeEditor;
ed.on('ExecCommand', function(e)
{
    // Note: this runs after the execution
    var selection = ed.selection.getContent();
    var dom = $('<body></body>').html(selection);
    var changed = 0;

    dom.find('.mceNonEditable').each(function(){
        // we apply the styles... like switches -> it would be better to check sibling elements and check their styling
        var o = $(this);
        // We have to store to attribute, because TinyMCE replaces stuff in the selection string!!!
        // It erases defined styles from our nonEditable object, but never adds!!
        var styles = o.attr('data-style');
        if (typeof styles != 'undefined') styles = styles.replace(/::/g, ':'); // back to correct formatting
        else styles = o.attr('style');
        styles = (typeof styles == 'undefined' ? '' : styles).split(';');
        var toggleStyle = function(k, v) {
            var found=0;
            for(var i=0; i<styles.length; i++){
                var pair = styles[i].split(':', 2);
                if (pair[0] == k) {
                    if (v == '' || pair[1] == v)
                        delete styles[i];
                    else
                        styles[i] = pair[0]+':'+v; // new value
                    found=1;
                }
            }
            styles = styles.join(';');
            if (!found) styles+= (styles == '' ? '' : ';')+k+':'+v;
            o.attr('style', styles)
                .attr('data-style', styles.replace(/:/g, '::')); // so tinymce doesn't remove this
            changed = 1;
        };
        var x = e.command.toLowerCase();
        if (x == 'mcetoggleformat') {
            x = e.value.toLowerCase(); // bold, italic etc.
        }
        if(x=='bold') toggleStyle("font-weight", "bold");
        if(x=='italic') toggleStyle("font-style", "italic");
        if(x=='linethrough' || x=='strikethrough') toggleStyle("text-decoration", "line-through");
        if(x=='underline') toggleStyle("text-decoration", "underline");
        if(x=='mceapplytextcolor') toggleStyle("color", e.value);
        if(x=='fontname') toggleStyle("font-family", e.value);
        if(x=='fontsize') toggleStyle("font-size", e.value);
        if (x=='removeformat'){
            ["font-weight", "font-style", "text-decoration", "color", "font-family", "font-size"].forEach(function(key){
                toggleStyle(key, '');
            });
        }
    });
    // change the selection string
    if (changed) {
        ed.selection.setContent( dom.html() );
    }
});

答案 4 :(得分:0)

我还使用了lestrade的答案作为实现的起点。我的实现在编辑器中更改了原始DOM,并且不使用setContent覆盖现有节点。

editor.on('ExecCommand', function(e) {
    //list of selected nonEditable elements
    const nonEditableElements = [];
    //get the list of nonEditable elements if the selection is a Range
    if(editor.selection.getSel().type === "Range"){
        //get the range ancestor container
        editor.selection.getRng().commonAncestorContainer
            //get the ancestor container children
            .querySelectorAll('*')
            //if the child is in the selection and has a mceNonEditable class add it to the nonEditableElements list
            .forEach(function(element){
                if(editor.selection.getRng().intersectsNode(element) && element.classList.contains('mceNonEditable')) {
                    nonEditableElements.push(element);
                }
            });
    }
    //check if the selection contains nonEditableElements
    if (nonEditableElements.length > 0) {
        //function toggles the style on the selected nonEditable elements
        function styleToggle(key, value){
            nonEditableElements.forEach(function(element){
                if(element.style[key] === value){
                    element.style[key] = "";
                }else{
                    element.style[key] = value;
                }
            });
        }

        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");
                }
                break;
            case ("mceApplyTextcolor"):
                if(e.ui === "forecolor"){
                    styleToggle ("color", e.value);
                }else if(e.ui === "hilitecolor"){
                    styleToggle ("background-color", e.value);
                }
                break;
            case ("FontName"):
                styleToggle ("font-family", e.value);
                break;
            case ("FontSize"):
                styleToggle ("font-size", e.value);
                break;
            case ("RemoveFormat"):
                nonEditableElements.forEach(function(element){
                    ["font-weight", "font-style", "text-decoration",
                        "text-decoration", "color", "font-family", "font-size"].forEach(function(key){
                        element.style[key]="";
                    });
                });
                break;
        }
    }
});