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