5300a1ee6f19e3a33556067cdf9d04df2765b055
1 /***************************************************************
4 * (c) 2008-2012 Stanislas Rolland <typo3(arobas)sjbr.ca>
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.
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.
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.
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
28 * Language Plugin for TYPO3 htmlArea RTE
30 HTMLArea
.Language
= Ext
.extend(HTMLArea
.Plugin
, {
32 * This function gets called by the class constructor
34 configurePlugin: function (editor
) {
36 * Setting up some properties from PageTSConfig
38 this.buttonsConfiguration
= this.editorConfiguration
.buttons
;
39 this.useAttribute
= {};
40 this.useAttribute
.lang
= (this.buttonsConfiguration
.language
&& this.buttonsConfiguration
.language
.useLangAttribute
) ? this.buttonsConfiguration
.language
.useLangAttribute
: true;
41 this.useAttribute
.xmlLang
= (this.buttonsConfiguration
.language
&& this.buttonsConfiguration
.language
.useXmlLangAttribute
) ? this.buttonsConfiguration
.language
.useXmlLangAttribute
: false;
42 if (!this.useAttribute
.lang
&& !this.useAttribute
.xmlLang
) {
43 this.useAttribute
.lang
= true;
46 // Importing list of allowed attributes
47 if (this.getPluginInstance('TextStyle')) {
48 this.allowedAttributes
= this.getPluginInstance('TextStyle').allowedAttributes
;
50 if (!this.allowedAttributes
&& this.getPluginInstance('InlineElements')) {
51 this.allowedAttributes
= this.getPluginInstance('InlineElements').allowedAttributes
;
53 if (!this.allowedAttributes
&& this.getPluginInstance('BlockElements')) {
54 this.allowedAttributes
= this.getPluginInstance('BlockElements').allowedAttributes
;
56 if (!this.allowedAttributes
) {
57 this.allowedAttributes
= new Array('id', 'title', 'lang', 'xml:lang', 'dir', 'class');
59 this.allowedAttributes
.push('className');
63 * Registering plugin "About" information
65 var pluginInformation
= {
67 developer
: 'Stanislas Rolland',
68 developerUrl
: 'http://www.sjbr.ca/',
69 copyrightOwner
: 'Stanislas Rolland',
70 sponsor
: this.localize('Technische Universitat Ilmenau'),
71 sponsorUrl
: 'http://www.tu-ilmenau.de/',
74 this.registerPluginInformation(pluginInformation
);
76 * Registering the buttons
78 var buttonList
= this.buttonList
, buttonId
;
79 for (var i
= 0, n
= buttonList
.length
; i
< n
; ++i
) {
80 var button
= buttonList
[i
];
82 var buttonConfiguration
= {
84 tooltip
: this.localize(buttonId
+ '-Tooltip'),
85 iconCls
: 'htmlarea-action-' + button
[2],
86 action
: 'onButtonPress',
89 this.registerButton(buttonConfiguration
);
92 * Registering the dropdown list
94 var buttonId
= 'Language';
95 if (this.buttonsConfiguration
[buttonId
.toLowerCase()] && this.buttonsConfiguration
[buttonId
.toLowerCase()].dataUrl
) {
96 var dropDownConfiguration
= {
98 tooltip
: this.localize(buttonId
+ '-Tooltip'),
99 storeUrl
: this.buttonsConfiguration
[buttonId
.toLowerCase()].dataUrl
,
102 if (this.buttonsConfiguration
.language
) {
103 dropDownConfiguration
.width
= this.buttonsConfiguration
.language
.width
? parseInt(this.buttonsConfiguration
.language
.width
, 10) : 200;
104 if (this.buttonsConfiguration
.language
.listWidth
) {
105 dropDownConfiguration
.listWidth
= parseInt(this.buttonsConfiguration
.language
.listWidth
, 10);
107 if (this.buttonsConfiguration
.language
.maxHeight
) {
108 dropDownConfiguration
.maxHeight
= parseInt(this.buttonsConfiguration
.language
.maxHeight
, 10);
111 this.registerDropDown(dropDownConfiguration
);
116 * The list of buttons added by this plugin
119 ['LeftToRight', null, 'text-direction-left-to-right'],
120 ['RightToLeft', null, 'text-direction-right-to-left'],
121 ['ShowLanguageMarks', null, 'language-marks-show']
124 * This function gets called when the editor is generated
126 onGenerate: function () {
127 var select
= this.getButton('Language');
129 if (select
.getStore().getCount() > 1) {
130 this.addLanguageMarkingRules();
132 // Monitor the language combo's store being loaded
133 select
.mon(select
.getStore(), 'load', function () {
134 this.addLanguageMarkingRules();
135 var selection
= this.editor
.getSelection(),
136 selectionEmpty
= selection
.isEmpty(),
137 ancestors
= selection
.getAllAncestors(),
138 endPointsInSameBlock
= selection
.endPointsInSameBlock();
139 this.onUpdateToolbar(select
, this.getEditorMode(), selectionEmpty
, ancestors
, endPointsInSameBlock
);
145 * This function adds rules to the stylesheet for language mark highlighting
146 * Model: body.htmlarea-show-language-marks *[lang=en]:before { content: "en: "; }
147 * Works in IE8, but not in earlier versions of IE
149 addLanguageMarkingRules: function () {
150 var select
= this.getButton('Language');
152 var styleSheet
= this.editor
.document
.styleSheets
[0];
153 select
.getStore().each(function (option
) {
154 var selector
= 'body.htmlarea-show-language-marks *[' + 'lang="' + option
.get('value') + '"]:before';
155 var style
= 'content: "' + option
.get('value') + ': ";';
156 var rule
= selector
+ ' { ' + style
+ ' }';
159 styleSheet
.insertRule(rule
, styleSheet
.cssRules
.length
);
161 this.appendToLog('onGenerate', 'Error inserting css rule: ' + rule
+ ' Error text: ' + e
, 'warn');
164 styleSheet
.addRule(selector
, style
);
171 * This function gets called when a button was pressed.
173 * @param object editor: the editor instance
174 * @param string id: the button id or the key
176 * @return boolean false if action is completed
178 onButtonPress: function (editor
, id
, target
) {
179 // Could be a button or its hotkey
180 var buttonId
= this.translateHotKey(id
);
181 buttonId
= buttonId
? buttonId
: id
;
186 this.setDirAttribute(buttonId
);
188 case 'ShowLanguageMarks':
189 this.toggleLanguageMarks();
197 * Sets the dir attribute
199 * @param string buttonId: the button id
203 setDirAttribute: function (buttonId
) {
204 var direction
= (buttonId
== 'RightToLeft') ? 'rtl' : 'ltr';
205 var element
= this.editor
.getSelection().getParentElement();
207 if (/^bdo$/i.test(element
.nodeName
)) {
208 element
.dir
= direction
;
210 element
.dir
= (element
.dir
== direction
|| element
.style
.direction
== direction
) ? '' : direction
;
212 element
.style
.direction
= '';
216 * Toggles the display of language marks
218 * @param boolean forceLanguageMarks: if set, language marks are displayed whatever the current state
222 toggleLanguageMarks: function (forceLanguageMarks
) {
223 var body
= this.editor
.document
.body
;
224 if (!HTMLArea
.DOM
.hasClass(body
, 'htmlarea-show-language-marks')) {
225 HTMLArea
.DOM
.addClass(body
,'htmlarea-show-language-marks');
226 } else if (!forceLanguageMarks
) {
227 HTMLArea
.DOM
.removeClass(body
,'htmlarea-show-language-marks');
231 * This function gets called when some language was selected in the drop-down list
233 onChange: function (editor
, combo
, record
, index
) {
234 this.applyLanguageMark(combo
.getValue());
237 * This function applies the langauge mark to the selection
239 applyLanguageMark: function (language
) {
240 var statusBarSelection
= this.editor
.statusBar
? this.editor
.statusBar
.getSelection() : null;
241 var range
= this.editor
.getSelection().createRange();
242 var parent
= this.editor
.getSelection().getParentElement();
243 var selectionEmpty
= this.editor
.getSelection().isEmpty();
244 var endPointsInSameBlock
= this.editor
.getSelection().endPointsInSameBlock();
245 var fullNodeSelected
= false;
246 if (!selectionEmpty
) {
247 if (endPointsInSameBlock
) {
248 var ancestors
= this.editor
.getSelection().getAllAncestors();
249 for (var i
= 0; i
< ancestors
.length
; ++i
) {
250 fullNodeSelected
= (statusBarSelection
=== ancestors
[i
])
251 && ((!Ext
.isIE
&& ancestors
[i
].textContent
=== range
.toString()) || (Ext
.isIE
&& ((this.editor
.getSelection().getType() !== 'Control' && ancestors
[i
].innerText
=== range
.text
) || (this.editor
.getSelection().getType() === 'Control' && ancestors
[i
].innerText
=== range
.item(0).text
))));
252 if (fullNodeSelected
) {
253 parent
= ancestors
[i
];
257 // Working around bug in Safari selectNodeContents
258 if (!fullNodeSelected
&& Ext
.isWebKit
&& statusBarSelection
&& statusBarSelection
.textContent
=== range
.toString()) {
259 fullNodeSelected
= true;
260 parent
= statusBarSelection
;
264 if (selectionEmpty
|| fullNodeSelected
) {
265 // Selection is empty or parent is selected in the status bar
267 // Set language attributes
268 this.setLanguageAttributes(parent
, language
);
270 } else if (endPointsInSameBlock
) {
271 // The selection is not empty, nor full element
272 if (language
!= 'none') {
273 // Add span element with lang attribute(s)
274 var newElement
= this.editor
.document
.createElement('span');
275 this.setLanguageAttributes(newElement
, language
);
276 this.editor
.getDomNode().wrapWithInlineElement(newElement
, range
);
282 this.setLanguageAttributeOnBlockElements(language
);
287 * This function gets the language attribute on the given element
289 * @param object element: the element from which to retrieve the attribute value
291 * @return string value of the lang attribute, or of the xml:lang attribute
293 getLanguageAttribute: function (element
) {
294 var xmllang
= 'none';
296 // IE7 complains about xml:lang
297 xmllang
= element
.getAttribute('xml:lang') ? element
.getAttribute('xml:lang') : 'none';
299 return element
.getAttribute('lang') ? element
.getAttribute('lang') : xmllang
;
302 * This function sets the language attributes on the given element
304 * @param object element: the element on which to set the value of the lang and/or xml:lang attribute
305 * @param string language: value of the lang attributes, or "none", in which case, the attribute(s) is(are) removed
309 setLanguageAttributes: function (element
, language
) {
311 if (language
== 'none') {
312 // Remove language mark, if any
313 element
.removeAttribute('lang');
315 // Do not let IE7 complain
316 element
.removeAttribute('xml:lang');
318 // Remove the span tag if it has no more attribute
319 if (/^span$/i.test(element
.nodeName
) && !HTMLArea
.DOM
.hasAllowedAttributes(element
, this.allowedAttributes
)) {
320 this.editor
.getDomNode().removeMarkup(element
);
323 if (this.useAttribute
.lang
) {
324 element
.setAttribute('lang', language
);
326 if (this.useAttribute
.xmlLang
) {
328 // Do not let IE7 complain
329 element
.setAttribute('xml:lang', language
);
336 * This function gets the language attributes from blocks sibling of the block containing the start container of the selection
338 * @return string value of the lang attribute, or of the xml:lang attribute, or "none", if all blocks sibling do not have the same attribute value as the block containing the start container
340 getLanguageAttributeFromBlockElements: function () {
341 var endBlocks
= this.editor
.getSelection().getEndBlocks();
342 var startAncestors
= HTMLArea
.DOM
.getBlockAncestors(endBlocks
.start
);
343 var endAncestors
= HTMLArea
.DOM
.getBlockAncestors(endBlocks
.end
);
345 while (index
< startAncestors
.length
&& index
< endAncestors
.length
&& startAncestors
[index
] === endAncestors
[index
]) {
348 if (endBlocks
.start
=== endBlocks
.end
) {
351 var language
= this.getLanguageAttribute(startAncestors
[index
]);
352 for (var block
= startAncestors
[index
]; block
; block
= block
.nextSibling
) {
353 if (HTMLArea
.DOM
.isBlockElement(block
)) {
354 if (this.getLanguageAttribute(block
) != language
|| this.getLanguageAttribute(block
) == 'none') {
359 if (block
== endAncestors
[index
]) {
366 * This function sets the language attributes on blocks sibling of the block containing the start container of the selection
368 setLanguageAttributeOnBlockElements: function (language
) {
369 var endBlocks
= this.editor
.getSelection().getEndBlocks();
370 var startAncestors
= HTMLArea
.DOM
.getBlockAncestors(endBlocks
.start
);
371 var endAncestors
= HTMLArea
.DOM
.getBlockAncestors(endBlocks
.end
);
373 while (index
< startAncestors
.length
&& index
< endAncestors
.length
&& startAncestors
[index
] === endAncestors
[index
]) {
376 if (endBlocks
.start
=== endBlocks
.end
) {
379 for (var block
= startAncestors
[index
]; block
; block
= block
.nextSibling
) {
380 if (HTMLArea
.DOM
.isBlockElement(block
)) {
381 this.setLanguageAttributes(block
, language
);
383 if (block
== endAncestors
[index
]) {
390 * This function gets called when the toolbar is updated
392 onUpdateToolbar: function (button
, mode
, selectionEmpty
, ancestors
, endPointsInSameBlock
) {
393 if (mode
=== 'wysiwyg' && this.editor
.isEditable()) {
394 var statusBarSelection
= this.editor
.statusBar
? this.editor
.statusBar
.getSelection() : null;
395 var range
= this.editor
.getSelection().createRange();
396 var parent
= this.editor
.getSelection().getParentElement();
397 switch (button
.itemId
) {
401 var direction
= (button
.itemId
=== 'RightToLeft') ? 'rtl' : 'ltr';
402 button
.setInactive(parent
.dir
!= direction
&& parent
.style
.direction
!= direction
);
403 button
.setDisabled(/^body$/i.test(parent
.nodeName
));
405 button
.setDisabled(true);
408 case 'ShowLanguageMarks':
409 button
.setInactive(!HTMLArea
.DOM
.hasClass(this.editor
.document
.body
, 'htmlarea-show-language-marks'));
412 // Updating the language drop-down
413 var fullNodeSelected
= false;
414 var language
= this.getLanguageAttribute(parent
);
415 if (!selectionEmpty
) {
416 if (endPointsInSameBlock
) {
417 for (var i
= 0; i
< ancestors
.length
; ++i
) {
418 fullNodeSelected
= (statusBarSelection
=== ancestors
[i
])
419 && ((!Ext
.isIE
&& ancestors
[i
].textContent
=== range
.toString()) || (Ext
.isIE
&& ((this.editor
.getSelection().getType() !== 'Control' && ancestors
[i
].innerText
=== range
.text
) || (this.editor
.getSelection().getType() === 'Control' && ancestors
[i
].innerText
=== range
.item(0).text
))));
420 if (fullNodeSelected
) {
421 parent
= ancestors
[i
];
425 // Working around bug in Safari selectNodeContents
426 if (!fullNodeSelected
&& Ext
.isWebKit
&& statusBarSelection
&& statusBarSelection
.textContent
=== range
.toString()) {
427 fullNodeSelected
= true;
428 parent
= statusBarSelection
;
430 language
= this.getLanguageAttribute(parent
);
432 language
= this.getLanguageAttributeFromBlockElements();
435 this.updateValue(button
, language
, selectionEmpty
, fullNodeSelected
, endPointsInSameBlock
);
443 * This function updates the language drop-down list
445 updateValue: function (select
, language
, selectionEmpty
, fullNodeSelected
, endPointsInSameBlock
) {
446 var store
= select
.getStore();
448 if ((store
.findExact('value', language
) != -1) && (selectionEmpty
|| fullNodeSelected
|| !endPointsInSameBlock
)) {
449 select
.setValue(language
);
450 store
.insert(0, new store
.recordType({
451 text
: this.localize('Remove language mark'),
455 store
.insert(0, new store
.recordType({
456 text
: this.localize('No language mark'),
459 select
.setValue('none');
461 select
.setDisabled(!(store
.getCount()>1) || (selectionEmpty
&& /^body$/i.test(this.editor
.getSelection().getParentElement().nodeName
)));