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