dc2efd30bb11fc47b88cbf150b40ae47d63127e5
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / DefinitionList / definition-list.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2008-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 * DefinitionList Plugin for TYPO3 htmlArea RTE
29 */
30 HTMLArea.DefinitionList = Ext.extend(HTMLArea.BlockElements, {
31 /*
32 * This function gets called by the class constructor
33 */
34 configurePlugin: function (editor) {
35 /*
36 * Setting up some properties from PageTSConfig
37 */
38 this.buttonsConfiguration = this.editorConfiguration.buttons;
39 var parentPlugin = this.getPluginInstance('BlockElements');
40 this.tags = parentPlugin.tags;
41 this.useClass = parentPlugin.useClass;
42 this.useBlockquote = parentPlugin.useBlockquote;
43 this.useAlignAttribute = parentPlugin.useAlignAttribute;
44 this.allowedBlockElements = parentPlugin.allowedBlockElements;
45 this.indentedList = null;
46 this.standardBlockElements = parentPlugin.standardBlockElements;
47 this.formatBlockItems = parentPlugin.formatBlockItems;
48 /*
49 * Registering plugin "About" information
50 */
51 var pluginInformation = {
52 version : '2.0',
53 developer : 'Stanislas Rolland',
54 developerUrl : 'http://www.sjbr.ca/',
55 copyrightOwner : 'Stanislas Rolland',
56 sponsor : this.localize('Technische Universitat Ilmenau'),
57 sponsorUrl : 'http://www.tu-ilmenau.de/',
58 license : 'GPL'
59 };
60 this.registerPluginInformation(pluginInformation);
61 /*
62 * Registering the buttons
63 */
64 Ext.each(this.buttonList, function (button) {
65 var buttonId = button[0];
66 var buttonConfiguration = {
67 id : buttonId,
68 tooltip : this.localize(buttonId + '-Tooltip'),
69 contextMenuTitle: this.localize(buttonId + '-contextMenuTitle'),
70 helpText : this.localize(buttonId + '-helpText'),
71 iconCls : 'htmlarea-action-' + button[5],
72 action : 'onButtonPress',
73 context : button[1],
74 hotKey : ((this.buttonsConfiguration[button[3]] && this.buttonsConfiguration[button[3]].hotKey) ? this.buttonsConfiguration[button[3]].hotKey : (button[2] ? button[2] : null)),
75 noAutoUpdate : button[4]
76 };
77 this.registerButton(buttonConfiguration);
78 }, this);
79 return true;
80 },
81 /*
82 * The list of buttons added by this plugin
83 */
84 buttonList: [
85 ['Indent', null, 'TAB', 'indent', false, 'indent'],
86 ['Outdent', null, 'SHIFT-TAB', 'outdent', false, 'outdent'],
87 ['DefinitionList', null, null, 'definitionlist', true, 'definition-list'],
88 ['DefinitionItem', 'dd,dt', null, 'definitionitem', false, 'definition-list-item']
89 ],
90 /*
91 * This function gets called when the plugin is generated
92 * Avoid re-execution of the base function
93 */
94 onGenerate: Ext.emptyFn,
95 /*
96 * This function gets called when a button was pressed.
97 *
98 * @param object editor: the editor instance
99 * @param string id: the button id or the key
100 * @param object target: the target element of the contextmenu event, when invoked from the context menu
101 * @param string className: the className to be assigned to the element
102 *
103 * @return boolean false if action is completed
104 */
105 onButtonPress: function (editor, id, target, className) {
106 // Could be a button or its hotkey
107 var buttonId = this.translateHotKey(id);
108 buttonId = buttonId ? buttonId : id;
109 var range = this.editor.getSelection().createRange();
110 var statusBarSelection = this.editor.statusBar ? this.editor.statusBar.getSelection() : null;
111 var parentElement = statusBarSelection ? statusBarSelection : this.editor.getSelection().getParentElement();
112 if (target) {
113 parentElement = target;
114 }
115 while (parentElement && (!HTMLArea.DOM.isBlockElement(parentElement) || /^(li)$/i.test(parentElement.nodeName))) {
116 parentElement = parentElement.parentNode;
117 }
118 switch (buttonId) {
119 case 'Indent' :
120 if (/^(dd|dt)$/i.test(parentElement.nodeName) && this.indentDefinitionList(parentElement)) {
121 break;
122 } else {
123 HTMLArea.DefinitionList.superclass.onButtonPress.call(this, editor, id, target, className);
124 }
125 break;
126 case 'Outdent' :
127 if (/^(dt)$/i.test(parentElement.nodeName) && this.outdentDefinitionList()) {
128 break;
129 } else {
130 HTMLArea.DefinitionList.superclass.onButtonPress.call(this, editor, id, target, className);
131 }
132 break;
133 case 'DefinitionList':
134 var bookmark = this.editor.getBookMark().get(range);
135 this.insertDefinitionList();
136 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
137 break;
138 case 'DefinitionItem':
139 var bookmark = this.editor.getBookMark().get(range);
140 this.remapNode(parentElement, (parentElement.nodeName.toLowerCase() === 'dt') ? 'dd' : 'dt');
141 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
142 break;
143 default:
144 HTMLArea.DefinitionList.superclass.onButtonPress.call(this, editor, id, target, className);
145 }
146 return false;
147 },
148 /*
149 * This function remaps a node to the specified node name
150 */
151 remapNode: function (node, nodeName) {
152 var newNode = HTMLArea.DOM.convertNode(node, nodeName);
153 var attributes = node.attributes, attributeName, attributeValue;
154 for (var i = attributes.length; --i >= 0;) {
155 attributeName = attributes.item(i).nodeName;
156 attributeValue = node.getAttribute(attributeName);
157 if (attributeValue) newNode.setAttribute(attributeName, attributeValue);
158 }
159 // In IE, the above fails to update the classname and style attributes.
160 if (Ext.isIE) {
161 if (node.style.cssText) {
162 newNode.style.cssText = node.style.cssText;
163 }
164 if (node.className) {
165 newNode.setAttribute('class', node.className);
166 if (!newNode.className) {
167 // IE before IE8
168 newNode.setAttribute('className', node.className);
169 }
170 } else {
171 newNode.removeAttribute('class');
172 newNode.removeAttribute('className');
173 }
174 }
175
176 if (this.tags && this.tags[nodeName] && this.tags[nodeName].allowedClasses) {
177 if (newNode.className && /\S/.test(newNode.className)) {
178 var allowedClasses = this.tags[nodeName].allowedClasses;
179 var classNames = newNode.className.trim().split(' ');
180 for (var i = classNames.length; --i >= 0;) {
181 if (!allowedClasses.test(classNames[i])) {
182 HTMLArea.DOM.removeClass(newNode, classNames[i]);
183 }
184 }
185 }
186 }
187 return newNode;
188 },
189 /*
190 * Insert a definition list
191 */
192 insertDefinitionList: function () {
193 var endBlocks = this.editor.getSelection().getEndBlocks();
194 var list = null;
195 if (this.editor.getSelection().isEmpty()) {
196 if (/^(body|div|address|pre|blockquote|li|td|dd)$/i.test(endBlocks.start.nodeName)) {
197 list = this.editor.document.createElement('dl');
198 var term = list.appendChild(this.editor.document.createElement('dt'));
199 while (endBlocks.start.firstChild) {
200 term.appendChild(endBlocks.start.firstChild);
201 }
202 list = endBlocks.start.appendChild(list);
203 } else if (/^(p|h[1-6])$/i.test(endBlocks.start.nodeName)) {
204 var list = endBlocks.start.parentNode.insertBefore(this.editor.document.createElement('dl'), endBlocks.start);
205 endBlocks.start = list.appendChild(endBlocks.start);
206 endBlocks.start = this.remapNode(endBlocks.start, 'dt');
207 }
208 } else if (endBlocks.start != endBlocks.end && /^(p|h[1-6])$/i.test(endBlocks.start.nodeName)) {
209 // We wrap the selected elements in a dl element
210 var paragraphs = endBlocks.start.nodeName.toLowerCase() === 'p';
211 list = this.wrapSelectionInBlockElement('dl');
212 var items = list.childNodes;
213 for (var i = 0, n = items.length; i < n; ++i) {
214 var paragraphItem = items[i].nodeName.toLowerCase() === 'p';
215 this.remapNode(items[i], paragraphs ? ((i % 2) ? 'dd' : 'dt') : (paragraphItem ? 'dd' : 'dt'));
216 }
217 }
218 return list;
219 },
220 /*
221 * Indent a definition list
222 */
223 indentDefinitionList: function (parentElement) {
224 var range = this.editor.getSelection().createRange();
225 var endBlocks = this.editor.getSelection().getEndBlocks();
226 if (this.editor.getSelection().isEmpty() && /^dd$/i.test(parentElement.nodeName)) {
227 var list = parentElement.appendChild(this.editor.document.createElement('dl'));
228 var term = list.appendChild(this.editor.document.createElement('dt'));
229 if (!Ext.isIE) {
230 if (Ext.isWebKit) {
231 term.innerHTML = '<br />';
232 } else {
233 term.appendChild(this.editor.document.createTextNode(''));
234 }
235 } else {
236 term.innerHTML = '\x20';
237 }
238 this.editor.getSelection().selectNodeContents(term, false);
239 return true;
240 } else if (endBlocks.start && /^dt$/i.test(endBlocks.start.nodeName) && endBlocks.start.previousSibling) {
241 var sibling = endBlocks.start.previousSibling;
242 var bookmark = this.editor.getBookMark().get(range);
243 if (/^dd$/i.test(sibling.nodeName)) {
244 var list = this.wrapSelectionInBlockElement('dl');
245 list = sibling.appendChild(list);
246 // May need to merge the list if it has a previous sibling
247 if (list.previousSibling && /^dl$/i.test(list.previousSibling.nodeName)) {
248 while (list.firstChild) {
249 list.previousSibling.appendChild(list.firstChild);
250 }
251 HTMLArea.DOM.removeFromParent(list);
252 }
253 } else if (/^dt$/i.test(sibling.nodeName)) {
254 var definition = this.editor.document.createElement('dd');
255 definition.appendChild(this.wrapSelectionInBlockElement('dl'));
256 sibling.parentNode.insertBefore(definition, sibling.nextSibling);
257 }
258 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
259 return true;
260 }
261 return false;
262 },
263 /*
264 * Outdent a definition list
265 */
266 outdentDefinitionList: function () {
267 var endBlocks = this.editor.getSelection().getEndBlocks();
268 if (/^dt$/i.test(endBlocks.start.nodeName)
269 && /^dl$/i.test(endBlocks.start.parentNode.nodeName)
270 && /^dd$/i.test(endBlocks.start.parentNode.parentNode.nodeName)
271 && !endBlocks.end.nextSibling) {
272 var bookmark = this.editor.getBookMark().get(this.editor.getSelection().createRange());
273 var dl = endBlocks.start.parentNode;
274 var dd = dl.parentNode;
275 if (this.editor.getSelection().isEmpty()) {
276 dd.parentNode.insertBefore(endBlocks.start, dd.nextSibling);
277 } else {
278 var selected = this.wrapSelectionInBlockElement('dl');
279 while (selected.lastChild) {
280 dd.parentNode.insertBefore(selected.lastChild, dd.nextSibling);
281 }
282 selected.parentNode.removeChild(selected);
283 }
284 // We may have outdented all the child nodes of a list
285 if (!dl.hasChildNodes()) {
286 dd.removeChild(dl);
287 if (!dd.hasChildNodes()) {
288 dd.parentNode.removeChild(dd);
289 }
290 }
291 this.editor.getSelection().selectRange(this.editor.getBookMark().moveTo(bookmark));
292 return true;
293 }
294 return false;
295 },
296
297 /*
298 * This function gets called when the toolbar is updated
299 */
300 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
301 var editor = this.editor;
302 if (mode === 'wysiwyg' && this.editor.isEditable()) {
303 var statusBarSelection = editor.statusBar ? editor.statusBar.getSelection() : null;
304 var parentElement = statusBarSelection ? statusBarSelection : editor.getSelection().getParentElement();
305 if (!/^(body)$/i.test(parentElement.nodeName)) {
306 var endBlocks = editor.getSelection().getEndBlocks();
307 switch (button.itemId) {
308 case 'Outdent':
309 if (/^(dt)$/i.test(endBlocks.start.nodeName)
310 && /^(dl)$/i.test(endBlocks.start.parentNode.nodeName)
311 && /^(dd)$/i.test(endBlocks.start.parentNode.parentNode.nodeName)
312 && !endBlocks.end.nextSibling) {
313 button.setDisabled(false);
314 } else {
315 HTMLArea.DefinitionList.superclass.onUpdateToolbar.call(this, button, mode, selectionEmpty, ancestors);
316 }
317 break;
318 case 'DefinitionList':
319 button.setDisabled(!(selectionEmpty && /^(p|div|address|pre|blockquote|h[1-6]|li|td|dd)$/i.test(endBlocks.start.nodeName))
320 && !(endBlocks.start != endBlocks.end && /^(p|h[1-6])$/i.test(endBlocks.start.nodeName)));
321 break;
322 }
323 } else {
324 switch (button.itemId) {
325 case 'Outdent':
326 HTMLArea.DefinitionList.superclass.onUpdateToolbar.call(this, button, mode, selectionEmpty, ancestors);
327 break;
328 }
329 }
330 }
331 }
332 });