Interim commit.
[yaffs-website] / web / modules / contrib / entity_embed / js / plugins / drupalentity / plugin.js
1 /**
2  * @file
3  * Drupal Entity embed plugin.
4  */
5
6 (function ($, Drupal, CKEDITOR) {
7
8   "use strict";
9
10   CKEDITOR.plugins.add('drupalentity', {
11     // This plugin requires the Widgets System defined in the 'widget' plugin.
12     requires: 'widget',
13
14     // The plugin initialization logic goes inside this method.
15     beforeInit: function (editor) {
16       // Configure CKEditor DTD for custom drupal-entity element.
17       // @see https://www.drupal.org/node/2448449#comment-9717735
18       var dtd = CKEDITOR.dtd, tagName;
19       dtd['drupal-entity'] = {'#': 1};
20       // Register drupal-entity element as allowed child, in each tag that can
21       // contain a div element.
22       for (tagName in dtd) {
23         if (dtd[tagName].div) {
24           dtd[tagName]['drupal-entity'] = 1;
25         }
26       }
27
28       // Generic command for adding/editing entities of all types.
29       editor.addCommand('editdrupalentity', {
30         allowedContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
31         requiredContent: 'drupal-entity[data-embed-button,data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
32         modes: { wysiwyg : 1 },
33         canUndo: true,
34         exec: function (editor, data) {
35           data = data || {};
36
37           var existingElement = getSelectedEmbeddedEntity(editor);
38
39           var existingValues = {};
40           if (existingElement && existingElement.$ && existingElement.$.firstChild) {
41             var embedDOMElement = existingElement.$.firstChild;
42             // Populate array with the entity's current attributes.
43             var attribute = null, attributeName;
44             for (var key = 0; key < embedDOMElement.attributes.length; key++) {
45               attribute = embedDOMElement.attributes.item(key);
46               attributeName = attribute.nodeName.toLowerCase();
47               if (attributeName.substring(0, 15) === 'data-cke-saved-') {
48                 continue;
49               }
50               existingValues[attributeName] = existingElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
51             }
52           }
53
54           var embed_button_id = data.id ? data.id : existingValues['data-embed-button'];
55
56           var dialogSettings = {
57             dialogClass: 'entity-select-dialog',
58             resizable: false
59           };
60
61           var saveCallback = function (values) {
62             var entityElement = editor.document.createElement('drupal-entity');
63             var attributes = values.attributes;
64             for (var key in attributes) {
65               entityElement.setAttribute(key, attributes[key]);
66             }
67
68             editor.insertHtml(entityElement.getOuterHtml());
69             if (existingElement) {
70               // Detach the behaviors that were attached when the entity content
71               // was inserted.
72               Drupal.runEmbedBehaviors('detach', existingElement.$);
73               existingElement.remove();
74             }
75           };
76
77           // Open the entity embed dialog for corresponding EmbedButton.
78           Drupal.ckeditor.openDialog(editor, Drupal.url('entity-embed/dialog/' + editor.config.drupal.format + '/' + embed_button_id), existingValues, saveCallback, dialogSettings);
79         }
80       });
81
82       // Register the entity embed widget.
83       editor.widgets.add('drupalentity', {
84         // Minimum HTML which is required by this widget to work.
85         allowedContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
86         requiredContent: 'drupal-entity[data-entity-type,data-entity-uuid,data-entity-embed-display,data-entity-embed-display-settings,data-align,data-caption]',
87
88         // Simply recognize the element as our own. The inner markup if fetched
89         // and inserted the init() callback, since it requires the actual DOM
90         // element.
91         upcast: function (element) {
92           var attributes = element.attributes;
93           if (attributes['data-entity-type'] === undefined || (attributes['data-entity-id'] === undefined && attributes['data-entity-uuid'] === undefined) || (attributes['data-view-mode'] === undefined && attributes['data-entity-embed-display'] === undefined)) {
94             return;
95           }
96           // Generate an ID for the element, so that we can use the Ajax
97           // framework.
98           element.attributes.id = generateEmbedId();
99           return element;
100         },
101
102         // Fetch the rendered entity.
103         init: function () {
104           /** @type {CKEDITOR.dom.element} */
105           var element = this.element;
106           // Use the Ajax framework to fetch the HTML, so that we can retrieve
107           // out-of-band assets (JS, CSS...).
108           var entityEmbedPreview = Drupal.ajax({
109             base: element.getId(),
110             element: element.$,
111             url: Drupal.url('embed/preview/' + editor.config.drupal.format + '?' + $.param({
112               value: element.getOuterHtml()
113             })),
114             progress: {type: 'none'},
115             // Use a custom event to trigger the call.
116             event: 'entity_embed_dummy_event'
117           });
118           entityEmbedPreview.execute();
119         },
120
121         // Downcast the element.
122         downcast: function (element) {
123           // Only keep the wrapping element.
124           element.setHtml('');
125           // Remove the auto-generated ID.
126           delete element.attributes.id;
127           return element;
128         }
129       });
130
131       // Register the toolbar buttons.
132       if (editor.ui.addButton) {
133         for (var key in editor.config.DrupalEntity_buttons) {
134           var button = editor.config.DrupalEntity_buttons[key];
135           editor.ui.addButton(button.id, {
136             label: button.label,
137             data: button,
138             allowedContent: 'drupal-entity[!data-entity-type,!data-entity-uuid,!data-entity-embed-display,!data-entity-embed-display-settings,!data-align,!data-caption,!data-embed-button]',
139             click: function(editor) {
140               editor.execCommand('editdrupalentity', this.data);
141             },
142             icon: button.image
143           });
144         }
145       }
146
147       // Register context menu option for editing widget.
148       if (editor.contextMenu) {
149         editor.addMenuGroup('drupalentity');
150         editor.addMenuItem('drupalentity', {
151           label: Drupal.t('Edit Entity'),
152           icon: this.path + 'entity.png',
153           command: 'editdrupalentity',
154           group: 'drupalentity'
155         });
156
157         editor.contextMenu.addListener(function(element) {
158           if (isEditableEntityWidget(editor, element)) {
159             return { drupalentity: CKEDITOR.TRISTATE_OFF };
160           }
161         });
162       }
163
164       // Execute widget editing action on double click.
165       editor.on('doubleclick', function (evt) {
166         var element = getSelectedEmbeddedEntity(editor) || evt.data.element;
167
168         if (isEditableEntityWidget(editor, element)) {
169           editor.execCommand('editdrupalentity');
170         }
171       });
172     }
173   });
174
175   /**
176    * Get the surrounding drupalentity widget element.
177    *
178    * @param {CKEDITOR.editor} editor
179    */
180   function getSelectedEmbeddedEntity(editor) {
181     var selection = editor.getSelection();
182     var selectedElement = selection.getSelectedElement();
183     if (isEditableEntityWidget(editor, selectedElement)) {
184       return selectedElement;
185     }
186
187     return null;
188   }
189
190   /**
191    * Checks if the given element is an editable drupalentity widget.
192    *
193    * @param {CKEDITOR.editor} editor
194    * @param {CKEDITOR.htmlParser.element} element
195    */
196   function isEditableEntityWidget (editor, element) {
197     var widget = editor.widgets.getByElement(element, true);
198     if (!widget || widget.name !== 'drupalentity') {
199       return false;
200     }
201
202     var button = $(element.$.firstChild).attr('data-embed-button');
203     if (!button) {
204       // If there was no data-embed-button attribute, not editable.
205       return false;
206     }
207
208     // The button itself must be valid.
209     return editor.config.DrupalEntity_buttons.hasOwnProperty(button);
210   }
211
212   /**
213    * Generates unique HTML IDs for the widgets.
214    *
215    * @returns {string}
216    */
217   function generateEmbedId() {
218     if (typeof generateEmbedId.counter == 'undefined') {
219       generateEmbedId.counter = 0;
220     }
221     return 'entity-embed-' + generateEmbedId.counter++;
222   }
223
224 })(jQuery, Drupal, CKEDITOR);