Medium-editor: Unwanted <span style="font-size: 18px;"><br></span> added

Created on 8 Apr 2015  Â·  47Comments  Â·  Source: yabwe/medium-editor

Sometimes when return key is pressed, a <span style="font-size: 18px;"><br></span> is added.
How can I prevent that?

Thank you.

enhancement html cleanup

Most helpful comment

Here's my jQuery code ;)
Just insert and the span elements will be removed...

$('body').delegate('[contenteditable=true]','DOMNodeInserted',function() {
    if (event.target.tagName == 'SPAN') {
        event.target.outerHTML = event.target.innerHTML;
    }
});

All 47 comments

+1

@davi added the tag, but this is a contenteditable "core" issue where if you have css rules that are styling things these span[style] tags are created when two conflicting font-rules end up bumping up against each other. (i have a similar issue with custom styled headings having a different line-height than the following paragraph, and backspacing the paragraph into the heading leaves the remainer of the paragraph less-font-weight'd and more-line-height'ed (because the p and h3 have different font settings)

I believe this has been discussed, but @daviferreira any more thoughts on using Scribe instead of native contenteditable? (see: https://github.com/lolJS/ghostwriter)

@bborn: is Scribe tested against IE or Safari yet, or is it still just Firefox and Chrome?

@nchase not sure - according to this it seems like they're not supporting older browsers (but it's not clear exactly what they mean buy that). Looks like Safari 6 is at least partially supported.

@bborn: they run automated tests against Chrome and Firefox, but not Safari or IE.

If they aren't running-and-maintaining tests that exercise the code in Safari and IE, they aren't actually supporting them – a change to the code could break important things without anyone knowing until the code gets used in the wild, which is not safe.

Yup - however it may just be due to their testing setup. See here: https://github.com/guardian/scribe/issues/249 (bad Selenium support for Safari)

Thank you for your answers, and your good explication @phiggins42.
So I assume I can't do anything about that!

I'll try to figure out what's in conflict in my case.
Thank you!

Here's my hack to fix it:

medium = new MediumEditor(.. your config here ...);
el = medium.elements[0];
$(el).on("input", function() {
  $(el).find("span[style]").contents().unwrap();
});

This basically just removes all spans with a style attribute.

I have a similar hack in place, but it causes all sorts of strange behavior to undo stack doing that dom manipulation that way.

Yeah, good point - hadn't thought of that. This issue is a pretty big deal for me; it basically renders the editor useless (since you can't count on consistent output). Any other ideas on how to fix this reliably? I know it's a content editable problem, but it still feels like something that MediumEditor should account for and solve.

I have a partial solution involving (one) of the causes, a backspace at the beginning of a node with trailing content causes the mismatched styles ... i do the unwrapping there to minimize the impact on undo, and am investigating using range and insertHTML to select the spans and convert them that way (which would fix undo, but create two undo steps, which is still less than ideal)

define([], function(){

    function replaceinner(node){
        var range = document.createRange(), inner;
        range.selectNode(node);
        inner = range.cloneContents().textContent;
        range.deleteContents();
        range.insertNode(document.createTextNode(inner));
    }

    function _toarray(qsa){
        return Array.prototype.slice.apply(qsa);
    }

    function unwrapper(node){
        // find all <span>'s in `node`, and unwrap them
        // preserving document history(?). at the least
        // this doesn't muck with cursor position
        var qsa = node.querySelectorAll("span[style]");
        _toarray(qsa).forEach(replaceinner);
    }

    return unwrapper;

});

This is really bad, that is some way to remove this

  1. remove it by jQuery just like @phiggins42
  2. catch input, Don't use document.execCommand, code like this:
