2 * Copyright (C) 2009 Mihai Şucan
4 * This file is part of PaintWeb.
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.
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.
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/>.
19 * $URL: http://code.google.com/p/paintweb $
20 * $Date: 2009-11-04 20:18:05 +0200 $
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.
29 // The plugin URL. This points to the location of this TinyMCE plugin.
32 // Reference to the DOM element of the overlay button displayed on top of the
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.
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
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.
81 // Tells if the current image has been ever updated using "image save".
86 if (!window.tinymce) {
87 alert('It looks like the PaintWeb plugin for TinyMCE cannot run.' +
88 'TinyMCE was not detected!');
92 // Basic functionality used by PaintWeb.
93 if (!window.XMLHttpRequest || !window.getComputedStyle ||
94 !document.createElement('canvas').getContext) {
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);
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) {
128 * Load PaintWeb. This function tells TinyMCE to load the PaintWeb script.
130 function paintwebLoad () {
131 if (window.PaintWeb) {
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.
146 function paintwebLoaded () {
147 if (paintwebInstance) {
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;
165 config.imageLoad = targetImage;
166 config.guiPlaceholder = pwContainer;
169 config.lang = targetEditor.getParam('language');
172 for (var prop in config) {
173 paintwebConfig[prop] = config[prop];
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
188 * @param {pwlib.appEvent.appInit} ev The PaintWeb application event object.
190 function paintwebInitialized (ev) {
191 if (overlayButton && targetEditor) {
192 overlayButton.value = targetEditor.getLang('paintweb.overlayButton',
196 if (ev.state !== PaintWeb.INIT_DONE) {
197 alert('PaintWeb initialization failed! ' + ev.errorMessage);
198 paintwebInstance = null;
205 pwlib = window.pwlib;
206 paintwebInstance.events.add('imageSave', paintwebSave);
207 paintwebInstance.events.add('imageSaveResult', paintwebSaveResult);
210 paintwebInstance.events.add('viewportSizeChange',
211 paintwebViewportSizeChange);
218 * The <code>click</code> event handler for the Save button displayed on the
221 function pluginSaveButton () {
223 paintwebInstance.imageSave();
227 * The <code>click</code> event handler for the Cancel button displayed on the
230 function pluginCancelButton () {
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.
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.
244 function paintwebSave (ev) {
245 if (paintwebConfig.tinymce.imageSaveDataURL) {
248 var url = imgIsDataURL ? '-' : targetEditor.dom.getAttrib(targetImage,
251 paintwebInstance.events.dispatch(new pwlib.appEvent.imageSaveResult(true,
254 } else if (pluginBar) {
255 if (pluginBarTimeout) {
256 clearTimeout(pluginBarTimeout);
257 pluginBarTimeout = null;
260 pluginBar.firstChild.innerHTML
261 = targetEditor.getLang('paintweb.statusSavingImage', 'Saving image...');
266 * The <code>imageSaveResult</code> application event handler for PaintWeb.
268 * @param {pwlib.appEvent.imageSaveResult} ev The PaintWeb application event
271 function paintwebSaveResult (ev) {
276 // Update the status message in the "plugin bar".
279 pluginBar.firstChild.innerHTML
280 = targetEditor.getLang('paintweb.statusImageSaved',
281 'Image save was succesful!');
283 pluginBar.firstChild.innerHTML
284 = targetEditor.getLang('paintweb.statusImageSaveFailed',
285 'Image save failed!');
288 if (pluginBarTimeout) {
289 clearTimeout(pluginBarTimeout);
292 pluginBarTimeout = setTimeout(pluginBarResetContent, pluginBarDelay);
299 // store the new URL. When PaintWeb is closed, the image src attribute is
301 imgNewUrl = ev.urlNew;
305 pwSaveReturn = false;
312 * Reset the text content of the plugin bar.
314 function pluginBarResetContent () {
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
331 * @param {pwlib.appEvent.viewportSizeChange} ev The application event object.
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.
342 function paintwebEditStart () {
343 if (!checkEditableImage(targetImage)) {
348 if (pwDestroyTimer) {
349 clearTimeout(pwDestroyTimer);
350 pwDestroyTimer = null;
353 var pwStart = function () {
354 if (overlayButton && overlayButton.parentNode) {
355 overlayButton.value = targetEditor.getLang('paintweb.overlayLoading',
356 'Loading PaintWeb...');
359 if (paintwebInstance) {
360 paintwebInstance.imageLoad(targetImage);
367 var dataURLfilterLoaded = function () {
368 tinymce.dom.Event.remove(targetImage, 'load', dataURLfilterLoaded);
369 imgIsDataURL = false;
373 var src = targetEditor.dom.getAttrib(targetImage, 'src');
375 if (src.substr(0, 5) === 'data:') {
378 imgIsDataURL = false;
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),
391 if (window.console && console.log) {
392 console.log('TinyMCE.PaintWeb: failed to preload image data URL!');
397 success: function (result) {
403 result = tinymce.util.JSON.parse(result);
404 if (!result || !result.successful || !result.urlNew) {
409 imgNewUrl = targetImage.src;
410 tinymce.dom.Event.add(targetImage, 'load', dataURLfilterLoaded);
411 targetEditor.dom.setAttrib(targetImage, 'src', result.urlNew);
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"
432 * @param {String} [title]
434 function paintwebNewImage (width, height, bgrColor, alt, title) {
435 width = parseInt(width) || 0;
436 height = parseInt(height) || 0;
437 if (!width || !height) {
441 var canvas = tinymce.DOM.create('canvas', {
444 context = canvas.getContext('2d');
447 context.fillStyle = bgrColor;
448 context.fillRect(0, 0, width, height);
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() !==
461 targetEditor.dom.setAttrib(elem, 'alt', alt);
464 targetEditor.dom.setAttrib(elem, 'title', title);
466 elem.src = canvas.toDataURL();
467 elem.setAttribute('mce_src', elem.src);
468 elem.removeAttribute('id');
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.
483 function paintwebShow (ev) {
485 if (paintwebConfig.tinymce.syncViewportSize) {
486 rect = tinymce.DOM.getRect(targetEditor.getContentAreaContainer());
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();
497 if (rect && rect.w && rect.h) {
498 paintwebInstance.gui.resizeTo(rect.w + 'px', rect.h + 'px');
502 pluginBarResetContent();
504 var placeholder = paintwebConfig.guiPlaceholder;
505 if (!pluginBar.parentNode) {
506 placeholder.parentNode.insertBefore(pluginBar, placeholder);
512 * Hide PaintWeb from the screen. This hides the PaintWeb target object of the
513 * current instance, and displays back the TinyMCE container element.
515 function paintwebHide () {
516 paintwebInstance.gui.hide();
518 if (overlayButton && targetEditor) {
519 overlayButton.value = targetEditor.getLang('paintweb.overlayButton',
523 if (pluginBar && pluginBar.parentNode) {
524 targetContainer.parentNode.removeChild(pluginBar);
527 // Update the target image src attribute if needed.
529 // The tinymce.utl.URI class mangles data URLs.
530 if (imgNewUrl.substr(0, 5) !== 'data:') {
531 targetEditor.dom.setAttrib(targetImage, 'src', imgNewUrl);
533 targetImage.src = imgNewUrl;
534 if (targetImage.hasAttribute('mce_src')) {
535 targetImage.setAttribute('mce_src', imgNewUrl);
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) {
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);
559 targetEditor.dom.setAttrib(targetImage, 'src', src);
562 targetContainer.style.display = '';
566 targetEditor.focus();
568 if (!pwDestroyTimer) {
569 pwDestroyTimer = setTimeout(paintwebDestroy, pwDestroyDelay);
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.
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;
591 * The "paintwebEdit" command. This function is invoked when the user clicks the
592 * PaintWeb button on the toolbar.
594 function paintwebEditCommand () {
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();
609 targetContainer = this.getContainer();
612 // If PaintWeb won't start, then we create a new image.
613 if (!paintwebEditStart() && tag !== 'img') {
614 this.windowManager.open(
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)),
622 plugin_url: pluginUrl,
623 newImageFn: paintwebNewImage
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
634 * @param {HTMLImageElement} n The image element.
635 * @returns {Boolean} True if the image can be edited, or false otherwise.
637 function checkEditableImage (n) {
643 if (n.nodeName.toLowerCase() !== 'img' || !url) {
647 var pos = url.indexOf(':'),
648 proto = url.substr(0, pos + 1).toLowerCase();
650 if (proto === 'data:') {
654 if (proto !== 'http:' && proto !== 'https:') {
658 var host = url.replace(/^https?:\/\//i, '');
659 pos = host.indexOf('/');
661 host = host.substr(0, pos);
664 if (host !== window.location.host) {
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
678 function overlayButtonAdd (ed, n) {
679 if (!overlayButton || !ed || !n) {
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;
693 pNode = n.parentNode;
694 sibling = n.nextSibling;
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
716 function overlayButtonCleanup (ed) {
717 if (!overlayButton || !ed || !ed.getDoc) {
721 var root, elems, pNode;
724 if (overlayButton.parentNode) {
725 pNode = overlayButton.parentNode;
726 pNode.removeChild(overlayButton);
729 overlayButton._targetImage = null;
733 if (!root || !root.getElementsByClassName) {
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]);
745 // Load plugin specific language pack
746 tinymce.PluginManager.requireLangPack('paintweb');
748 tinymce.create('tinymce.plugins.paintweb', {
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
754 * @param {tinymce.Editor} ed Editor instance that the plugin is initialized
756 * @param {String} url Absolute URL to where the plugin is located.
758 init: function (ed, 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',
771 image: pluginUrl + '/img/paintweb2.gif'
774 // Add a node change handler which enables the PaintWeb button in the UI
775 // when an image is selected.
777 // In Opera, due to bug DSK-265135, we only listen for the keyup and
779 ed.onKeyUp.add(this.edNodeChange);
780 ed.onMouseUp.add(this.edNodeChange);
782 ed.onNodeChange.add(this.edNodeChange);
785 var config = ed.getParam('paintweb_config') || {};
786 if (!config.tinymce) {
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);
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
808 overlayButtonCleanup(ed);
810 ed.onKeyDown.addToTop(t.overlayButtonEvent);
811 ed.onMouseDown.addToTop(t.overlayButtonEvent);
814 overlayButton = tinymce.DOM.create('input', {
816 'class': 'paintweb_tinymce_overlayButton',
817 'style': 'position:absolute',
818 'value': ed.getLang('paintweb.overlayButton', 'Edit')});
821 // Handle the dblclick events for image elements, if the user wants it.
822 if (config.tinymce.dblclickHandler) {
823 ed.onDblClick.add(this.edDblClick);
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', {
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', {
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);
861 * The <code>nodeChange</code> event handler for the TinyMCE editor. This
862 * method provides visual feedback for editable image elements.
866 * @param {tinymce.Editor} ed The editor instance that the plugin is
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) {
879 var disabled = !checkEditableImage(n);
881 if (n.nodeName.toLowerCase() === 'img' && disabled) {
882 cm.setDisabled('paintwebEdit', true);
883 cm.setActive('paintwebEdit', false);
885 cm.setDisabled('paintwebEdit', false);
886 cm.setActive('paintwebEdit', !disabled);
889 if (!overlayButton) {
894 overlayButtonAdd(ed, n);
895 } else if (overlayButton._targetImage) {
896 overlayButton._targetImage = null;
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.
905 * @param {tinymce.Editor} ed The TinyMCE editor instance.
906 * @param {Event} ev The DOM Event object.
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) {
916 targetContainer = ed.getContainer();
917 targetImage = overlayButton._targetImage;
921 } else if (n && n.nodeName.toLowerCase() !== 'img') {
922 // ... otherwise make sure the document is clean.
923 overlayButtonCleanup(ed);
928 * The <code>dblclick</code> event handler for the editor. This method starts
929 * PaintWeb when the user double clicks an editable image element.
931 * @param {tinymce.Editor} ed The TinyMCE editor instance.
932 * @param {Event} ev The DOM Event object.
934 edDblClick: function (ed, ev) {
935 if (!targetImage && checkEditableImage(ev.target)) {
937 targetContainer = ed.getContainer();
938 targetImage = ev.target;
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.
952 * @param {tinymce.Editor} ed The TinyMCE editor instance.
953 * @param {Event} ev The DOM Event object.
955 edSubmit: function (ed, ev) {
956 // Check if PaintWeb is active.
957 if (!targetImage || !paintwebInstance) {
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.
965 // Save the textarea content once again.
970 // The image is not saved, thus we prevent form submission.
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);
983 pluginBarTimeout = setTimeout(pluginBarResetContent, pluginBarDelay);
985 // tabIndex is needed so we can focus and scroll to the plugin bar.
986 pluginBar.tabIndex = 5;
988 pluginBar.tabIndex = -1;
991 if (typeof paintwebConfig.tinymce.onSubmitUnsaved === 'function') {
992 paintwebConfig.tinymce.onSubmitUnsaved(ev, ed, paintwebInstance);
997 * This is the <code>contextmenu</code> event handler for the ContextMenu
998 * plugin provided in the default TinyMCE installation.
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.
1005 pluginContextMenu: function (plugin, menu, elem) {
1006 if (checkEditableImage(elem)) {
1008 title: 'paintweb.contextMenuEdit',
1009 cmd: 'paintwebEdit',
1010 image: pluginUrl + '/img/paintweb2.gif'
1016 * Returns information about the plugin as a name/value array.
1017 * The current keys are longname, author, authorurl, infourl and version.
1019 * @returns {Object} Name/value array containing information about the plugin.
1021 getInfo: function () {
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',
1032 // Register the PaintWeb plugin
1033 tinymce.PluginManager.add('paintweb', tinymce.plugins.paintweb);
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: