Version 1
[yaffs-website] / web / core / modules / ckeditor / js / plugins / drupallink / plugin.js
1 /**
2  * @file
3  * Drupal Link plugin.
4  *
5  * @ignore
6  */
7
8 (function ($, Drupal, drupalSettings, CKEDITOR) {
9
10   'use strict';
11
12   function parseAttributes(editor, element) {
13     var parsedAttributes = {};
14
15     var domElement = element.$;
16     var attribute;
17     var attributeName;
18     for (var attrIndex = 0; attrIndex < domElement.attributes.length; attrIndex++) {
19       attribute = domElement.attributes.item(attrIndex);
20       attributeName = attribute.nodeName.toLowerCase();
21       // Ignore data-cke-* attributes; they're CKEditor internals.
22       if (attributeName.indexOf('data-cke-') === 0) {
23         continue;
24       }
25       // Store the value for this attribute, unless there's a data-cke-saved-
26       // alternative for it, which will contain the quirk-free, original value.
27       parsedAttributes[attributeName] = element.data('cke-saved-' + attributeName) || attribute.nodeValue;
28     }
29
30     // Remove any cke_* classes.
31     if (parsedAttributes.class) {
32       parsedAttributes.class = CKEDITOR.tools.trim(parsedAttributes.class.replace(/cke_\S+/, ''));
33     }
34
35     return parsedAttributes;
36   }
37
38   function getAttributes(editor, data) {
39     var set = {};
40     for (var attributeName in data) {
41       if (data.hasOwnProperty(attributeName)) {
42         set[attributeName] = data[attributeName];
43       }
44     }
45
46     // CKEditor tracks the *actual* saved href in a data-cke-saved-* attribute
47     // to work around browser quirks. We need to update it.
48     set['data-cke-saved-href'] = set.href;
49
50     // Remove all attributes which are not currently set.
51     var removed = {};
52     for (var s in set) {
53       if (set.hasOwnProperty(s)) {
54         delete removed[s];
55       }
56     }
57
58     return {
59       set: set,
60       removed: CKEDITOR.tools.objectKeys(removed)
61     };
62   }
63
64   CKEDITOR.plugins.add('drupallink', {
65     icons: 'drupallink,drupalunlink',
66     hidpi: true,
67
68     init: function (editor) {
69       // Add the commands for link and unlink.
70       editor.addCommand('drupallink', {
71         allowedContent: {
72           a: {
73             attributes: {
74               '!href': true
75             },
76             classes: {}
77           }
78         },
79         requiredContent: new CKEDITOR.style({
80           element: 'a',
81           attributes: {
82             href: ''
83           }
84         }),
85         modes: {wysiwyg: 1},
86         canUndo: true,
87         exec: function (editor) {
88           var drupalImageUtils = CKEDITOR.plugins.drupalimage;
89           var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
90           var linkElement = getSelectedLink(editor);
91
92           // Set existing values based on selected element.
93           var existingValues = {};
94           if (linkElement && linkElement.$) {
95             existingValues = parseAttributes(editor, linkElement);
96           }
97           // Or, if an image widget is focused, we're editing a link wrapping
98           // an image widget.
99           else if (focusedImageWidget && focusedImageWidget.data.link) {
100             existingValues = CKEDITOR.tools.clone(focusedImageWidget.data.link);
101           }
102
103           // Prepare a save callback to be used upon saving the dialog.
104           var saveCallback = function (returnValues) {
105             // If an image widget is focused, we're not editing an independent
106             // link, but we're wrapping an image widget in a link.
107             if (focusedImageWidget) {
108               focusedImageWidget.setData('link', CKEDITOR.tools.extend(returnValues.attributes, focusedImageWidget.data.link));
109               editor.fire('saveSnapshot');
110               return;
111             }
112
113             editor.fire('saveSnapshot');
114
115             // Create a new link element if needed.
116             if (!linkElement && returnValues.attributes.href) {
117               var selection = editor.getSelection();
118               var range = selection.getRanges(1)[0];
119
120               // Use link URL as text with a collapsed cursor.
121               if (range.collapsed) {
122                 // Shorten mailto URLs to just the email address.
123                 var text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
124                 range.insertNode(text);
125                 range.selectNodeContents(text);
126               }
127
128               // Create the new link by applying a style to the new text.
129               var style = new CKEDITOR.style({element: 'a', attributes: returnValues.attributes});
130               style.type = CKEDITOR.STYLE_INLINE;
131               style.applyToRange(range);
132               range.select();
133
134               // Set the link so individual properties may be set below.
135               linkElement = getSelectedLink(editor);
136             }
137             // Update the link properties.
138             else if (linkElement) {
139               for (var attrName in returnValues.attributes) {
140                 if (returnValues.attributes.hasOwnProperty(attrName)) {
141                   // Update the property if a value is specified.
142                   if (returnValues.attributes[attrName].length > 0) {
143                     var value = returnValues.attributes[attrName];
144                     linkElement.data('cke-saved-' + attrName, value);
145                     linkElement.setAttribute(attrName, value);
146                   }
147                   // Delete the property if set to an empty string.
148                   else {
149                     linkElement.removeAttribute(attrName);
150                   }
151                 }
152               }
153             }
154
155             // Save snapshot for undo support.
156             editor.fire('saveSnapshot');
157           };
158           // Drupal.t() will not work inside CKEditor plugins because CKEditor
159           // loads the JavaScript file instead of Drupal. Pull translated
160           // strings from the plugin settings that are translated server-side.
161           var dialogSettings = {
162             title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
163             dialogClass: 'editor-link-dialog'
164           };
165
166           // Open the dialog for the edit form.
167           Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/link/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
168         }
169       });
170       editor.addCommand('drupalunlink', {
171         contextSensitive: 1,
172         startDisabled: 1,
173         requiredContent: new CKEDITOR.style({
174           element: 'a',
175           attributes: {
176             href: ''
177           }
178         }),
179         exec: function (editor) {
180           var style = new CKEDITOR.style({element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1});
181           editor.removeStyle(style);
182         },
183         refresh: function (editor, path) {
184           var element = path.lastElement && path.lastElement.getAscendant('a', true);
185           if (element && element.getName() === 'a' && element.getAttribute('href') && element.getChildCount()) {
186             this.setState(CKEDITOR.TRISTATE_OFF);
187           }
188           else {
189             this.setState(CKEDITOR.TRISTATE_DISABLED);
190           }
191         }
192       });
193
194       // CTRL + K.
195       editor.setKeystroke(CKEDITOR.CTRL + 75, 'drupallink');
196
197       // Add buttons for link and unlink.
198       if (editor.ui.addButton) {
199         editor.ui.addButton('DrupalLink', {
200           label: Drupal.t('Link'),
201           command: 'drupallink'
202         });
203         editor.ui.addButton('DrupalUnlink', {
204           label: Drupal.t('Unlink'),
205           command: 'drupalunlink'
206         });
207       }
208
209       editor.on('doubleclick', function (evt) {
210         var element = getSelectedLink(editor) || evt.data.element;
211
212         if (!element.isReadOnly()) {
213           if (element.is('a')) {
214             editor.getSelection().selectElement(element);
215             editor.getCommand('drupallink').exec();
216           }
217         }
218       });
219
220       // If the "menu" plugin is loaded, register the menu items.
221       if (editor.addMenuItems) {
222         editor.addMenuItems({
223           link: {
224             label: Drupal.t('Edit Link'),
225             command: 'drupallink',
226             group: 'link',
227             order: 1
228           },
229
230           unlink: {
231             label: Drupal.t('Unlink'),
232             command: 'drupalunlink',
233             group: 'link',
234             order: 5
235           }
236         });
237       }
238
239       // If the "contextmenu" plugin is loaded, register the listeners.
240       if (editor.contextMenu) {
241         editor.contextMenu.addListener(function (element, selection) {
242           if (!element || element.isReadOnly()) {
243             return null;
244           }
245           var anchor = getSelectedLink(editor);
246           if (!anchor) {
247             return null;
248           }
249
250           var menu = {};
251           if (anchor.getAttribute('href') && anchor.getChildCount()) {
252             menu = {link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF};
253           }
254           return menu;
255         });
256       }
257     }
258   });
259
260   /**
261    * Get the surrounding link element of current selection.
262    *
263    * The following selection will all return the link element.
264    *
265    * @example
266    *  <a href="#">li^nk</a>
267    *  <a href="#">[link]</a>
268    *  text[<a href="#">link]</a>
269    *  <a href="#">li[nk</a>]
270    *  [<b><a href="#">li]nk</a></b>]
271    *  [<a href="#"><b>li]nk</b></a>
272    *
273    * @param {CKEDITOR.editor} editor
274    *   The CKEditor editor object
275    *
276    * @return {?HTMLElement}
277    *   The selected link element, or null.
278    *
279    */
280   function getSelectedLink(editor) {
281     var selection = editor.getSelection();
282     var selectedElement = selection.getSelectedElement();
283     if (selectedElement && selectedElement.is('a')) {
284       return selectedElement;
285     }
286
287     var range = selection.getRanges(true)[0];
288
289     if (range) {
290       range.shrink(CKEDITOR.SHRINK_TEXT);
291       return editor.elementPath(range.getCommonAncestor()).contains('a', 1);
292     }
293     return null;
294   }
295
296   // Expose an API for other plugins to interact with drupallink widgets.
297   // (Compatible with the official CKEditor link plugin's API:
298   // http://dev.ckeditor.com/ticket/13885.)
299   CKEDITOR.plugins.drupallink = {
300     parseLinkAttributes: parseAttributes,
301     getLinkAttributes: getAttributes
302   };
303
304 })(jQuery, Drupal, drupalSettings, CKEDITOR);