33847b0398b5b9821ef2a24f2feca6bcb42af2c7
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / DefaultLink / default-link.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2008-2010 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 * This script is a modified version of a script published under the htmlArea License.
25 * A copy of the htmlArea License may be found in the textfile HTMLAREA_LICENSE.txt.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /*
30 * Default Link Plugin for TYPO3 htmlArea RTE
31 *
32 * TYPO3 SVN ID: $Id$
33 */
34 HTMLArea.DefaultLink = HTMLArea.Plugin.extend({
35 constructor: function(editor, pluginName) {
36 this.base(editor, pluginName);
37 },
38 /*
39 * This function gets called by the class constructor
40 */
41 configurePlugin: function(editor) {
42 this.baseURL = this.editorConfiguration.baseURL;
43 this.pageTSConfiguration = this.editorConfiguration.buttons.link;
44 this.stripBaseUrl = this.pageTSConfiguration && this.pageTSConfiguration.stripBaseUrl && this.pageTSConfiguration.stripBaseUrl;
45 this.showTarget = !(this.pageTSConfiguration && this.pageTSConfiguration.targetSelector && this.pageTSConfiguration.targetSelector.disabled);
46 /*
47 * Registering plugin "About" information
48 */
49 var pluginInformation = {
50 version : '2.1',
51 developer : 'Stanislas Rolland',
52 developerUrl : 'http://www.sjbr.ca/',
53 copyrightOwner : 'Stanislas Rolland',
54 sponsor : 'SJBR',
55 sponsorUrl : 'http://www.sjbr.ca/',
56 license : 'GPL'
57 };
58 this.registerPluginInformation(pluginInformation);
59 /*
60 * Registering the buttons
61 */
62 var buttonList = this.buttonList, buttonId;
63 for (var i = 0; i < buttonList.length; ++i) {
64 var button = buttonList[i];
65 buttonId = button[0];
66 var buttonConfiguration = {
67 id : buttonId,
68 tooltip : this.localize(buttonId.toLowerCase()),
69 iconCls : 'htmlarea-action-' + button[4],
70 action : 'onButtonPress',
71 hotKey : (this.pageTSConfiguration ? this.pageTSConfiguration.hotKey : null),
72 context : button[1],
73 selection : button[2],
74 dialog : button[3]
75 };
76 this.registerButton(buttonConfiguration);
77 }
78 return true;
79 },
80 /*
81 * The list of buttons added by this plugin
82 */
83 buttonList: [
84 ['CreateLink', 'a,img', false, true, 'link-edit'],
85 ['UnLink', 'a', false, false, 'unlink']
86 ],
87 /*
88 * Sets of default configuration values for dialogue form fields
89 */
90 configDefaults: {
91 combo: {
92 editable: true,
93 selectOnFocus: true,
94 typeAhead: true,
95 triggerAction: 'all',
96 forceSelection: true,
97 mode: 'local',
98 valueField: 'value',
99 displayField: 'text',
100 helpIcon: true,
101 tpl: '<tpl for="."><div ext:qtip="{value}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{text}</div></tpl>'
102 }
103 },
104 /*
105 * This function gets called when the 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 *
111 * @return boolean false if action is completed
112 */
113 onButtonPress: function(editor, id, target) {
114 // Could be a button or its hotkey
115 var buttonId = this.translateHotKey(id);
116 buttonId = buttonId ? buttonId : id;
117 this.editor.focus();
118 this.link = this.editor.getParentElement();
119 var el = HTMLArea.getElementObject(this.link, 'a');
120 if (el && /^a$/i.test(el.nodeName)) {
121 this.link = el;
122 }
123 if (!this.link || !/^a$/i.test(this.link.nodeName)) {
124 this.link = null;
125 }
126 switch (buttonId) {
127 case 'UnLink':
128 this.unLink();
129 break;
130 case 'CreateLink':
131 if (!this.link) {
132 var selection = this.editor._getSelection();
133 if (this.editor._selectionEmpty(selection)) {
134 Ext.MessageBox.alert('', this.localize('Select some text'));
135 break;
136 }
137 this.parameters = {
138 href: 'http://',
139 title: '',
140 target: ''
141 };
142 } else {
143 this.parameters = {
144 href: (Ext.isIE && this.stripBaseUrl) ? this.stripBaseURL(this.link.href) : this.link.getAttribute('href'),
145 title: this.link.title,
146 target: this.link.target
147 };
148 }
149 // Open dialogue window
150 this.openDialogue(
151 buttonId,
152 this.getButton(buttonId).tooltip.title,
153 this.getWindowDimensions(
154 {
155 width: 470,
156 height:150
157 },
158 buttonId
159 )
160 );
161 break;
162 }
163 return false;
164 },
165 /*
166 * Open the dialogue window
167 *
168 * @param string buttonId: the button id
169 * @param string title: the window title
170 * @param integer dimensions: the opening width of the window
171 *
172 * @return void
173 */
174 openDialogue: function (buttonId, title, dimensions) {
175 this.dialog = new Ext.Window({
176 title: this.localize(title) || title,
177 cls: 'htmlarea-window',
178 border: false,
179 width: dimensions.width,
180 height: 'auto',
181 // As of ExtJS 3.1, JS error with IE when the window is resizable
182 resizable: !Ext.isIE,
183 iconCls: this.getButton(buttonId).iconCls,
184 listeners: {
185 afterrender: {
186 fn: this.onAfterRender,
187 scope: this
188 },
189 close: {
190 fn: this.onClose,
191 scope: this
192 }
193 },
194 items: [{
195 xtype: 'fieldset',
196 defaultType: 'textfield',
197 labelWidth: 100,
198 defaults: {
199 helpIcon: true,
200 width: 250,
201 labelSeparator: ''
202 },
203 items: [{
204 itemId: 'href',
205 name: 'href',
206 fieldLabel: this.localize('URL:'),
207 value: this.parameters.href,
208 helpTitle: this.localize('link_href_tooltip')
209 },{
210 itemId: 'title',
211 name: 'title',
212 fieldLabel: this.localize('Title (tooltip):'),
213 value: this.parameters.title,
214 helpTitle: this.localize('link_title_tooltip')
215 }, Ext.apply({
216 xtype: 'combo',
217 fieldLabel: this.localize('Target:'),
218 itemId: 'target',
219 helpTitle: this.localize('link_target_tooltip'),
220 store: new Ext.data.ArrayStore({
221 autoDestroy: true,
222 fields: [ { name: 'text'}, { name: 'value'}],
223 data: [
224 [this.localize('target_none'), ''],
225 [this.localize('target_blank'), '_blank'],
226 [this.localize('target_self'), '_self'],
227 [this.localize('target_top'), '_top'],
228 [this.localize('target_other'), '_other']
229 ]
230 }),
231 listeners: {
232 select: {
233 fn: this.onTargetSelect
234 }
235 },
236 hidden: !this.showTarget
237 }, this.configDefaults['combo'])
238 ,{
239 itemId: 'frame',
240 name: 'frame',
241 fieldLabel: this.localize('frame'),
242 helpTitle: this.localize('frame_help'),
243 hideLabel: true,
244 hidden: true
245 }
246 ]
247 }
248 ],
249 buttons: [
250 this.buildButtonConfig('OK', this.onOK),
251 this.buildButtonConfig('Cancel', this.onCancel)
252 ]
253 });
254 this.show();
255 },
256 /*
257 * Handler invoked after the dialogue window is rendered
258 * If the current target is not in the available options, show frame field
259 */
260 onAfterRender: function (dialog) {
261 var targetCombo = dialog.find('itemId', 'target')[0];
262 if (!targetCombo.hidden && this.parameters.target) {
263 var frameField = dialog.find('itemId', 'frame')[0];
264 var index = targetCombo.getStore().find('value', this.parameters.target);
265 if (index == -1) {
266 // The target is a specific frame name
267 targetCombo.setValue('_other');
268 frameField.setValue(this.parameters.target);
269 frameField.show();
270 frameField.label.show();
271 } else {
272 targetCombo.setValue(this.parameters.target);
273 }
274 }
275 },
276 /*
277 * Handler invoked when a target is selected
278 */
279 onTargetSelect: function (combo, record) {
280 var frameField = combo.ownerCt.getComponent('frame');
281 if (record.get('value') == '_other') {
282 frameField.show();
283 frameField.label.show();
284 frameField.focus();
285 } else if (!frameField.hidden) {
286 frameField.hide();
287 frameField.label.hide();
288 }
289 },
290 /*
291 * Handler invoked when the OK button is clicked
292 */
293 onOK: function () {
294 var hrefField = this.dialog.find('itemId', 'href')[0];
295 var href = hrefField.getValue().trim();
296 if (href) {
297 var title = this.dialog.find('itemId', 'title')[0].getValue();
298 var target = this.dialog.find('itemId', 'target')[0].getValue();
299 if (target == '_other') {
300 target = this.dialog.find('itemId', 'frame')[0].getValue().trim();
301 }
302 this.createLink(href, title, target);
303 this.close();
304 } else {
305 Ext.MessageBox.alert('', this.localize('link_url_required'), function () { hrefField.focus(); });
306 }
307 return false;
308 },
309 /*
310 * Create the link
311 *
312 * @param string href: the value of href attribute
313 * @param string title: the value of title attribute
314 * @param string target: the value of target attribute
315 *
316 * @return void
317 */
318 createLink: function (href, title, target) {
319 var a = this.link;
320 if (!a) {
321 this.editor.focus();
322 this.restoreSelection();
323 this.editor.document.execCommand('CreateLink', false, href);
324 a = this.editor.getParentElement();
325 if (!Ext.isIE && !/^a$/i.test(a.nodeName)) {
326 var range = this.editor._createRange(this.editor._getSelection());
327 if (range.startContainer.nodeType != 3) {
328 a = range.startContainer.childNodes[range.startOffset];
329 } else {
330 a = range.startContainer.nextSibling;
331 }
332 this.editor.selectNode(a);
333 }
334 var el = HTMLArea.getElementObject(a, 'a');
335 if (el != null && /^a$/i.test(el.nodeName)) {
336 a = el;
337 }
338 } else {
339 a.href = href;
340 }
341 if (a && /^a$/i.test(a.nodeName)) {
342 a.title = title;
343 a.target = target;
344 if (Ext.isOpera) {
345 this.editor.selectNodeContents(a, false);
346 } else {
347 this.editor.selectNodeContents(a);
348 }
349 }
350 },
351 /*
352 * Unlink the selection
353 */
354 unLink: function () {
355 this.editor.focus();
356 this.restoreSelection();
357 if (this.link) {
358 this.editor.selectNode(this.link);
359 }
360 this.editor.document.execCommand('Unlink', false, '');
361 },
362 /*
363 * IE makes relative links absolute. This function reverts this conversion.
364 *
365 * @param string url: the url
366 *
367 * @return string the url stripped out of the baseurl
368 */
369 stripBaseURL: function (url) {
370 var baseurl = this.baseURL;
371 // strip to last directory in case baseurl points to a file
372 baseurl = baseurl.replace(/[^\/]+$/, '');
373 var basere = new RegExp(baseurl);
374 url = url.replace(basere, '');
375 // strip host-part of URL which is added by MSIE to links relative to server root
376 baseurl = baseurl.replace(/^(https?:\/\/[^\/]+)(.*)$/, "$1");
377 basere = new RegExp(baseurl);
378 return url.replace(basere, '');
379 },
380 /*
381 * This function gets called when the toolbar is updated
382 */
383 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors) {
384 if (mode === 'wysiwyg' && this.editor.isEditable()) {
385 switch (button.itemId) {
386 case 'CreateLink':
387 button.setDisabled(selectionEmpty && !button.isInContext(mode, selectionEmpty, ancestors));
388 if (!button.disabled) {
389 var node = this.editor.getParentElement();
390 var el = HTMLArea.getElementObject(node, 'a');
391 if (el != null && /^a$/i.test(el.nodeName)) {
392 node = el;
393 }
394 if (node != null && /^a$/i.test(node.nodeName)) {
395 button.setTooltip({ title: this.localize('Modify link') });
396 } else {
397 button.setTooltip({ title: this.localize('Insert link') });
398 }
399 }
400 break;
401 case 'UnLink':
402 var link = false;
403 // Let's see if a link was double-clicked in Firefox
404 if (Ext.isGecko && !selectionEmpty) {
405 var range = this.editor._createRange(this.editor._getSelection());
406 if (range.startContainer.nodeType == 1 && range.startContainer == range.endContainer && (range.endOffset - range.startOffset == 1)) {
407 var node = range.startContainer.childNodes[range.startOffset];
408 if (node && /^a$/i.test(node.nodeName) && node.textContent == range.toString()) {
409 link = true;
410 }
411 }
412 }
413 button.setDisabled(!link && !button.isInContext(mode, selectionEmpty, ancestors));
414 break;
415 }
416 }
417 }
418 });