2
votes

The CMS I'm using has a really annoying WYSIWYG editor called Summernote. This editor is nice looking and actually works quite well, but the HTML it outputs is shockingly bad and doesn't have any configuration options for styling output. For example, this is some bold, italic text with an underline:

<span style="text-decoration: underline;">
    <span style="font-style: italic;">
        <span style="font-weight: bold;">
            Help!! I'm being squashed by 3 heavy tags.
        </span>
    </span>
</span>

Images have inline CSS styles, giving the images fixed widths, which is uber-useless for responsive sites:

<img src="..." style="width:832px;">

And YouTube iFrame's have fixed sizes which fly off mobile phone screens:

<iframe src="..." width="560" height="380">

So, as you can see, it's not great, but I don't have the option of changing the editor (not allowed to) and I need to come up with a way of rectifying these issues before the HTML is saved in the database. In my case, using jQuery.

So, after a little trial and error, I've managed to fix the responsive issues with this little snippet:

var code = $('#summernote').code();
var newCode = $('<div></div>').append(code)
    .find('iframe').wrap('<div class="flexVideo"/>')
    .end()
    .find('img').removeAttr('style').wrap('<div class="flexPhoto"/>')
    .end()
    .html();

Working JSFIDDLE demo.

As you can see, this wraps all iFrame's in a responsive wrapper, wraps all images in another wrapper (for non-responsive purposes) and removes all inline styles from the images making them more fluid. This works great, but I'm really struggling on the last part; replacing all of those span tags with proper elements and I unfortunately need to ask for some help.

I understand that using something like this could select the SPAN elements for BOLD and using wrapInner() and unwrap() I could replace the spans with proper textual elements:

$('span').filter(function() {
     return $(this).css('font-weight') == 'bold';
}).wrapInner('<b></b>').unwrap()

BUT

1) I cannot even get the wrapInner() to work on it's own (check this fiddle).

2) If this is actually possible, both physically and efficiently, please advice how and also advise on how to chain them all together.

3) Sometimes the spans orders are different, dependant on the order of buttons clicked.

This is my shoddy, failure of an attempt:

var code = $('#summernote').code();
var newCode = $('<div></div>').append(code)
    .find('iframe').wrap('<div class="flexVideo"/>')
    .end()
    .find('img').removeAttr('style').wrap('<div class="flexPhoto"/>')
    .end()
    .find('span').filter(function() {
        return $(this).css('font-weight') == 'bold';
    })
    .wrapInner('<b></b>')
    .unwrap()
    .end()
    .html();

See this failing JSFIDDLE demo. There are no errors, but it's certainly not doing as it's told. I've tried shouting at it, but that didn't work.

THE AMAZING ANSWER

For all those frustrated Summernote users, you may find this useful. Why is it amazing? Well, although it's split into multiple lines in this example, this is actually a one-liner!!

var newCode = $('<div></div>').append(code)
   .find('iframe')
       .wrap('<div class="flexVideo"/>')
   .end()
   .find('img')
       .removeAttr('style')
       .wrap('<div class="flexPhoto"/>')
   .end()
   .find('span')
       .filter("[style*='underline']")
           .removeAttr('style')
           .addClass('underline')
       .end()
       .filter("[style*='bold']")
           .wrapInner('<b></b>')
           .children()
               .unwrap()
           .end()
       .end()
       .filter("[style*='italic']")
           .wrapInner('<i></i>')
           .children()
               .unwrap()
           .end()
       .end()
   .end()
   .html();

What does it do?

  • Removes inline styles from images restricting responsiveness.
  • Wraps images in a DIV (personal preference for overlaying images with buttons and text).
  • Wraps iframe videos in a DIV to allow responsiveness.
  • Replaces inline text-decoration:underline with class underline.
  • Replaces <span style="font-style:italic"> tags with proper <i> tags.
  • Replaces <span style="font-weight:bold"> tags with proper <b> tags.
