Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176
This commit is contained in:
commit
9921556621
13277 changed files with 1459781 additions and 0 deletions
BIN
core/modules/ckeditor/js/plugins/drupalimage/image.png
Normal file
BIN
core/modules/ckeditor/js/plugins/drupalimage/image.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 470 B |
230
core/modules/ckeditor/js/plugins/drupalimage/plugin.js
Normal file
230
core/modules/ckeditor/js/plugins/drupalimage/plugin.js
Normal file
|
@ -0,0 +1,230 @@
|
|||
/**
|
||||
* @file
|
||||
* Drupal Image plugin.
|
||||
*
|
||||
* This alters the existing CKEditor image2 widget plugin to:
|
||||
* - require a data-entity-type and a data-entity-uuid attribute (which Drupal
|
||||
* uses to track where images are being used)
|
||||
* - use a Drupal-native dialog (that is in fact just an alterable Drupal form
|
||||
* like any other) instead of CKEditor's own dialogs.
|
||||
*
|
||||
* @see \Drupal\editor\Form\EditorImageDialog
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
(function ($, Drupal, CKEDITOR) {
|
||||
|
||||
"use strict";
|
||||
|
||||
CKEDITOR.plugins.add('drupalimage', {
|
||||
requires: 'image2',
|
||||
|
||||
beforeInit: function (editor) {
|
||||
// Override the image2 widget definition to require and handle the
|
||||
// additional data-entity-type and data-entity-uuid attributes.
|
||||
editor.on('widgetDefinition', function (event) {
|
||||
var widgetDefinition = event.data;
|
||||
if (widgetDefinition.name !== 'image') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Override requiredContent & allowedContent.
|
||||
widgetDefinition.requiredContent = 'img[alt,src,width,height,data-entity-type,data-entity-uuid]';
|
||||
widgetDefinition.allowedContent.img.attributes += ',!data-entity-type,!data-entity-uuid';
|
||||
// We don't allow <figure>, <figcaption>, <div> or <p> in our downcast.
|
||||
delete widgetDefinition.allowedContent.figure;
|
||||
delete widgetDefinition.allowedContent.figcaption;
|
||||
delete widgetDefinition.allowedContent.div;
|
||||
delete widgetDefinition.allowedContent.p;
|
||||
|
||||
// Override the 'link' part, to completely disable image2's link
|
||||
// support: http://dev.ckeditor.com/ticket/11341.
|
||||
widgetDefinition.parts.link = 'This is a nonsensical selector to disable this functionality completely';
|
||||
|
||||
// Override downcast(): since we only accept <img> in our upcast method,
|
||||
// the element is already correct. We only need to update the element's
|
||||
// data-entity-uuid attribute.
|
||||
widgetDefinition.downcast = function (element) {
|
||||
element.attributes['data-entity-uuid'] = this.data['data-entity-uuid'];
|
||||
};
|
||||
|
||||
// We want to upcast <img> elements to a DOM structure required by the
|
||||
// image2 widget; we only accept an <img> tag, and that <img> tag MAY
|
||||
// have a data-entity-type and a data-entity-uuid attribute.
|
||||
widgetDefinition.upcast = function (element, data) {
|
||||
if (element.name !== 'img') {
|
||||
return;
|
||||
}
|
||||
// Don't initialize on pasted fake objects.
|
||||
else if (element.attributes['data-cke-realelement']) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse the data-entity-type attribute.
|
||||
data['data-entity-type'] = element.attributes['data-entity-type'];
|
||||
// Parse the data-entity-uuid attribute.
|
||||
data['data-entity-uuid'] = element.attributes['data-entity-uuid'];
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
// Protected; keys of the widget data to be sent to the Drupal dialog.
|
||||
// Keys in the hash are the keys for image2's data, values are the keys
|
||||
// that the Drupal dialog uses.
|
||||
widgetDefinition._mapDataToDialog = {
|
||||
'src': 'src',
|
||||
'alt': 'alt',
|
||||
'width': 'width',
|
||||
'height': 'height',
|
||||
'data-entity-type': 'data-entity-type',
|
||||
'data-entity-uuid': 'data-entity-uuid'
|
||||
};
|
||||
|
||||
// Protected; transforms widget's data object to the format used by the
|
||||
// \Drupal\editor\Form\EditorImageDialog dialog, keeping only the data
|
||||
// listed in widgetDefinition._dataForDialog.
|
||||
widgetDefinition._dataToDialogValues = function (data) {
|
||||
var dialogValues = {};
|
||||
var map = widgetDefinition._mapDataToDialog;
|
||||
Object.keys(widgetDefinition._mapDataToDialog).forEach(function (key) {
|
||||
dialogValues[map[key]] = data[key];
|
||||
});
|
||||
return dialogValues;
|
||||
};
|
||||
|
||||
// Protected; the inverse of _dataToDialogValues.
|
||||
widgetDefinition._dialogValuesToData = function (dialogReturnValues) {
|
||||
var data = {};
|
||||
var map = widgetDefinition._mapDataToDialog;
|
||||
Object.keys(widgetDefinition._mapDataToDialog).forEach(function (key) {
|
||||
if (dialogReturnValues.hasOwnProperty(map[key])) {
|
||||
data[key] = dialogReturnValues[map[key]];
|
||||
}
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
// Protected; creates Drupal dialog save callback.
|
||||
widgetDefinition._createDialogSaveCallback = function (editor, widget) {
|
||||
return function (dialogReturnValues) {
|
||||
var firstEdit = !widget.ready;
|
||||
|
||||
// Dialog may have blurred the widget. Re-focus it first.
|
||||
if (!firstEdit) {
|
||||
widget.focus();
|
||||
}
|
||||
|
||||
editor.fire('saveSnapshot');
|
||||
|
||||
// Pass `true` so DocumentFragment will also be returned.
|
||||
var container = widget.wrapper.getParent(true);
|
||||
var image = widget.parts.image;
|
||||
|
||||
// Set the updated widget data, after the necessary conversions from
|
||||
// the dialog's return values.
|
||||
// Note: on widget#setData this widget instance might be destroyed.
|
||||
var data = widgetDefinition._dialogValuesToData(dialogReturnValues.attributes);
|
||||
widget.setData(data);
|
||||
|
||||
// Retrieve the widget once again. It could've been destroyed
|
||||
// when shifting state, so might deal with a new instance.
|
||||
widget = editor.widgets.getByElement(image);
|
||||
|
||||
// It's first edit, just after widget instance creation, but before it was
|
||||
// inserted into DOM. So we need to retrieve the widget wrapper from
|
||||
// inside the DocumentFragment which we cached above and finalize other
|
||||
// things (like ready event and flag).
|
||||
if (firstEdit) {
|
||||
editor.widgets.finalizeCreation(container);
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
// (Re-)focus the widget.
|
||||
widget.focus();
|
||||
// Save snapshot for undo support.
|
||||
editor.fire('saveSnapshot');
|
||||
});
|
||||
|
||||
return widget;
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
// Add a widget#edit listener to every instance of image2 widget in order
|
||||
// to handle its editing with a Drupal-native dialog.
|
||||
// This includes also a case just after the image was created
|
||||
// and dialog should be opened for it for the first time.
|
||||
editor.widgets.on('instanceCreated', function (event) {
|
||||
var widget = event.data;
|
||||
|
||||
if (widget.name !== 'image') {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.on('edit', function (event) {
|
||||
// Cancel edit event to break image2's dialog binding
|
||||
// (and also to prevent automatic insertion before opening dialog).
|
||||
event.cancel();
|
||||
|
||||
// Open drupalimage dialog.
|
||||
editor.execCommand('editdrupalimage', {
|
||||
existingValues: widget.definition._dataToDialogValues(widget.data),
|
||||
saveCallback: widget.definition._createDialogSaveCallback(editor, widget),
|
||||
// Drupal.t() will not work inside CKEditor plugins because CKEditor
|
||||
// loads the JavaScript file instead of Drupal. Pull translated
|
||||
// strings from the plugin settings that are translated server-side.
|
||||
dialogTitle: widget.data.src ? editor.config.drupalImage_dialogTitleEdit : editor.config.drupalImage_dialogTitleAdd
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Register the "editdrupalimage" command, which essentially just replaces
|
||||
// the "image" command's CKEditor dialog with a Drupal-native dialog.
|
||||
editor.addCommand('editdrupalimage', {
|
||||
allowedContent: 'img[alt,!src,width,height,!data-entity-type,!data-entity-uuid]',
|
||||
requiredContent: 'img[alt,src,width,height,data-entity-type,data-entity-uuid]',
|
||||
modes: {wysiwyg: 1},
|
||||
canUndo: true,
|
||||
exec: function (editor, data) {
|
||||
var dialogSettings = {
|
||||
title: data.dialogTitle,
|
||||
dialogClass: 'editor-image-dialog'
|
||||
};
|
||||
Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/image/' + editor.config.drupal.format), data.existingValues, data.saveCallback, dialogSettings);
|
||||
}
|
||||
});
|
||||
|
||||
// Register the toolbar button.
|
||||
if (editor.ui.addButton) {
|
||||
editor.ui.addButton('DrupalImage', {
|
||||
label: Drupal.t('Image'),
|
||||
// Note that we use the original image2 command!
|
||||
command: 'image',
|
||||
icon: this.path + '/image.png'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Disable image2's integration with the link/drupallink plugins: don't
|
||||
// allow the widget itself to become a link. Support for that may be added
|
||||
// by an text filter that adds a data- attribute specifically for that.
|
||||
afterInit: function (editor) {
|
||||
if (editor.plugins.drupallink) {
|
||||
var cmd = editor.getCommand('drupallink');
|
||||
// Needs to be refreshed on selection changes.
|
||||
cmd.contextSensitive = 1;
|
||||
// Disable command and cancel event when the image widget is selected.
|
||||
cmd.on('refresh', function (evt) {
|
||||
var widget = editor.widgets.focused;
|
||||
if (widget && widget.name === 'image') {
|
||||
this.setState(CKEDITOR.TRISTATE_DISABLED);
|
||||
evt.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(jQuery, Drupal, CKEDITOR);
|
247
core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
Normal file
247
core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
/**
|
||||
* @file
|
||||
* Drupal Image Caption plugin.
|
||||
*
|
||||
* This alters the existing CKEditor image2 widget plugin, which is already
|
||||
* altered by the Drupal Image plugin, to:
|
||||
* - allow for the data-caption and data-align attributes to be set
|
||||
* - mimic the upcasting behavior of the caption_filter filter.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
(function (CKEDITOR) {
|
||||
|
||||
"use strict";
|
||||
|
||||
CKEDITOR.plugins.add('drupalimagecaption', {
|
||||
requires: 'drupalimage',
|
||||
|
||||
beforeInit: function (editor) {
|
||||
// Disable default placeholder text that comes with CKEditor's image2
|
||||
// plugin: it has an inferior UX (it requires the user to manually delete
|
||||
// the place holder text).
|
||||
editor.lang.image2.captionPlaceholder = '';
|
||||
|
||||
// Drupal.t() will not work inside CKEditor plugins because CKEditor loads
|
||||
// the JavaScript file instead of Drupal. Pull translated strings from the
|
||||
// plugin settings that are translated server-side.
|
||||
var placeholderText = editor.config.drupalImageCaption_captionPlaceholderText;
|
||||
|
||||
// Override the image2 widget definition to handle the additional
|
||||
// data-align and data-caption attributes.
|
||||
editor.on('widgetDefinition', function (event) {
|
||||
var widgetDefinition = event.data;
|
||||
if (widgetDefinition.name !== 'image') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only perform the downcasting/upcasting for to the enabled filters.
|
||||
var captionFilterEnabled = editor.config.drupalImageCaption_captionFilterEnabled;
|
||||
var alignFilterEnabled = editor.config.drupalImageCaption_alignFilterEnabled;
|
||||
|
||||
// Override default features definitions for drupalimagecaption.
|
||||
CKEDITOR.tools.extend(widgetDefinition.features, {
|
||||
caption: {
|
||||
requiredContent: 'img[data-caption]'
|
||||
},
|
||||
align: {
|
||||
requiredContent: 'img[data-align]'
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Override requiredContent & allowedContent.
|
||||
widgetDefinition.requiredContent = 'img[alt,src,width,height,data-entity-type,data-entity-uuid,data-align,data-caption]';
|
||||
widgetDefinition.allowedContent.img.attributes += ',data-align,data-caption';
|
||||
|
||||
// Override allowedContent setting for the 'caption' nested editable.
|
||||
// This must match what caption_filter enforces.
|
||||
// @see \Drupal\filter\Plugin\Filter\FilterCaption::process()
|
||||
// @see \Drupal\Component\Utility\Xss::filter()
|
||||
widgetDefinition.editables.caption.allowedContent = 'a[!href]; em strong cite code br';
|
||||
|
||||
// Override downcast(): ensure we *only* output <img>, but also ensure
|
||||
// we include the data-entity-type, data-entity-uuid, data-align and
|
||||
// data-caption attributes.
|
||||
widgetDefinition.downcast = function (element) {
|
||||
// Find an image element in the one being downcasted (can be itself).
|
||||
var img = findElementByName(element, 'img');
|
||||
var caption = this.editables.caption;
|
||||
var captionHtml = caption && caption.getData();
|
||||
var attrs = img.attributes;
|
||||
|
||||
if (captionFilterEnabled) {
|
||||
// If image contains a non-empty caption, serialize caption to the
|
||||
// data-caption attribute.
|
||||
if (captionHtml) {
|
||||
attrs['data-caption'] = captionHtml;
|
||||
}
|
||||
}
|
||||
if (alignFilterEnabled) {
|
||||
if (this.data.align !== 'none') {
|
||||
attrs['data-align'] = this.data.align;
|
||||
}
|
||||
}
|
||||
attrs['data-entity-type'] = this.data['data-entity-type'];
|
||||
attrs['data-entity-uuid'] = this.data['data-entity-uuid'];
|
||||
|
||||
return img;
|
||||
};
|
||||
|
||||
// We want to upcast <img> elements to a DOM structure required by the
|
||||
// image2 widget. Depending on a case it may be:
|
||||
// - just an <img> tag (non-captioned, not-centered image),
|
||||
// - <img> tag in a paragraph (non-captioned, centered image),
|
||||
// - <figure> tag (captioned image).
|
||||
// We take the same attributes into account as downcast() does.
|
||||
widgetDefinition.upcast = function (element, data) {
|
||||
if (element.name !== 'img' || !element.attributes['data-entity-type'] || !element.attributes['data-entity-uuid']) {
|
||||
return;
|
||||
}
|
||||
// Don't initialize on pasted fake objects.
|
||||
else if (element.attributes['data-cke-realelement']) {
|
||||
return;
|
||||
}
|
||||
|
||||
var attrs = element.attributes;
|
||||
var retElement = element;
|
||||
var caption;
|
||||
|
||||
// We won't need the attributes during editing: we'll use widget.data
|
||||
// to store them (except the caption, which is stored in the DOM).
|
||||
if (captionFilterEnabled) {
|
||||
caption = attrs['data-caption'];
|
||||
delete attrs['data-caption'];
|
||||
}
|
||||
if (alignFilterEnabled) {
|
||||
data.align = attrs['data-align'];
|
||||
delete attrs['data-align'];
|
||||
}
|
||||
data['data-entity-type'] = attrs['data-entity-type'];
|
||||
delete attrs['data-entity-type'];
|
||||
data['data-entity-uuid'] = attrs['data-entity-uuid'];
|
||||
delete attrs['data-entity-uuid'];
|
||||
|
||||
if (captionFilterEnabled) {
|
||||
// Unwrap from <p> wrapper created by HTML parser for a captioned
|
||||
// image. The captioned image will be transformed to <figure>, so we
|
||||
// don't want the <p> anymore.
|
||||
if (element.parent.name === 'p' && caption) {
|
||||
var index = element.getIndex();
|
||||
var splitBefore = index > 0;
|
||||
var splitAfter = index + 1 < element.parent.children.length;
|
||||
|
||||
if (splitBefore) {
|
||||
element.parent.split(index);
|
||||
}
|
||||
index = element.getIndex();
|
||||
if (splitAfter) {
|
||||
element.parent.split(index + 1);
|
||||
}
|
||||
|
||||
element.parent.replaceWith(element);
|
||||
retElement = element;
|
||||
}
|
||||
|
||||
// If this image has a caption, create a full <figure> structure.
|
||||
if (caption) {
|
||||
var figure = new CKEDITOR.htmlParser.element('figure');
|
||||
caption = new CKEDITOR.htmlParser.fragment.fromHtml(caption, 'figcaption');
|
||||
|
||||
// Use Drupal's data-placeholder attribute to insert a CSS-based,
|
||||
// translation-ready placeholder for empty captions. Note that it
|
||||
// also must to be done for new instances (see
|
||||
// widgetDefinition._createDialogSaveCallback).
|
||||
caption.attributes['data-placeholder'] = placeholderText;
|
||||
|
||||
element.replaceWith(figure);
|
||||
figure.add(element);
|
||||
figure.add(caption);
|
||||
figure.attributes['class'] = editor.config.image2_captionedClass;
|
||||
retElement = figure;
|
||||
}
|
||||
}
|
||||
|
||||
if (alignFilterEnabled) {
|
||||
// If this image doesn't have a caption (or the caption filter is
|
||||
// disabled), but it is centered, make sure that it's wrapped with
|
||||
// <p>, which will become a part of the widget.
|
||||
if (data.align === 'center' && (!captionFilterEnabled || !caption)) {
|
||||
var p = new CKEDITOR.htmlParser.element('p');
|
||||
element.replaceWith(p);
|
||||
p.add(element);
|
||||
// Apply the class for centered images.
|
||||
p.addClass(editor.config.image2_alignClasses[1]);
|
||||
retElement = p;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the upcasted element (<img>, <figure> or <p>).
|
||||
return retElement;
|
||||
};
|
||||
|
||||
// Protected; keys of the widget data to be sent to the Drupal dialog.
|
||||
// Append to the values defined by the drupalimage plugin.
|
||||
// @see core/modules/ckeditor/js/plugins/drupalimage/plugin.js
|
||||
CKEDITOR.tools.extend(widgetDefinition._mapDataToDialog, {
|
||||
'align': 'data-align',
|
||||
'data-caption': 'data-caption',
|
||||
'hasCaption': 'hasCaption'
|
||||
});
|
||||
|
||||
// Override Drupal dialog save callback.
|
||||
var originalCreateDialogSaveCallback = widgetDefinition._createDialogSaveCallback;
|
||||
widgetDefinition._createDialogSaveCallback = function (editor, widget) {
|
||||
var saveCallback = originalCreateDialogSaveCallback.call(this, editor, widget);
|
||||
|
||||
return function (dialogReturnValues) {
|
||||
// Ensure hasCaption is a boolean. image2 assumes it always works
|
||||
// with booleans; if this is not the case, then
|
||||
// CKEDITOR.plugins.image2.stateShifter() will incorrectly mark
|
||||
// widget.data.hasCaption as "changed" (e.g. when hasCaption === 0
|
||||
// instead of hasCaption === false). This causes image2's "state
|
||||
// shifter" to enter the wrong branch of the algorithm and blow up.
|
||||
dialogReturnValues.attributes.hasCaption = !!dialogReturnValues.attributes.hasCaption;
|
||||
|
||||
var actualWidget = saveCallback(dialogReturnValues);
|
||||
|
||||
// By default, the template of captioned widget has no
|
||||
// data-placeholder attribute. Note that it also must be done when
|
||||
// upcasting existing elements (see widgetDefinition.upcast).
|
||||
if (dialogReturnValues.attributes.hasCaption) {
|
||||
actualWidget.editables.caption.setAttribute('data-placeholder', placeholderText);
|
||||
}
|
||||
};
|
||||
};
|
||||
}, null, null, 20); // Low priority to ensure drupalimage's event handler runs first.
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Finds an element by its name.
|
||||
*
|
||||
* Function will check first the passed element itself and then all its
|
||||
* children in DFS order.
|
||||
*
|
||||
* @param {CKEDITOR.htmlParser.element} element
|
||||
* @param {string} name
|
||||
*
|
||||
* @return {CKEDITOR.htmlParser.element}
|
||||
*/
|
||||
function findElementByName(element, name) {
|
||||
if (element.name === name) {
|
||||
return element;
|
||||
}
|
||||
|
||||
var found = null;
|
||||
element.forEach(function (el) {
|
||||
if (el.name === name) {
|
||||
found = el;
|
||||
// Stop here.
|
||||
return false;
|
||||
}
|
||||
}, CKEDITOR.NODE_ELEMENT);
|
||||
return found;
|
||||
}
|
||||
|
||||
})(CKEDITOR);
|
BIN
core/modules/ckeditor/js/plugins/drupallink/link.png
Normal file
BIN
core/modules/ckeditor/js/plugins/drupallink/link.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 328 B |
232
core/modules/ckeditor/js/plugins/drupallink/plugin.js
Normal file
232
core/modules/ckeditor/js/plugins/drupallink/plugin.js
Normal file
|
@ -0,0 +1,232 @@
|
|||
/**
|
||||
* @file
|
||||
* Drupal Link plugin.
|
||||
*
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
(function ($, Drupal, drupalSettings, CKEDITOR) {
|
||||
|
||||
"use strict";
|
||||
|
||||
CKEDITOR.plugins.add('drupallink', {
|
||||
init: function (editor) {
|
||||
// Add the commands for link and unlink.
|
||||
editor.addCommand('drupallink', {
|
||||
allowedContent: 'a[!href,target]',
|
||||
requiredContent: 'a[href]',
|
||||
modes: {wysiwyg: 1},
|
||||
canUndo: true,
|
||||
exec: function (editor) {
|
||||
var linkElement = getSelectedLink(editor);
|
||||
var linkDOMElement = null;
|
||||
|
||||
// Set existing values based on selected element.
|
||||
var existingValues = {};
|
||||
if (linkElement && linkElement.$) {
|
||||
linkDOMElement = linkElement.$;
|
||||
|
||||
// Populate an array with the link's current attributes.
|
||||
var attribute = null;
|
||||
var attributeName;
|
||||
for (var attrIndex = 0; attrIndex < linkDOMElement.attributes.length; attrIndex++) {
|
||||
attribute = linkDOMElement.attributes.item(attrIndex);
|
||||
attributeName = attribute.nodeName.toLowerCase();
|
||||
// Don't consider data-cke-saved- attributes; they're just there to
|
||||
// work around browser quirks.
|
||||
if (attributeName.substring(0, 15) === 'data-cke-saved-') {
|
||||
continue;
|
||||
}
|
||||
// Store the value for this attribute, unless there's a
|
||||
// data-cke-saved- alternative for it, which will contain the quirk-
|
||||
// free, original value.
|
||||
existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare a save callback to be used upon saving the dialog.
|
||||
var saveCallback = function (returnValues) {
|
||||
editor.fire('saveSnapshot');
|
||||
|
||||
// Create a new link element if needed.
|
||||
if (!linkElement && returnValues.attributes.href) {
|
||||
var selection = editor.getSelection();
|
||||
var range = selection.getRanges(1)[0];
|
||||
|
||||
// Use link URL as text with a collapsed cursor.
|
||||
if (range.collapsed) {
|
||||
// Shorten mailto URLs to just the email address.
|
||||
var text = new CKEDITOR.dom.text(returnValues.attributes.href.replace(/^mailto:/, ''), editor.document);
|
||||
range.insertNode(text);
|
||||
range.selectNodeContents(text);
|
||||
}
|
||||
|
||||
// Ignore a disabled target attribute.
|
||||
if (returnValues.attributes.target === 0) {
|
||||
delete returnValues.attributes.target;
|
||||
}
|
||||
|
||||
// Create the new link by applying a style to the new text.
|
||||
var style = new CKEDITOR.style({element: 'a', attributes: returnValues.attributes});
|
||||
style.type = CKEDITOR.STYLE_INLINE;
|
||||
style.applyToRange(range);
|
||||
range.select();
|
||||
|
||||
// Set the link so individual properties may be set below.
|
||||
linkElement = getSelectedLink(editor);
|
||||
}
|
||||
// Update the link properties.
|
||||
else if (linkElement) {
|
||||
for (var attrName in returnValues.attributes) {
|
||||
if (returnValues.attributes.hasOwnProperty(attrName)) {
|
||||
// Update the property if a value is specified.
|
||||
if (returnValues.attributes[attrName].length > 0) {
|
||||
var value = returnValues.attributes[attrName];
|
||||
linkElement.data('cke-saved-' + attrName, value);
|
||||
linkElement.setAttribute(attrName, value);
|
||||
}
|
||||
// Delete the property if set to an empty string.
|
||||
else {
|
||||
linkElement.removeAttribute(attrName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save snapshot for undo support.
|
||||
editor.fire('saveSnapshot');
|
||||
};
|
||||
// Drupal.t() will not work inside CKEditor plugins because CKEditor
|
||||
// loads the JavaScript file instead of Drupal. Pull translated strings
|
||||
// from the plugin settings that are translated server-side.
|
||||
var dialogSettings = {
|
||||
title: linkElement ? editor.config.drupalLink_dialogTitleEdit : editor.config.drupalLink_dialogTitleAdd,
|
||||
dialogClass: 'editor-link-dialog'
|
||||
};
|
||||
|
||||
// Open the dialog for the edit form.
|
||||
Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/link/' + editor.config.drupal.format), existingValues, saveCallback, dialogSettings);
|
||||
}
|
||||
});
|
||||
editor.addCommand('drupalunlink', {
|
||||
contextSensitive: 1,
|
||||
startDisabled: 1,
|
||||
allowedContent: 'a[!href]',
|
||||
requiredContent: 'a[href]',
|
||||
exec: function (editor) {
|
||||
var style = new CKEDITOR.style({element: 'a', type: CKEDITOR.STYLE_INLINE, alwaysRemoveElement: 1});
|
||||
editor.removeStyle(style);
|
||||
},
|
||||
refresh: function (editor, path) {
|
||||
var element = path.lastElement && path.lastElement.getAscendant('a', true);
|
||||
if (element && element.getName() === 'a' && element.getAttribute('href') && element.getChildCount()) {
|
||||
this.setState(CKEDITOR.TRISTATE_OFF);
|
||||
}
|
||||
else {
|
||||
this.setState(CKEDITOR.TRISTATE_DISABLED);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// CTRL + K.
|
||||
editor.setKeystroke(CKEDITOR.CTRL + 75, 'drupallink');
|
||||
|
||||
// Add buttons for link and unlink.
|
||||
if (editor.ui.addButton) {
|
||||
editor.ui.addButton('DrupalLink', {
|
||||
label: Drupal.t('Link'),
|
||||
command: 'drupallink',
|
||||
icon: this.path + '/link.png'
|
||||
});
|
||||
editor.ui.addButton('DrupalUnlink', {
|
||||
label: Drupal.t('Unlink'),
|
||||
command: 'drupalunlink',
|
||||
icon: this.path + '/unlink.png'
|
||||
});
|
||||
}
|
||||
|
||||
editor.on('doubleclick', function (evt) {
|
||||
var element = getSelectedLink(editor) || evt.data.element;
|
||||
|
||||
if (!element.isReadOnly()) {
|
||||
if (element.is('a')) {
|
||||
editor.getSelection().selectElement(element);
|
||||
editor.getCommand('drupallink').exec();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If the "menu" plugin is loaded, register the menu items.
|
||||
if (editor.addMenuItems) {
|
||||
editor.addMenuItems({
|
||||
link: {
|
||||
label: Drupal.t('Edit Link'),
|
||||
command: 'drupallink',
|
||||
group: 'link',
|
||||
order: 1
|
||||
},
|
||||
|
||||
unlink: {
|
||||
label: Drupal.t('Unlink'),
|
||||
command: 'drupalunlink',
|
||||
group: 'link',
|
||||
order: 5
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// If the "contextmenu" plugin is loaded, register the listeners.
|
||||
if (editor.contextMenu) {
|
||||
editor.contextMenu.addListener(function (element, selection) {
|
||||
if (!element || element.isReadOnly()) {
|
||||
return null;
|
||||
}
|
||||
var anchor = getSelectedLink(editor);
|
||||
if (!anchor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var menu = {};
|
||||
if (anchor.getAttribute('href') && anchor.getChildCount()) {
|
||||
menu = {link: CKEDITOR.TRISTATE_OFF, unlink: CKEDITOR.TRISTATE_OFF};
|
||||
}
|
||||
return menu;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the surrounding link element of current selection.
|
||||
*
|
||||
* The following selection will all return the link element.
|
||||
*
|
||||
* @example
|
||||
* <a href="#">li^nk</a>
|
||||
* <a href="#">[link]</a>
|
||||
* text[<a href="#">link]</a>
|
||||
* <a href="#">li[nk</a>]
|
||||
* [<b><a href="#">li]nk</a></b>]
|
||||
* [<a href="#"><b>li]nk</b></a>
|
||||
*
|
||||
* @param {CKEDITOR.editor} editor
|
||||
*
|
||||
* @return {?bool}
|
||||
*/
|
||||
function getSelectedLink(editor) {
|
||||
var selection = editor.getSelection();
|
||||
var selectedElement = selection.getSelectedElement();
|
||||
if (selectedElement && selectedElement.is('a')) {
|
||||
return selectedElement;
|
||||
}
|
||||
|
||||
var range = selection.getRanges(true)[0];
|
||||
|
||||
if (range) {
|
||||
range.shrink(CKEDITOR.SHRINK_TEXT);
|
||||
return editor.elementPath(range.getCommonAncestor()).contains('a', 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
})(jQuery, Drupal, drupalSettings, CKEDITOR);
|
BIN
core/modules/ckeditor/js/plugins/drupallink/unlink.png
Normal file
BIN
core/modules/ckeditor/js/plugins/drupallink/unlink.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 312 B |
Reference in a new issue