2329e7fd3d2c94ff979365d7f2de77a35243936a
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / TYPO3Link / typo3link.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2005-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 *
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.0",
52 developer : "Stanislas Rolland",
53 developerUrl : "http://www.fructifor.ca/",
54 copyrightOwner : "Stanislas Rolland",
55 sponsor : "Fructifor Inc.",
56 sponsorUrl : "http://www.fructifor.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
158 this.editor._doc.execCommand("CreateLink", false, theLink);
159
160 selection = this.editor._getSelection();
161 range = this.editor._createRange(selection);
162 node = this.editor.getParentElement();
163 el = HTMLArea.getElementObject(node, "a");
164 if (el != null && /^a$/i.test(el.nodeName)) node = el;
165 if (node) {
166 if (HTMLArea.classesAnchorSetup && cur_class) {
167 for (var i = HTMLArea.classesAnchorSetup.length; --i >= 0;) {
168 anchorClass = HTMLArea.classesAnchorSetup[i];
169 if (anchorClass.name == cur_class && anchorClass.image) {
170 imageNode = this.editor._doc.createElement("img");
171 imageNode.src = anchorClass.image;
172 imageNode.alt = anchorClass.altText;
173 addIconAfterLink = anchorClass.addIconAfterLink;
174 break;
175 }
176 }
177 }
178 // We may have created multiple links in as many blocks
179 this.setLinkAttributes(node, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink);
180 }
181 this.dialog.close();
182 },
183
184 /*
185 * Unlink the selection.
186 * This function is called from the TYPO3 link popup and from the context menu.
187 */
188 unLink : function() {
189 this.editor.focusEditor();
190 var node = this.editor.getParentElement();
191 var el = HTMLArea.getElementObject(node, "a");
192 if (el != null && /^a$/i.test(el.nodeName)) node = el;
193 if (node != null && /^a$/i.test(node.nodeName)) this.editor.selectNode(node);
194 if (HTMLArea.classesAnchorSetup) {
195 var selection = this.editor._getSelection();
196 var range = this.editor._createRange(selection);
197 if (HTMLArea.is_gecko) {
198 this.cleanAllLinks(node, range, false);
199 } else {
200 this.cleanAllLinks(node, range, true);
201 this.editor._doc.execCommand("Unlink", false, "");
202 }
203 } else {
204 this.editor._doc.execCommand("Unlink", false, "");
205 }
206 if (this.dialog) {
207 this.dialog.close();
208 }
209 },
210
211 /*
212 * Set attributes of anchors intersecting a range in the given node
213 */
214 setLinkAttributes : function(node, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink) {
215 if (/^a$/i.test(node.nodeName)) {
216 var nodeInRange = false;
217 if (HTMLArea.is_gecko) {
218 nodeInRange = this.editor.rangeIntersectsNode(range, node);
219 } else {
220 if (this.editor._getSelection().type.toLowerCase() == "control") {
221 // we assume an image is selected
222 nodeInRange = true;
223 } else {
224 var nodeRange = this.editor._doc.body.createTextRange();
225 nodeRange.moveToElementText(node);
226 nodeInRange = range.inRange(nodeRange) || (range.compareEndPoints("StartToStart", nodeRange) == 0) || (range.compareEndPoints("EndToEnd", nodeRange) == 0);
227 }
228 }
229 if (nodeInRange) {
230 if (imageNode != null) {
231 if (addIconAfterLink) {
232 node.appendChild(imageNode.cloneNode(false));
233 } else {
234 node.insertBefore(imageNode.cloneNode(false), node.firstChild);
235 }
236 }
237 if (cur_target.trim()) node.target = cur_target.trim();
238 else node.removeAttribute("target");
239 if (cur_class.trim()) {
240 node.className = cur_class.trim();
241 } else {
242 if (HTMLArea.is_gecko) {
243 node.removeAttribute('class');
244 } else {
245 node.removeAttribute('className');
246 }
247 }
248 if (cur_title.trim()) {
249 node.title = cur_title.trim();
250 } else {
251 node.removeAttribute("title");
252 node.removeAttribute("rtekeep");
253 }
254 }
255 } else {
256 for (var i = node.firstChild;i;i = i.nextSibling) {
257 if (i.nodeType == 1 || i.nodeType == 11) {
258 this.setLinkAttributes(i, range, cur_target, cur_class, cur_title, imageNode, addIconAfterLink);
259 }
260 }
261 }
262 },
263
264 /*
265 * Clean up images in special anchor classes
266 */
267 cleanClassesAnchorImages : function(node) {
268 var nodeArray = [], splitArray1 = [], splitArray2 = [];
269 for (var childNode = node.firstChild; childNode; childNode = childNode.nextSibling) {
270 if (/^img$/i.test(childNode.nodeName)) {
271 splitArray1 = childNode.src.split("/");
272 for (var i = HTMLArea.classesAnchorSetup.length; --i >= 0;) {
273 if (HTMLArea.classesAnchorSetup[i]["image"]) {
274 splitArray2 = HTMLArea.classesAnchorSetup[i]["image"].split("/");
275 if (splitArray1[splitArray1.length-1] == splitArray2[splitArray2.length-1]) {
276 nodeArray.push(childNode);
277 break;
278 }
279 }
280 }
281 }
282 }
283 for (i = nodeArray.length; --i >= 0;) {
284 node.removeChild(nodeArray[i]);
285 }
286 },
287
288 /*
289 * Clean up all anchors intesecting with the range in the given node
290 */
291 cleanAllLinks : function(node, range, keepLinks) {
292 if (/^a$/i.test(node.nodeName)) {
293 var intersection = false;
294 if (HTMLArea.is_gecko) {
295 intersection = this.editor.rangeIntersectsNode(range, node);
296 } else {
297 if (this.editor._getSelection().type.toLowerCase() == "control") {
298 // we assume an image is selected
299 intersection = true;
300 } else {
301 var nodeRange = this.editor._doc.body.createTextRange();
302 nodeRange.moveToElementText(node);
303 intersection = range.inRange(nodeRange) || ((range.compareEndPoints("StartToStart", nodeRange) > 0) && (range.compareEndPoints("StartToEnd", nodeRange) < 0)) || ((range.compareEndPoints("EndToStart", nodeRange) > 0) && (range.compareEndPoints("EndToEnd", nodeRange) < 0));
304 }
305 }
306 if (intersection) {
307 this.cleanClassesAnchorImages(node);
308 if (!keepLinks) {
309 while(node.firstChild) node.parentNode.insertBefore(node.firstChild, node);
310 node.parentNode.removeChild(node);
311 }
312 }
313 } else {
314 for (var i = node.firstChild;i;i = i.nextSibling) {
315 if (i.nodeType == 1 || i.nodeType == 11) this.cleanAllLinks(i, range, keepLinks);
316 }
317 }
318 }
319 });
320