2
Reason for downvote please? I explained myself well first time, provided code and shared my attempt. - TheCarver
IMHO you are ruining your code, @PaparazzoKid. If you were coding the page yourself I seriously doubt you would have the strong tag inside the em tag. Or vice versa. Styling should not come from html, but from css :) - skip405
@skip405 - Unless I've misunderstood your point, I have to disagree with you there. Yes, if I was coding it myself it probably would be different, but I'm not ruining my code. Using <em>You are in <strong>BIG</strong> trouble</em> is perfectly acceptable markup. There's a good read about it here: stackoverflow.com/questions/2200590/… - TheCarver
sorry, not sure I get your reference to another question where 4 out of 6 answers tell you exactly what I did )) c2n.me/iFlZXD. As for wrapping the whole sentence in em, I would certainly try to avoid that. I guess I just didn't make myself clear enough. Anyways, I guess this question has been answered and solves your problem. Cheers :) - skip405
@skip405: Oi, I'm not done with you yet hehehe kidding. I think I get your point now, although this whole subject is starting to confuse me! So would you suggest using <b> and <i> instead of <strong> and <em> and style them with CSS? - TheCarver

2 Answers

2
votes

Your selectors are all muddled, and you weren't correctly referencing the elements you needed within the chain.

In this instance (for diagnositic purposes) I removed .html() from the end and outputted newCode into a console.log() command which I could interrogate in fireBug.

This showed me that the filter selector couldn't find a matching element. I changed the filter to use a different selector. Then I realised that you were trying to unwrap the wrong element which was removing most of your mark-up. So I descended to the child of the matching bold spans and then unwrapped them. I then ended the chain.

var code = '<span style="text-decoration: underline;"><span style="font-style: italic;"><span style="font-weight: bold;">asdasdad</span></span></span><br><br><img src="" style="width:748px;"><br><br><iframe src="" width="560" height="380"><iframe>';

var newCode = $('<div></div>').append(code)
    .find('iframe')
        .wrap('<div class="flexVideo"/>')
    .end()
    .find('img')
        .removeAttr('style')
        .wrap('<div class="flexPhoto"/>')
    .end()
    .find('span')
        .filter("[style*='bold']")
            .wrapInner('<strong></strong>')
            .children()
                .unwrap()
            .end()    
        .end()   
    .end()
    .html();

$('.output').text(newCode);

See this updated fiddle for details: http://jsfiddle.net/P2Ry8/6/

0
votes

The .unwrap() in your case unwraps the span element, whereas you need to unwrap the newly created <strong>. The basic idea can be seen here: http://jsfiddle.net/skip405/DLuD4/

The changed html contains two spans not three.

I decided to write it from scratch :)

To drill into covering spans we need to check the so-called "first-level" spans and to change them to corresponding html-elements (which I personally don't like, as the html-tags have their semantics, which may not be clear to the one using a wysiwyg), but that's another topic to discuss. I also search for the child spans and remove their style attributes as well.

Anyways, I managed to practically change your code to get the desired effect. Which you may find here: http://jsfiddle.net/skip405/DLuD4/2/

The final result is not 100% better, but that's a start, hope you'll find it useful.

Edit: To make things a bit DRYer I modified my original version and updated the fiddle. http://jsfiddle.net/skip405/DLuD4/4/

$('.wysiwyg').each(function(){
    var $wysiwyg = $(this),
        $directSpans = $wysiwyg.children('span');

    //first lets create top-level elements
    $directSpans.each(function(){
        var $span = $(this),
            $innerSpans = $span.find('span'),
            element = '';

        switch( $span.attr('style') ) {
            case 'font-weight: bold;':
                element = 'strong';
                break;
            case 'font-style: italic;':
                element = 'em';
                break;
            case 'text-decoration: underline;':
                element = 'u';
                break;
        }

        $span.wrapInner('<' + element + ' />').find(element).unwrap();

        $innerSpans.each(function(){
            var innerSpan = $(this),
                innerStyle = innerSpan.attr('style'),
                className = '';

            switch( innerStyle ) {
                case 'font-weight: bold;':
                    className = 'bold';
                    break;
                case 'font-style: italic;':
                    className = 'italic';
                    break;
                case 'text-decoration: underline;':
                    className = 'underline';
                    break;
            }
            innerSpan.removeAttr('style').parent().addClass(className);
        });

    });

});