upgrades core to 8.4.2
This commit is contained in:
@@ -5,8 +5,8 @@ package: Core
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-08-16
|
||||
version: '8.3.7'
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1502903957
|
||||
datestamp: 1509719929
|
||||
|
||||
@@ -36,6 +36,7 @@ function contextual_toolbar() {
|
||||
'#attributes' => [
|
||||
'class' => ['toolbar-icon', 'toolbar-icon-edit'],
|
||||
'aria-pressed' => 'false',
|
||||
'type' => 'button',
|
||||
],
|
||||
],
|
||||
'#wrapper_attributes' => [
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Contextual module.
|
||||
*/
|
||||
|
||||
(function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) {
|
||||
const options = $.extend(drupalSettings.contextual,
|
||||
// Merge strings on top of drupalSettings so that they are not mutable.
|
||||
{
|
||||
strings: {
|
||||
open: Drupal.t('Open'),
|
||||
close: Drupal.t('Close'),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Clear the cached contextual links whenever the current user's set of
|
||||
// permissions changes.
|
||||
const cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash');
|
||||
const permissionsHash = drupalSettings.user.permissionsHash;
|
||||
if (cachedPermissionsHash !== permissionsHash) {
|
||||
if (typeof permissionsHash === 'string') {
|
||||
_.chain(storage).keys().each((key) => {
|
||||
if (key.substring(0, 18) === 'Drupal.contextual.') {
|
||||
storage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a contextual link: updates its DOM, sets up model and views.
|
||||
*
|
||||
* @param {jQuery} $contextual
|
||||
* A contextual links placeholder DOM element, containing the actual
|
||||
* contextual links as rendered by the server.
|
||||
* @param {string} html
|
||||
* The server-side rendered HTML for this contextual link.
|
||||
*/
|
||||
function initContextual($contextual, html) {
|
||||
const $region = $contextual.closest('.contextual-region');
|
||||
const contextual = Drupal.contextual;
|
||||
|
||||
$contextual
|
||||
// Update the placeholder to contain its rendered contextual links.
|
||||
.html(html)
|
||||
// Use the placeholder as a wrapper with a specific class to provide
|
||||
// positioning and behavior attachment context.
|
||||
.addClass('contextual')
|
||||
// Ensure a trigger element exists before the actual contextual links.
|
||||
.prepend(Drupal.theme('contextualTrigger'));
|
||||
|
||||
// Set the destination parameter on each of the contextual links.
|
||||
const destination = `destination=${Drupal.encodePath(drupalSettings.path.currentPath)}`;
|
||||
$contextual.find('.contextual-links a').each(function () {
|
||||
const url = this.getAttribute('href');
|
||||
const glue = (url.indexOf('?') === -1) ? '?' : '&';
|
||||
this.setAttribute('href', url + glue + destination);
|
||||
});
|
||||
|
||||
// Create a model and the appropriate views.
|
||||
const model = new contextual.StateModel({
|
||||
title: $region.find('h2').eq(0).text().trim(),
|
||||
});
|
||||
const viewOptions = $.extend({ el: $contextual, model }, options);
|
||||
contextual.views.push({
|
||||
visual: new contextual.VisualView(viewOptions),
|
||||
aural: new contextual.AuralView(viewOptions),
|
||||
keyboard: new contextual.KeyboardView(viewOptions),
|
||||
});
|
||||
contextual.regionViews.push(new contextual.RegionView(
|
||||
$.extend({ el: $region, model }, options)),
|
||||
);
|
||||
|
||||
// Add the model to the collection. This must happen after the views have
|
||||
// been associated with it, otherwise collection change event handlers can't
|
||||
// trigger the model change event handler in its views.
|
||||
contextual.collection.add(model);
|
||||
|
||||
// Let other JavaScript react to the adding of a new contextual link.
|
||||
$(document).trigger('drupalContextualLinkAdded', {
|
||||
$el: $contextual,
|
||||
$region,
|
||||
model,
|
||||
});
|
||||
|
||||
// Fix visual collisions between contextual link triggers.
|
||||
adjustIfNestedAndOverlapping($contextual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a contextual link is nested & overlapping, if so: adjusts it.
|
||||
*
|
||||
* This only deals with two levels of nesting; deeper levels are not touched.
|
||||
*
|
||||
* @param {jQuery} $contextual
|
||||
* A contextual links placeholder DOM element, containing the actual
|
||||
* contextual links as rendered by the server.
|
||||
*/
|
||||
function adjustIfNestedAndOverlapping($contextual) {
|
||||
const $contextuals = $contextual
|
||||
// @todo confirm that .closest() is not sufficient
|
||||
.parents('.contextual-region').eq(-1)
|
||||
.find('.contextual');
|
||||
|
||||
// Early-return when there's no nesting.
|
||||
if ($contextuals.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the two contextual links overlap, then we move the second one.
|
||||
const firstTop = $contextuals.eq(0).offset().top;
|
||||
const secondTop = $contextuals.eq(1).offset().top;
|
||||
if (firstTop === secondTop) {
|
||||
const $nestedContextual = $contextuals.eq(1);
|
||||
|
||||
// Retrieve height of nested contextual link.
|
||||
let height = 0;
|
||||
const $trigger = $nestedContextual.find('.trigger');
|
||||
// Elements with the .visually-hidden class have no dimensions, so this
|
||||
// class must be temporarily removed to the calculate the height.
|
||||
$trigger.removeClass('visually-hidden');
|
||||
height = $nestedContextual.height();
|
||||
$trigger.addClass('visually-hidden');
|
||||
|
||||
// Adjust nested contextual link's position.
|
||||
$nestedContextual.css({ top: $nestedContextual.position().top + height });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches outline behavior for regions associated with contextual links.
|
||||
*
|
||||
* Events
|
||||
* Contextual triggers an event that can be used by other scripts.
|
||||
* - drupalContextualLinkAdded: Triggered when a contextual link is added.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches the outline behavior to the right context.
|
||||
*/
|
||||
Drupal.behaviors.contextual = {
|
||||
attach(context) {
|
||||
const $context = $(context);
|
||||
|
||||
// Find all contextual links placeholders, if any.
|
||||
let $placeholders = $context.find('[data-contextual-id]').once('contextual-render');
|
||||
if ($placeholders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect the IDs for all contextual links placeholders.
|
||||
const ids = [];
|
||||
$placeholders.each(function () {
|
||||
ids.push($(this).attr('data-contextual-id'));
|
||||
});
|
||||
|
||||
// Update all contextual links placeholders whose HTML is cached.
|
||||
const uncachedIDs = _.filter(ids, (contextualID) => {
|
||||
const html = storage.getItem(`Drupal.contextual.${contextualID}`);
|
||||
if (html && html.length) {
|
||||
// Initialize after the current execution cycle, to make the AJAX
|
||||
// request for retrieving the uncached contextual links as soon as
|
||||
// possible, but also to ensure that other Drupal behaviors have had
|
||||
// the chance to set up an event listener on the Backbone collection
|
||||
// Drupal.contextual.collection.
|
||||
window.setTimeout(() => {
|
||||
initContextual($context.find(`[data-contextual-id="${contextualID}"]`), html);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Perform an AJAX request to let the server render the contextual links
|
||||
// for each of the placeholders.
|
||||
if (uncachedIDs.length > 0) {
|
||||
$.ajax({
|
||||
url: Drupal.url('contextual/render'),
|
||||
type: 'POST',
|
||||
data: { 'ids[]': uncachedIDs },
|
||||
dataType: 'json',
|
||||
success(results) {
|
||||
_.each(results, (html, contextualID) => {
|
||||
// Store the metadata.
|
||||
storage.setItem(`Drupal.contextual.${contextualID}`, html);
|
||||
// If the rendered contextual links are empty, then the current
|
||||
// user does not have permission to access the associated links:
|
||||
// don't render anything.
|
||||
if (html.length > 0) {
|
||||
// Update the placeholders to contain its rendered contextual
|
||||
// links. Usually there will only be one placeholder, but it's
|
||||
// possible for multiple identical placeholders exist on the
|
||||
// page (probably because the same content appears more than
|
||||
// once).
|
||||
$placeholders = $context.find(`[data-contextual-id="${contextualID}"]`);
|
||||
|
||||
// Initialize the contextual links.
|
||||
for (let i = 0; i < $placeholders.length; i++) {
|
||||
initContextual($placeholders.eq(i), html);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Namespace for contextual related functionality.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
Drupal.contextual = {
|
||||
|
||||
/**
|
||||
* The {@link Drupal.contextual.View} instances associated with each list
|
||||
* element of contextual links.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
views: [],
|
||||
|
||||
/**
|
||||
* The {@link Drupal.contextual.RegionView} instances associated with each
|
||||
* contextual region element.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
regionViews: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* A Backbone.Collection of {@link Drupal.contextual.StateModel} instances.
|
||||
*
|
||||
* @type {Backbone.Collection}
|
||||
*/
|
||||
Drupal.contextual.collection = new Backbone.Collection([], { model: Drupal.contextual.StateModel });
|
||||
|
||||
/**
|
||||
* A trigger is an interactive element often bound to a click handler.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing a DOM fragment.
|
||||
*/
|
||||
Drupal.theme.contextualTrigger = function () {
|
||||
return '<button class="trigger visually-hidden focusable" type="button"></button>';
|
||||
};
|
||||
}(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage));
|
||||
@@ -1,24 +1,18 @@
|
||||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Contextual module.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal, drupalSettings, _, Backbone, JSON, storage) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var options = $.extend(drupalSettings.contextual,
|
||||
// Merge strings on top of drupalSettings so that they are not mutable.
|
||||
{
|
||||
strings: {
|
||||
open: Drupal.t('Open'),
|
||||
close: Drupal.t('Close')
|
||||
}
|
||||
var options = $.extend(drupalSettings.contextual, {
|
||||
strings: {
|
||||
open: Drupal.t('Open'),
|
||||
close: Drupal.t('Close')
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Clear the cached contextual links whenever the current user's set of
|
||||
// permissions changes.
|
||||
var cachedPermissionsHash = storage.getItem('Drupal.contextual.permissionsHash');
|
||||
var permissionsHash = drupalSettings.user.permissionsHash;
|
||||
if (cachedPermissionsHash !== permissionsHash) {
|
||||
@@ -32,143 +26,81 @@
|
||||
storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a contextual link: updates its DOM, sets up model and views.
|
||||
*
|
||||
* @param {jQuery} $contextual
|
||||
* A contextual links placeholder DOM element, containing the actual
|
||||
* contextual links as rendered by the server.
|
||||
* @param {string} html
|
||||
* The server-side rendered HTML for this contextual link.
|
||||
*/
|
||||
function initContextual($contextual, html) {
|
||||
var $region = $contextual.closest('.contextual-region');
|
||||
var contextual = Drupal.contextual;
|
||||
|
||||
$contextual
|
||||
// Update the placeholder to contain its rendered contextual links.
|
||||
.html(html)
|
||||
// Use the placeholder as a wrapper with a specific class to provide
|
||||
// positioning and behavior attachment context.
|
||||
.addClass('contextual')
|
||||
// Ensure a trigger element exists before the actual contextual links.
|
||||
.prepend(Drupal.theme('contextualTrigger'));
|
||||
$contextual.html(html).addClass('contextual').prepend(Drupal.theme('contextualTrigger'));
|
||||
|
||||
// Set the destination parameter on each of the contextual links.
|
||||
var destination = 'destination=' + Drupal.encodePath(drupalSettings.path.currentPath);
|
||||
$contextual.find('.contextual-links a').each(function () {
|
||||
var url = this.getAttribute('href');
|
||||
var glue = (url.indexOf('?') === -1) ? '?' : '&';
|
||||
var glue = url.indexOf('?') === -1 ? '?' : '&';
|
||||
this.setAttribute('href', url + glue + destination);
|
||||
});
|
||||
|
||||
// Create a model and the appropriate views.
|
||||
var model = new contextual.StateModel({
|
||||
title: $region.find('h2').eq(0).text().trim()
|
||||
});
|
||||
var viewOptions = $.extend({el: $contextual, model: model}, options);
|
||||
var viewOptions = $.extend({ el: $contextual, model: model }, options);
|
||||
contextual.views.push({
|
||||
visual: new contextual.VisualView(viewOptions),
|
||||
aural: new contextual.AuralView(viewOptions),
|
||||
keyboard: new contextual.KeyboardView(viewOptions)
|
||||
});
|
||||
contextual.regionViews.push(new contextual.RegionView(
|
||||
$.extend({el: $region, model: model}, options))
|
||||
);
|
||||
contextual.regionViews.push(new contextual.RegionView($.extend({ el: $region, model: model }, options)));
|
||||
|
||||
// Add the model to the collection. This must happen after the views have
|
||||
// been associated with it, otherwise collection change event handlers can't
|
||||
// trigger the model change event handler in its views.
|
||||
contextual.collection.add(model);
|
||||
|
||||
// Let other JavaScript react to the adding of a new contextual link.
|
||||
$(document).trigger('drupalContextualLinkAdded', {
|
||||
$el: $contextual,
|
||||
$region: $region,
|
||||
model: model
|
||||
});
|
||||
|
||||
// Fix visual collisions between contextual link triggers.
|
||||
adjustIfNestedAndOverlapping($contextual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a contextual link is nested & overlapping, if so: adjusts it.
|
||||
*
|
||||
* This only deals with two levels of nesting; deeper levels are not touched.
|
||||
*
|
||||
* @param {jQuery} $contextual
|
||||
* A contextual links placeholder DOM element, containing the actual
|
||||
* contextual links as rendered by the server.
|
||||
*/
|
||||
function adjustIfNestedAndOverlapping($contextual) {
|
||||
var $contextuals = $contextual
|
||||
// @todo confirm that .closest() is not sufficient
|
||||
.parents('.contextual-region').eq(-1)
|
||||
.find('.contextual');
|
||||
var $contextuals = $contextual.parents('.contextual-region').eq(-1).find('.contextual');
|
||||
|
||||
// Early-return when there's no nesting.
|
||||
if ($contextuals.length === 1) {
|
||||
if ($contextuals.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the two contextual links overlap, then we move the second one.
|
||||
var firstTop = $contextuals.eq(0).offset().top;
|
||||
var secondTop = $contextuals.eq(1).offset().top;
|
||||
if (firstTop === secondTop) {
|
||||
var $nestedContextual = $contextuals.eq(1);
|
||||
|
||||
// Retrieve height of nested contextual link.
|
||||
var height = 0;
|
||||
var $trigger = $nestedContextual.find('.trigger');
|
||||
// Elements with the .visually-hidden class have no dimensions, so this
|
||||
// class must be temporarily removed to the calculate the height.
|
||||
|
||||
$trigger.removeClass('visually-hidden');
|
||||
height = $nestedContextual.height();
|
||||
$trigger.addClass('visually-hidden');
|
||||
|
||||
// Adjust nested contextual link's position.
|
||||
$nestedContextual.css({top: $nestedContextual.position().top + height});
|
||||
$nestedContextual.css({ top: $nestedContextual.position().top + height });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches outline behavior for regions associated with contextual links.
|
||||
*
|
||||
* Events
|
||||
* Contextual triggers an event that can be used by other scripts.
|
||||
* - drupalContextualLinkAdded: Triggered when a contextual link is added.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches the outline behavior to the right context.
|
||||
*/
|
||||
Drupal.behaviors.contextual = {
|
||||
attach: function (context) {
|
||||
attach: function attach(context) {
|
||||
var $context = $(context);
|
||||
|
||||
// Find all contextual links placeholders, if any.
|
||||
var $placeholders = $context.find('[data-contextual-id]').once('contextual-render');
|
||||
if ($placeholders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect the IDs for all contextual links placeholders.
|
||||
var ids = [];
|
||||
$placeholders.each(function () {
|
||||
ids.push($(this).attr('data-contextual-id'));
|
||||
});
|
||||
|
||||
// Update all contextual links placeholders whose HTML is cached.
|
||||
var uncachedIDs = _.filter(ids, function initIfCached(contextualID) {
|
||||
var uncachedIDs = _.filter(ids, function (contextualID) {
|
||||
var html = storage.getItem('Drupal.contextual.' + contextualID);
|
||||
if (html && html.length) {
|
||||
// Initialize after the current execution cycle, to make the AJAX
|
||||
// request for retrieving the uncached contextual links as soon as
|
||||
// possible, but also to ensure that other Drupal behaviors have had
|
||||
// the chance to set up an event listener on the Backbone collection
|
||||
// Drupal.contextual.collection.
|
||||
window.setTimeout(function () {
|
||||
initContextual($context.find('[data-contextual-id="' + contextualID + '"]'), html);
|
||||
});
|
||||
@@ -177,30 +109,19 @@
|
||||
return true;
|
||||
});
|
||||
|
||||
// Perform an AJAX request to let the server render the contextual links
|
||||
// for each of the placeholders.
|
||||
if (uncachedIDs.length > 0) {
|
||||
$.ajax({
|
||||
url: Drupal.url('contextual/render'),
|
||||
type: 'POST',
|
||||
data: {'ids[]': uncachedIDs},
|
||||
data: { 'ids[]': uncachedIDs },
|
||||
dataType: 'json',
|
||||
success: function (results) {
|
||||
success: function success(results) {
|
||||
_.each(results, function (html, contextualID) {
|
||||
// Store the metadata.
|
||||
storage.setItem('Drupal.contextual.' + contextualID, html);
|
||||
// If the rendered contextual links are empty, then the current
|
||||
// user does not have permission to access the associated links:
|
||||
// don't render anything.
|
||||
|
||||
if (html.length > 0) {
|
||||
// Update the placeholders to contain its rendered contextual
|
||||
// links. Usually there will only be one placeholder, but it's
|
||||
// possible for multiple identical placeholders exist on the
|
||||
// page (probably because the same content appears more than
|
||||
// once).
|
||||
$placeholders = $context.find('[data-contextual-id="' + contextualID + '"]');
|
||||
|
||||
// Initialize the contextual links.
|
||||
for (var i = 0; i < $placeholders.length; i++) {
|
||||
initContextual($placeholders.eq(i), html);
|
||||
}
|
||||
@@ -212,45 +133,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Namespace for contextual related functionality.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
Drupal.contextual = {
|
||||
|
||||
/**
|
||||
* The {@link Drupal.contextual.View} instances associated with each list
|
||||
* element of contextual links.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
views: [],
|
||||
|
||||
/**
|
||||
* The {@link Drupal.contextual.RegionView} instances associated with each
|
||||
* contextual region element.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
regionViews: []
|
||||
};
|
||||
|
||||
/**
|
||||
* A Backbone.Collection of {@link Drupal.contextual.StateModel} instances.
|
||||
*
|
||||
* @type {Backbone.Collection}
|
||||
*/
|
||||
Drupal.contextual.collection = new Backbone.Collection([], {model: Drupal.contextual.StateModel});
|
||||
Drupal.contextual.collection = new Backbone.Collection([], { model: Drupal.contextual.StateModel });
|
||||
|
||||
/**
|
||||
* A trigger is an interactive element often bound to a click handler.
|
||||
*
|
||||
* @return {string}
|
||||
* A string representing a DOM fragment.
|
||||
*/
|
||||
Drupal.theme.contextualTrigger = function () {
|
||||
return '<button class="trigger visually-hidden focusable" type="button"></button>';
|
||||
};
|
||||
|
||||
})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage);
|
||||
})(jQuery, Drupal, drupalSettings, _, Backbone, window.JSON, window.sessionStorage);
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Contextual module's edit toolbar tab.
|
||||
*/
|
||||
|
||||
(function ($, Drupal, Backbone) {
|
||||
const strings = {
|
||||
tabbingReleased: Drupal.t('Tabbing is no longer constrained by the Contextual module.'),
|
||||
tabbingConstrained: Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.'),
|
||||
pressEsc: Drupal.t('Press the esc key to exit.'),
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a contextual link: updates its DOM, sets up model and views.
|
||||
*
|
||||
* @param {HTMLElement} context
|
||||
* A contextual links DOM element as rendered by the server.
|
||||
*/
|
||||
function initContextualToolbar(context) {
|
||||
if (!Drupal.contextual || !Drupal.contextual.collection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const contextualToolbar = Drupal.contextualToolbar;
|
||||
const model = contextualToolbar.model = new contextualToolbar.StateModel({
|
||||
// Checks whether localStorage indicates we should start in edit mode
|
||||
// rather than view mode.
|
||||
// @see Drupal.contextualToolbar.VisualView.persist
|
||||
isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false',
|
||||
}, {
|
||||
contextualCollection: Drupal.contextual.collection,
|
||||
});
|
||||
|
||||
const viewOptions = {
|
||||
el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
|
||||
model,
|
||||
strings,
|
||||
};
|
||||
new contextualToolbar.VisualView(viewOptions);
|
||||
new contextualToolbar.AuralView(viewOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches contextual's edit toolbar tab behavior.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches contextual toolbar behavior on a contextualToolbar-init event.
|
||||
*/
|
||||
Drupal.behaviors.contextualToolbar = {
|
||||
attach(context) {
|
||||
if ($('body').once('contextualToolbar-init').length) {
|
||||
initContextualToolbar(context);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Namespace for the contextual toolbar.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
Drupal.contextualToolbar = {
|
||||
|
||||
/**
|
||||
* The {@link Drupal.contextualToolbar.StateModel} instance.
|
||||
*
|
||||
* @type {?Drupal.contextualToolbar.StateModel}
|
||||
*/
|
||||
model: null,
|
||||
};
|
||||
}(jQuery, Drupal, Backbone));
|
||||
@@ -1,24 +1,17 @@
|
||||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Contextual module's edit toolbar tab.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal, Backbone) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var strings = {
|
||||
tabbingReleased: Drupal.t('Tabbing is no longer constrained by the Contextual module.'),
|
||||
tabbingConstrained: Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.'),
|
||||
pressEsc: Drupal.t('Press the esc key to exit.')
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes a contextual link: updates its DOM, sets up model and views.
|
||||
*
|
||||
* @param {HTMLElement} context
|
||||
* A contextual links DOM element as rendered by the server.
|
||||
*/
|
||||
function initContextualToolbar(context) {
|
||||
if (!Drupal.contextual || !Drupal.contextual.collection) {
|
||||
return;
|
||||
@@ -26,9 +19,6 @@
|
||||
|
||||
var contextualToolbar = Drupal.contextualToolbar;
|
||||
var model = contextualToolbar.model = new contextualToolbar.StateModel({
|
||||
// Checks whether localStorage indicates we should start in edit mode
|
||||
// rather than view mode.
|
||||
// @see Drupal.contextualToolbar.VisualView.persist
|
||||
isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false'
|
||||
}, {
|
||||
contextualCollection: Drupal.contextual.collection
|
||||
@@ -43,35 +33,15 @@
|
||||
new contextualToolbar.AuralView(viewOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches contextual's edit toolbar tab behavior.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches contextual toolbar behavior on a contextualToolbar-init event.
|
||||
*/
|
||||
Drupal.behaviors.contextualToolbar = {
|
||||
attach: function (context) {
|
||||
attach: function attach(context) {
|
||||
if ($('body').once('contextualToolbar-init').length) {
|
||||
initContextualToolbar(context);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Namespace for the contextual toolbar.
|
||||
*
|
||||
* @namespace
|
||||
*/
|
||||
Drupal.contextualToolbar = {
|
||||
|
||||
/**
|
||||
* The {@link Drupal.contextualToolbar.StateModel} instance.
|
||||
*
|
||||
* @type {?Drupal.contextualToolbar.StateModel}
|
||||
*/
|
||||
model: null
|
||||
};
|
||||
|
||||
})(jQuery, Drupal, Backbone);
|
||||
})(jQuery, Drupal, Backbone);
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of a contextual link's trigger, list & region.
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
/**
|
||||
* Models the state of a contextual link's trigger, list & region.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
Drupal.contextual.StateModel = Backbone.Model.extend(/** @lends Drupal.contextual.StateModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*
|
||||
* @prop {string} title
|
||||
* @prop {bool} regionIsHovered
|
||||
* @prop {bool} hasFocus
|
||||
* @prop {bool} isOpen
|
||||
* @prop {bool} isLocked
|
||||
*/
|
||||
defaults: /** @lends Drupal.contextual.StateModel# */{
|
||||
|
||||
/**
|
||||
* The title of the entity to which these contextual links apply.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
title: '',
|
||||
|
||||
/**
|
||||
* Represents if the contextual region is being hovered.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
regionIsHovered: false,
|
||||
|
||||
/**
|
||||
* Represents if the contextual trigger or options have focus.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
hasFocus: false,
|
||||
|
||||
/**
|
||||
* Represents if the contextual options for an entity are available to
|
||||
* be selected (i.e. whether the list of options is visible).
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isOpen: false,
|
||||
|
||||
/**
|
||||
* When the model is locked, the trigger remains active.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isLocked: false,
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens or closes the contextual link.
|
||||
*
|
||||
* If it is opened, then also give focus.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
toggleOpen() {
|
||||
const newIsOpen = !this.get('isOpen');
|
||||
this.set('isOpen', newIsOpen);
|
||||
if (newIsOpen) {
|
||||
this.focus();
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes this contextual link.
|
||||
*
|
||||
* Does not call blur() because we want to allow a contextual link to have
|
||||
* focus, yet be closed for example when hovering.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
close() {
|
||||
this.set('isOpen', false);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gives focus to this contextual link.
|
||||
*
|
||||
* Also closes + removes focus from every other contextual link.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
focus() {
|
||||
this.set('hasFocus', true);
|
||||
const cid = this.cid;
|
||||
this.collection.each((model) => {
|
||||
if (model.cid !== cid) {
|
||||
model.close().blur();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes focus from this contextual link, unless it is open.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
blur() {
|
||||
if (!this.get('isOpen')) {
|
||||
this.set('hasFocus', false);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
}(Drupal, Backbone));
|
||||
@@ -1,78 +1,25 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of a contextual link's trigger, list & region.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Models the state of a contextual link's trigger, list & region.
|
||||
*
|
||||
* @constructor
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*/
|
||||
Drupal.contextual.StateModel = Backbone.Model.extend(/** @lends Drupal.contextual.StateModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*
|
||||
* @prop {string} title
|
||||
* @prop {bool} regionIsHovered
|
||||
* @prop {bool} hasFocus
|
||||
* @prop {bool} isOpen
|
||||
* @prop {bool} isLocked
|
||||
*/
|
||||
defaults: /** @lends Drupal.contextual.StateModel# */{
|
||||
|
||||
/**
|
||||
* The title of the entity to which these contextual links apply.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
Drupal.contextual.StateModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
title: '',
|
||||
|
||||
/**
|
||||
* Represents if the contextual region is being hovered.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
regionIsHovered: false,
|
||||
|
||||
/**
|
||||
* Represents if the contextual trigger or options have focus.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
hasFocus: false,
|
||||
|
||||
/**
|
||||
* Represents if the contextual options for an entity are available to
|
||||
* be selected (i.e. whether the list of options is visible).
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isOpen: false,
|
||||
|
||||
/**
|
||||
* When the model is locked, the trigger remains active.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isLocked: false
|
||||
},
|
||||
|
||||
/**
|
||||
* Opens or closes the contextual link.
|
||||
*
|
||||
* If it is opened, then also give focus.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
toggleOpen: function () {
|
||||
toggleOpen: function toggleOpen() {
|
||||
var newIsOpen = !this.get('isOpen');
|
||||
this.set('isOpen', newIsOpen);
|
||||
if (newIsOpen) {
|
||||
@@ -80,30 +27,11 @@
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Closes this contextual link.
|
||||
*
|
||||
* Does not call blur() because we want to allow a contextual link to have
|
||||
* focus, yet be closed for example when hovering.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
close: function () {
|
||||
close: function close() {
|
||||
this.set('isOpen', false);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gives focus to this contextual link.
|
||||
*
|
||||
* Also closes + removes focus from every other contextual link.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
focus: function () {
|
||||
focus: function focus() {
|
||||
this.set('hasFocus', true);
|
||||
var cid = this.cid;
|
||||
this.collection.each(function (model) {
|
||||
@@ -113,20 +41,11 @@
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes focus from this contextual link, unless it is open.
|
||||
*
|
||||
* @return {Drupal.contextual.StateModel}
|
||||
* The current contextual state model.
|
||||
*/
|
||||
blur: function () {
|
||||
blur: function blur() {
|
||||
if (!this.get('isOpen')) {
|
||||
this.set('hasFocus', false);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(Drupal, Backbone);
|
||||
})(Drupal, Backbone);
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of Contextual module's edit toolbar tab.
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
Drupal.contextualToolbar.StateModel = Backbone.Model.extend(/** @lends Drupal.contextualToolbar.StateModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*
|
||||
* @prop {bool} isViewing
|
||||
* @prop {bool} isVisible
|
||||
* @prop {number} contextualCount
|
||||
* @prop {Drupal~TabbingContext} tabbingContext
|
||||
*/
|
||||
defaults: /** @lends Drupal.contextualToolbar.StateModel# */{
|
||||
|
||||
/**
|
||||
* Indicates whether the toggle is currently in "view" or "edit" mode.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isViewing: true,
|
||||
|
||||
/**
|
||||
* Indicates whether the toggle should be visible or hidden. Automatically
|
||||
* calculated, depends on contextualCount.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isVisible: false,
|
||||
|
||||
/**
|
||||
* Tracks how many contextual links exist on the page.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
contextualCount: 0,
|
||||
|
||||
/**
|
||||
* A TabbingContext object as returned by {@link Drupal~TabbingManager}:
|
||||
* the set of tabbable elements when edit mode is enabled.
|
||||
*
|
||||
* @type {?Drupal~TabbingContext}
|
||||
*/
|
||||
tabbingContext: null,
|
||||
},
|
||||
|
||||
/**
|
||||
* Models the state of the edit mode toggle.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*
|
||||
* @param {object} attrs
|
||||
* Attributes for the backbone model.
|
||||
* @param {object} options
|
||||
* An object with the following option:
|
||||
* @param {Backbone.collection} options.contextualCollection
|
||||
* The collection of {@link Drupal.contextual.StateModel} models that
|
||||
* represent the contextual links on the page.
|
||||
*/
|
||||
initialize(attrs, options) {
|
||||
// Respond to new/removed contextual links.
|
||||
this.listenTo(options.contextualCollection, 'reset remove add', this.countContextualLinks);
|
||||
this.listenTo(options.contextualCollection, 'add', this.lockNewContextualLinks);
|
||||
|
||||
// Automatically determine visibility.
|
||||
this.listenTo(this, 'change:contextualCount', this.updateVisibility);
|
||||
|
||||
// Whenever edit mode is toggled, lock all contextual links.
|
||||
this.listenTo(this, 'change:isViewing', (model, isViewing) => {
|
||||
options.contextualCollection.each((contextualModel) => {
|
||||
contextualModel.set('isLocked', !isViewing);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the number of contextual link models in the collection.
|
||||
*
|
||||
* @param {Drupal.contextual.StateModel} contextualModel
|
||||
* The contextual links model that was added or removed.
|
||||
* @param {Backbone.Collection} contextualCollection
|
||||
* The collection of contextual link models.
|
||||
*/
|
||||
countContextualLinks(contextualModel, contextualCollection) {
|
||||
this.set('contextualCount', contextualCollection.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lock newly added contextual links if edit mode is enabled.
|
||||
*
|
||||
* @param {Drupal.contextual.StateModel} contextualModel
|
||||
* The contextual links model that was added.
|
||||
* @param {Backbone.Collection} [contextualCollection]
|
||||
* The collection of contextual link models.
|
||||
*/
|
||||
lockNewContextualLinks(contextualModel, contextualCollection) {
|
||||
if (!this.get('isViewing')) {
|
||||
contextualModel.set('isLocked', true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically updates visibility of the view/edit mode toggle.
|
||||
*/
|
||||
updateVisibility() {
|
||||
this.set('isVisible', this.get('contextualCount') > 0);
|
||||
},
|
||||
|
||||
});
|
||||
}(Drupal, Backbone));
|
||||
@@ -1,119 +1,44 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone Model for the state of Contextual module's edit toolbar tab.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.contextualToolbar.StateModel = Backbone.Model.extend(/** @lends Drupal.contextualToolbar.StateModel# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*
|
||||
* @prop {bool} isViewing
|
||||
* @prop {bool} isVisible
|
||||
* @prop {number} contextualCount
|
||||
* @prop {Drupal~TabbingContext} tabbingContext
|
||||
*/
|
||||
defaults: /** @lends Drupal.contextualToolbar.StateModel# */{
|
||||
|
||||
/**
|
||||
* Indicates whether the toggle is currently in "view" or "edit" mode.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
Drupal.contextualToolbar.StateModel = Backbone.Model.extend({
|
||||
defaults: {
|
||||
isViewing: true,
|
||||
|
||||
/**
|
||||
* Indicates whether the toggle should be visible or hidden. Automatically
|
||||
* calculated, depends on contextualCount.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
isVisible: false,
|
||||
|
||||
/**
|
||||
* Tracks how many contextual links exist on the page.
|
||||
*
|
||||
* @type {number}
|
||||
*/
|
||||
contextualCount: 0,
|
||||
|
||||
/**
|
||||
* A TabbingContext object as returned by {@link Drupal~TabbingManager}:
|
||||
* the set of tabbable elements when edit mode is enabled.
|
||||
*
|
||||
* @type {?Drupal~TabbingContext}
|
||||
*/
|
||||
tabbingContext: null
|
||||
},
|
||||
|
||||
/**
|
||||
* Models the state of the edit mode toggle.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.Model
|
||||
*
|
||||
* @param {object} attrs
|
||||
* Attributes for the backbone model.
|
||||
* @param {object} options
|
||||
* An object with the following option:
|
||||
* @param {Backbone.collection} options.contextualCollection
|
||||
* The collection of {@link Drupal.contextual.StateModel} models that
|
||||
* represent the contextual links on the page.
|
||||
*/
|
||||
initialize: function (attrs, options) {
|
||||
// Respond to new/removed contextual links.
|
||||
initialize: function initialize(attrs, options) {
|
||||
this.listenTo(options.contextualCollection, 'reset remove add', this.countContextualLinks);
|
||||
this.listenTo(options.contextualCollection, 'add', this.lockNewContextualLinks);
|
||||
|
||||
// Automatically determine visibility.
|
||||
this.listenTo(this, 'change:contextualCount', this.updateVisibility);
|
||||
|
||||
// Whenever edit mode is toggled, lock all contextual links.
|
||||
this.listenTo(this, 'change:isViewing', function (model, isViewing) {
|
||||
options.contextualCollection.each(function (contextualModel) {
|
||||
contextualModel.set('isLocked', !isViewing);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the number of contextual link models in the collection.
|
||||
*
|
||||
* @param {Drupal.contextual.StateModel} contextualModel
|
||||
* The contextual links model that was added or removed.
|
||||
* @param {Backbone.Collection} contextualCollection
|
||||
* The collection of contextual link models.
|
||||
*/
|
||||
countContextualLinks: function (contextualModel, contextualCollection) {
|
||||
countContextualLinks: function countContextualLinks(contextualModel, contextualCollection) {
|
||||
this.set('contextualCount', contextualCollection.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lock newly added contextual links if edit mode is enabled.
|
||||
*
|
||||
* @param {Drupal.contextual.StateModel} contextualModel
|
||||
* The contextual links model that was added.
|
||||
* @param {Backbone.Collection} [contextualCollection]
|
||||
* The collection of contextual link models.
|
||||
*/
|
||||
lockNewContextualLinks: function (contextualModel, contextualCollection) {
|
||||
lockNewContextualLinks: function lockNewContextualLinks(contextualModel, contextualCollection) {
|
||||
if (!this.get('isViewing')) {
|
||||
contextualModel.set('isLocked', true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically updates visibility of the view/edit mode toggle.
|
||||
*/
|
||||
updateVisibility: function () {
|
||||
updateVisibility: function updateVisibility() {
|
||||
this.set('isVisible', this.get('contextualCount') > 0);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(Drupal, Backbone);
|
||||
})(Drupal, Backbone);
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the aural view of the edit mode toggle.
|
||||
*/
|
||||
|
||||
(function ($, Drupal, Backbone, _) {
|
||||
Drupal.contextualToolbar.AuralView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.AuralView# */{
|
||||
|
||||
/**
|
||||
* Tracks whether the tabbing constraint announcement has been read once.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
announcedOnce: false,
|
||||
|
||||
/**
|
||||
* Renders the aural view of the edit mode toggle (screen reader support).
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*
|
||||
* @param {object} options
|
||||
* Options for the view.
|
||||
*/
|
||||
initialize(options) {
|
||||
this.options = options;
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.listenTo(this.model, 'change:isViewing', this.manageTabbing);
|
||||
|
||||
$(document).on('keyup', _.bind(this.onKeypress, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextualToolbar.AuralView}
|
||||
* The current contextual toolbar aural view.
|
||||
*/
|
||||
render() {
|
||||
// Render the state.
|
||||
this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Limits tabbing to the contextual links and edit mode toolbar tab.
|
||||
*/
|
||||
manageTabbing() {
|
||||
let tabbingContext = this.model.get('tabbingContext');
|
||||
// Always release an existing tabbing context.
|
||||
if (tabbingContext) {
|
||||
// Only announce release when the context was active.
|
||||
if (tabbingContext.active) {
|
||||
Drupal.announce(this.options.strings.tabbingReleased);
|
||||
}
|
||||
tabbingContext.release();
|
||||
}
|
||||
// Create a new tabbing context when edit mode is enabled.
|
||||
if (!this.model.get('isViewing')) {
|
||||
tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
|
||||
this.model.set('tabbingContext', tabbingContext);
|
||||
this.announceTabbingConstraint();
|
||||
this.announcedOnce = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Announces the current tabbing constraint.
|
||||
*/
|
||||
announceTabbingConstraint() {
|
||||
const strings = this.options.strings;
|
||||
Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
|
||||
'@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links'),
|
||||
}));
|
||||
Drupal.announce(strings.pressEsc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Responds to esc and tab key press events.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The keypress event.
|
||||
*/
|
||||
onKeypress(event) {
|
||||
// The first tab key press is tracked so that an annoucement about tabbing
|
||||
// constraints can be raised if edit mode is enabled when the page is
|
||||
// loaded.
|
||||
if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
|
||||
this.announceTabbingConstraint();
|
||||
// Set announce to true so that this conditional block won't run again.
|
||||
this.announcedOnce = true;
|
||||
}
|
||||
// Respond to the ESC key. Exit out of edit mode.
|
||||
if (event.keyCode === 27) {
|
||||
this.model.set('isViewing', true);
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
}(jQuery, Drupal, Backbone, _));
|
||||
@@ -1,32 +1,15 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the aural view of the edit mode toggle.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function ($, Drupal, Backbone, _) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.contextualToolbar.AuralView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.AuralView# */{
|
||||
|
||||
/**
|
||||
* Tracks whether the tabbing constraint announcement has been read once.
|
||||
*
|
||||
* @type {bool}
|
||||
*/
|
||||
Drupal.contextualToolbar.AuralView = Backbone.View.extend({
|
||||
announcedOnce: false,
|
||||
|
||||
/**
|
||||
* Renders the aural view of the edit mode toggle (screen reader support).
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*
|
||||
* @param {object} options
|
||||
* Options for the view.
|
||||
*/
|
||||
initialize: function (options) {
|
||||
initialize: function initialize(options) {
|
||||
this.options = options;
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
@@ -34,31 +17,21 @@
|
||||
|
||||
$(document).on('keyup', _.bind(this.onKeypress, this));
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextualToolbar.AuralView}
|
||||
* The current contextual toolbar aural view.
|
||||
*/
|
||||
render: function () {
|
||||
// Render the state.
|
||||
render: function render() {
|
||||
this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Limits tabbing to the contextual links and edit mode toolbar tab.
|
||||
*/
|
||||
manageTabbing: function () {
|
||||
manageTabbing: function manageTabbing() {
|
||||
var tabbingContext = this.model.get('tabbingContext');
|
||||
// Always release an existing tabbing context.
|
||||
|
||||
if (tabbingContext) {
|
||||
if (tabbingContext.active) {
|
||||
Drupal.announce(this.options.strings.tabbingReleased);
|
||||
}
|
||||
tabbingContext.release();
|
||||
Drupal.announce(this.options.strings.tabbingReleased);
|
||||
}
|
||||
// Create a new tabbing context when edit mode is enabled.
|
||||
|
||||
if (!this.model.get('isViewing')) {
|
||||
tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
|
||||
this.model.set('tabbingContext', tabbingContext);
|
||||
@@ -66,39 +39,23 @@
|
||||
this.announcedOnce = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Announces the current tabbing constraint.
|
||||
*/
|
||||
announceTabbingConstraint: function () {
|
||||
announceTabbingConstraint: function announceTabbingConstraint() {
|
||||
var strings = this.options.strings;
|
||||
Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
|
||||
'@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
|
||||
}));
|
||||
Drupal.announce(strings.pressEsc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Responds to esc and tab key press events.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The keypress event.
|
||||
*/
|
||||
onKeypress: function (event) {
|
||||
// The first tab key press is tracked so that an annoucement about tabbing
|
||||
// constraints can be raised if edit mode is enabled when the page is
|
||||
// loaded.
|
||||
onKeypress: function onKeypress(event) {
|
||||
if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
|
||||
this.announceTabbingConstraint();
|
||||
// Set announce to true so that this conditional block won't run again.
|
||||
|
||||
this.announcedOnce = true;
|
||||
}
|
||||
// Respond to the ESC key. Exit out of edit mode.
|
||||
|
||||
if (event.keyCode === 27) {
|
||||
this.model.set('isViewing', true);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(jQuery, Drupal, Backbone, _);
|
||||
})(jQuery, Drupal, Backbone, _);
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the visual view of the edit mode toggle.
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
Drupal.contextualToolbar.VisualView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.VisualView# */{
|
||||
|
||||
/**
|
||||
* Events for the Backbone view.
|
||||
*
|
||||
* @return {object}
|
||||
* A mapping of events to be used in the view.
|
||||
*/
|
||||
events() {
|
||||
// Prevents delay and simulated mouse events.
|
||||
const touchEndToClick = function (event) {
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
};
|
||||
|
||||
return {
|
||||
click() {
|
||||
this.model.set('isViewing', !this.model.get('isViewing'));
|
||||
},
|
||||
touchend: touchEndToClick,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the visual view of the edit mode toggle.
|
||||
*
|
||||
* Listens to mouse & touch and handles edit mode toggle interactions.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.listenTo(this.model, 'change:isViewing', this.persist);
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextualToolbar.VisualView}
|
||||
* The current contextual toolbar visual view.
|
||||
*/
|
||||
render() {
|
||||
// Render the visibility.
|
||||
this.$el.toggleClass('hidden', !this.model.get('isVisible'));
|
||||
// Render the state.
|
||||
this.$el.find('button').toggleClass('is-active', !this.model.get('isViewing'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Model change handler; persists the isViewing value to localStorage.
|
||||
*
|
||||
* `isViewing === true` is the default, so only stores in localStorage when
|
||||
* it's not the default value (i.e. false).
|
||||
*
|
||||
* @param {Drupal.contextualToolbar.StateModel} model
|
||||
* A {@link Drupal.contextualToolbar.StateModel} model.
|
||||
* @param {bool} isViewing
|
||||
* The value of the isViewing attribute in the model.
|
||||
*/
|
||||
persist(model, isViewing) {
|
||||
if (!isViewing) {
|
||||
localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
|
||||
}
|
||||
else {
|
||||
localStorage.removeItem('Drupal.contextualToolbar.isViewing');
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
}(Drupal, Backbone));
|
||||
@@ -1,84 +1,43 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the visual view of the edit mode toggle.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.contextualToolbar.VisualView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.VisualView# */{
|
||||
|
||||
/**
|
||||
* Events for the Backbone view.
|
||||
*
|
||||
* @return {object}
|
||||
* A mapping of events to be used in the view.
|
||||
*/
|
||||
events: function () {
|
||||
// Prevents delay and simulated mouse events.
|
||||
var touchEndToClick = function (event) {
|
||||
Drupal.contextualToolbar.VisualView = Backbone.View.extend({
|
||||
events: function events() {
|
||||
var touchEndToClick = function touchEndToClick(event) {
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
};
|
||||
|
||||
return {
|
||||
click: function () {
|
||||
click: function click() {
|
||||
this.model.set('isViewing', !this.model.get('isViewing'));
|
||||
},
|
||||
|
||||
touchend: touchEndToClick
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the visual view of the edit mode toggle.
|
||||
*
|
||||
* Listens to mouse & touch and handles edit mode toggle interactions.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize: function () {
|
||||
initialize: function initialize() {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
this.listenTo(this.model, 'change:isViewing', this.persist);
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextualToolbar.VisualView}
|
||||
* The current contextual toolbar visual view.
|
||||
*/
|
||||
render: function () {
|
||||
// Render the visibility.
|
||||
render: function render() {
|
||||
this.$el.toggleClass('hidden', !this.model.get('isVisible'));
|
||||
// Render the state.
|
||||
|
||||
this.$el.find('button').toggleClass('is-active', !this.model.get('isViewing'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Model change handler; persists the isViewing value to localStorage.
|
||||
*
|
||||
* `isViewing === true` is the default, so only stores in localStorage when
|
||||
* it's not the default value (i.e. false).
|
||||
*
|
||||
* @param {Drupal.contextualToolbar.StateModel} model
|
||||
* A {@link Drupal.contextualToolbar.StateModel} model.
|
||||
* @param {bool} isViewing
|
||||
* The value of the isViewing attribute in the model.
|
||||
*/
|
||||
persist: function (model, isViewing) {
|
||||
persist: function persist(model, isViewing) {
|
||||
if (!isViewing) {
|
||||
localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
localStorage.removeItem('Drupal.contextualToolbar.isViewing');
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(Drupal, Backbone);
|
||||
})(Drupal, Backbone);
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the aural view of a contextual link.
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
Drupal.contextual.AuralView = Backbone.View.extend(/** @lends Drupal.contextual.AuralView# */{
|
||||
|
||||
/**
|
||||
* Renders the aural view of a contextual link (i.e. screen reader support).
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*
|
||||
* @param {object} options
|
||||
* Options for the view.
|
||||
*/
|
||||
initialize(options) {
|
||||
this.options = options;
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
|
||||
// Use aria-role form so that the number of items in the list is spoken.
|
||||
this.$el.attr('role', 'form');
|
||||
|
||||
// Initial render.
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const isOpen = this.model.get('isOpen');
|
||||
|
||||
// Set the hidden property of the links.
|
||||
this.$el.find('.contextual-links')
|
||||
.prop('hidden', !isOpen);
|
||||
|
||||
// Update the view of the trigger.
|
||||
this.$el.find('.trigger')
|
||||
.text(Drupal.t('@action @title configuration options', {
|
||||
'@action': (!isOpen) ? this.options.strings.open : this.options.strings.close,
|
||||
'@title': this.model.get('title'),
|
||||
}))
|
||||
.attr('aria-pressed', isOpen);
|
||||
},
|
||||
|
||||
});
|
||||
}(Drupal, Backbone));
|
||||
@@ -1,55 +1,30 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the aural view of a contextual link.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.contextual.AuralView = Backbone.View.extend(/** @lends Drupal.contextual.AuralView# */{
|
||||
|
||||
/**
|
||||
* Renders the aural view of a contextual link (i.e. screen reader support).
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*
|
||||
* @param {object} options
|
||||
* Options for the view.
|
||||
*/
|
||||
initialize: function (options) {
|
||||
Drupal.contextual.AuralView = Backbone.View.extend({
|
||||
initialize: function initialize(options) {
|
||||
this.options = options;
|
||||
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
|
||||
// Use aria-role form so that the number of items in the list is spoken.
|
||||
this.$el.attr('role', 'form');
|
||||
|
||||
// Initial render.
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
render: function () {
|
||||
render: function render() {
|
||||
var isOpen = this.model.get('isOpen');
|
||||
|
||||
// Set the hidden property of the links.
|
||||
this.$el.find('.contextual-links')
|
||||
.prop('hidden', !isOpen);
|
||||
this.$el.find('.contextual-links').prop('hidden', !isOpen);
|
||||
|
||||
// Update the view of the trigger.
|
||||
this.$el.find('.trigger')
|
||||
.text(Drupal.t('@action @title configuration options', {
|
||||
'@action': (!isOpen) ? this.options.strings.open : this.options.strings.close,
|
||||
'@title': this.model.get('title')
|
||||
}))
|
||||
.attr('aria-pressed', isOpen);
|
||||
this.$el.find('.trigger').text(Drupal.t('@action @title configuration options', {
|
||||
'@action': !isOpen ? this.options.strings.open : this.options.strings.close,
|
||||
'@title': this.model.get('title')
|
||||
})).attr('aria-pressed', isOpen);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(Drupal, Backbone);
|
||||
})(Drupal, Backbone);
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides keyboard interaction for a contextual link.
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
Drupal.contextual.KeyboardView = Backbone.View.extend(/** @lends Drupal.contextual.KeyboardView# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
events: {
|
||||
'focus .trigger': 'focus',
|
||||
'focus .contextual-links a': 'focus',
|
||||
'blur .trigger': function () {
|
||||
this.model.blur();
|
||||
},
|
||||
'blur .contextual-links a': function () {
|
||||
// Set up a timeout to allow a user to tab between the trigger and the
|
||||
// contextual links without the menu dismissing.
|
||||
const that = this;
|
||||
this.timer = window.setTimeout(() => {
|
||||
that.model.close().blur();
|
||||
}, 150);
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Provides keyboard interaction for a contextual link.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
/**
|
||||
* The timer is used to create a delay before dismissing the contextual
|
||||
* links on blur. This is only necessary when keyboard users tab into
|
||||
* contextual links without edit mode (i.e. without TabbingManager).
|
||||
* That means that if we decide to disable tabbing of contextual links
|
||||
* without edit mode, all this timer logic can go away.
|
||||
*
|
||||
* @type {NaN|number}
|
||||
*/
|
||||
this.timer = NaN;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets focus on the model; Clears the timer that dismisses the links.
|
||||
*/
|
||||
focus() {
|
||||
// Clear the timeout that might have been set by blurring a link.
|
||||
window.clearTimeout(this.timer);
|
||||
this.model.focus();
|
||||
},
|
||||
|
||||
});
|
||||
}(Drupal, Backbone));
|
||||
@@ -1,24 +1,19 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides keyboard interaction for a contextual link.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal, Backbone) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.contextual.KeyboardView = Backbone.View.extend(/** @lends Drupal.contextual.KeyboardView# */{
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
Drupal.contextual.KeyboardView = Backbone.View.extend({
|
||||
events: {
|
||||
'focus .trigger': 'focus',
|
||||
'focus .contextual-links a': 'focus',
|
||||
'blur .trigger': function () { this.model.blur(); },
|
||||
'blur .contextual-links a': function () {
|
||||
// Set up a timeout to allow a user to tab between the trigger and the
|
||||
// contextual links without the menu dismissing.
|
||||
'blur .trigger': function blurTrigger() {
|
||||
this.model.blur();
|
||||
},
|
||||
'blur .contextual-links a': function blurContextualLinksA() {
|
||||
var that = this;
|
||||
this.timer = window.setTimeout(function () {
|
||||
that.model.close().blur();
|
||||
@@ -26,36 +21,12 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Provides keyboard interaction for a contextual link.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize: function () {
|
||||
|
||||
/**
|
||||
* The timer is used to create a delay before dismissing the contextual
|
||||
* links on blur. This is only necessary when keyboard users tab into
|
||||
* contextual links without edit mode (i.e. without TabbingManager).
|
||||
* That means that if we decide to disable tabbing of contextual links
|
||||
* without edit mode, all this timer logic can go away.
|
||||
*
|
||||
* @type {NaN|number}
|
||||
*/
|
||||
initialize: function initialize() {
|
||||
this.timer = NaN;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets focus on the model; Clears the timer that dismisses the links.
|
||||
*/
|
||||
focus: function () {
|
||||
// Clear the timeout that might have been set by blurring a link.
|
||||
focus: function focus() {
|
||||
window.clearTimeout(this.timer);
|
||||
this.model.focus();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(Drupal, Backbone);
|
||||
})(Drupal, Backbone);
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that renders the visual view of a contextual region element.
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone, Modernizr) {
|
||||
Drupal.contextual.RegionView = Backbone.View.extend(/** @lends Drupal.contextual.RegionView# */{
|
||||
|
||||
/**
|
||||
* Events for the Backbone view.
|
||||
*
|
||||
* @return {object}
|
||||
* A mapping of events to be used in the view.
|
||||
*/
|
||||
events() {
|
||||
let mapping = {
|
||||
mouseenter() {
|
||||
this.model.set('regionIsHovered', true);
|
||||
},
|
||||
mouseleave() {
|
||||
this.model.close().blur().set('regionIsHovered', false);
|
||||
},
|
||||
};
|
||||
// We don't want mouse hover events on touch.
|
||||
if (Modernizr.touchevents) {
|
||||
mapping = {};
|
||||
}
|
||||
return mapping;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the visual view of a contextual region element.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
this.listenTo(this.model, 'change:hasFocus', this.render);
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextual.RegionView}
|
||||
* The current contextual region view.
|
||||
*/
|
||||
render() {
|
||||
this.$el.toggleClass('focus', this.model.get('hasFocus'));
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
}(Drupal, Backbone, Modernizr));
|
||||
@@ -1,57 +1,34 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that renders the visual view of a contextual region element.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal, Backbone, Modernizr) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.contextual.RegionView = Backbone.View.extend(/** @lends Drupal.contextual.RegionView# */{
|
||||
|
||||
/**
|
||||
* Events for the Backbone view.
|
||||
*
|
||||
* @return {object}
|
||||
* A mapping of events to be used in the view.
|
||||
*/
|
||||
events: function () {
|
||||
Drupal.contextual.RegionView = Backbone.View.extend({
|
||||
events: function events() {
|
||||
var mapping = {
|
||||
mouseenter: function () { this.model.set('regionIsHovered', true); },
|
||||
mouseleave: function () {
|
||||
mouseenter: function mouseenter() {
|
||||
this.model.set('regionIsHovered', true);
|
||||
},
|
||||
mouseleave: function mouseleave() {
|
||||
this.model.close().blur().set('regionIsHovered', false);
|
||||
}
|
||||
};
|
||||
// We don't want mouse hover events on touch.
|
||||
|
||||
if (Modernizr.touchevents) {
|
||||
mapping = {};
|
||||
}
|
||||
return mapping;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the visual view of a contextual region element.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize: function () {
|
||||
initialize: function initialize() {
|
||||
this.listenTo(this.model, 'change:hasFocus', this.render);
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextual.RegionView}
|
||||
* The current contextual region view.
|
||||
*/
|
||||
render: function () {
|
||||
render: function render() {
|
||||
this.$el.toggleClass('focus', this.model.get('hasFocus'));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(Drupal, Backbone, Modernizr);
|
||||
})(Drupal, Backbone, Modernizr);
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the visual view of a contextual link.
|
||||
*/
|
||||
|
||||
(function (Drupal, Backbone, Modernizr) {
|
||||
Drupal.contextual.VisualView = Backbone.View.extend(/** @lends Drupal.contextual.VisualView# */{
|
||||
|
||||
/**
|
||||
* Events for the Backbone view.
|
||||
*
|
||||
* @return {object}
|
||||
* A mapping of events to be used in the view.
|
||||
*/
|
||||
events() {
|
||||
// Prevents delay and simulated mouse events.
|
||||
const touchEndToClick = function (event) {
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
};
|
||||
const mapping = {
|
||||
'click .trigger': function () {
|
||||
this.model.toggleOpen();
|
||||
},
|
||||
'touchend .trigger': touchEndToClick,
|
||||
'click .contextual-links a': function () {
|
||||
this.model.close().blur();
|
||||
},
|
||||
'touchend .contextual-links a': touchEndToClick,
|
||||
};
|
||||
// We only want mouse hover events on non-touch.
|
||||
if (!Modernizr.touchevents) {
|
||||
mapping.mouseenter = function () {
|
||||
this.model.focus();
|
||||
};
|
||||
}
|
||||
return mapping;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the visual view of a contextual link. Listens to mouse & touch.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize() {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextual.VisualView}
|
||||
* The current contextual visual view.
|
||||
*/
|
||||
render() {
|
||||
const isOpen = this.model.get('isOpen');
|
||||
// The trigger should be visible when:
|
||||
// - the mouse hovered over the region,
|
||||
// - the trigger is locked,
|
||||
// - and for as long as the contextual menu is open.
|
||||
const isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen;
|
||||
|
||||
this.$el
|
||||
// The open state determines if the links are visible.
|
||||
.toggleClass('open', isOpen)
|
||||
// Update the visibility of the trigger.
|
||||
.find('.trigger').toggleClass('visually-hidden', !isVisible);
|
||||
|
||||
// Nested contextual region handling: hide any nested contextual triggers.
|
||||
if ('isOpen' in this.model.changed) {
|
||||
this.$el.closest('.contextual-region')
|
||||
.find('.contextual .trigger:not(:first)')
|
||||
.toggle(!isOpen);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
});
|
||||
}(Drupal, Backbone, Modernizr));
|
||||
@@ -1,80 +1,50 @@
|
||||
/**
|
||||
* @file
|
||||
* A Backbone View that provides the visual view of a contextual link.
|
||||
*/
|
||||
* DO NOT EDIT THIS FILE.
|
||||
* See the following change record for more information,
|
||||
* https://www.drupal.org/node/2815083
|
||||
* @preserve
|
||||
**/
|
||||
|
||||
(function (Drupal, Backbone, Modernizr) {
|
||||
|
||||
'use strict';
|
||||
|
||||
Drupal.contextual.VisualView = Backbone.View.extend(/** @lends Drupal.contextual.VisualView# */{
|
||||
|
||||
/**
|
||||
* Events for the Backbone view.
|
||||
*
|
||||
* @return {object}
|
||||
* A mapping of events to be used in the view.
|
||||
*/
|
||||
events: function () {
|
||||
// Prevents delay and simulated mouse events.
|
||||
var touchEndToClick = function (event) {
|
||||
Drupal.contextual.VisualView = Backbone.View.extend({
|
||||
events: function events() {
|
||||
var touchEndToClick = function touchEndToClick(event) {
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
};
|
||||
var mapping = {
|
||||
'click .trigger': function () { this.model.toggleOpen(); },
|
||||
'click .trigger': function clickTrigger() {
|
||||
this.model.toggleOpen();
|
||||
},
|
||||
'touchend .trigger': touchEndToClick,
|
||||
'click .contextual-links a': function () { this.model.close().blur(); },
|
||||
'click .contextual-links a': function clickContextualLinksA() {
|
||||
this.model.close().blur();
|
||||
},
|
||||
'touchend .contextual-links a': touchEndToClick
|
||||
};
|
||||
// We only want mouse hover events on non-touch.
|
||||
|
||||
if (!Modernizr.touchevents) {
|
||||
mapping.mouseenter = function () { this.model.focus(); };
|
||||
mapping.mouseenter = function () {
|
||||
this.model.focus();
|
||||
};
|
||||
}
|
||||
return mapping;
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the visual view of a contextual link. Listens to mouse & touch.
|
||||
*
|
||||
* @constructs
|
||||
*
|
||||
* @augments Backbone.View
|
||||
*/
|
||||
initialize: function () {
|
||||
initialize: function initialize() {
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @return {Drupal.contextual.VisualView}
|
||||
* The current contextual visual view.
|
||||
*/
|
||||
render: function () {
|
||||
render: function render() {
|
||||
var isOpen = this.model.get('isOpen');
|
||||
// The trigger should be visible when:
|
||||
// - the mouse hovered over the region,
|
||||
// - the trigger is locked,
|
||||
// - and for as long as the contextual menu is open.
|
||||
|
||||
var isVisible = this.model.get('isLocked') || this.model.get('regionIsHovered') || isOpen;
|
||||
|
||||
this.$el
|
||||
// The open state determines if the links are visible.
|
||||
.toggleClass('open', isOpen)
|
||||
// Update the visibility of the trigger.
|
||||
.find('.trigger').toggleClass('visually-hidden', !isVisible);
|
||||
this.$el.toggleClass('open', isOpen).find('.trigger').toggleClass('visually-hidden', !isVisible);
|
||||
|
||||
// Nested contextual region handling: hide any nested contextual triggers.
|
||||
if ('isOpen' in this.model.changed) {
|
||||
this.$el.closest('.contextual-region')
|
||||
.find('.contextual .trigger:not(:first)')
|
||||
.toggle(!isOpen);
|
||||
this.$el.closest('.contextual-region').find('.contextual .trigger:not(:first)').toggle(!isOpen);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
})(Drupal, Backbone, Modernizr);
|
||||
})(Drupal, Backbone, Modernizr);
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
namespace Drupal\contextual;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Render\RendererInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
@@ -11,9 +12,32 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
/**
|
||||
* Returns responses for Contextual module routes.
|
||||
*/
|
||||
class ContextualController implements ContainerAwareInterface {
|
||||
class ContextualController implements ContainerInjectionInterface {
|
||||
|
||||
use ContainerAwareTrait;
|
||||
/**
|
||||
* The renderer.
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $render;
|
||||
|
||||
/**
|
||||
* Constructors a new ContextualController
|
||||
*
|
||||
* @param \Drupal\Core\Render\RendererInterface $renderer
|
||||
* The renderer.
|
||||
*/
|
||||
public function __construct(RendererInterface $renderer) {
|
||||
$this->renderer = $renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('renderer')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested rendered contextual links.
|
||||
@@ -38,7 +62,7 @@ class ContextualController implements ContainerAwareInterface {
|
||||
'#type' => 'contextual_links',
|
||||
'#contextual_links' => _contextual_id_to_links($id),
|
||||
];
|
||||
$rendered[$id] = $this->container->get('renderer')->renderRoot($element);
|
||||
$rendered[$id] = $this->renderer->renderRoot($element);
|
||||
}
|
||||
|
||||
return new JsonResponse($rendered);
|
||||
|
||||
@@ -78,7 +78,7 @@ class ContextualLinks extends RenderElement {
|
||||
$class = Html::getClass($class);
|
||||
$links[$class] = [
|
||||
'title' => $item['title'],
|
||||
'url' => Url::fromRoute(isset($item['route_name']) ? $item['route_name'] : '', isset($item['route_parameters']) ? $item['route_parameters'] : []),
|
||||
'url' => Url::fromRoute(isset($item['route_name']) ? $item['route_name'] : '', isset($item['route_parameters']) ? $item['route_parameters'] : [], $item['localized_options']),
|
||||
];
|
||||
}
|
||||
$element['#links'] = $links;
|
||||
|
||||
@@ -138,7 +138,7 @@ class ContextualLinks extends FieldPluginBase {
|
||||
'#type' => 'contextual_links_placeholder',
|
||||
'#id' => _contextual_links_to_id($contextual_links),
|
||||
];
|
||||
return drupal_render($element);
|
||||
return \Drupal::service('renderer')->render($element);
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
@@ -148,6 +148,6 @@ class ContextualLinks extends FieldPluginBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() { }
|
||||
public function query() {}
|
||||
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class ContextualDynamicContextTest extends WebTestBase {
|
||||
// Get a page where contextual links are directly rendered.
|
||||
$this->drupalGet(Url::fromRoute('menu_test.contextual_test'));
|
||||
$this->assertEscaped("<script>alert('Welcome to the jungle!')</script>");
|
||||
$this->assertLink('Edit menu - contextual');
|
||||
$this->assertRaw('<li class="menu-testcontextual-hidden-manage-edit"><a href="' . base_path() . 'menu-test-contextual/1/edit" class="use-ajax" data-dialog-type="modal" data-is-something>Edit menu - contextual</a></li>');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
name: 'Contextual Test'
|
||||
type: module
|
||||
description: 'Provides test contextual links.'
|
||||
package: Testing
|
||||
# version: VERSION
|
||||
# core: 8.x
|
||||
dependencies:
|
||||
- contextual
|
||||
|
||||
# Information added by Drupal.org packaging script on 2017-11-03
|
||||
version: '8.4.2'
|
||||
core: '8.x'
|
||||
project: 'drupal'
|
||||
datestamp: 1509719929
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
contextual_test:
|
||||
title: 'Test Link'
|
||||
route_name: 'contextual_test'
|
||||
group: 'contextual_test'
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides test contextual link on blocks.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_block_view_alter().
|
||||
*/
|
||||
function contextual_test_block_view_alter(array &$build, BlockPluginInterface $block) {
|
||||
$build['#contextual_links']['contextual_test'] = [
|
||||
'route_parameters' => [],
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
contextual_test:
|
||||
path: '/contextual-tests'
|
||||
defaults:
|
||||
_controller: '\Drupal\contextual_test\Controller\TestController::render'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\contextual_test\Controller;
|
||||
|
||||
/**
|
||||
* Test controller to provide a callback for the contextual link.
|
||||
*/
|
||||
class TestController {
|
||||
|
||||
/**
|
||||
* Callback for the contextual link.
|
||||
*
|
||||
* @return array
|
||||
* Render array.
|
||||
*/
|
||||
public function render() {
|
||||
return [
|
||||
'#type' => 'markup',
|
||||
'#markup' => 'Everything is contextual!',
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\contextual\FunctionalJavascript;
|
||||
|
||||
/**
|
||||
* Functions for testing contextual links.
|
||||
*/
|
||||
trait ContextualLinkClickTrait {
|
||||
|
||||
/**
|
||||
* Clicks a contextual link.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector for the element that contains the contextual link.
|
||||
* @param string $link_locator
|
||||
* The link id, title, or text.
|
||||
* @param bool $force_visible
|
||||
* If true then the button will be forced to visible so it can be clicked.
|
||||
*/
|
||||
protected function clickContextualLink($selector, $link_locator, $force_visible = TRUE) {
|
||||
if ($force_visible) {
|
||||
$this->toggleContextualTriggerVisibility($selector);
|
||||
}
|
||||
|
||||
$element = $this->getSession()->getPage()->find('css', $selector);
|
||||
$element->find('css', '.contextual button')->press();
|
||||
$element->findLink($link_locator)->click();
|
||||
|
||||
if ($force_visible) {
|
||||
$this->toggleContextualTriggerVisibility($selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility of a contextual trigger.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector for the element that contains the contextual link.
|
||||
*/
|
||||
protected function toggleContextualTriggerVisibility($selector) {
|
||||
// Hovering over the element itself with should be enough, but does not
|
||||
// work. Manually remove the visually-hidden class.
|
||||
$this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').toggleClass('visually-hidden');");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,8 @@ use Drupal\user\Entity\Role;
|
||||
*/
|
||||
class ContextualLinksTest extends JavascriptTestBase {
|
||||
|
||||
use ContextualLinkClickTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -23,6 +25,7 @@ class ContextualLinksTest extends JavascriptTestBase {
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->createUser(['access contextual links']));
|
||||
$this->placeBlock('system_branding_block', ['id' => 'branding']);
|
||||
}
|
||||
|
||||
@@ -30,10 +33,6 @@ class ContextualLinksTest extends JavascriptTestBase {
|
||||
* Tests the visibility of contextual links.
|
||||
*/
|
||||
public function testContextualLinksVisibility() {
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access contextual links'
|
||||
]));
|
||||
|
||||
$this->drupalGet('user');
|
||||
$contextualLinks = $this->assertSession()->waitForElement('css', '.contextual button');
|
||||
$this->assertEmpty($contextualLinks);
|
||||
@@ -59,4 +58,27 @@ class ContextualLinksTest extends JavascriptTestBase {
|
||||
$this->assertNotEmpty($contextualLinks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test clicking contextual links.
|
||||
*/
|
||||
public function testContextualLinksClick() {
|
||||
$this->container->get('module_installer')->install(['contextual_test']);
|
||||
// Test clicking contextual link without toolbar.
|
||||
$this->drupalGet('user');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->clickContextualLink('#block-branding', 'Test Link');
|
||||
$this->assertSession()->pageTextContains('Everything is contextual!');
|
||||
|
||||
// Test clicking contextual link with toolbar.
|
||||
$this->container->get('module_installer')->install(['toolbar']);
|
||||
$this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['access toolbar']);
|
||||
$this->drupalGet('user');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
// Click "Edit" in toolbar to show contextual links.
|
||||
$this->getSession()->getPage()->find('css', '.contextual-toolbar-tab button')->press();
|
||||
$this->clickContextualLink('#block-branding', 'Test Link', FALSE);
|
||||
$this->assertSession()->pageTextContains('Everything is contextual!');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\contextual\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Tests edit mode.
|
||||
*
|
||||
* @group contextual
|
||||
*/
|
||||
class EditModeTest extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* CSS selector for Drupal's announce element.
|
||||
*/
|
||||
const ANNOUNCE_SELECTOR = '#drupal-live-announce';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'block',
|
||||
'user',
|
||||
'system',
|
||||
'breakpoint',
|
||||
'toolbar',
|
||||
'contextual',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->createUser([
|
||||
'administer blocks',
|
||||
'access contextual links',
|
||||
'access toolbar',
|
||||
]));
|
||||
$this->placeBlock('system_powered_by_block', ['id' => 'powered']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Drupal.announce messages appear.
|
||||
*/
|
||||
public function testAnnounceEditMode() {
|
||||
$web_assert = $this->assertSession();
|
||||
$this->drupalGet('user');
|
||||
|
||||
// After the page loaded we need to additionally wait until the settings
|
||||
// tray Ajax activity is done.
|
||||
$web_assert->assertWaitOnAjaxRequest();
|
||||
|
||||
// Enable edit mode.
|
||||
$this->pressToolbarEditButton();
|
||||
$this->assertAnnounceEditMode();
|
||||
// Disable edit mode.
|
||||
$this->pressToolbarEditButton();
|
||||
$this->assertAnnounceLeaveEditMode();
|
||||
// Enable edit mode again.
|
||||
$this->pressToolbarEditButton();
|
||||
// Finally assert that the 'edit mode enabled' announcement is still correct
|
||||
// after toggling the edit mode at least once.
|
||||
$this->assertAnnounceEditMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Presses the toolbar edit mode.
|
||||
*/
|
||||
protected function pressToolbarEditButton() {
|
||||
$edit_button = $this->getSession()->getPage()->find('css', '#toolbar-bar div.contextual-toolbar-tab button');
|
||||
$edit_button->press();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the correct message was announced when entering edit mode.
|
||||
*/
|
||||
protected function assertAnnounceEditMode() {
|
||||
$web_assert = $this->assertSession();
|
||||
// Wait for contextual trigger button.
|
||||
$web_assert->waitForElementVisible('css', '.contextual trigger');
|
||||
$web_assert->elementContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is constrained to a set of');
|
||||
$web_assert->elementNotContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is no longer constrained by the Contextual module.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the correct message was announced when leaving edit mode.
|
||||
*/
|
||||
protected function assertAnnounceLeaveEditMode() {
|
||||
$web_assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
// Wait till all the contextual links are hidden.
|
||||
$page->waitFor(1, function () use ($page, $web_assert) {
|
||||
return empty($page->find('css', '.contextual .trigger.visually-hidden'));
|
||||
});
|
||||
$web_assert->elementContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is no longer constrained by the Contextual module.');
|
||||
$web_assert->elementNotContains('css', static::ANNOUNCE_SELECTOR, 'Tabbing is constrained to a set of');
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user