ListButton.prototype.command = function(param) {
    var $contents, $endBlock, $furthestEnd, $furthestStart, $parent, $startBlock, endLevel, endNode, getListLevel, j, len, node, range, ref, results, startLevel, startNode;
    range = this.editor.selection.getRange();
    startNode = range.startContainer;
    endNode = range.endContainer;
    $startBlock = this.editor.util.closestBlockEl(startNode);
    $endBlock = this.editor.util.closestBlockEl(endNode);
    this.editor.selection.save();
    range.setStartBefore($startBlock[0]);
    range.setEndAfter($endBlock[0]);
    if ($startBlock.is('li') && $endBlock.is('li')) {
      $furthestStart = this.editor.util.furthestNode($startBlock, 'ul, ol');
      $furthestEnd = this.editor.util.furthestNode($endBlock, 'ul, ol');
      if ($furthestStart.is($furthestEnd)) {
        getListLevel = function($li) {
          var lvl;
          lvl = 1;
          while (!$li.parent().is($furthestStart)) {
            lvl += 1;
            $li = $li.parent();
          }
          return lvl;
        };
        startLevel = getListLevel($startBlock);
        endLevel = getListLevel($endBlock);
        if (startLevel > endLevel) {
          $parent = $endBlock.parent();
        } else {
          $parent = $startBlock.parent();
        }
        range.setStartBefore($parent[0]);
        range.setEndAfter($parent[0]);
      } else {
        range.setStartBefore($furthestStart[0]);
        range.setEndAfter($furthestEnd[0]);
      }
    }
    $contents = $(range.extractContents());
    results = [];
    $contents.children().each((function(_this) {
      return function(i, el) {
        var c, converted, j, len, results1;
        converted = _this._convertEl(el);
        results1 = [];
        for (j = 0, len = converted.length; j < len; j++) {
          c = converted[j];
          if (results.length && results[results.length - 1].is(_this.type) && c.is(_this.type)) {
            results1.push(results[results.length - 1].append(c.children()));
          } else {
            results1.push(results.push(c));
          }
        }
        return results1;
      };
    })(this));
    ref = results.reverse();
    for (j = 0, len = ref.length; j < len; j++) {
      node = ref[j];
      range.insertNode(node[0]);
    }
    this.editor.selection.restore();
    return this.editor.trigger('valuechanged');
  };

  ListButton.prototype._convertEl = function(el) {
    var $el, anotherType, block, child, children, j, len, ref, results;
    $el = $(el);
    results = [];
    anotherType = this.type === 'ul' ? 'ol' : 'ul';
    if ($el.is(this.type)) {
      $el.children('li').each((function(_this) {
        return function(i, li) {
          var $childList, $li, block;
          $li = $(li);
          $childList = $li.children('ul, ol').remove();
          block = $('<p/>').append($(li).html() || _this.editor.util.phBr);
          results.push(block);
          if ($childList.length > 0) {
            return results.push($childList);
          }
        };
      })(this));
    } else if ($el.is(anotherType)) {
      block = $('<' + this.type + '/>').append($el.html());
      results.push(block);
    } else if ($el.is('blockquote')) {
      ref = $el.children().get();
      for (j = 0, len = ref.length; j < len; j++) {
        child = ref[j];
        children = this._convertEl(child);
      }
      $.merge(results, children);
    } else if ($el.is('table')) {

    } else {
      block = $('<' + this.type + '><li></li></' + this.type + '>');
      block.find('li').append($el.html() || this.editor.util.phBr);
      results.push(block);
    }
    return results;
  };

This code from simditor, please search in github, keyword: simditor

code insigh: Handle selection by our code, then clean the no need html tag and insert into document!

any plans on this? it's really annoying

no plans, just hope :pray:

+1

Any plans, hopes or payers on a "when-css-collides-f*-it-and-remove-the-span"-option? I would really like this...

+1

+1 on the do not span option

It also happens with this scenario:

1) Type text, markup: <p> text </p>
2) Bold text, resulting markup <b> text
3) H2 text, resulting markup: <h2> <b> text </b></h2>
4) Put the caret before the text
5) Press Return, new line is created
7) Put the carert before the text and press backspace
If the previous sibling is a <p> we'll have:
8) Resulting markup: <p>previous text <b font-size: calc(1.4375rem - 2px); line-height: 1.7;> text </b> </p>

This clearly mess up the style and the formatting

+1

Yes, that hurts so much! I use medium-editor from React component, thus I do something like that:

