a976b609f3e1eea32257d3a7badbdac2456ec270
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / TYPO3Color / typo3color.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2004-2010 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 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /*
28 * TYPO3 Color Plugin for TYPO3 htmlArea RTE
29 *
30 * TYPO3 SVN ID: $Id$
31 */
32 HTMLArea.TYPO3Color = HTMLArea.Plugin.extend({
33 constructor: function(editor, pluginName) {
34 this.base(editor, pluginName);
35 },
36 /*
37 * This function gets called by the class constructor
38 */
39 configurePlugin: function(editor) {
40 this.buttonsConfiguration = this.editorConfiguration.buttons;
41 this.colorsConfiguration = this.editorConfiguration.colors;
42 this.disableColorPicker = this.editorConfiguration.disableColorPicker;
43 // Coloring will use the style attribute
44 if (this.editor.plugins.TextStyle && this.editor.plugins.TextStyle.instance) {
45 this.editor.plugins.TextStyle.instance.addAllowedAttribute('style');
46 this.allowedAttributes = this.editor.plugins.TextStyle.instance.allowedAttributes;
47 }
48 if (this.editor.plugins.InlineElements && this.editor.plugins.InlineElements.instance) {
49 this.editor.plugins.InlineElements.instance.addAllowedAttribute('style');
50 if (!this.allowedAllowedAttributes) {
51 this.allowedAttributes = this.editor.plugins.InlineElements.instance.allowedAttributes;
52 }
53 }
54 if (this.editor.plugins.BlockElements && this.editor.plugins.BlockElements.instance) {
55 this.editor.plugins.BlockElements.instance.addAllowedAttribute('style');
56 }
57 if (!this.allowedAttributes) {
58 this.allowedAttributes = new Array('id', 'title', 'lang', 'xml:lang', 'dir', 'class', 'style');
59 if (Ext.isIE) {
60 this.allowedAttributes.push('className');
61 }
62 }
63 /*
64 * Registering plugin "About" information
65 */
66 var pluginInformation = {
67 version : '4.1',
68 developer : 'Stanislas Rolland',
69 developerUrl : 'http://www.sjbr.ca/',
70 copyrightOwner : 'Stanislas Rolland',
71 sponsor : 'SJBR',
72 sponsorUrl : 'http://www.sjbr.ca/',
73 license : 'GPL'
74 };
75 this.registerPluginInformation(pluginInformation);
76 /*
77 * Registering the buttons
78 */
79 var buttonList = this.buttonList, buttonId;
80 for (var i = 0; i < buttonList.length; ++i) {
81 var button = buttonList[i];
82 buttonId = button[0];
83 var buttonConfiguration = {
84 id : buttonId,
85 tooltip : this.localize(buttonId),
86 iconCls : 'htmlarea-action-' + button[2],
87 action : 'onButtonPress',
88 hotKey : (this.buttonsConfiguration[button[1]] ? this.buttonsConfiguration[button[1]].hotKey : null),
89 dialog : true
90 };
91 this.registerButton(buttonConfiguration);
92 }
93 return true;
94 },
95 /*
96 * The list of buttons added by this plugin
97 */
98 buttonList: [
99 ['ForeColor', 'textcolor', 'color-foreground'],
100 ['HiliteColor', 'bgcolor', 'color-background']
101 ],
102 /*
103 * Conversion object: button name to corresponding style property name
104 */
105 styleProperty: {
106 ForeColor : 'color',
107 HiliteColor : 'backgroundColor'
108 },
109 colors: [
110 '000000', '222222', '444444', '666666', '999999', 'BBBBBB', 'DDDDDD', 'FFFFFF',
111 '660000', '663300', '996633', '003300', '003399', '000066', '330066', '660066',
112 '990000', '993300', 'CC9900', '006600', '0033FF', '000099', '660099', '990066',
113 'CC0000', 'CC3300', 'FFCC00', '009900', '0066FF', '0000CC', '663399', 'CC0099',
114 'FF0000', 'FF3300', 'FFFF00', '00CC00', '0099FF', '0000FF', '9900CC', 'FF0099',
115 'CC3333', 'FF6600', 'FFFF33', '00FF00', '00CCFF', '3366FF', '9933FF', 'FF00FF',
116 'FF6666', 'FF6633', 'FFFF66', '66FF66', '00FFFF', '3399FF', '9966FF', 'FF66FF',
117 'FF9999', 'FF9966', 'FFFF99', '99FF99', '99FFFF', '66CCFF', '9999FF', 'FF99FF',
118 'FFCCCC', 'FFCC99', 'FFFFCC', 'CCFFCC', 'CCFFFF', '99CCFF', 'CCCCFF', 'FFCCFF'
119 ],
120 /*
121 * This function gets called when the button was pressed.
122 *
123 * @param object editor: the editor instance
124 * @param string id: the button id or the key
125 * @param object target: the target element of the contextmenu event, when invoked from the context menu
126 *
127 * @return boolean false if action is completed
128 */
129 onButtonPress: function (editor, id, target) {
130 // Could be a button or its hotkey
131 var buttonId = this.translateHotKey(id);
132 buttonId = buttonId ? buttonId : id;
133 var element = this.editor.getParentElement();
134 this.openDialogue(
135 buttonId + '_title',
136 {
137 element: element,
138 buttonId: buttonId
139 },
140 this.getWindowDimensions({ width: 350}, buttonId),
141 this.buildItemsConfig(element, buttonId),
142 this.setColor
143 );
144 },
145 /*
146 * Build the window items config
147 */
148 buildItemsConfig: function (element, buttonId) {
149 var itemsConfig = [];
150 var paletteItems = [];
151 // Standard colors palette (boxed)
152 if (!this.disableColorPicker) {
153 paletteItems.push({
154 xtype: 'container',
155 items: {
156 xtype: 'colorpalette',
157 itemId: 'color-palette',
158 colors: this.colors,
159 cls: 'color-palette',
160 value: (element && element.style[this.styleProperty[buttonId]]) ? HTMLArea.util.Color.colorToHex(element.style[this.styleProperty[buttonId]]).substr(1, 6) : '',
161 allowReselect: true,
162 listeners: {
163 select: {
164 fn: this.onSelect,
165 scope: this
166 }
167 }
168 }
169 });
170 }
171 // Custom colors palette (boxed)
172 if (this.colorsConfiguration) {
173 paletteItems.push({
174 xtype: 'container',
175 items: {
176 xtype: 'colorpalette',
177 itemId: 'custom-colors',
178 cls: 'htmlarea-custom-colors',
179 colors: this.colorsConfiguration,
180 value: (element && element.style[this.styleProperty[buttonId]]) ? HTMLArea.util.Color.colorToHex(element.style[this.styleProperty[buttonId]]).substr(1, 6) : '',
181 tpl: new Ext.XTemplate(
182 '<tpl for="."><a href="#" class="color-{1}" hidefocus="on"><em><span style="background:#{1}" unselectable="on">&#160;</span></em><span unselectable="on">{0}<span></a></tpl>'
183 ),
184 allowReselect: true,
185 listeners: {
186 select: {
187 fn: this.onSelect,
188 scope: this
189 }
190 }
191 }
192 });
193 }
194 itemsConfig.push({
195 xtype: 'container',
196 layout: 'hbox',
197 items: paletteItems
198 });
199 itemsConfig.push({
200 xtype: 'displayfield',
201 itemId: 'show-color',
202 cls: 'show-color',
203 width: 60,
204 height: 22,
205 helpTitle: this.localize(buttonId)
206 });
207 itemsConfig.push({
208 itemId: 'color',
209 cls: 'color',
210 width: 60,
211 minValue: 0,
212 value: (element && element.style[this.styleProperty[buttonId]]) ? HTMLArea.util.Color.colorToHex(element.style[this.styleProperty[buttonId]]).substr(1, 6) : '',
213 enableKeyEvents: true,
214 fieldLabel: this.localize(buttonId),
215 helpTitle: this.localize(buttonId),
216 listeners: {
217 change: {
218 fn: this.onChange,
219 scope: this
220 },
221 afterrender: {
222 fn: this.onAfterRender,
223 scope: this
224 }
225 }
226 });
227 return {
228 xtype: 'fieldset',
229 title: this.localize('color_title'),
230 defaultType: 'textfield',
231 labelWidth: 175,
232 defaults: {
233 helpIcon: false
234 },
235 items: itemsConfig
236 };
237 },
238 /*
239 * On select handler: set the value of the color field, display the new color and update the other palette
240 */
241 onSelect: function (palette, color) {
242 this.dialog.find('itemId', 'color')[0].setValue(color);
243 this.showColor(color);
244 if (palette.getItemId() == 'color-palette') {
245 var customPalette = this.dialog.find('itemId', 'custom-colors')[0];
246 if (customPalette) {
247 customPalette.deSelect();
248 }
249 } else {
250 var standardPalette = this.dialog.find('itemId', 'color-palette')[0];
251 if (standardPalette) {
252 standardPalette.deSelect();
253 }
254 }
255 },
256 /*
257 * Display the selected color
258 */
259 showColor: function (color) {
260 if (color) {
261 var newColor = color;
262 if (newColor.indexOf('#') == 0) {
263 newColor = newColor.substr(1);
264 }
265 this.dialog.find('itemId', 'show-color')[0].el.setStyle('backgroundColor', HTMLArea.util.Color.colorToHex(parseInt(newColor, 16)));
266 }
267 },
268 /*
269 * On change handler: display the new color and select it in the palettes, if it exists
270 */
271 onChange: function (field, value) {
272 if (value) {
273 var color = value.toUpperCase();
274 this.showColor(color);
275 var standardPalette = this.dialog.find('itemId', 'color-palette')[0];
276 if (standardPalette) {
277 standardPalette.select(color);
278 }
279 var customPalette = this.dialog.find('itemId', 'custom-colors')[0];
280 if (customPalette) {
281 customPalette.select(color);
282 }
283 }
284 },
285 /*
286 * On after render handler: display the color
287 */
288 onAfterRender: function (field) {
289 if (!Ext.isEmpty(field.getValue())) {
290 this.showColor(field.getValue());
291 }
292 },
293 /*
294 * Open the dialogue window
295 *
296 * @param string title: the window title
297 * @param object arguments: some arguments for the handler
298 * @param integer dimensions: the opening width of the window
299 * @param object tabItems: the configuration of the tabbed panel
300 * @param function handler: handler when the OK button if clicked
301 *
302 * @return void
303 */
304 openDialogue: function (title, arguments, dimensions, items, handler) {
305 if (this.dialog) {
306 this.dialog.close();
307 }
308 this.dialog = new Ext.Window({
309 title: this.localize(title),
310 arguments: arguments,
311 cls: 'htmlarea-window',
312 border: false,
313 width: dimensions.width,
314 height: 'auto',
315 // As of ExtJS 3.1, JS error with IE when the window is resizable
316 resizable: !Ext.isIE,
317 iconCls: this.getButton(arguments.buttonId).iconCls,
318 listeners: {
319 close: {
320 fn: this.onClose,
321 scope: this
322 }
323 },
324 items: {
325 xtype: 'container',
326 layout: 'form',
327 defaults: {
328 labelWidth: 150
329 },
330 items: items
331 },
332 buttons: [
333 this.buildButtonConfig('OK', handler),
334 this.buildButtonConfig('Cancel', this.onCancel)
335 ]
336 });
337 this.show();
338 },
339 /*
340 * Set the color and close the dialogue
341 */
342 setColor: function(button, event) {
343 this.restoreSelection();
344 var buttonId = this.dialog.arguments.buttonId;
345 var color = this.dialog.find('itemId', 'color')[0].getValue();
346 if (color) {
347 if (color.indexOf('#') == 0) {
348 color = color.substr(1);
349 }
350 color = HTMLArea.util.Color.colorToHex(parseInt(color, 16));
351 }
352 this.editor.focus();
353 var element,
354 fullNodeSelected = false;
355 var selection = this.editor._getSelection();
356 var range = this.editor._createRange(selection);
357 var parent = this.editor.getParentElement(selection, range);
358 var selectionEmpty = this.editor._selectionEmpty(selection);
359 var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
360 if (!selectionEmpty) {
361 var ancestors = this.editor.getAllAncestors();
362 var fullySelectedNode = this.editor.getFullySelectedNode(selection, range, ancestors);
363 if (fullySelectedNode) {
364 fullNodeSelected = true;
365 parent = fullySelectedNode;
366 }
367 }
368 if (selectionEmpty || fullNodeSelected) {
369 element = parent;
370 // Set the color in the style attribute
371 element.style[this.styleProperty[buttonId]] = color;
372 // Remove the span tag if it has no more attribute
373 if ((element.nodeName.toLowerCase() === 'span') && !HTMLArea.hasAllowedAttributes(element, this.allowedAttributes)) {
374 this.editor.removeMarkup(element);
375 }
376 } else if (statusBarSelection) {
377 var element = statusBarSelection;
378 // Set the color in the style attribute
379 element.style[this.styleProperty[buttonId]] = color;
380 // Remove the span tag if it has no more attribute
381 if ((element.nodeName.toLowerCase() === 'span') && !HTMLArea.hasAllowedAttributes(element, this.allowedAttributes)) {
382 this.editor.removeMarkup(element);
383 }
384 } else if (color && this.editor.endPointsInSameBlock()) {
385 var element = this.editor._doc.createElement('span');
386 // Set the color in the style attribute
387 element.style[this.styleProperty[buttonId]] = color;
388 this.editor.wrapWithInlineElement(element, selection, range);
389 }
390 if (!Ext.isIE) {
391 range.detach();
392 }
393 this.close();
394 event.stopEvent();
395 },
396 /*
397 * This function gets called when the toolbar is updated
398 */
399 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors, endPointsInSameBlock) {
400 if (mode === 'wysiwyg' && this.editor.isEditable()) {
401 var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null,
402 parentElement = statusBarSelection ? statusBarSelection : this.editor.getParentElement(),
403 disabled = !endPointsInSameBlock || (selectionEmpty && /^body$/i.test(parentElement.nodeName));
404 button.setInactive(!parentElement.style[this.styleProperty[button.itemId]]);
405 button.setDisabled(disabled);
406 }
407 }
408 });