2010/01/18

Alpha 5 of WriteArea

These days I've kept on working on WriteArea and today I've uploaded the fifth alpha version. This version is quite better with regards to the detection of other Rich Editors and finally removes a little code that I added for their detection but I knew that shouldn't be included in a final version.

Most important: this version includes an option in the preferences to open the editor in a bottom pane of the page. I've borrowed the close button from the AdBlock Plus skin and I will try to confirm that this is OK before releasing the final version.

There's a little problem with this bottom pane: it isn't aware of tabs, so when you switch tabs it remains there and it's a little annoying, because if you close the pane you might lose all the content. I'll try to find a solution to this problem, although I'm getting already a little tired of so much work in this extension for the last few days.

2010/01/14

Alpha release of Write Area

I've uploaded a beta version of Write Area to AMO as a little preview of what's next.

New plugin

It includes the Syntax Highlighter plugin by Darren James. Remember that you must configure your page with the SyntaxHighlighter by Alex Gorbatchev

With this feature you can include code in your pages like this:

  var script =  writearea.injectScript(this.parentWindow.document, command);
  var info = script.getUserData('editorInfo');
  script.parentNode.removeChild(script);
  // undetected framework
  if (!info)
   info = {
    type:"undetected",
    data:element.ownerDocument.body.innerHTML
   };

In the editor you click the "code" button and in that dialog you paste the contents and select the programming language that must be used to highlight it. In the editor it will be shown like other <pre> code (currently in blue, but I might change that), and in the final page it will show the code as you see above if you have properly installed the js and css files as explained by Alex Gorbatchev.

Not just textareas

The second feature is the ability to use the editor even on top of other editors. It tries to detect correctly some editors, but it should be able to handle any system where a WYSIWYG editor is used. Just right click on the content and click the Write Area entry, and the rest is just the same.

I'll explain this a little better: there are lots of places where some kind of rich editor is used: CMS, Forums, WebMail... The problem is that some of those places use old versions or editors that have very few features, so trying to use them can be a little painful.

My first motivation for writing this extension was that kind of situation where I had to deal for example with the editor here at blogspot. Fortunately for me I was able to disable it and use a plain textarea and easily work with it in my extension, but not all the editors have the ability to be switched off or to show the current source in a textarea, and one day I had a simple idea to detect this situation (check if the document has designMode == 'on') and work with the innerHTML of the body.

This works as long as the editor doesn't use special elements (like the icons shown here for anchors) as that means that the innerHTML isn't really the HTML output of the editor, to get that info (or to properly change the content so it shows any special elements) the API of each editor must be used. That means that there's a set of editors that I'm detecting and in the rest of cases the body.innerHTML is used for read/write.

The current sets of editors that I've coded to detect is as follows:

  •     CKEditor
  •     FCKeditor
  •     TinyMCE 2 & 3
  •     CuteEditor
  •     YUI Editor
  •     Xinha
  •     EssentialObjects
  •     HTMLArea

I could go on adding support for any other editor out there, but I think that this is enough to test the mechanism and verify that I could increase it in the future. I'm realizing right now that the detection could be extended even with a configuration option to add the required script, this is for example the part that detects CKEditor:

 if (window.CKEDITOR)
 {
  match = nodeId.match(/cke_contents_(.*)$/);
  if (match)
  {
   return {
    type: "CKEDITOR",
    data: CKEDITOR.instances[ match[1] ].getData(),
    setDataTemplate: "CKEDITOR.instances['" + match[1] + "'].setData(##DATA##)"
   };
  }
 }

The important trick here is that the code inside the extension can't call any method or access any custom javascript object of the web page, this is done in order to protect the browser from any external attack that tries to run code with Chrome privileges.

So by default the if (window.CKEDITOR) check always fails because as I said, the code in the extension can use the window of the document, and standard properties like window.document, but won't return anything that has been set by a script.

As a test phase I used the real window as returned by window.wrappedJSObject and I was able to verify that the ideas worked, and kept on that way for a little while, but finally today I have changed all the detection code so that it's done in a single place (previously the code looked quite different) and it's merged into a big string that it's added to the web document  as a script. That script executes a function that includes all the detection parts and returns an object that it's set as a property of the script DOM node, and then in the other side of the fence, the extension gets back that object with the detected data.

Isn't a little strange that I can't get an element added normally to an object like window, but it's possible to pass an object using node.setUserData?

So back to what's important:
Now you can use this extension almost anywhere, it doesn't matter if it's your blog, your CMS or a WebMail app like Hotmail, GMail or Yahoo, if you want to overcome some limitation of the editor, or a "silly" restriction set by whoever added that editor leaving only a few features, you can right click on it and launch the WriteArea extension to work as you please. Note that you might need to disable the option in the preferences of Firefox to allow the pages to replace the default context menu, and you should be aware that dueto bug 486990 if you are using Firefox 3.5 the page might be able to ignore that restriction (but Firefox 3.6 works nicely).

I plan to do some other enhancements before releasing this as a full new version, but I think that it's worth publishing it as a beta to let people test it.

I might edit this post to explain better how to use each of those two features and why they are useful, but I would like to hear your comments about them meanwhile.

Edit 17th January: I've added an example of the output of the Syntax Highlighter plugin and an explanation of how the detection of other editors work.

2010/01/12

New releases of CKEditor and CKFinder

To start the new year, the team at CKSource has released new versions of CKEditor  and CKFinder.

CKEditor 3.1

It is an evolution of the previous version. It's focused mostly on introducing New features, several of them to bring back parts of the old FCKeditor feature set, but there are also new parts like the integration with jQuery and the new Paste system that allows to have a control about what's being pasted into the content.

As these features have been rewritten, don't be surprised if there's a little bug here and there, so if you're sure that something isn't working then head up to the dev site and search for existing bugs (if it's something obvious and easy to reproduce it's highly likely that someone has already reported it). If you can't find any ticket about the problem then file a new one with the steps to reproduce the problem (what's the browser, a sample page to load, what must be done after the page is loaded...) and then if it's confirmed it might be fixed in a future release.

If you don't report the bug with the proper steps to reproduce the problem then your problem might remain there for a long time, but even if you provide a clean set of steps to reproduce, just remember that browsers have bugs so maybe it can't be fixed because it would need some very complex workaround that can introduce other problems or it's just impossible to fix in javascript.

The best way to learn more about the new features is to read the announcement by Frederico and then download CKEditor 3.1 to test them.

Just remember that there's a lot of work going on in CKEditor as you can see in the roadmap.

CKFinder 1.4.2

The simple announcement by Wiktor explains it well, some little bugs, improved compatibility and new translations. The focus of the work is now on the new version that will try to address the missing features that are requested in the CKFinder forum and trying to look even further by making it possible to create more easily new features in the future that no one has dared to request.

There's one thing to notice about this release, as I explained in the previous post Firefox 3.6 hasn't included support of onreadystatechange and the previous versions of CKFinder fail to enable the upload button. The new release includes a little workaround so download CKFinder before your users upgrade to Firefox 3.6 and they call you to ask why does it fail.

2010/01/06

Delivering code half-done in a web browser

Today I was checking something about CKFinder and I've realized that the Upload button doesn't work in the latest beta of Firefox 3.6.

That's strange and quite important as lots of people will be using it as soon as it is released, so I dug into the code to find out the problem. After tracing the differences in the execution of 3.5 and 3.6 I've finally found that when an iframe is created it finish the initialization by checking for document.readyState to avoid problems using an unfinished document in IE, and now that code was triggered also by Firefox 3.6

The fact that Firefox includes a property that was previously supported by IE (and IIRC also Opera and Webkit) shouldn't break anything, that code path uses the document.onreadystatechange to properly listen for the change of state and finish the initialization.

The real problem is that such event hasn't been added to Firefox, so the code never executes!

Everything was OK without support for document.readyState, but now they have added it without including the document.onreadystatechange, so things might get broken due to this big oversight. I suggest all of you to review your code and check for the usage of this event (not to be confused with XHR.onreadystatechange) because Firefox 3.6 might be a real pain for you if you don't patch your code it in due time.

Mozilla guys: thank you for bringing us this extra work.

2010/01/01

Zoom Plugin for CKEditor

Recently all the browsers that are implementing the latest and greatest elements of CSS 3 have added support for CSS Transforms, and so I decided to have a little fun while creating a little example about how to create a custom dropdown element in the CKEditor toolbar.

The idea is to provide a new dropdown that allows the user to set the zoom level for the editor, this example is gonna be a little rough and it can be greatly improved, but this is mostly a proof of concept about how to do it.

The first step is to create the skeleton for the plugin:

CKEDITOR.plugins.add( 'zoom',
{
    requires : [ 'richcombo' ],

    init : function( editor )
    {

    } //Init
} );

That code should be placed in a "plugin.js" file located in the plugins/zoom folder of your CKEditor installation. If you want to create another plugin, you'll use another folder name and put that name as the first parameter in the call to CKEDITOR.plugins.add()

For this plugin we need to be sure that the code from the "richcombo" plugin is loaded, as that's the real code that will create the dropdown element, so we specify it in the requires field of the plugin.

Finally this plugin must be added to the CKEditor instance, so in your config.js (or whatever other place you want to specify it) add it to the plugins that must be loaded:

    config.extraPlugins = 'zoom';

You can place an alert() inside the init function to verify that the plugin is being loaded when you refresh the page with CKEditor. If it doesn't work, verify the error console and try to clear your cache.

Now we need to fill up the code in the init function to create the dropdown:

    init : function( editor )
    {
        var config = editor.config;

        editor.ui.addRichCombo( 'Zoom',
            {
                label : "100 %",
                title : 'Zoom',
                multiSelect : false,

                panel :
                {
                    css : [ CKEDITOR.getUrl( editor.skinPath + 'editor.css' ) ].concat( config.contentsCss )
                },

                init : function()
                {
                    var zoomOptions = [50, 75, 100, 125, 150, 200, 400],
                        zoom;
                   
                    this.startGroup( 'Zoom level' );
                    // Loop over the Array, adding all items to the
                    // combo.
                    for ( i = 0 ; i < zoomOptions.length ; i++ )
                    {
                        zoom = zoomOptions[ i ];
                        // value, html, text
                        this.add( zoom, zoom + " %", zoom + " %" );
                    }
                    // Default value on first click
                    this.setValue(100, "100 %");
                }
            });
        // End of richCombo element

    } //Init

In this function we are creating the dropdown as a "RichCombo" element with editor.ui.addRichCombo() The function takes as parameters the name of the RichCombo and the object that contains the definition. Inside this object there's an init() function that will be called to initialize the dropdown on first click, here we specify the options available in the RichCombo and add them with this.add()

So now we can add to the toolbar of our CKEditor this 'Zoom' RichCombo element:

...
    ['Maximize', 'ShowBlocks', 'Zoom'],
...

Refreshing the page will show now the dropdown and clicking on it will show the panel, although we still haven't specified what must be done  when an option is selected. Doing it in a simple way and not trying to optimize how it works we can add this function to the RichCombo definition:

...
                    // Default value on first click
                    this.setValue(100, "100 %");
                },

                onClick : function( value )
                {
                    var body = editor.document.getBody().$;
 
                    body.style.MozTransformOrigin = "top left";
                    body.style.MozTransform = "scale(" + (value/100)  + ")";

                    body.style.WebkitTransformOrigin = "top left";
                    body.style.WebkitTransform = "scale(" + (value/100)  + ")";

                    body.style.OTransformOrigin = "top left";
                    body.style.OTransform = "scale(" + (value/100)  + ")";

                    body.style.TransformOrigin = "top left";
                    body.style.Transform = "scale(" + (value/100)  + ")";
                    // IE
                    body.style.zoom = value/100;
                }
            });
        // End of richCombo element
...

This code blindly tries to set the property for each browser, without bothering about which one is being used of if it supports CSS Transforms at all. We know that IE doesn't support them, but it has supported since long ago the style.zoom so in this case we can use just that.

A correct implementation of the plugin should verify that the browser does support CSS Transforms (or is IE and can use .zoom) before adding the RichCombo, and in the onClick event handler should use just the correct property instead of trying all of them.

This plugin also lacks internationalization, because that wasn't the point of this test, but it should be easy to add and to fix other little things.

But there are two problems that can be seen with just this code:

The first one is that the width of the combo in the toolbar is a little too big to specify some little text like "100%". This is being tracked in ticket 4559 but as you can see by reading all the comments there's no easy solution that can be applied to automatically fix it. So the solution is to add some rules in the stylesheet, although that makes deploying the plugin a little harder. Maybe the RichCombo should be allowed to specify a width in its definition?

The second problem appears only in Webkit browsers (Safari and Chrome), when you open up the panel it will take full height, and it's a little ugly. Other combos have their size specified in the skins css but that doesn't seems like a good solution if you want to publish a plugin that will be used for other people, as well as making upgrading a little harder.

