Copy/Paste text from Word into tinyMCE

What good is building a really nice CMS with top-notch WYSIWYG editor if you handed it to clients that will blindly copy/paste Word documents into it ?

Well, it will just render a big ugly mess of proprietary and contradictory pseudo-css rules. You could even be blamed for it.

The solution

The latest versions of tinyMCE came bundled with an improved pasteplugin. It will automatically attempt to clean bad pasted text by operating some dark voodoo magic on it.

It does it quite well to be honest, removing almost all messy formatting. From the tests I tried, I was still getting useless crap in the resulting text (CSS comments, <span> with overly long style of color and background-color definitions, etc).

I finally decided to take a more brutal approach. The pasteplugin contained a _insertPlainText method that was supposed to be used in conjunction with a toolbar icon.

This method removes all formatting. Period.

As I didn't want to have to click on a toolbar icon before pasting my text (and none of my actual clients would ever think of doing that either), I came up with a very simple plugin to do the boring stuff for me.

The plugin

The only goal of this plugin is setting to truethe pasteAsPlainTextproperty of every editor. This property is defaulted to falsebut can be swapped using the toolbar icon.

I also forced the paste_text_sticky setting to true, preventing the previous property to revert to falseafter the first pasting.

Here's my plugin full code :

Be sure to include the pasteplugin in your plugin list, and insert my plugin *after *the paste plugin.

/**
 *    Will automatically convert pasted text so no junk code will be included.
 *    This plugin depends on the core paste plugin.
 **/
(function() {
  tinymce.create('tinymce.plugins.pasteAsPlainTextPlugin', {
    init : function(editor, url) {
      // We force the pasting to occur as plain text
      editor.pasteAsPlainText = true;
      // We also put it as sticky to allow for multiple pastings
      editor.settings.paste_text_sticky = true;

      // Adding some special post process
      editor.settings.paste_postprocess = function(a, o) {
        var text = o.content,
          split = String.fromCharCode(13)+String.fromCharCode(10)
        ;

        // If content is long text without HTML, We'll break it into <p>ieces
        if (text.charAt(0)!='<' && text.indexOf(split)!=-1) {
          // Adding <p> around each line
          var node = document.createElement('div'),
            sentences = text.split(split)
          ;
          for(var i=0,max=sentences.length;i!=max;i++) {
            node.innerHTML+='<p>'+sentences[i]+'</p>';
          }

          // Saving back in original content/node
          o.node = node;
          o.content = node.innerHTML;
        }
        return o;
      }
    }
  });

  // Register plugin
  tinymce.PluginManager.add('pasteAsPlainText', tinymce.plugins[pasteAsPlainTextPlugin]);
})();

Update

I added a postprocesscalback after seing that the pasted text was a little too plain in Webkit. All my text was displayed on the same line, without the nice breaking into paragraph that Firefox showed.

It occured because tinyMCE used the event.clipboardData property that Webkit browsers provides and allow for easy retrieving of clipboard data. Unfortunatly it returned a really plain text, and I had to apply a little loop to replace each new line with a paragraph.

How to prevent a FOUC but still gracefully degrade the jQuery UI tabs

I finally managed to fix something that was bugging me for a very long time. I'm talking about the jQuery UI tabs.

They are pretty useful actually, but I always hated that FOUC they produce. For one split second, you'll see all your tabs content, then they'll be neatly re-arranged in tabs.

What I want are my tabs displayed correctly right away, on first load.

First, the markup

Here's a typical tabs markup :

<div class="tabs">
    <ul>
        <li><a href="#firstTab">First tab</a></li>
        <li><a href="#secondTab">Second tab</a></li>
    </ul>

    <div class="tabPanel" id="firstTab">
        First tab content
    </div>
   
    <div class="tabPanel" id="secondTab">
        Second tab content
    </div>
</div>

CSS to hide all tabs if Javascript is disabled

If your Javascript is disabled, so will jQuery UI. We will then hide the <ul> because it serves no purpose here. We will only show it if js is enabled

.tabs ul { display:none; }
.js .tabs ul { display:block; }

Applying jQuery UI tabs

By doing a$('.tabs').tabs(); jQuery UI will treat your <ul> as your tab menu and all your .tabPanel as their corresponding contents. It will hide all your panels, except for the first one. It does so by adding a.ui-tabs- panel class to every .tabPanel as well as a .ui-tabs-hide to every panel it hides.

Right now, you should add another CSS rule to hide the unused panels :

.ui-tabs-hide { display: none; }

But if you look at your page now, you'll see all your tabs content before they get hidden. That is the FOUC I was talking about. The jQuery UI documentation indicate that to remove it, you should directly add the .ui-tabs-hide class to panels you'll want to hide.

As also pointed in the doc, it will not gracefully degrade because users without Javascript won't even be able to see your other tabs. Also, it asks you to add server-side logic (HTML markup with jQuery specific classes) for something that should be handled entirely client-side.

Removing the FOUC while gracefully degrade

Ok, so what I did was writing two simple rules that will directly hide all unused panels while still displaying the active one, even before jQuery UI takes action.

.js .tabPanel + .tabPanel { display:none;}

That way, no FOUC, and users without Javascript still see all the content. Unfortunatly, if you now try clicking on your tabs, you'll see that nothing happens and you get stuck with your default panel always visible.

Fixing the jQuery UI tabs

As jQuery will add new classes to my elements, I'll just have to write more specific rules that use those classes. Here's the little piece of logic I came up with :