var dom = ReactDOM.findDOMNode(this);
    this.medium = new MediumEditor(dom, this.props.options);
    this.medium.subscribe('editableInput', function (e) {
      _this._updated = true;
      // cleanse those span tags
      var spanList = dom.getElementsByTagName('span');
      for (var i = 0; i < spanList.length; i++) {
        spanList[i].outerHTML = spanList[i].innerHTML;
      };
      _this.change(dom.innerHTML);
    });

That's similar to the unwrap solution with the same undo issues.

This "feature" is also driving me nuts. The workaround I have right now is basically setting CSS rules and when I save the files, I remove the spans from the content. Not ideal, but gets around the bug.

.wrapper-class span[style] {
    font-size: inherit !important;
    line-height: inherit !important;
 }

I'm using this https://gist.github.com/steoo/6aa45e6c6d99974fbd2d to temporary fix the problem

Had a similar problem. Adding an unordered list caused the listitems to contain a span with inline style:

<div id="medium-editor">
  ...
  <ul>
    <li><span style="line-height: 1.3...">This is a listitem</span></li>
  </ul>
   ...
</div>

Solved this by overwriting some CSS:

#medium-editor { line-height: 100% !important; }
#medium-editor ul { line-height: inherit !important; }
#medium-editor ul li { line-height: inherit !important; }

After overriding, the span stopped appearing.
This may work with font-size, too.

Getting this issue as everyone else. Needs a proper fix!

Alternatively, someone (I need more time!) should bite the bullet and create an editor that doesn't rely on the terrible ContentEditable, as Medium have done.

I think the fundamentals would take a while to put into place, but after that it should be pretty plain sailing without any insane unexpected behaviour.

@assembledadam see #434 and https://github.com/yabwe/words – if you know anyone who has time to contribute (or even think about these problems) we'd greatly appreciate it!

Wow, I had no idea about these projects - thanks for the pointer. They both look pretty fresh, but Carbon especially looks to be quite feature rich and works well in the demo.

Going to try them both and see how it goes. Thanks again!

Aw, these spans are a plague.

Here's another hack, assuming you want to save the result to a form

  $(newEditableElements).bind('input propertychange', function() {
    var $this = $(this);
    var $clone = $this.clone();
    // contenteditable has a habit of inserting spans when you remove a newline
    $clone.find("span[style]").contents().unwrap();
    // if already in a b/i/u/a tag it'll add a style to that instead
    $clone.find("[style]").attr('style', null);

    // do something with $clone.html()
  });

I just strip them out as the user is saving. ;-)

http://1999.io/

On Tue, Jul 5, 2016 at 10:44 AM, Adam Howard [email protected]
wrote:

Here's another hack, assuming you want to save the result to a form

$(newEditableElements).bind('input propertychange', function() {
var $this = $(this);
var $clone = $this.clone();
// contenteditable has a habit of inserting spans when you remove a newline
$clone.find("span[style]").contents().unwrap();
// if already in a b/i/u/a tag it'll add a style to that instead
$clone.find("[style]").attr('style', null);

// do something with $clone.html()

});

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/yabwe/medium-editor/issues/543#issuecomment-230499220,
or mute the thread
https://github.com/notifications/unsubscribe/ABm9O2ASG6emukTwAf1flL4RgXdZvLTHks5qSm3pgaJpZM4D8Wjy
.

sure, was just showing the cloning method

I'm using this to remove the spans. Works great and is pure JS.

removeSpans(element) {
    var spans = element.getElementsByTagName('span');
    for (var i = 0; i < spans.length; i++) {
        spans[i].outerHTML = spans[i].innerHTML
    }
}

That seems like a sensible idea but I think editing in place messes with the undo history https://github.com/yabwe/medium-editor/issues/543#issuecomment-91237543

True. In my case if I don't remove the spans in the editor it completely screws with font sizes. If that needs to be done afterwards just throw it in pre-submit.

This is a short pure JS solution. That's the particular reason I wanted to share.

It seems that at least on Chrome this has a pure-CSS solution: don't use relative units for line-height or font-size. Explicitly specifying those in pixels for the container element solves the problem.

