3a65dc2f0e9dfd321cee6d362b827185fb13dc33
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / DefaultImage / default-image.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2008-2012 Stanislas Rolland <typo3(arobas)sjbr.ca>
5 * All rights reserved
6 *
7 * This script is part of the TYPO3 project. The TYPO3 project is
8 * free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * The GNU General Public License can be found at
14 * http://www.gnu.org/copyleft/gpl.html.
15 * A copy is found in the textfile GPL.txt and important notices to the license
16 * from the author is found in LICENSE.txt distributed with these scripts.
17 *
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This script is a modified version of a script published under the htmlArea License.
25 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /*
30 * Image Plugin for TYPO3 htmlArea RTE
31 */
32 HTMLArea.DefaultImage = Ext.extend(HTMLArea.Plugin, {
33 /*
34 * This function gets called by the class constructor
35 */
36 configurePlugin: function (editor) {
37 this.baseURL = this.editorConfiguration.baseURL;
38 this.pageTSConfiguration = this.editorConfiguration.buttons.image;
39 if (this.pageTSConfiguration && this.pageTSConfiguration.properties && this.pageTSConfiguration.properties.removeItems) {
40 this.removeItems = this.pageTSConfiguration.properties.removeItems.split(',');
41 var layout = 0;
42 var padding = 0;
43 for (var i = 0, n = this.removeItems.length; i < n; ++i) {
44 this.removeItems[i] = this.removeItems[i].replace(/(?:^\s+|\s+$)/g, '');
45 if (/^(align|border|float)$/i.test(this.removeItems[i])) {
46 ++layout;
47 }
48 if (/^(paddingTop|paddingRight|paddingBottom|paddingLeft)$/i.test(this.removeItems[i])) {
49 ++padding;
50 }
51 }
52 if (layout == 3) {
53 this.removeItems.push('layout');
54 }
55 if (layout == 4) {
56 this.removeItems.push('padding');
57 }
58 this.removeItems = new RegExp( '^(' + this.removeItems.join('|') + ')$', 'i');
59 } else {
60 this.removeItems = new RegExp( '^(none)$', 'i');
61 }
62 /*
63 * Registering plugin "About" information
64 */
65 var pluginInformation = {
66 version : '2.3',
67 developer : 'Stanislas Rolland',
68 developerUrl : 'http://www.sjbr.ca/',
69 copyrightOwner : 'Stanislas Rolland',
70 sponsor : 'SJBR',
71 sponsorUrl : 'http://www.sjbr.ca/',
72 license : 'GPL'
73 };
74 this.registerPluginInformation(pluginInformation);
75 /*
76 * Registering the button
77 */
78 var buttonId = 'InsertImage';
79 var buttonConfiguration = {
80 id : buttonId,
81 tooltip : this.localize('insertimage'),
82 action : 'onButtonPress',
83 hotKey : (this.pageTSConfiguration ? this.pageTSConfiguration.hotKey : null),
84 dialog : true,
85 iconCls : 'htmlarea-action-image-edit'
86 };
87 this.registerButton(buttonConfiguration);
88 return true;
89 },
90 /*
91 * Sets of default configuration values for dialogue form fields
92 */
93 configDefaults: {
94 combo: {
95 editable: true,
96 selectOnFocus: true,
97 typeAhead: true,
98 triggerAction: 'all',
99 forceSelection: true,
100 mode: 'local',
101 valueField: 'value',
102 displayField: 'text',
103 helpIcon: true,
104 tpl: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
105 }
106 },
107 /*
108 * This function gets called when the button was pressed.
109 *
110 * @param object editor: the editor instance
111 * @param string id: the button id or the key
112 *
113 * @return boolean false if action is completed
114 */
115 onButtonPress: function(editor, id) {
116 // Could be a button or its hotkey
117 var buttonId = this.translateHotKey(id);
118 buttonId = buttonId ? buttonId : id;
119 this.image = this.editor.getSelection().getParentElement();
120 if (this.image && !/^img$/i.test(this.image.nodeName)) {
121 this.image = null;
122 }
123 if (this.image) {
124 this.parameters = {
125 base: this.baseURL,
126 url: this.image.getAttribute('src'),
127 alt: this.image.alt,
128 border: isNaN(parseInt(this.image.style.borderWidth)) ? '' : parseInt(this.image.style.borderWidth),
129 align: this.image.style.verticalAlign ? this.image.style.verticalAlign : '',
130 paddingTop: isNaN(parseInt(this.image.style.paddingTop)) ? '' : parseInt(this.image.style.paddingTop),
131 paddingRight: isNaN(parseInt(this.image.style.paddingRight)) ? '' : parseInt(this.image.style.paddingRight),
132 paddingBottom: isNaN(parseInt(this.image.style.paddingBottom)) ? '' : parseInt(this.image.style.paddingBottom),
133 paddingLeft: isNaN(parseInt(this.image.style.paddingLeft)) ? '' : parseInt(this.image.style.paddingLeft),
134 cssFloat: Ext.isIE ? this.image.style.styleFloat : this.image.style.cssFloat
135 };
136 } else {
137 this.parameters = {
138 base: this.baseURL,
139 url: '',
140 alt: '',
141 border: '',
142 align: '',
143 paddingTop: '',
144 paddingRight: '',
145 paddingBottom: '',
146 paddingLeft: '',
147 cssFloat: ''
148 };
149 }
150 // Open dialogue window
151 this.openDialogue(
152 buttonId,
153 this.getButton(buttonId).tooltip.title,
154 this.getWindowDimensions(
155 {
156 width: 460,
157 height:300
158 },
159 buttonId
160 ),
161 this.buildTabItems()
162 );
163 return false;
164 },
165 /*
166 * Open the dialogue window
167 *
168 * @param string buttonId: the button id
169 * @param string title: the window title
170 * @param integer dimensions: the opening width of the window
171 * @param object tabItems: the configuration of the tabbed panel
172 *
173 * @return void
174 */
175 openDialogue: function (buttonId, title, dimensions, tabItems) {
176 this.dialog = new Ext.Window({
177 title: this.localize(title) || title,
178 cls: 'htmlarea-window',
179 border: false,
180 width: dimensions.width,
181 height: 'auto',
182 iconCls: this.getButton(buttonId).iconCls,
183 listeners: {
184 close: {
185 fn: this.onClose,
186 scope: this
187 }
188 },
189 items: {
190 xtype: 'tabpanel',
191 itemId: 'tabpanel',
192 activeTab: 0,
193 defaults: {
194 xtype: 'container',
195 layout: 'form',
196 defaults: {
197 labelWidth: 100
198 }
199 },
200 listeners: {
201 tabchange: {
202 fn: this.syncHeight,
203 scope: this
204 }
205 },
206 items: tabItems
207 },
208 buttons: [
209 this.buildButtonConfig('OK', this.onOK),
210 this.buildButtonConfig('Cancel', this.onCancel)
211 ]
212 });
213 this.show();
214 },
215 /*
216 * Build the configuration of the the tab items
217 *
218 * @return array the configuration array of tab items
219 */
220 buildTabItems: function () {
221 var tabItems = [];
222 // General tab
223 tabItems.push({
224 title: this.localize('General'),
225 items: [{
226 xtype: 'fieldset',
227 defaultType: 'textfield',
228 defaults: {
229 helpIcon: true,
230 width: 300,
231 labelSeparator: ''
232 },
233 items: [{
234 itemId: 'url',
235 fieldLabel: this.localize('Image URL:'),
236 value: this.parameters.url,
237 helpTitle: this.localize('Enter the image URL here')
238 },{
239 itemId: 'alt',
240 fieldLabel: this.localize('Alternate text:'),
241 value: this.parameters.alt,
242 helpTitle: this.localize('For browsers that dont support images')
243 }
244 ]
245 },{
246 xtype: 'fieldset',
247 title: this.localize('Image Preview'),
248 items: [{
249 // The preview iframe
250 xtype: 'box',
251 itemId: 'image-preview',
252 autoEl: {
253 name: 'ipreview',
254 tag: 'iframe',
255 cls: 'image-preview',
256 src: this.parameters.url
257 }
258 },{
259 xtype: 'button',
260 minWidth: 150,
261 text: this.localize('Preview'),
262 itemId: 'preview',
263 style: {
264 marginTop: '5px',
265 'float': 'right'
266 },
267 listeners: {
268 click: {
269 fn: this.onPreviewClick,
270 scope: this
271 }
272 }
273 }
274 ]
275 }
276 ]
277 });
278 // Layout tab
279 if (!this.removeItems.test('layout')) {
280 tabItems.push({
281 title: this.localize('Layout'),
282 items: [{
283 xtype: 'fieldset',
284 defaultType: 'textfield',
285 defaults: {
286 helpIcon: true,
287 width: 250,
288 labelSeparator: ''
289 },
290 items: [
291 Ext.apply({
292 xtype: 'combo',
293 fieldLabel: this.localize('Image alignment:'),
294 itemId: 'align',
295 value: this.parameters.align,
296 helpTitle: this.localize('Positioning of this image'),
297 store: new Ext.data.ArrayStore({
298 autoDestroy: true,
299 fields: [ { name: 'text'}, { name: 'value'}],
300 data: [
301 [this.localize('Not set'), ''],
302 [this.localize('Bottom'), 'bottom'],
303 [this.localize('Middle'), 'middle'],
304 [this.localize('Top'), 'top']
305 ]
306 }),
307 hidden: this.removeItems.test('align'),
308 hideLabel: this.removeItems.test('align')
309 }, this.configDefaults['combo'])
310 ,{
311 itemId: 'border',
312 fieldLabel: this.localize('Border thickness:'),
313 width: 100,
314 value: this.parameters.border,
315 helpTitle: this.localize('Leave empty for no border'),
316 hidden: this.removeItems.test('border'),
317 hideLabel: this.removeItems.test('border')
318 },
319 Ext.apply({
320 xtype: 'combo',
321 fieldLabel: this.localize('Float:'),
322 itemId: 'cssFloat',
323 value: this.parameters.cssFloat,
324 helpTitle: this.localize('Where the image should float'),
325 store: new Ext.data.ArrayStore({
326 autoDestroy: true,
327 fields: [ { name: 'text'}, { name: 'value'}],
328 data: [
329 [this.localize('Not set'), ''],
330 [this.localize('Non-floating'), 'none'],
331 [this.localize('Left'), 'left'],
332 [this.localize('Right'), 'right']
333 ]
334 }),
335 hidden: this.removeItems.test('float'),
336 hideLabel: this.removeItems.test('float')
337 }, this.configDefaults['combo'])
338 ]
339 }]
340 });
341 }
342 // Padding tab
343 if (!this.removeItems.test('padding')) {
344 tabItems.push({
345 title: this.localize('Spacing and padding'),
346 items: [{
347 xtype: 'fieldset',
348 defaultType: 'textfield',
349 defaults: {
350 helpIcon: true,
351 width: 100,
352 labelSeparator: ''
353 },
354 items: [{
355 itemId: 'paddingTop',
356 fieldLabel: this.localize('Top:'),
357 value: this.parameters.paddingTop,
358 helpTitle: this.localize('Top padding'),
359 hidden: this.removeItems.test('paddingTop'),
360 hideLabel: this.removeItems.test('paddingTop')
361 },{
362 itemId: 'paddingRight',
363 fieldLabel: this.localize('Right:'),
364 value: this.parameters.paddingRight,
365 helpTitle: this.localize('Right padding'),
366 hidden: this.removeItems.test('paddingRight'),
367 hideLabel: this.removeItems.test('paddingRight')
368 },{
369 itemId: 'paddingBottom',
370 fieldLabel: this.localize('Bottom:'),
371 value: this.parameters.paddingBottom,
372 helpTitle: this.localize('Bottom padding'),
373 hidden: this.removeItems.test('paddingBottom'),
374 hideLabel: this.removeItems.test('paddingBottom')
375 },{
376 itemId: 'paddingLeft',
377 fieldLabel: this.localize('Left:'),
378 value: this.parameters.paddingLeft,
379 helpTitle: this.localize('Left padding'),
380 hidden: this.removeItems.test('paddingLeft'),
381 hideLabel: this.removeItems.test('paddingLeft')
382 }
383 ]
384 }]
385 });
386 }
387 return tabItems;
388 },
389 /*
390 * Handler invoked when the Preview button is clicked
391 */
392 onPreviewClick: function () {
393 var tabPanel = this.dialog.find('itemId', 'tabpanel')[0];
394 var urlField = this.dialog.find('itemId', 'url')[0];
395 var url = urlField.getValue().trim();
396 if (url) {
397 try {
398 window.ipreview.location.replace(url);
399 } catch (e) {
400 TYPO3.Dialog.InformationDialog({
401 title: this.localize('Image Preview'),
402 msg: this.localize('image_url_invalid'),
403 fn: function () { tabPanel.setActiveTab(0); urlField.focus(); }
404 });
405 }
406 } else {
407 TYPO3.Dialog.InformationDialog({
408 title: this.localize('Image Preview'),
409 msg: this.localize('image_url_first'),
410 fn: function () { tabPanel.setActiveTab(0); urlField.focus(); }
411 });
412 }
413 return false;
414 },
415 /*
416 * Handler invoked when the OK button is clicked
417 */
418 onOK: function () {
419 var urlField = this.dialog.find('itemId', 'url')[0];
420 var url = urlField.getValue().trim();
421 if (url) {
422 var fieldNames = ['url', 'alt', 'align', 'border', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'cssFloat'];
423 Ext.each(fieldNames, function (fieldName) {
424 var field = this.dialog.find('itemId', fieldName)[0];
425 if (field && !field.hidden) {
426 this.parameters[fieldName] = field.getValue();
427 }
428 }, this);
429 this.insertImage();
430 this.close();
431 } else {
432 var tabPanel = this.dialog.find('itemId', 'tabpanel')[0];
433 TYPO3.Dialog.InformationDialog({
434 title: this.localize('image_url'),
435 msg: this.localize('image_url_required'),
436 fn: function () { tabPanel.setActiveTab(0); urlField.focus(); }
437 });
438 }
439 return false;
440 },
441 /*
442 * Insert the image
443 */
444 insertImage: function() {
445 this.restoreSelection();
446 var image = this.image;
447 if (!image) {
448 var range = this.editor.getSelection().createRange();
449 this.editor.getSelection().execCommand('InsertImage', false, this.parameters.url);
450 if (Ext.isWebKit) {
451 this.editor.getDomNode().cleanAppleStyleSpans(this.editor.document.body);
452 }
453 if (Ext.isIE) {
454 image = range.parentElement();
455 if (!/^img$/i.test(image.nodeName)) {
456 image = image.previousSibling;
457 }
458 this.editor.getSelection().selectNode(image);
459 } else {
460 var range = this.editor.getSelection().createRange();
461 image = range.startContainer;
462 image = image.lastChild;
463 while (image && !/^img$/i.test(image.nodeName)) {
464 image = image.previousSibling;
465 }
466 }
467 } else {
468 image.src = this.parameters.url;
469 }
470 if (/^img$/i.test(image.nodeName)) {
471 Ext.iterate(this.parameters, function (fieldName, value) {
472 switch (fieldName) {
473 case 'alt':
474 image.alt = value;
475 break;
476 case 'border':
477 if (parseInt(value)) {
478 image.style.borderWidth = parseInt(value) + 'px';
479 image.style.borderStyle = 'solid';
480 } else {
481 image.style.borderWidth = '';
482 image.style.borderStyle = 'none';
483 }
484 break;
485 case 'align':
486 image.style.verticalAlign = value;
487 break;
488 case 'paddingTop':
489 case 'paddingRight':
490 case 'paddingBottom':
491 case 'paddingLeft':
492 if (parseInt(value)) {
493 image.style[fieldName] = parseInt(value) + 'px';
494 } else {
495 image.style[fieldName] = '';
496 }
497 break;
498 case 'cssFloat':
499 if (Ext.isIE) {
500 image.style.styleFloat = value;
501 } else {
502 image.style.cssFloat = value;
503 }
504 break;
505 }
506 });
507 }
508 },
509 /*
510 * This function gets called when the toolbar is updated
511 */
512 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
513 if (mode === 'wysiwyg' && this.editor.isEditable() && button.itemId === 'InsertImage' && !button.disabled) {
514 var image = this.editor.getSelection().getParentElement();
515 if (image && !/^img$/i.test(image.nodeName)) {
516 image = null;
517 }
518 if (image) {
519 button.setTooltip({ title: this.localize('Modify image') });
520 } else {
521 button.setTooltip({ title: this.localize('Insert image') });
522 }
523 }
524 }
525 });