Fixed a bug in the PaintWeb plugin for TinyMCE.
[moodle/mihaisucan.git] / lib / editor / tinymce / jscripts / tiny_mce / plugins / paintweb / editor_plugin_src.js
blob9214122b40feec7afc20d9d892c2152fd8ea08b2
1 /*
2  * Copyright (C) 2009 Mihai Şucan
3  *
4  * This file is part of PaintWeb.
5  *
6  * PaintWeb is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * PaintWeb is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with PaintWeb.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * $URL: http://code.google.com/p/paintweb $
20  * $Date: 2009-11-04 20:18:05 +0200 $
21  */
23 /**
24  * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
25  * @fileOverview This is a plugin for TinyMCE which integrates PaintWeb.
26  */
28 (function() {
29 // The plugin URL. This points to the location of this TinyMCE plugin.
30 var pluginUrl = null;
32 // Reference to the DOM element of the overlay button displayed on top of the 
33 // selected image.
34 var overlayButton = null;
36 // Reference to the PaintWeb configuration object.
37 var paintwebConfig = null;
39 // Reference to the current PaintWeb instance object.
40 var paintwebInstance = null;
42 // Reference to the TinyMCE "plugin bar" displayed above PaintWeb, when PaintWeb 
43 // is displayed. This "plugin bar" allows the user to save/cancel image edits.
44 var pluginBar = null;
46 // The delay used for displaying temporary messages in the plugin bar.
47 var pluginBarDelay = 10000; // 10 seconds
49 // The timeout ID used for the plugin bar when a temporary message is displayed.
50 var pluginBarTimeout = null;
52 // Once PaintWeb is closed, the instance remains active. This value controls how 
53 // long the PaintWeb instance is kept alive. Once the time elapsed, the PaintWeb 
54 // instance is destroyed entirely.
55 var pwDestroyDelay = 30000; // 30 seconds
57 // The timeout ID used for destroying the PaintWeb instance.
58 var pwDestroyTimer = null;
60 // Tells if the user intends to save the image and return to TinyMCE or not.  
61 // This value is set to true when the user clicks the "Save" button in the 
62 // plugin bar.
63 var pwSaveReturn = false;
65 // Reference to the container of the current TinyMCE editor instance.
66 var targetContainer = null;
68 // The current TinyMCE editor instance.
69 var targetEditor = null;
71 // Reference to the image element being edited.
72 var targetImage = null;
74 // Tells if the image being edited uses a data URL or not.
75 var imgIsDataURL = false;
77 // The new image URL. Once the user saves the image, the remote server might 
78 // require a change for the image URL.
79 var imgNewUrl = null;
81 // Tells if the current image has been ever updated using "image save".
82 var imgSaved = false;
84 var pwlib = null;
86 if (!window.tinymce) {
87   alert('It looks like the PaintWeb plugin for TinyMCE cannot run.' +
88     'TinyMCE was not detected!');
89   return;
92 // Basic functionality used by PaintWeb.
93 if (!window.XMLHttpRequest || !window.getComputedStyle || 
94   !document.createElement('canvas').getContext) {
95   return;
98 var isOpera, isWebkit, isGecko;
100 // Image data URLs are considered external resources when they are drawn in 
101 // a Canvas element. This happens only in Gecko 1.9.0 or older (Firefox 3.0) and  
102 // in Webkit (Chrome/Safari). This is a problem because PaintWeb cannot save the 
103 // image once such data URL is loaded.
104 var dataURLfilterNeeded = (function () {
105   var ua   = navigator.userAgent.toLowerCase();
106   isOpera  = window.opera || /\b(opera|presto)\b/.test(ua);
107   isWebkit = !isOpera && /\b(applewebkit|webkit)\b/.test(ua);
108   isGecko  = !isOpera && !isWebkit && /\bgecko\b/.test(ua);
110   if (isWebkit) {
111     return true;
112   }
114   if (isGecko) {
115     var geckoRev = /\brv\:([^;\)\s]+)[;\)\s]/.exec(ua);
116     if (geckoRev && geckoRev[1]) {
117       geckoRev = geckoRev[1].replace(/[^\d]+$/, '').split('.');
118       if (geckoRev[0] == 1 && geckoRev[1] <= 9 && geckoRev[2] < 1) {
119         return true;
120       }
121     }
122   }
124   return false;
125 })();
128  * Load PaintWeb. This function tells TinyMCE to load the PaintWeb script.
129  */
130 function paintwebLoad () {
131   if (window.PaintWeb) {
132     paintwebLoaded();
133     return;
134   }
136   var config = targetEditor.getParam('paintweb_config'),
137       src    = config.tinymce.paintwebFolder + 'paintweb.js';
139   tinymce.ScriptLoader.load(src, paintwebLoaded);
143  * The event handler for the PaintWeb script load. This function creates a new 
144  * instance of PaintWeb and configures it.
145  */
146 function paintwebLoaded () {
147   if (paintwebInstance) {
148     return;
149   }
151   paintwebInstance = new PaintWeb();
152   paintwebConfig   = paintwebInstance.config;
154   var config      = targetEditor.getParam('paintweb_config'),
155       textarea    = targetEditor.getElement();
156       pNode       = targetContainer.parentNode,
157       pwContainer = tinymce.DOM.create('div');
159   pNode.insertBefore(pwContainer, textarea.nextSibling);
161   if (!PaintWeb.baseFolder) {
162     PaintWeb.baseFolder = config.tinymce.paintwebFolder;
163   }
165   config.imageLoad      = targetImage;
166   config.guiPlaceholder = pwContainer;
168   if (!config.lang) {
169     config.lang = targetEditor.getParam('language');
170   }
172   for (var prop in config) {
173     paintwebConfig[prop] = config[prop];
174   }
176   // Give PaintWeb access to the TinyMCE editor instance.
177   paintwebConfig.tinymceEditor = targetEditor;
179   paintwebInstance.init(paintwebInitialized);
183  * The initialization event handler for PaintWeb. When PaintWeb is initialized 
184  * this method configures the PaintWeb instance to work properly. A bar 
185  * representing the plugin is also added, to let the user save/cancel image 
186  * edits.
188  * @param {pwlib.appEvent.appInit} ev The PaintWeb application event object.
189  */
190 function paintwebInitialized (ev) {
191   if (overlayButton && targetEditor) {
192     overlayButton.value = targetEditor.getLang('paintweb.overlayButton', 
193         'Edit');
194   }
196   if (ev.state !== PaintWeb.INIT_DONE) {
197     alert('PaintWeb initialization failed! ' + ev.errorMessage);
198     paintwebInstance = null;
199     targetImage = null;
200     targetEditor = null;
202     return;
203   }
205   pwlib = window.pwlib;
206   paintwebInstance.events.add('imageSave',       paintwebSave);
207   paintwebInstance.events.add('imageSaveResult', paintwebSaveResult);
209   if (pluginBar) {
210     paintwebInstance.events.add('viewportSizeChange', 
211         paintwebViewportSizeChange);
212   }
214   paintwebShow(ev);
218  * The <code>click</code> event handler for the Save button displayed on the 
219  * plugin bar.
220  */
221 function pluginSaveButton () {
222   pwSaveReturn = true;
223   paintwebInstance.imageSave();
227  * The <code>click</code> event handler for the Cancel button displayed on the 
228  * plugin bar.
229  */
230 function pluginCancelButton () {
231   paintwebHide();
235  * The <code>imageSave</code> application event handler for PaintWeb. When the 
236  * user elects to save the image in PaintWeb, this function is invoked to 
237  * provide visual feedback in the plugin bar.
238  * 
239  * <p>If the <var>imageSaveDataURL</var> boolean property is set to true, then 
240  * the source of the image is also updated to hold the new data URL.
242  * @param {pwlib.appEvent.imageSave} ev The PaintWeb application event object.
243  */
244 function paintwebSave (ev) {
245   if (paintwebConfig.tinymce.imageSaveDataURL) {
246     ev.preventDefault();
248     var url = imgIsDataURL ? '-' : targetEditor.dom.getAttrib(targetImage, 
249         'src');
251     paintwebInstance.events.dispatch(new pwlib.appEvent.imageSaveResult(true, 
252           url, ev.dataURL));
254   } else if (pluginBar) {
255     if (pluginBarTimeout) {
256       clearTimeout(pluginBarTimeout);
257       pluginBarTimeout = null;
258     }
260     pluginBar.firstChild.innerHTML 
261       = targetEditor.getLang('paintweb.statusSavingImage', 'Saving image...');
262   }
266  * The <code>imageSaveResult</code> application event handler for PaintWeb.
268  * @param {pwlib.appEvent.imageSaveResult} ev The PaintWeb application event 
269  * object.
270  */
271 function paintwebSaveResult (ev) {
272   if (!targetImage) {
273     return;
274   }
276   // Update the status message in the "plugin bar".
277   if (pluginBar) {
278     if (ev.successful) {
279       pluginBar.firstChild.innerHTML 
280         = targetEditor.getLang('paintweb.statusImageSaved',
281             'Image save was succesful!');
282     } else {
283       pluginBar.firstChild.innerHTML 
284         = targetEditor.getLang('paintweb.statusImageSaveFailed',
285             'Image save failed!');
286     }
288     if (pluginBarTimeout) {
289       clearTimeout(pluginBarTimeout);
290     }
292     pluginBarTimeout = setTimeout(pluginBarResetContent, pluginBarDelay);
293   }
295   if (ev.successful) {
296     imgSaved = true;
298     if (ev.urlNew) {
299       // store the new URL. When PaintWeb is closed, the image src attribute is 
300       // updated.
301       imgNewUrl = ev.urlNew;
302     }
304     if (pwSaveReturn) {
305       pwSaveReturn = false;
306       paintwebHide();
307     }
308   }
312  * Reset the text content of the plugin bar.
313  */
314 function pluginBarResetContent () {
315   if (!pluginBar) {
316     return;
317   }
319   pluginBarTimeout = null;
321   pluginBar.firstChild.innerHTML 
322     = targetEditor.getLang('paintweb.statusImageEditing',
323         'You are editing an image from TinyMCE.');
327  * The <code>viewportSizeChange</code> PaintWeb application event handler. This 
328  * synchronises the size of the TinyMCE plugin bar with that of the PaintWeb 
329  * GUI.
331  * @param {pwlib.appEvent.viewportSizeChange} ev The application event object.
332  */
333 function paintwebViewportSizeChange (ev) {
334   tinymce.DOM.setStyle(pluginBar, 'width', ev.width);
338  * Start PaintWeb. This function performs the actual PaintWeb invocation.
340  * @returns {Boolean} True PaintWeb is about to start, or false otherwise.
341  */
342 function paintwebEditStart () {
343   if (!checkEditableImage(targetImage)) {
344     targetImage = null;
345     return false;
346   }
348   if (pwDestroyTimer) {
349     clearTimeout(pwDestroyTimer);
350     pwDestroyTimer = null;
351   }
353   var pwStart = function () {
354     if (overlayButton && overlayButton.parentNode) {
355       overlayButton.value = targetEditor.getLang('paintweb.overlayLoading', 
356           'Loading PaintWeb...');
357     }
359     if (paintwebInstance) {
360       paintwebInstance.imageLoad(targetImage);
361       paintwebShow();
362     } else {
363       paintwebLoad();
364     }
365   };
367   var dataURLfilterLoaded = function () {
368     tinymce.dom.Event.remove(targetImage, 'load', dataURLfilterLoaded);
369     imgIsDataURL = false;
370     pwStart();
371   };
373   var src = targetEditor.dom.getAttrib(targetImage, 'src');
375   if (src.substr(0, 5) === 'data:') {
376     imgIsDataURL = true;
377   } else {
378     imgIsDataURL = false;
379   }
381   var cfg = imgIsDataURL && dataURLfilterNeeded ?
382               targetEditor.getParam('paintweb_config') : null;
384   if (cfg && cfg.tinymce.imageDataURLfilter) {
385     tinymce.util.XHR.send({
386       url: cfg.tinymce.imageDataURLfilter,
387       content_type: 'application/x-www-form-urlencoded',
388       data: 'url=-&dataURL=' + encodeURIComponent(src),
390       error: function () {
391         if (window.console && console.log) {
392           console.log('TinyMCE.PaintWeb: failed to preload image data URL!');
393         }
394         pwStart();
395       },
397       success: function (result) {
398         if (!result) {
399           pwStart();
400           return;
401         }
403         result = tinymce.util.JSON.parse(result);
404         if (!result || !result.successful || !result.urlNew) {
405           pwStart();
406           return;
407         }
409         imgNewUrl = targetImage.src;
410         tinymce.dom.Event.add(targetImage, 'load', dataURLfilterLoaded);
411         targetEditor.dom.setAttrib(targetImage, 'src', result.urlNew);
412       }
413     });
415   } else {
416     pwStart();
417   }
419   src = null;
421   return true;
425  * Create a new image and start PaintWeb.
427  * @param {Number} width The image width.
428  * @param {Number} height The image height.
429  * @param {String} [bgrColor] The image background color.
430  * @param {String} [alt] The alternative text / the value for the "alt" 
431  * attribute.
432  * @param {String} [title]
433  */
434 function paintwebNewImage (width, height, bgrColor, alt, title) {
435   width  = parseInt(width) || 0;
436   height = parseInt(height) || 0;
437   if (!width || !height) {
438     return;
439   }
441   var canvas  = tinymce.DOM.create('canvas', {
442                   'width':  width,
443                   'height': height}),
444       context = canvas.getContext('2d');
446   if (bgrColor) {
447     context.fillStyle = bgrColor;
448     context.fillRect(0, 0, width, height);
449   }
451   targetEditor.execCommand('mceInsertContent', false,
452       '<img id="paintwebNewImage">');
454   var elem = targetEditor.dom.get('paintwebNewImage');
455   if (!elem || elem.id !== 'paintwebNewImage' || elem.nodeName.toLowerCase() !== 
456       'img') {
457     return;
458   }
460   if (alt) {
461     targetEditor.dom.setAttrib(elem, 'alt', alt);
462   }
463   if (title) {
464     targetEditor.dom.setAttrib(elem, 'title', title);
465   }
466   elem.src = canvas.toDataURL();
467   elem.setAttribute('mce_src', elem.src);
468   elem.removeAttribute('id');
470   targetImage = elem;
471   canvas      = null;
472   context     = null;
474   paintwebEditStart();
478  * Show PaintWeb on-screen. This function hides the current TinyMCE editor 
479  * instance and shows up the PaintWeb instance.
481  * @param [ev] Event object.
482  */
483 function paintwebShow (ev) {
484   var rect = null;
485   if (paintwebConfig.tinymce.syncViewportSize) {
486     rect = tinymce.DOM.getRect(targetEditor.getContentAreaContainer());
487   }
489   tinymce.DOM.setStyle(targetContainer, 'display', 'none');
491   // Give PaintWeb access to the TinyMCE editor instance.
492   paintwebConfig.tinymceEditor = targetEditor;
493   if (!ev || ev.type !== 'appInit') {
494     paintwebInstance.gui.show();
495   }
497   if (rect && rect.w && rect.h) {
498     paintwebInstance.gui.resizeTo(rect.w + 'px', rect.h + 'px');
499   }
501   if (pluginBar) {
502     pluginBarResetContent();
504     var placeholder = paintwebConfig.guiPlaceholder;
505     if (!pluginBar.parentNode) {
506       placeholder.parentNode.insertBefore(pluginBar, placeholder);
507     }
508   }
512  * Hide PaintWeb from the screen. This hides the PaintWeb target object of the 
513  * current instance, and displays back the TinyMCE container element.
514  */
515 function paintwebHide () {
516   paintwebInstance.gui.hide();
518   if (overlayButton && targetEditor) {
519     overlayButton.value = targetEditor.getLang('paintweb.overlayButton', 
520         'Edit');
521   }
523   if (pluginBar && pluginBar.parentNode) {
524     targetContainer.parentNode.removeChild(pluginBar);
525   }
527   // Update the target image src attribute if needed.
528   if (imgNewUrl) {
529     // The tinymce.utl.URI class mangles data URLs.
530     if (imgNewUrl.substr(0, 5) !== 'data:') {
531       targetEditor.dom.setAttrib(targetImage, 'src', imgNewUrl);
532     } else {
533       targetImage.src = imgNewUrl;
534       if (targetImage.hasAttribute('mce_src')) {
535         targetImage.setAttribute('mce_src', imgNewUrl);
536       }
537     }
539     imgNewUrl = null;
541   } else if (!imgIsDataURL && imgSaved) {
542     // Force a refresh for the target image from the server.
544     var src = targetEditor.dom.getAttrib(targetImage, 'src'),
545         rnd = (new Date()).getMilliseconds() * Math.round(Math.random() * 100);
547     if (src.indexOf('?') === -1) {
548       src += '?' + rnd;
549     } else {
550       if (/\?[0-9]+$/.test(src)) {
551         src = src.replace(/\?[0-9]+$/, '?' + rnd);
552       } else if (/&[0-9]+$/.test(src)) {
553         src = src.replace(/&[0-9]+$/, '&' + rnd);
554       } else {
555         src += '&' + rnd;
556       }
557     }
559     targetEditor.dom.setAttrib(targetImage, 'src', src);
560   }
562   targetContainer.style.display = '';
563   targetImage = null;
564   imgSaved = false;
566   targetEditor.focus();
568   if (!pwDestroyTimer) {
569     pwDestroyTimer = setTimeout(paintwebDestroy, pwDestroyDelay);
570   }
574  * After a given time of idleness, when the user stops from working with 
575  * PaintWeb, we destroy the current PaintWeb instance, to release some memory.
576  */
577 function paintwebDestroy () {
578   if (paintwebInstance) {
579     paintwebInstance.destroy();
581     var pNode = paintwebConfig.guiPlaceholder.parentNode;
582     pNode.removeChild(paintwebConfig.guiPlaceholder);
584     paintwebInstance = null;
585     paintwebConfig = null;
586     pwDestroyTimer = null;
587   }
591  * The "paintwebEdit" command. This function is invoked when the user clicks the 
592  * PaintWeb button on the toolbar.
593  */
594 function paintwebEditCommand () {
595   if (targetImage) {
596     return;
597   }
599   var n = this.selection.getNode(),
600       tag = n.nodeName.toLowerCase();
602   if (tag !== 'img' && overlayButton && overlayButton.parentNode && 
603       overlayButton._targetImage) {
604     n = overlayButton._targetImage;
605     tag = n.nodeName.toLowerCase();
606   }
608   targetEditor    = this;
609   targetContainer = this.getContainer();
610   targetImage     = n;
612   // If PaintWeb won't start, then we create a new image.
613   if (!paintwebEditStart() && tag !== 'img') {
614     this.windowManager.open(
615       {
616         file:   pluginUrl + '/newimage.html',
617         width:  350 + parseInt(this.getLang('paintweb.dlg_delta_width',  0)),
618         height: 200 + parseInt(this.getLang('paintweb.dlg_delta_height', 0)),
619         inline: 1
620       },
621       {
622         plugin_url: pluginUrl,
623         newImageFn: paintwebNewImage
624       }
625     );
626   }
630  * Check if an image element can be edited with PaintWeb. The image element 
631  * source must be a data URI or it must be an image from the same domain as 
632  * the page.
634  * @param {HTMLImageElement} n The image element.
635  * @returns {Boolean} True if the image can be edited, or false otherwise.
636  */
637 function checkEditableImage (n) {
638   if (!n) {
639     return false;
640   }
642   var url = n.src;
643   if (n.nodeName.toLowerCase() !== 'img' || !url) {
644     return false;
645   }
647   var pos = url.indexOf(':'),
648       proto = url.substr(0, pos + 1).toLowerCase();
650   if (proto === 'data:') {
651     return true;
652   }
654   if (proto !== 'http:' && proto !== 'https:') {
655     return false;
656   }
658   var host = url.replace(/^https?:\/\//i, '');
659   pos = host.indexOf('/');
660   if (pos > -1) {
661     host = host.substr(0, pos);
662   }
664   if (host !== window.location.host) {
665     return false;
666   }
668   return true;
672  * Add the overlay button to an image element node.
674  * @param {tinymce.Editor} ed The TinyMCE editor instance.
675  * @param {Element} n The image element node you want to add to the overlay 
676  * button.
677  */
678 function overlayButtonAdd (ed, n) {
679   if (!overlayButton || !ed || !n) {
680     return;
681   }
683   var offsetTop  = 5,
684       offsetLeft = 5,
685       sibling    = null,
686       pNode;
688   // Try to avoid adding the overlay button inside an anchor.
689   if (n.parentNode.nodeName.toLowerCase() === 'a') {
690     pNode   = n.parentNode.parentNode;
691     sibling = n.parentNode.nextSibling;
692   } else {
693     pNode   = n.parentNode;
694     sibling = n.nextSibling;
695   }
697   overlayButton._targetImage = n;
699   ed.dom.setStyles(overlayButton, {
700      'top':  (n.offsetTop  + offsetTop)  + 'px',
701      'left': (n.offsetLeft + offsetLeft) + 'px'});
703   overlayButton.value = ed.getLang('paintweb.overlayButton', 'Edit');
704   pNode.insertBefore(overlayButton, sibling);
708  * Clear the document of the TinyMCE editor instance of any possible PaintWeb 
709  * overlay button remnant. This makes sure that the iframe DOM document does not 
710  * contain any PaintWeb overlay button. Firefox remembers the overlay button 
711  * after a page refresh.
713  * @param {tinymce.Editor} ed The editor instance that the plugin is 
714  * initialized in.
715  */
716 function overlayButtonCleanup (ed) {
717   if (!overlayButton || !ed || !ed.getDoc) {
718     return;
719   }
721   var root, elems, pNode;
723   if (overlayButton) {
724     if (overlayButton.parentNode) {
725       pNode = overlayButton.parentNode;
726       pNode.removeChild(overlayButton);
727     }
729     overlayButton._targetImage = null;
730   }
732   root = ed.getDoc();
733   if (!root || !root.getElementsByClassName) {
734     return;
735   }
737   elems = root.getElementsByClassName(overlayButton.className);
739   for (var i = 0; i < elems.length; i++) {
740     pNode = elems[i].parentNode;
741     pNode.removeChild(elems[i]);
742   }
745 // Load plugin specific language pack
746 tinymce.PluginManager.requireLangPack('paintweb');
748 tinymce.create('tinymce.plugins.paintweb', {
749   /**
750    * Initializes the plugin. This method sets-up the current editor instance, by 
751    * adding a new button, <var>paintwebEdit</var>, and by setting up several 
752    * event listeners.
753    *
754    * @param {tinymce.Editor} ed Editor instance that the plugin is initialized 
755    * in.
756    * @param {String} url Absolute URL to where the plugin is located.
757    */
758   init: function (ed, url) {
759     var t = this;
761     pluginUrl = url;
763     // Register the command so that it can be invoked by using 
764     // tinyMCE.activeEditor.execCommand('paintwebEdit');
765     ed.addCommand('paintwebEdit', paintwebEditCommand, ed);
767     // Register PaintWeb button
768     ed.addButton('paintwebEdit', {
769       title: 'paintweb.toolbarButton',
770       cmd:   'paintwebEdit',
771       image: pluginUrl + '/img/paintweb2.gif'
772     });
774     // Add a node change handler which enables the PaintWeb button in the UI 
775     // when an image is selected.
776     if (isOpera) {
777       // In Opera, due to bug DSK-265135, we only listen for the keyup and 
778       // mouseup events.
779       ed.onKeyUp.add(this.edNodeChange);
780       ed.onMouseUp.add(this.edNodeChange);
781     } else {
782       ed.onNodeChange.add(this.edNodeChange);
783     }
785     var config = ed.getParam('paintweb_config') || {};
786     if (!config.tinymce) {
787       config.tinymce = {};
788     }
790     // Integrate into the ContextMenu plugin if the user desires so.
791     if (config.tinymce.contextMenuItem && ed.plugins.contextmenu) {
792       ed.plugins.contextmenu.onContextMenu.add(this.pluginContextMenu);
793     }
795     // Listen for the form submission event. This is needed when the user is 
796     // inside PaintWeb, editing an image. The user is warned that image changed 
797     // and it's not saved, and the form submission event is cancelled.
798     ed.onSubmit.add(this.edSubmit);
800     // Create the overlay button element if the configuration allows so.
801     if (config.tinymce.overlayButton) {
802       // Make sure the button doesn't show up in the article.
803       ed.onBeforeGetContent.add(overlayButtonCleanup);
805       ed.onInit.add(function (ed) {
806         // Cleanup after initialization. Firefox remembers the content between 
807         // page reloads.
808         overlayButtonCleanup(ed);
810         ed.onKeyDown.addToTop(t.overlayButtonEvent);
811         ed.onMouseDown.addToTop(t.overlayButtonEvent);
812       });
814       overlayButton = tinymce.DOM.create('input', {
815           'type':  'button',
816           'class': 'paintweb_tinymce_overlayButton',
817           'style': 'position:absolute',
818           'value': ed.getLang('paintweb.overlayButton', 'Edit')});
819     }
821     // Handle the dblclick events for image elements, if the user wants it.
822     if (config.tinymce.dblclickHandler) {
823       ed.onDblClick.add(this.edDblClick);
824     }
826     // Add a "plugin bar" above the PaintWeb editor, when PaintWeb is active.  
827     // This bar shows the image file name being edited, and provides two buttons 
828     // for image save and for cancelling any image edits.
829     if (config.tinymce.pluginBar) {
830       pluginBar = tinymce.DOM.create('div', {
831           'class': 'paintweb_tinymce_status',
832           'style': 'display:none'});
834       var saveBtn  = tinymce.DOM.create('input', {
835           'type':  'button',
836           'class': 'paintweb_tinymce_save',
837           'title': ed.getLang('paintweb.imageSaveButtonTitle',
838                      'Save the image and return to TinyMCE.'),
839           'value': ed.getLang('paintweb.imageSaveButton', 'Save')});
841       saveBtn.addEventListener('click', pluginSaveButton, false);
843       var cancelBtn = tinymce.DOM.create('input', {
844           'type':  'button',
845           'class': 'paintweb_tinymce_cancel',
846           'title': ed.getLang('paintweb.cancelEditButtonTitle',
847                      'Cancel image edits and return to TinyMCE.'),
848           'value': ed.getLang('paintweb.cancelEditButton', 'Cancel')});
850       cancelBtn.addEventListener('click', pluginCancelButton, false);
852       var textSpan = tinymce.DOM.create('span');
854       pluginBar.appendChild(textSpan);
855       pluginBar.appendChild(saveBtn);
856       pluginBar.appendChild(cancelBtn);
857     }
858   },
860   /**
861    * The <code>nodeChange</code> event handler for the TinyMCE editor. This 
862    * method provides visual feedback for editable image elements.
863    *
864    * @private
865    *
866    * @param {tinymce.Editor} ed The editor instance that the plugin is 
867    * initialized in.
868    */
869   edNodeChange: function (ed) {
870     var cm = ed.controlManager,
871         n = ed.selection.getNode();
873     // Do not do anything inside the overlay button.
874     if (!n || overlayButton && overlayButton._targetImage && n && n.className 
875         === overlayButton.className) {
876       return;
877     }
879     var disabled = !checkEditableImage(n);
881     if (n.nodeName.toLowerCase() === 'img' && disabled) {
882       cm.setDisabled('paintwebEdit', true);
883       cm.setActive('paintwebEdit', false);
884     } else {
885       cm.setDisabled('paintwebEdit', false);
886       cm.setActive('paintwebEdit', !disabled);
887     }
889     if (!overlayButton) {
890       return;
891     }
893     if (!disabled) {
894       overlayButtonAdd(ed, n);
895     } else if (overlayButton._targetImage) {
896       overlayButton._targetImage = null;
897     }
898   },
900   /**
901    * The <code>mousedown</code> and <code>keydown</code> event handler for the 
902    * editor. This method starts PaintWeb when the user clicks the "Edit" overlay 
903    * button, or cleans the document of any overlay button element.
904    *
905    * @param {tinymce.Editor} ed The TinyMCE editor instance.
906    * @param {Event} ev The DOM Event object.
907    */
908   overlayButtonEvent: function (ed, ev) {
909     var n = ev.type === 'mousedown' ? ev.target : ed.selection.getNode();
911     // If the user clicked the Edit overlay button, then we consider the user 
912     // wants to start PaintWeb.
913     if (!targetImage && ev.type === 'mousedown' && overlayButton && n && 
914         n.className === overlayButton.className && overlayButton._targetImage) {
915       targetEditor = ed;
916       targetContainer = ed.getContainer();
917       targetImage = overlayButton._targetImage;
919       paintwebEditStart();
921     } else if (n && n.nodeName.toLowerCase() !== 'img') {
922       // ... otherwise make sure the document is clean.
923       overlayButtonCleanup(ed);
924     }
925   },
927   /**
928    * The <code>dblclick</code> event handler for the editor. This method starts 
929    * PaintWeb when the user double clicks an editable image element.
930    *
931    * @param {tinymce.Editor} ed The TinyMCE editor instance.
932    * @param {Event} ev The DOM Event object.
933    */
934   edDblClick: function (ed, ev) {
935     if (!targetImage && checkEditableImage(ev.target)) {
936       targetEditor = ed;
937       targetContainer = ed.getContainer();
938       targetImage = ev.target;
939       ev.target.focus();
941       paintwebEditStart();
942     }
943   },
945   /**
946    * The <code>submit</code> event handler for the form associated to the 
947    * textarea of the current TinyMCE editor instance. This method checks if the 
948    * current PaintWeb instance is open and if the user has made changes to the 
949    * image. If yes, then the form submission is cancelled and the user is warned 
950    * about losing unsaved changes.
951    *
952    * @param {tinymce.Editor} ed The TinyMCE editor instance.
953    * @param {Event} ev The DOM Event object.
954    */
955   edSubmit: function (ed, ev) {
956     // Check if PaintWeb is active.
957     if (!targetImage || !paintwebInstance) {
958       return;
959     }
961     // Check if the image has been modified.
962     if (!paintwebInstance.image.modified) {
963       // If not, then hide PaintWeb so we can update the target image.
964       paintwebHide();
965       // Save the textarea content once again.
966       ed.save();
967       return;
968     }
970     // The image is not saved, thus we prevent form submission.
971     ev.preventDefault();
973     if (pluginBar) {
974       var str = ed.getLang('paintweb.submitUnsaved',
975             'The image is not saved! You cannot submit the form. Please save ' +
976             'the image changes, or cancel image editing, then try again.');
977       pluginBar.firstChild.innerHTML = str;
979       if (pluginBarTimeout) {
980         clearTimeout(pluginBarTimeout);
981       }
983       pluginBarTimeout = setTimeout(pluginBarResetContent, pluginBarDelay);
985       // tabIndex is needed so we can focus and scroll to the plugin bar.
986       pluginBar.tabIndex = 5;
987       pluginBar.focus();
988       pluginBar.tabIndex = -1;
989     }
991     if (typeof paintwebConfig.tinymce.onSubmitUnsaved === 'function') {
992       paintwebConfig.tinymce.onSubmitUnsaved(ev, ed, paintwebInstance);
993     }
994   },
996   /**
997    * This is the <code>contextmenu</code> event handler for the ContextMenu 
998    * plugin provided in the default TinyMCE installation.
999    *
1000    * @param {tinymce.plugin.contextmenu} plugin Instance of the ContextMenu 
1001    * plugin of TinyMCE.
1002    * @param {tinymce.ui.DropMenu} menu The dropmenu instance.
1003    * @param {Element} elem The selected element.
1004    */
1005   pluginContextMenu: function (plugin, menu, elem) {
1006     if (checkEditableImage(elem)) {
1007       menu.add({
1008         title: 'paintweb.contextMenuEdit',
1009         cmd:   'paintwebEdit',
1010         image: pluginUrl + '/img/paintweb2.gif'
1011       });
1012     }
1013   },
1015   /**
1016    * Returns information about the plugin as a name/value array.
1017    * The current keys are longname, author, authorurl, infourl and version.
1018    *
1019    * @returns {Object} Name/value array containing information about the plugin.
1020    */
1021   getInfo: function () {
1022     return {
1023       longname:  'PaintWeb - online painting application',
1024       author:    'Mihai Şucan',
1025       authorurl: 'http://www.robodesign.ro/mihai',
1026       infourl:   'http://code.google.com/p/paintweb',
1027       version:   '0.9'
1028     };
1029   }
1032 // Register the PaintWeb plugin
1033 tinymce.PluginManager.add('paintweb', tinymce.plugins.paintweb);
1034 })();
1036 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: