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