.js .tabPanel.ui-tabs-panel { display:block;}
.js .tabPanel.ui-tabs-panel.ui-tabs-hide { display:none;}

All the jQuery panels are shown, except for the one hidden by jQuery. All those rules being more and more specific, they will get applied once the tabs are activated but still override the previous display: declarations.

Conclusion

This is how I fixed an issue that was bugging me for years. Unfortunatly the CSS rules being dependent on the markup used, I haven't yet been able to write them in a global form that could be added to the main jQuery UI css files.

*[FOUC]: Flash Of Unstyled Content

CSS for Javascript enabled browsers

In a previous post I was wondering if my way of loading js-specific CSS rules was right. I was loading a specific css file for js overrides.

This allow for lighter stylesheets for users without Javascript (they won't load useless rules), but also resulted in slower rendering for other users because they needed to load two stylesheets instead of one.

After much pondering, I now think that loading all the rules in one stylesheet is the way to go.

In addition with the number of request mentionned, keeping all your rules in the same file is much more easier to maintain. Js-specific rules aren't that big either so the small additional bytes are usually better than a full http request.

I now prefix all my Javascript rules by .js. I add a js class to the htmlelement directly from the head, this helps in preventing any FOUC that may occur because we add the class before the body rendering.

<head>
    <script>document.documentElement.className+=' js';</script>
</head>

Text emboss in CSS

I know only two _unperfect _ways of displaying an emboss text in CSS. As we can't use inset shadows with text-shadow, unlike box-shadow, we have to resort to other hacky ways.

Let me show you what can be done.

Simulate it with an optical illusion

Instead of creating a real inner shadow, you can just create an optical illusion by adding the correct shadow to make the viewer believe your text is embossed.

If you have dark text, just add a subtle white shadow at the bottom, while if you have light text, just add a subtle black one at the top.

<a class="emboss light">Lorem ipsum</a>
<a class="emboss dark">Lorem ipsum</a>
.emboss { font-size:1.2em; padding:50px; font-weight:bold; background:#0A539C; display:block; }
.light { color:#FFFFFF; text-shadow:0px -1px 0px rgba(0,0,0,0.9); }
.dark { color:#000000; text-shadow:0px 1px 0px rgba(255,255,255,0.1); }

I added an embossclass to more easily spot the effect, but the important code is in the lightand darkrules.

Add an almost transparent layer

For this second solution we will simply add a second layer with the exact same text above the first text. Just slightly moving it and making it partly transparent.

You could add twice your element in your markup, but that would be bad for accesibility as well as SEO, so we'll instead use the :before pseudo-class.

We can set the :before content using the content: property, and we can also use theattr() method to get the content of one of our element's attributes. We just need to put the exact same text in one of our attributes and we're good to go.

This can be easily done with a link and its titleattribute.

<a class="emboss lightAgain" title="Lorem ipsum">Lorem ipsum</a>
<a class="emboss darkAgain" title="Lorem ipsum">Lorem ipsum</a>
.lightAgain { color:#FFFFFF; position:relative; }
.lightAgain:before {
  content: attr(title);
  position:absolute;
  color:rgba(0,0,0,0.1);
  top:-1px;
  left:-1px;
  padding:50px;
}
.darkAgain { color:#000000; position:relative; }
.darkAgain:before {
   content: attr(title);
   position:absolute;
   color:rgba(255,255,255,0.1);
   top:1px;
   left:1px;
   padding:50px;
}

The effect is much better with this technique but it also has it share of drawback :

  • You have to write your content twice, once in the element and once in an attribute
  • You have to copy all your paddingrules in the :beforeelement or it won't get correctly positionned
  • You can't select the text with this effect

Hope those two little techniques helped someone, anyway.

How to hide/show elements based on Javascript

Sometimes you have a really great design with some really fancy Javascript goodness, like drag'n'drop and other shiny Ajaxy stuff.

But when you browse the website with Javascript disabled, none of that works and you end up with interface elements that should'nt be here because they do not work.

In those case, you'd rather display a nice message to your user, telling him that he can't use the feature withou Javascript enabled.

But how do you do that ?

Well, I myself load two different stylesheets. Remember that your website should be working without Javascript, this is just the last layer you add.

My default stylesheet will load all the rules for when Javascript is not enabled. No fancy cursor:move here.

Then I load a second stylesheet using Javascript, using document.write() in the <head>. And that's in this one that I write rules that overload the previous one. I add every styling that deals with Javascript-enabled features here.

Limitations

I may be changing the way I load the JS stylesheet in the future. I don't really like relying on document.write because it is evil. I also don't like the idea of getting one extra request.

I could add a js class to my body element (like modernizr does with all its CSS3 properties) and then target elements by prepending .js to the rule.

But it means adding rules in my main CSS file for users without JS that will still be downloading those extra useless bytes.

I haven't yet figured which way was the best (or should I say, the worst)

Convenient methodes

Whatever way you choose, one thing that really helped me was two real simple classes : jsOnand jsOffthat I add to elements.

Elements with jsOnwill only be visible if Javascript is enabled and hidden otherwise, while element with jsOffwill do the opposite.

Assuming you mark your body element with a js class if Javascript is enabled, here's how to do it :

.jsOn { display:none; }
.js .jsOn { display:block;
.jsOff { display:block;
.js .jsOff { display:none;

Hope all that helps at least someone.