f663be2b3a907296893ea1ebec9f06a25debef4b
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / TYPO3Link / typo3link.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2005-2008 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 * TYPO3Link plugin for htmlArea RTE
29 *
30 * TYPO3 SVN ID: $Id$
31 */
32 TYPO3Link = HTMLArea.Plugin.extend({
33
34 constructor : function(editor, pluginName) {
35 this.base(editor, pluginName);
36 },
37
38 /*
39 * This function gets called by the class constructor
40 */
41 configurePlugin : function(editor) {
42
43 this.pageTSConfiguration = this.editorConfiguration.buttons.link;
44 this.modulePath = this.pageTSConfiguration.pathLinkModule;
45 this.classesAnchorUrl = this.pageTSConfiguration.classesAnchorUrl;
46
47 /*
48 * Registering plugin "About" information
49 */
50 var pluginInformation = {
51 version : "1.1",
52 developer : "Stanislas Rolland",
53 developerUrl : "http://www.sjbr.ca/",
54 copyrightOwner : "Stanislas Rolland",
55 sponsor : "SJBR",
56 sponsorUrl : "http://www.sjbr.ca/",
57 license : "GPL"
58 };
59 this.registerPluginInformation(pluginInformation);
60
61 /*
62 * Registering the buttons
63 */
64 var buttonList = this.buttonList, buttonId;
65 for (var i = 0; i < buttonList.length; ++i) {
66 var button = buttonList[i];
67 buttonId = button[0];
68 var buttonConfiguration = {
69 id : buttonId,
70 tooltip : this.localize(buttonId.toLowerCase()),
71 action : "onButtonPress",
72 hotKey : (this.pageTSConfiguration ? this.pageTSConfiguration.hotKey : null),
73 context : button[1],
74 selection : button[2],
75 dialog : button[3]
76 };
77 this.registerButton(buttonConfiguration);
78 }
79
80 return true;
81 },
82
83 /*
84 * The list of buttons added by this plugin
85 */
86 buttonList : [
87 ["CreateLink", "a", true, true],
88 ["UnLink", "a", false, false]
89 ],
90
91 /*
92 * This function gets called when the button was pressed
93 *
94 * @param object editor: the editor instance
95 * @param string id: the button id or the key
96 * @param object target: the target element of the contextmenu event, when invoked from the context menu
97 *
98 * @return boolean false if action is completed
99 */
100 onButtonPress : function(editor, id, target) {
101 // Could be a button or its hotkey
102 var buttonId = this.translateHotKey(id);
103 buttonId = buttonId ? buttonId : id;
104
105 // Download the definition of special anchor classes if not yet done
106 if (this.classesAnchorUrl && (typeof(HTMLArea.classesAnchorSetup) === "undefined")) {
107 this.getJavascriptFile(this.classesAnchorUrl);
108 }
109
110 if (buttonId === "UnLink") {
111 this.unLink();
112 return false;
113 }
114
115 var additionalParameter;
116 var node = this.editor.getParentElement();
117 var el = HTMLArea.getElementObject(node, "a");
118 if (el != null && /^a$/i.test(el.nodeName)) node = el;
119 if (node != null && /^a$/i.test(node.nodeName)) {
120 additionalParameter = "&curUrl[href]=" + encodeURIComponent(node.getAttribute("href"));
121 if (node.target) additionalParameter += "&curUrl[target]=" + encodeURIComponent(node.target);
122 if (node.className) additionalParameter += "&curUrl[class]=" + encodeURIComponent(node.className);
123 if (node.title) additionalParameter += "&curUrl[title]=" + encodeURIComponent(node.title);
124 } else if (this.editor.hasSelectedText()) {
125 var text = this.editor.getSelectedHTML();
126 if (text && text != null) {
127 var offset = text.toLowerCase().indexOf("<a");
128 if (offset!=-1) {
129 var ATagContent = text.substring(offset+2);
130 offset = ATagContent.toUpperCase().indexOf(">");
131 ATagContent = ATagContent.substring(0,offset);
132 additionalParameter = "&curUrl[all]=" + encodeURIComponent(ATagContent);
133 }
134 }
135 }
136 this.dialog = this.openDialog("CreateLink", this.makeUrlFromModulePath(this.modulePath, additionalParameter), null, null, {width:550, height:350}, "yes");
137 return false;
138 },
139
140 /*
141 * Add a link to the selection.
142 * This function is called from the TYPO3 link popup.
143 */
144 createLink : function(theLink,cur_target,cur_class,cur_title) {
145 var selection, range, anchorClass, imageNode = null, addIconAfterLink;
146 this.editor.focusEditor();
147 var node = this.editor.getParentElement();
148 var el = HTMLArea.getElementObject(node, "a");
149 if (el != null && /^a$/i.test(el.nodeName)) node = el;
150 if (node != null && /^a$/i.test(node.nodeName)) this.editor.selectNode(node);
151 // Clean images from existing anchors otherwise Mozilla may create nested anchors
152 if (HTMLArea.classesAnchorSetup) {
153 selection = this.editor._getSelection();
154 range = this.editor._createRange(selection);
155 this.cleanAllLinks(node, range, true);
156 }
157 // In FF, if the url is the same except for upper/lower case of a file name, the link is not updated.
158 // Therefore, we remove the link before creating a new one.
159 if (HTMLArea.is_gecko) {
160 this.editor._doc.execCommand("UnLink", false, null);
161 }
162 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
163 this.editor._doc.execCommand("CreateLink", false, encodeURIComponent(theLink));
164 } else {
165 this.editor._doc.execCommand("CreateLink", false, theLink);
166 }
167
168 selection = this.editor._getSelection();
169 range = this.editor._createRange(selection);
170 node = this.editor.getParentElement();
171 el = HTMLArea.getElementObject(node, "a");
172 if (el != null && /^a$/i.test(el.nodeName)) node = el;
173 if (node) {
174 if (HTMLArea.classesAnchorSetup && cur_class) {
175 for (var i = HTMLArea.classesAnchorSetup.length; --i >= 0;) {
176 anchorClass = HTMLArea.classesAnchorSetup[i];
177 if (anchorClass.name == cur_class && anchorClass.image) {
178 imageNode = this.editor._doc.createElement("img");
179 imageNode.src = anchorClass.image;
180 imageNode.alt = anchorClass.altText;
181 addIconAfterLink = anchorClass.addIconAfterLink;
182 break;
183 }
184 }
185 }
186 // We may have created multiple links in as many blocks
187 this.setLinkAttributes(node, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink);
188 }
189 this.dialog.close();
190 },
191
192 /*
193 * Unlink the selection.
194 * This function is called from the TYPO3 link popup and from the context menu.
195 */
196 unLink : function() {
197 this.editor.focusEditor();
198 var node = this.editor.getParentElement();
199 var el = HTMLArea.getElementObject(node, "a");
200 if (el != null && /^a$/i.test(el.nodeName)) node = el;
201 if (node != null && /^a$/i.test(node.nodeName)) this.editor.selectNode(node);
202 if (HTMLArea.classesAnchorSetup) {
203 var selection = this.editor._getSelection();
204 var range = this.editor._createRange(selection);
205 if (HTMLArea.is_gecko) {
206 this.cleanAllLinks(node, range, false);
207 } else {
208 this.cleanAllLinks(node, range, true);
209 this.editor._doc.execCommand("Unlink", false, "");
210 }
211 } else {
212 this.editor._doc.execCommand("Unlink", false, "");
213 }
214 if (this.dialog) {
215 this.dialog.close();
216 }
217 },
218
219 /*
220 * Set attributes of anchors intersecting a range in the given node
221 */
222 setLinkAttributes : function(node, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink) {
223 if (/^a$/i.test(node.nodeName)) {
224 var nodeInRange = false;
225 if (HTMLArea.is_gecko) {
226 nodeInRange = this.editor.rangeIntersectsNode(range, node);
227 } else {
228 if (this.editor._getSelection().type.toLowerCase() == "control") {
229 // we assume an image is selected
230 nodeInRange = true;
231 } else {
232 var nodeRange = this.editor._doc.body.createTextRange();
233 nodeRange.moveToElementText(node);
234 nodeInRange = range.inRange(nodeRange) || (range.compareEndPoints("StartToStart", nodeRange) == 0) || (range.compareEndPoints("EndToEnd", nodeRange) == 0);
235 }
236 }
237 if (nodeInRange) {
238 if (imageNode != null) {
239 if (addIconAfterLink) {
240 node.appendChild(imageNode.cloneNode(false));
241 } else {
242 node.insertBefore(imageNode.cloneNode(false), node.firstChild);
243 }
244 }
245 if (HTMLArea.is_gecko && !HTMLArea.is_safari && !HTMLArea.is_opera) {
246 node.href = decodeURIComponent(node.href);
247 }
248 if (cur_target.trim()) node.target = cur_target.trim();
249 else node.removeAttribute("target");
250 if (cur_class.trim()) {
251 node.className = cur_class.trim();
252 } else {
253 if (HTMLArea.is_gecko) {
254 node.removeAttribute('class');
255 } else {
256 node.removeAttribute('className');
257 }
258 }
259 if (cur_title.trim()) {
260 node.title = cur_title.trim();
261 } else {
262 node.removeAttribute("title");
263 node.removeAttribute("rtekeep");
264 }
265 }
266 } else {
267 for (var i = node.firstChild;i;i = i.nextSibling) {
268 if (i.nodeType == 1 || i.nodeType == 11) {
269 this.setLinkAttributes(i, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink);
270 }
271 }
272 }
273 },
274
275 /*
276 * Clean up images in special anchor classes
277 */
278 cleanClassesAnchorImages : function(node) {
279 var nodeArray = [], splitArray1 = [], splitArray2 = [];
280 for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling) {
281 if (/^img$/i.test(childNode.nodeName)) {
282 splitArray1 = childNode.src.split("/");
283 for (var i = HTMLArea.classesAnchorSetup.length; --i >= 0;) {
284 if (HTMLArea.classesAnchorSetup[i]["image"]) {
285 splitArray2 = HTMLArea.classesAnchorSetup[i]["image"].split("/");
286 if (splitArray1[splitArray1.length-1] == splitArray2[splitArray2.length-1]) {
287 nodeArray.push(childNode);
288 break;
289 }
290 }
291 }
292 }
293 }
294 for (i = nodeArray.length; --i >= 0;) {
295 node.removeChild(nodeArray[i]);
296 }
297 },
298
299 /*
300 * Clean up all anchors intesecting with the range in the given node
301 */
302 cleanAllLinks : function(node, range, keepLinks) {
303 if (/^a$/i.test(node.nodeName)) {
304 var intersection = false;
305 if (HTMLArea.is_gecko) {
306 intersection = this.editor.rangeIntersectsNode(range, node);
307 } else {
308 if (this.editor._getSelection().type.toLowerCase() == "control") {
309 // we assume an image is selected
310 intersection = true;
311 } else {
312 var nodeRange = this.editor._doc.body.createTextRange();
313 nodeRange.moveToElementText(node);
314 intersection = range.inRange(nodeRange) || ((range.compareEndPoints("StartToStart", nodeRange) > 0) && (range.compareEndPoints("StartToEnd", nodeRange) < 0)) || ((range.compareEndPoints("EndToStart", nodeRange) > 0) && (range.compareEndPoints("EndToEnd", nodeRange) < 0));
315 }
316 }
317 if (intersection) {
318 this.cleanClassesAnchorImages(node);
319 if (!keepLinks) {
320 while(node.firstChild) node.parentNode.insertBefore(node.firstChild, node);
321 node.parentNode.removeChild(node);
322 }
323 }
324 } else {
325 for (var i = node.firstChild;i;i = i.nextSibling) {
326 if (i.nodeType == 1 || i.nodeType == 11) this.cleanAllLinks(i, range, keepLinks);
327 }
328 }
329 }
330 });
331