15a242acebe95108c0227bb7273662e7123c65b9
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Resources / Public / JavaScript / Plugins / DefaultLink.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 * Default Link Plugin for TYPO3 htmlArea RTE
16 */
17 define('TYPO3/CMS/Rtehtmlarea/Plugins/DefaultLink',
18 ['TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
19 'TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
20 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util',
21 'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM'],
22 function (Plugin, UserAgent, Util, Dom) {
23
24 var DefaultLink = function (editor, pluginName) {
25 this.constructor.super.call(this, editor, pluginName);
26 };
27 Util.inherit(DefaultLink, Plugin);
28 Util.apply(DefaultLink.prototype, {
29
30 /**
31 * This function gets called by the class constructor
32 */
33 configurePlugin: function (editor) {
34 this.baseURL = this.editorConfiguration.baseURL;
35 this.pageTSConfiguration = this.editorConfiguration.buttons.link;
36 this.stripBaseUrl = this.pageTSConfiguration && this.pageTSConfiguration.stripBaseUrl && this.pageTSConfiguration.stripBaseUrl;
37 this.showTarget = !(this.pageTSConfiguration && this.pageTSConfiguration.targetSelector && this.pageTSConfiguration.targetSelector.disabled);
38
39 /**
40 * Registering plugin "About" information
41 */
42 var pluginInformation = {
43 version : '2.3',
44 developer : 'Stanislas Rolland',
45 developerUrl : 'http://www.sjbr.ca/',
46 copyrightOwner : 'Stanislas Rolland',
47 sponsor : 'SJBR',
48 sponsorUrl : 'http://www.sjbr.ca/',
49 license : 'GPL'
50 };
51 this.registerPluginInformation(pluginInformation);
52 /*
53 * Registering the buttons
54 */
55 var buttonList = this.buttonList, buttonId;
56 for (var i = 0; i < buttonList.length; ++i) {
57 var button = buttonList[i];
58 buttonId = button[0];
59 var buttonConfiguration = {
60 id : buttonId,
61 tooltip : this.localize(buttonId.toLowerCase()),
62 iconCls : 'htmlarea-action-' + button[4],
63 action : 'onButtonPress',
64 hotKey : (this.pageTSConfiguration ? this.pageTSConfiguration.hotKey : null),
65 context : button[1],
66 selection : button[2],
67 dialog : button[3]
68 };
69 this.registerButton(buttonConfiguration);
70 }
71 return true;
72 },
73
74 /**
75 * The list of buttons added by this plugin
76 */
77 buttonList: [
78 ['CreateLink', 'a,img', false, true, 'link-edit'],
79 ['UnLink', 'a', false, false, 'unlink']
80 ],
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 (UserAgent.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,
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: (UserAgent.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,
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 }, Util.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 (!UserAgent.isIEBeforeIE9 && !/^a$/i.test(a.nodeName)) {
324 var range = this.editor.getSelection().createRange();
325 if (range.startContainer.nodeType !== 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 (UserAgent.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 button.setInactive(true);
382 if (mode === 'wysiwyg' && this.editor.isEditable()) {
383 switch (button.itemId) {
384 case 'CreateLink':
385 button.setDisabled(selectionEmpty && !button.isInContext(mode, selectionEmpty, ancestors));
386 if (!button.disabled) {
387 var node = this.editor.getSelection().getParentElement();
388 var el = this.editor.getSelection().getFirstAncestorOfType('a');
389 if (el != null) {
390 node = el;
391 }
392 if (node != null && /^a$/i.test(node.nodeName)) {
393 button.setTooltip(this.localize('Modify link'));
394 button.setInactive(false);
395 } else {
396 button.setTooltip(this.localize('Insert link'));
397 }
398 }
399 break;
400 case 'UnLink':
401 var link = false;
402 // Let's see if a link was double-clicked in Firefox
403 if (UserAgent.isGecko && !selectionEmpty) {
404 var range = this.editor.getSelection().createRange();
405 if (range.startContainer.nodeType === Dom.ELEMENT_NODE && range.startContainer == range.endContainer && (range.endOffset - range.startOffset == 1)) {
406 var node = range.startContainer.childNodes[range.startOffset];
407 if (node && /^a$/i.test(node.nodeName) && node.textContent == range.toString()) {
408 link = true;
409 }
410 }
411 }
412 button.setDisabled(!link && !button.isInContext(mode, selectionEmpty, ancestors));
413 break;
414 }
415 }
416 }
417 });
418
419 return DefaultLink;
420
421 });