ff586b83f2cf54a9ad492f458f5e894d3c1dba5f
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / BlockStyle / block-style.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2007-2008 Stanislas Rolland <stanislas.rolland(arobas)fructifor.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 * Block Style Plugin for TYPO3 htmlArea RTE
31 *
32 * TYPO3 SVN ID: $Id: block-style.js $
33 */
34 BlockStyle = HTMLArea.Plugin.extend({
35
36 constructor : function(editor, pluginName) {
37 this.base(editor, pluginName);
38 },
39
40 /*
41 * This function gets called by the class constructor
42 */
43 configurePlugin : function(editor) {
44 this.cssLoaded = false;
45 this.cssTimeout = null;
46 this.cssParseCount = 0;
47 this.cssArray = new Object();
48
49 this.classesUrl = this.editorConfiguration.classesUrl;
50 this.pageTSconfiguration = this.editorConfiguration.buttons.blockstyle;
51 this.tags = this.pageTSconfiguration.tags;
52 if (!this.tags) {
53 this.tags = new Object();
54 }
55 if (typeof(this.editorConfiguration.classesTag) !== "undefined") {
56 if (this.editorConfiguration.classesTag.div) {
57 if (!this.tags.div) {
58 this.tags.div = new Object();
59 }
60 if (!this.tags.div.allowedClasses) {
61 this.tags.div.allowedClasses = this.editorConfiguration.classesTag.div;
62 }
63 }
64 if (this.editorConfiguration.classesTag.td) {
65 if (!this.tags.td) {
66 this.tags.td = new Object();
67 }
68 if (!this.tags.td.allowedClasses) {
69 this.tags.td.allowedClasses = this.editorConfiguration.classesTag.td;
70 }
71 }
72 if (this.editorConfiguration.classesTag.table) {
73 if (!this.tags.table) {
74 this.tags.table = new Object();
75 }
76 if (!this.tags.table.allowedClasses) {
77 this.tags.table.allowedClasses = this.editorConfiguration.classesTag.table;
78 }
79 }
80 }
81 this.showTagFreeClasses = this.pageTSconfiguration.showTagFreeClasses || this.editorConfiguration.showTagFreeClasses;
82 this.prefixLabelWithClassName = this.pageTSconfiguration.prefixLabelWithClassName;
83 this.postfixLabelWithClassName = this.pageTSconfiguration.postfixLabelWithClassName;
84
85 /* Registering plugin "About" information */
86 var pluginInformation = {
87 version : "1.1",
88 developer : "Stanislas Rolland",
89 developerUrl : "http://www.fructifor.ca/",
90 copyrightOwner : "Stanislas Rolland",
91 sponsor : "Fructifor Inc. " + this.localize("Technische Universitat Ilmenau"),
92 sponsorUrl : "http://www.tu-ilmenau.de/",
93 license : "GPL"
94 };
95 this.registerPluginInformation(pluginInformation);
96
97 /* Registering the dropdown list */
98 var dropDownId = "BlockStyle";
99 var dropDownConfiguration = {
100 id : dropDownId,
101 tooltip : this.localize(dropDownId + "-Tooltip"),
102 textMode : false,
103 options : {"":""},
104 action : "onChange",
105 refresh : "generate",
106 context : null
107 };
108 this.registerDropDown(dropDownConfiguration);
109
110 return true;
111 },
112
113 /*
114 * This function gets called when some block style was selected in the drop-down list
115 */
116 onChange : function(editor, dropDownId) {
117 var select = document.getElementById(this.editor._toolbarObjects[dropDownId].elementId);
118 var className = select.value;
119
120 this.editor.focusEditor();
121 var blocks = this.getSelectedBlocks();
122 for (var k = 0; k < blocks.length; ++k) {
123 var parent = blocks[k];
124 while (parent && !HTMLArea.isBlockElement(parent) && parent.nodeName.toLowerCase() != "img") {
125 parent = parent.parentNode;
126 }
127 if (!k) {
128 var tagName = parent.tagName.toLowerCase();
129 }
130 if (parent.tagName.toLowerCase() == tagName) {
131 this.applyClassChange(parent, className);
132 }
133 }
134 },
135
136 /*
137 * This function applies the class change to the node
138 */
139 applyClassChange : function (node, className) {
140 if (className == "none") {
141 var classNames = node.className.trim().split(" ");
142 for (var i = classNames.length; --i >= 0;) {
143 if (!HTMLArea.reservedClassNames.test(classNames[i])) {
144 HTMLArea._removeClass(node, classNames[i]);
145 break;
146 }
147 }
148 } else {
149 HTMLArea._addClass(node, className);
150 }
151 },
152
153 /*
154 * This function gets the list of selected blocks
155 */
156 getSelectedBlocks : function() {
157 var block, range, i = 0, blocks = [];
158 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
159 var selection = this.editor._getSelection();
160 try {
161 while (range = selection.getRangeAt(i++)) {
162 block = this.editor.getParentElement(selection, range);
163 blocks.push(block);
164 }
165 } catch(e) {
166 /* finished walking through selection */
167 }
168 } else {
169 blocks.push(this.editor.getParentElement());
170 }
171 return blocks;
172 },
173
174 /*
175 * This function gets called when the editor is generated
176 */
177 onGenerate : function() {
178 if (HTMLArea.is_gecko) {
179 this.generate(this.editor, "BlockStyle");
180 }
181 },
182
183 /*
184 * This function gets called when the toolbar is being updated
185 */
186 onUpdateToolbar : function() {
187 if (this.editor.getMode() === "wysiwyg") {
188 this.generate(this.editor, "BlockStyle");
189 }
190 },
191
192 /*
193 * This function gets called when the editor has changed its mode to "wysiwyg"
194 */
195 onMode : function(mode) {
196 if (this.editor.getMode() === "wysiwyg") {
197 this.generate(this.editor, "BlockStyle");
198 }
199 },
200
201 /*
202 * This function gets called on plugin generation, on toolbar update and on change mode
203 * Re-initiate the parsing of the style sheets, if not yet completed, and refresh our toolbar components
204 */
205 generate : function(editor, dropDownId) {
206 if (this.cssLoaded && this.editor.getMode() === "wysiwyg" && this.editor.isEditable()) {
207 this.updateValue(dropDownId);
208 } else {
209 if (this.cssTimeout) {
210 if (editor._iframe.contentWindow) {
211 editor._iframe.contentWindow.clearTimeout(this.cssTimeout);
212 } else {
213 window.clearTimeout(this.cssTimeout);
214 }
215 this.cssTimeout = null;
216 }
217 if (this.classesUrl && (typeof(HTMLArea.classesLabels) === "undefined")) {
218 this.getJavascriptFile(this.classesUrl);
219 }
220 this.buildCssArray(editor, dropDownId);
221 }
222 },
223
224 /*
225 * This function updates the current value of the dropdown list
226 */
227 updateValue : function(dropDownId) {
228 var select = document.getElementById(this.editor._toolbarObjects[dropDownId].elementId);
229 this.initializeDropDown(select);
230 select.disabled = true;
231
232 var classNames = new Array();
233 var tagName = null;
234 var parent = this.editor.getParentElement();
235 while (parent && !HTMLArea.isBlockElement(parent) && parent.nodeName.toLowerCase() != "img") {
236 parent = parent.parentNode;
237 }
238 if (parent) {
239 tagName = parent.nodeName.toLowerCase();
240 classNames = this.getClassNames(parent);
241 }
242 if (tagName && tagName !== "body"){
243 this.buildDropDownOptions(select, tagName);
244 this.setSelectedOption(select, classNames);
245 }
246 select.className = "";
247 if (select.disabled) {
248 select.className = "buttonDisabled";
249 }
250 },
251
252 /*
253 * This function returns an array containing the class names assigned to the node
254 */
255 getClassNames : function (node) {
256 var classNames = new Array();
257 if (node) {
258 if (node.className && /\S/.test(node.className)) {
259 classNames = node.className.trim().split(" ");
260 }
261 if (HTMLArea.reservedClassNames.test(node.className)) {
262 var cleanClassNames = new Array();
263 var j = -1;
264 for (var i = 0; i < classNames.length; ++i) {
265 if (!HTMLArea.reservedClassNames.test(classNames[i])) {
266 cleanClassNames[++j] = classNames[i];
267 }
268 }
269 return cleanClassNames;
270 }
271 }
272 return classNames;
273 },
274
275 /*
276 * This function reinitializes the options of the dropdown
277 */
278 initializeDropDown : function (dropDown) {
279 if (HTMLArea.is_gecko) {
280 while(dropDown.options.length > 0) {
281 dropDown.remove(dropDown.options.length-1);
282 }
283 } else {
284 while(dropDown.options.length > 0) {
285 dropDown.options[dropDown.options.length-1] = null;
286 }
287 }
288 var option = dropDown.ownerDocument.createElement("option");
289 option.value = "none";
290 option.innerHTML = this.localize("No style");
291 dropDown.appendChild(option);
292 },
293
294 /*
295 * This function builds the options to be displayed in the dropDown box
296 */
297 buildDropDownOptions : function (dropDown, tagName) {
298 var cssArray = new Array();
299 this.initializeDropDown(dropDown);
300 // Get classes allowed for all tags
301 if (typeof(this.cssArray.all) !== "undefined") {
302 if (this.tags && this.tags[tagName]) {
303 var allowedClasses = this.tags[tagName].allowedClasses;
304 for (var cssClass in this.cssArray.all) {
305 if (allowedClasses.indexOf(cssClass) !== -1) {
306 cssArray[cssClass] = this.cssArray.all[cssClass];
307 }
308 }
309 } else {
310 for (var cssClass in this.cssArray.all) {
311 if (this.cssArray.all.hasOwnProperty(cssClass)) {
312 cssArray[cssClass] = this.cssArray.all[cssClass];
313 }
314 }
315 }
316 }
317 // Merge classes allowed for tagName and sort the array
318 if (typeof(this.cssArray[tagName]) !== "undefined") {
319 if (this.tags && this.tags[tagName]) {
320 var allowedClasses = this.tags[tagName].allowedClasses;
321 for (var cssClass in this.cssArray[tagName]) {
322 if (allowedClasses.indexOf(cssClass) !== -1) {
323 cssArray[cssClass] = this.cssArray[tagName][cssClass];
324 }
325 }
326 } else {
327 for (var cssClass in this.cssArray[tagName]) {
328 if (this.cssArray[tagName].hasOwnProperty(cssClass)) {
329 cssArray[cssClass] = this.cssArray[tagName][cssClass];
330 }
331 }
332 }
333 var sortedCssArray = new Object();
334 var cssArrayKeys = new Array();
335 for (var cssClass in cssArray) {
336 if (cssArray.hasOwnProperty(cssClass)) {
337 cssArrayKeys.push(cssClass);
338 }
339 }
340 function compare(a, b) {
341 x = cssArray[a];
342 y = cssArray[b];
343 return ((x < y) ? -1 : ((x > y) ? 1 : 0));
344 }
345 cssArrayKeys = cssArrayKeys.sort(compare);
346 for (var i = 0; i < cssArrayKeys.length; ++i) {
347 sortedCssArray[cssArrayKeys[i]] = cssArray[cssArrayKeys[i]];
348 }
349 cssArray = sortedCssArray;
350 }
351 var doc = dropDown.ownerDocument;
352 for (var cssClass in cssArray) {
353 if (cssArray.hasOwnProperty(cssClass) && cssArray[cssClass]) {
354 if (cssClass == "none") {
355 dropDown.options[0].innerHTML = cssArray[cssClass];
356 } else {
357 var option = doc.createElement("option");
358 option.value = cssClass;
359 option.innerHTML = cssArray[cssClass];
360 dropDown.appendChild(option);
361 if (!this.editor.config.disablePCexamples && HTMLArea.classesValues && HTMLArea.classesValues[cssClass] && !HTMLArea.classesNoShow[cssClass]) {
362 dropDown.options[dropDown.options.length-1].setAttribute("style", HTMLArea.classesValues[cssClass]);
363 }
364 }
365 }
366 }
367 },
368
369 /*
370 * This function sets the selected option of the dropDown box
371 */
372 setSelectedOption : function (select, classNames, noUnknown) {
373 select.selectedIndex = 0;
374 if (classNames.length) {
375 for (var i = select.options.length; --i >= 0;) {
376 if (classNames[classNames.length-1] == select.options[i].value) {
377 select.options[i].selected = true;
378 select.selectedIndex = i;
379 select.options[0].text = this.localize("Remove style");
380 break;
381 }
382 }
383 if (select.selectedIndex == 0 && !noUnknown) {
384 select.options[select.options.length] = new Option(this.localize("Unknown style"), classNames[classNames.length-1]);
385 select.options[select.options.length-1].selected = true;
386 select.selectedIndex = select.options.length-1;
387 }
388 for (var i = select.options.length; --i >= 0;) {
389 if (("," + classNames.join(",") + ",").indexOf("," + select.options[i].value + ",") !== -1) {
390 if (select.selectedIndex != i) {
391 select.options[i] = null;
392 }
393 }
394 }
395 }
396 if (select.options.length > 1) {
397 select.disabled = false;
398 } else {
399 select.disabled = true;
400 }
401 },
402
403 /*
404 * This function builds the main array of class selectors
405 */
406 buildCssArray : function(editor, dropDownId) {
407 this.cssArray = this.parseStyleSheet();
408 if (!this.cssLoaded && (this.cssParseCount < 17)) {
409 var buildCssArrayLaterFunctRef = this.makeFunctionReference("buildCssArray");
410 this.cssTimeout = editor._iframe.contentWindow ? editor._iframe.contentWindow.setTimeout(buildCssArrayLaterFunctRef, 200) : window.setTimeout(buildCssArrayLaterFunctRef, 200);
411 this.cssParseCount++;
412 } else {
413 this.cssTimeout = null;
414 this.cssLoaded = true;
415 this.cssArray = this.sortCssArray(this.cssArray);
416 this.updateValue(dropDownId);
417 }
418 },
419
420 /*
421 * This function parses the stylesheets
422 */
423 parseStyleSheet : function() {
424 var iframe = this.editor._iframe.contentWindow ? this.editor._iframe.contentWindow.document : this.editor._iframe.contentDocument;
425 var newCssArray = new Object();
426 this.cssLoaded = true;
427 for (var i = 0; i < iframe.styleSheets.length; i++) {
428 if (HTMLArea.is_gecko) {
429 try {
430 newCssArray = this.parseCssRule(iframe.styleSheets[i].cssRules, newCssArray);
431 } catch(e) {
432 this.cssLoaded = false;
433 }
434 } else {
435 try{
436 // @import StyleSheets (IE)
437 if (iframe.styleSheets[i].imports) {
438 newCssArray = this.parseCssIEImport(iframe.styleSheets[i].imports, newCssArray);
439 }
440 if (iframe.styleSheets[i].rules) {
441 newCssArray = this.parseCssRule(iframe.styleSheets[i].rules, newCssArray);
442 }
443 } catch(e) {
444 this.cssLoaded = false;
445 }
446 }
447 }
448 return newCssArray;
449 },
450
451 /*
452 * This function parses IE import rules
453 */
454 parseCssIEImport : function(cssIEImport, cssArray) {
455 var newCssArray = new Object();
456 newCssArray = cssArray;
457 for (var i=0; i < cssIEImport.length; i++) {
458 if (cssIEImport[i].imports) {
459 newCssArray = this.parseCssIEImport(cssIEImport[i].imports, newCssArray);
460 }
461 if (cssIEImport[i].rules) {
462 newCssArray = this.parseCssRule(cssIEImport[i].rules, newCssArray);
463 }
464 }
465 return newCssArray;
466 },
467
468 /*
469 * This function parses gecko css rules
470 */
471 parseCssRule : function(cssRules, cssArray) {
472 var newCssArray = new Object();
473 newCssArray = cssArray;
474 for (var rule = 0; rule < cssRules.length; rule++) {
475 // StyleRule
476 if (cssRules[rule].selectorText) {
477 newCssArray = this.parseSelectorText(cssRules[rule].selectorText, newCssArray);
478 } else {
479 // ImportRule (Mozilla)
480 if (cssRules[rule].styleSheet) {
481 newCssArray = this.parseCssRule(cssRules[rule].styleSheet.cssRules, newCssArray);
482 }
483 // MediaRule (Mozilla)
484 if (cssRules[rule].cssRules) {
485 newCssArray = this.parseCssRule(cssRules[rule].cssRules, newCssArray);
486 }
487 }
488 }
489 return newCssArray;
490 },
491
492 /*
493 * This function parses each selector rule
494 */
495 parseSelectorText : function(selectorText, cssArray) {
496 var cssElements = new Array();
497 var cssElement = new Array();
498 var tagName, className;
499 var newCssArray = new Object();
500 newCssArray = cssArray;
501 if (selectorText.search(/:+/) == -1) {
502 // split equal Styles (Mozilla-specific) e.q. head, body {border:0px}
503 // for ie not relevant. returns allways one element
504 cssElements = selectorText.split(",");
505 for (var k = 0; k < cssElements.length; k++) {
506 cssElement = cssElements[k].split(".");
507 tagName = cssElement[0].toLowerCase().trim();
508 if (!tagName) {
509 tagName = "all";
510 }
511 className = cssElement[1];
512 if (!HTMLArea.reservedClassNames.test(className)) {
513 if (((tagName != "all") && (!this.tags || !this.tags[tagName]))
514 || ((tagName == "all") && (!this.tags || !this.tags[tagName]) && this.showTagFreeClasses)
515 || (this.tags && this.tags[tagName] && this.tags[tagName].allowedClasses.indexOf(className) != -1)) {
516 if (!newCssArray[tagName]) {
517 newCssArray[tagName] = new Object();
518 }
519 if (className) {
520 cssName = className;
521 if (HTMLArea.classesLabels && HTMLArea.classesLabels[className]) {
522 cssName = this.prefixLabelWithClassName ? (className + " - " + HTMLArea.classesLabels[className]) : HTMLArea.classesLabels[className];
523 cssName = this.postfixLabelWithClassName ? (cssName + " - " + className) : cssName;
524 }
525 } else {
526 className = "none";
527 cssName = this.localize("Element style");
528 }
529 newCssArray[tagName][className] = cssName;
530 }
531 }
532 }
533 }
534 return newCssArray;
535 },
536
537 /*
538 * This function sorts the main array of class selectors
539 */
540 sortCssArray : function(cssArray) {
541 var newCssArray = new Object();
542 for (var tagName in cssArray) {
543 if (cssArray.hasOwnProperty(tagName)) {
544 newCssArray[tagName] = new Object();
545 var tagArrayKeys = new Array();
546 for (var cssClass in cssArray[tagName]) {
547 if (cssArray[tagName].hasOwnProperty(cssClass)) {
548 tagArrayKeys.push(cssClass);
549 }
550 }
551 function compare(a, b) {
552 x = cssArray[tagName][a];
553 y = cssArray[tagName][b];
554 return ((x < y) ? -1 : ((x > y) ? 1 : 0));
555 }
556 tagArrayKeys = tagArrayKeys.sort(compare);
557 for (var i = 0; i < tagArrayKeys.length; ++i) {
558 newCssArray[tagName][tagArrayKeys[i]] = cssArray[tagName][tagArrayKeys[i]];
559 }
560 }
561 }
562 return newCssArray;
563 }
564 });
565