Here's my jQuery code ;)
Just insert and the span elements will be removed...

$('body').delegate('[contenteditable=true]','DOMNodeInserted',function() {
    if (event.target.tagName == 'SPAN') {
        event.target.outerHTML = event.target.innerHTML;
    }
});

Would be good to have it fix in the next release. I used work around above.

define([], function(){

    function replaceinner(node){
        var range = document.createRange(), inner;
        range.selectNode(node);
        inner = range.cloneContents().textContent;
        range.deleteContents();
        range.insertNode(document.createTextNode(inner));
    }

    function _toarray(qsa){
        return Array.prototype.slice.apply(qsa);
    }

    function unwrapper(node){
        // find all <span>'s in `node`, and unwrap them
        // preserving document history(?). at the least
        // this doesn't muck with cursor position
        var qsa = node.querySelectorAll("span[style]");
        _toarray(qsa).forEach(replaceinner);
    }

    return unwrapper;

});

This works, thanks a lot !

It happens to me also when coping/pasting from other place.

This is still an issue in 2019.

I tracked this down to a call to execCommand('insertHTML') here: https://github.com/yabwe/medium-editor/blob/9156a11407451c0ee2e30d971798d6476de4d354/src/js/util.js#L457

Unfortunately, this is a browser issue, reproducible on Chrome here: https://jsfiddle.net/y4x3vu61/
It also seems to happen for example with execCommand('insertOrderedList').
We probably would have to get rid of all affected execCommand calls.

And if we do that, Then, We've to write the whole core-API again...

That takes a lot of time.

On Mon, Sep 28, 2020, 4:02 PM David Császár notifications@github.com
wrote:

I tracked this down to a call to execCommand('insertHTML') here:
https://github.com/yabwe/medium-editor/blob/9156a11407451c0ee2e30d971798d6476de4d354/src/js/util.js#L457

Unfortunately, this is a browser issue, reproducible on Chrome here:
https://jsfiddle.net/y4x3vu61/
It also seems to happen for example with execCommand('insertOrderedList').
We probably would have to get rid of all affected execCommand calls.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/yabwe/medium-editor/issues/543#issuecomment-699924147,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AIE2X25DWO7PAEVVXFKG473SIBQ3ZANCNFSM4A7RNDZA
.

As an alternative to not using execCommand, a workaround for typical cases (works on Safari and Chrome, the issues don't happen on Firefox) is to turn off custom styles during the action.

https://jsfiddle.net/hsn9a6jo/4/

Not sure if we want to have this kind of dirty stuff.
Also, we may need to extend the reset styles when new cases are discovered.
Also, some issues may not be resolved, if they happen without involving execCommand (like this one, just pressing return?).

FWIW here are some (red) specs as a basis: https://github.com/yabwe/medium-editor/compare/master...dcsaszar:dave-span-style-issues?expand=1

Here's a brute-force workaround that fixes the red specs, and thus may also fix a real integration:

/* CSS */
[data-medium-reset],
[data-medium-reset] * {
  font: unset;
  color: unset;
  background: unset;
  letter-spacing: unset;
  word-spacing: unset;
}
// JS
const originalExecCommand = document.execCommand.bind(document);

function myExecCommand(a,b,c) {
  const els = document.querySelectorAll('[contenteditable]');

  if (a.toLowerCase().match(/^(inserthtml|insertorderedlist|insertunorderedlist|outdent)$/)) {
    for (let i = 0; i < els.length; i++) els[i].setAttribute('data-medium-reset', 'true');
  }

  const result = originalExecCommand(a,b,c);

  for (let i = 0; i < els.length; i++) els[i].removeAttribute('data-medium-reset'); 

  return result;
}

document.execCommand = myExecCommand.bind(document);

Any updates regarding a proper fix for this?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ankur0890 picture ankur0890  Â·  4Comments

taiji202 picture taiji202  Â·  6Comments

sPaCeMoNk3yIam picture sPaCeMoNk3yIam  Â·  5Comments

tyler-johnson picture tyler-johnson  Â·  5Comments

thatdoorsajar picture thatdoorsajar  Â·  7Comments