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