Browser support: The zoom feature should work in IE6+, Firefox 3.5+, current Safari and Chrome (I don't have old versions to test) and Opera 10.5+ (still in alpha)

Happy 2010

2009/12/19

GeckoSpellChecker plugin for CKEditor

A little while ago I created the GeckoSpellChecker plugin for FCKeditor that allows to use the internal Firefox spellchecker from the editing area without any external server.

Now the plugin has a version ready for CKEditor. Be sure to download the 1.0 version for CKEditor, the 0.7 version will remain there for the people using FCKeditor.

I hope that you can find this useful.

2009/12/18

Recompressing ckeditor.js to fit your needs

The javascript code of CKEditor is delivered as one main ckeditor.js where all the code has been compressed and merged (of course, not ALL the code, just the code that is needed for the core and the default plugins, the code of the dialogs for example is loaded on demand). And one question that you might have is: how can I compress my own version? I'll try to provide here some explanations and notes about this, but despite its length it is far from a full guide and it might contain some incorrect parts.

First you have download the ckpackager.jar (there's also an .exe for those in windows that haven't installed Java) from
http://svn.fckeditor.net/CKPackager/trunk/bin/ckpackager.jar and put it at the root of your CKEditor folder. (Note that this is a SVN repository so it should contain the latest version)

Backup the existing ckeditor.js and ckeditor_basic.js so you can check that the process is working correctly.

Now the first step is to verify that you have Java correctly installed and that everything is correct in your environment, so fire up the console/terminal/command prompt and navigate to the CKEditor folder and type there the command to start the compilation. This should generate identical files to the previous ckeditor.js and ckeditor_basic.js if you haven't changed anything in the _source folder:

java -jar ckpackager.jar ckeditor.pack

Maybe the js files aren't identical to the previous ones due to some change in the version of CKPackager used to compress the released version of CKEditor and some fix/improvement made to CKPackager since that moment, but the overall should be similar and you should be able to use the new files without differences.

Working with the source files

Now you can try to apply changes to the files under _source and test them using the ckeditor_source.js, and when you are ready to deploy you just have to compress them all again. You should be careful with your changes because compressing the source means that any slight error can be fatal.

The new packager is based on Rhino, the javascript implementation in Java from the people at Mozilla, so I don't know how it does deal with missing semicolons, and other minor issues that can be fatal with some compression systems. Of course it's better to write code without problems instead of relying on the tools to fix them.

Selecting just what you want

The ckeditor.js file includes all the plugins (but not all the code of the plugins: the dialogs are lazy loaded only when needed as I said). So if you don't want to provide several options to your users you could remove those plugins, not only from the toolbar, but also from the packaged file so you can end up with a much smaller ckeditor.js file.

In order to change what's included you need to edit ckeditor.pack and remove the entries about plugins that you don't need.

Don't alter the entries under '_source/core/*' because the editor probably will fail. Remember that if you mess with the packaging process no one else can help you, you are the one that is editing the file and not knowing what to do isn't a good idea here.

An example of a change would be to remove the line

                '_source/plugins/elementspath/plugin.js',

if you don't want to show the elements path at the bottom of the editor (there's the config.removePlugins = 'elementspath' option but that means that the code is still loaded so if you are sure that you never need this option you can save some bytes). Then you save the file and issue the recompress command, and now the ckeditor.js is 2Kb smaller.

Of course it seems that it would be a trial/error the process to find what are the files that you can remove as one plugin might depend upon another and so you need it to run correctly. In order to generate the default ckeditor.pack file, the SVN version of CKEditor includes a _dev folder with some development tools and that includes a packager folder with a HTML file created to generate the correct content according to the current configuration (this is meant to be used in the SVN version, I don't know how it will deal with the released version).

Optimizing the downloads

If you check the ckeditor.pack file you can see that the lang file isn't compressed, this is due to the fact that not everybody speaks English, so packaging the en.js file for everybody would mean a waste of bytes, but if you know that your users speak only one language, then you could specify that language in the ckeditor.pack file and get it bundled along the others.

This will increase the size of ckeditor.js but remember that those bytes were downloaded separately as another request, so now the editor will be slightly faster as we have avoided that extra step.

At the bottom of the file you can see that the kama.js file is also being bundled, so if you are using another skin you could adjust that to remove the kama and put your own one, avoiding the extra download of your selected skin.

And if you are using some custom plugin you can include it in the list so it's ready along the rest of files and the user don't suffer an extra delay for the usage of your plugins.

This is just a quick explanation, but it should be enough to get started about how to recompress the code.