te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>javascript - Apply basic style to non editable elements in TinyMce - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Apply basic style to non editable elements in TinyMce - Stack Overflow

programmeradmin3浏览0评论

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.

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 : /

What I tried:

  • Configure the noneditable plugin differently (/)
  • 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!

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.

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/timotheejeannin/2hhpenm5/

What I tried:

  • Configure the noneditable plugin differently (https://www.tinymce./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!

Share Improve this question asked Nov 30, 2016 at 10:27 Timothée JeanninTimothée Jeannin 9,9123 gold badges58 silver badges66 bronze badges 1
  • I know this is an old question, but this answer seems to work for me for stackoverflow./a/4886911/2088345 – ahong Commented Feb 25, 2019 at 10:27
Add a ment  | 

5 Answers 5

Reset to default 9

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 mand 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/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 mands run that will turn contenteditable off temporarilly on noneditable fields
    editor.on('BeforeExecCommand', function (e) {
      // The mands we want to permit formatting noneditable items for
      var textFormatCommands = [
        'mceToggleFormat',
        'mceApplyTextcolor',
        'mceRemoveTextcolor'
      ];
      if (textFormatCommands.indexOf(e.mand) !== -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 mand 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;


  }
});

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

I have also used the answer from lestrade as a starting point to my implementation. My implementation changes the original DOM in the editor and does not overwrite existing nodes with 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().monAncestorContainer
            //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.mand) {
            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;
        }
    }
});

I believe you can do this with a simple custom toolbar button that you can add to your TinyMCE configuration.

When you click on a non-editable element you effectively get a DOM node that is the entire non-edtible element. You can absolutely then use DOM manipulation to add styles to that element or wrap the element in another tag. For example see this TinyMCE Fiddle:

http://fiddle.tinymce./sDfaab

When you click on the non-editable element and click the Add Style to Node button you will note that the entire non-editable element gets a new inline style that bolds the text.

This works for basic formatting.

UPDATE: it's not reliable at all to change the selection like suggested, because TinyMCE handles this event before and after, and will mess it up... the best way is to assign IDs to your noneditable objects and change them directly as DOM on ExecCommand!! So, in the selection get the object IDs and then change their styling in the DOM... not in the string HTML selection.

This is old code based on answer above (use just for inspiration):

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.mand.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() );
    }
});
发布评论

评论列表(0)

  1. 暂无评论