Initial Drupal 11 with DDEV setup
This commit is contained in:
537
web/core/modules/contextual/js/contextual.js
Normal file
537
web/core/modules/contextual/js/contextual.js
Normal file
@ -0,0 +1,537 @@
|
||||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Contextual module.
|
||||
*/
|
||||
|
||||
(function ($, Drupal, drupalSettings, 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;
|
||||
if (cachedPermissionsHash !== permissionsHash) {
|
||||
if (typeof permissionsHash === 'string') {
|
||||
Object.keys(storage).forEach((key) => {
|
||||
if (key.startsWith('Drupal.contextual.')) {
|
||||
storage.removeItem(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
storage.setItem('Drupal.contextual.permissionsHash', permissionsHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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[0].style.top =
|
||||
$nestedContextual.position().top + height;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// 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(
|
||||
Drupal.url(drupalSettings.path.currentPath + window.location.search),
|
||||
)}`;
|
||||
$contextual.find('.contextual-links a').each(function () {
|
||||
const url = this.getAttribute('href');
|
||||
const glue = url.includes('?') ? '&' : '?';
|
||||
this.setAttribute('href', url + glue + destination);
|
||||
});
|
||||
let title = '';
|
||||
const $regionHeading = $region.find('h2');
|
||||
if ($regionHeading.length) {
|
||||
title = $regionHeading[0].textContent.trim();
|
||||
}
|
||||
options.title = title;
|
||||
const contextualModelView = new Drupal.contextual.ContextualModelView(
|
||||
$contextual,
|
||||
$region,
|
||||
options,
|
||||
);
|
||||
contextual.instances.push(contextualModelView);
|
||||
// Fix visual collisions between contextual link triggers.
|
||||
adjustIfNestedAndOverlapping($contextual);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = $(
|
||||
once('contextual-render', '[data-contextual-id]', context),
|
||||
);
|
||||
if ($placeholders.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect the IDs for all contextual links placeholders.
|
||||
const ids = [];
|
||||
$placeholders.each(function () {
|
||||
ids.push({
|
||||
id: $(this).attr('data-contextual-id'),
|
||||
token: $(this).attr('data-contextual-token'),
|
||||
});
|
||||
});
|
||||
|
||||
const uncachedIDs = [];
|
||||
const uncachedTokens = [];
|
||||
ids.forEach((contextualID) => {
|
||||
const html = storage.getItem(`Drupal.contextual.${contextualID.id}`);
|
||||
if (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 collection
|
||||
// Drupal.contextual.collection.
|
||||
window.setTimeout(() => {
|
||||
initContextual(
|
||||
$context
|
||||
.find(`[data-contextual-id="${contextualID.id}"]:empty`)
|
||||
.eq(0),
|
||||
html,
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
uncachedIDs.push(contextualID.id);
|
||||
uncachedTokens.push(contextualID.token);
|
||||
});
|
||||
|
||||
// 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, 'tokens[]': uncachedTokens },
|
||||
dataType: 'json',
|
||||
success(results) {
|
||||
Object.entries(results).forEach(([contextualID, html]) => {
|
||||
// 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
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Drupal.contextual = {
|
||||
/**
|
||||
* The {@link Drupal.contextual.View} instances associated with each list
|
||||
* element of contextual links.
|
||||
*
|
||||
* @type {Array}
|
||||
*
|
||||
* @deprecated in drupal:9.4.0 and is removed from drupal:12.0.0. There is no
|
||||
* replacement.
|
||||
*/
|
||||
views: [],
|
||||
|
||||
/**
|
||||
* The {@link Drupal.contextual.RegionView} instances associated with each
|
||||
* contextual region element.
|
||||
*
|
||||
* @type {Array}
|
||||
*
|
||||
* @deprecated in drupal:9.4.0 and is removed from drupal:12.0.0. There is no
|
||||
* replacement.
|
||||
*/
|
||||
regionViews: [],
|
||||
instances: new Proxy([], {
|
||||
set: function set(obj, prop, value) {
|
||||
obj[prop] = value;
|
||||
window.dispatchEvent(new Event('contextual-instances-added'));
|
||||
return true;
|
||||
},
|
||||
deleteProperty(target, prop) {
|
||||
if (prop in target) {
|
||||
delete target[prop];
|
||||
window.dispatchEvent(new Event('contextual-instances-removed'));
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Models the state of a contextual link's trigger, list & region.
|
||||
*/
|
||||
ContextualModelView: class {
|
||||
constructor($contextual, $region, options) {
|
||||
this.title = options.title || '';
|
||||
this.regionIsHovered = false;
|
||||
this._hasFocus = false;
|
||||
this._isOpen = false;
|
||||
this._isLocked = false;
|
||||
this.strings = options.strings;
|
||||
this.timer = NaN;
|
||||
this.modelId = btoa(Math.random()).substring(0, 12);
|
||||
this.$region = $region;
|
||||
this.$contextual = $contextual;
|
||||
|
||||
if (!document.body.classList.contains('touchevents')) {
|
||||
this.$region.on({
|
||||
mouseenter: () => {
|
||||
this.regionIsHovered = true;
|
||||
},
|
||||
mouseleave: () => {
|
||||
this.close().blur();
|
||||
this.regionIsHovered = false;
|
||||
},
|
||||
'mouseleave mouseenter': () => this.render(),
|
||||
});
|
||||
this.$contextual.on('mouseenter', () => {
|
||||
this.focus();
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
|
||||
this.$contextual.on(
|
||||
{
|
||||
click: () => {
|
||||
this.toggleOpen();
|
||||
},
|
||||
touchend: () => {
|
||||
Drupal.contextual.ContextualModelView.touchEndToClick();
|
||||
},
|
||||
focus: () => {
|
||||
this.focus();
|
||||
},
|
||||
blur: () => {
|
||||
this.blur();
|
||||
},
|
||||
'click blur touchend focus': () => this.render(),
|
||||
},
|
||||
'.trigger',
|
||||
);
|
||||
|
||||
this.$contextual.on(
|
||||
{
|
||||
click: () => {
|
||||
this.close().blur();
|
||||
},
|
||||
touchend: (event) => {
|
||||
Drupal.contextual.ContextualModelView.touchEndToClick(event);
|
||||
},
|
||||
focus: () => {
|
||||
this.focus();
|
||||
},
|
||||
blur: () => {
|
||||
this.waitCloseThenBlur();
|
||||
},
|
||||
'click blur touchend focus': () => this.render(),
|
||||
},
|
||||
'.contextual-links a',
|
||||
);
|
||||
|
||||
this.render();
|
||||
|
||||
// Let other JavaScript react to the adding of a new contextual link.
|
||||
$(document).trigger('drupalContextualLinkAdded', {
|
||||
$el: $contextual,
|
||||
$region,
|
||||
model: this,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the rendered representation of the current contextual links.
|
||||
*/
|
||||
render() {
|
||||
const { isOpen } = this;
|
||||
const isVisible = this.isLocked || this.regionIsHovered || isOpen;
|
||||
this.$region.toggleClass('focus', this.hasFocus);
|
||||
this.$contextual
|
||||
.toggleClass('open', isOpen)
|
||||
// Update the visibility of the trigger.
|
||||
.find('.trigger')
|
||||
.toggleClass('visually-hidden', !isVisible);
|
||||
|
||||
this.$contextual.find('.contextual-links').prop('hidden', !isOpen);
|
||||
const trigger = this.$contextual.find('.trigger').get(0);
|
||||
trigger.textContent = Drupal.t('@action @title configuration options', {
|
||||
'@action': !isOpen ? this.strings.open : this.strings.close,
|
||||
'@title': this.title,
|
||||
});
|
||||
trigger.setAttribute('aria-pressed', isOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents delay and simulated mouse events.
|
||||
*
|
||||
* @param {jQuery.Event} event the touch end event.
|
||||
*/
|
||||
static touchEndToClick(event) {
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a timeout to allow a user to tab between the trigger and the
|
||||
* contextual links without the menu dismissing.
|
||||
*/
|
||||
waitCloseThenBlur() {
|
||||
this.timer = window.setTimeout(() => {
|
||||
this.isOpen = false;
|
||||
this.hasFocus = false;
|
||||
this.render();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens or closes the contextual link.
|
||||
*
|
||||
* If it is opened, then also give focus.
|
||||
*
|
||||
* @return {Drupal.contextual.ContextualModelView}
|
||||
* The current contextual model view.
|
||||
*/
|
||||
toggleOpen() {
|
||||
const newIsOpen = !this.isOpen;
|
||||
this.isOpen = newIsOpen;
|
||||
if (newIsOpen) {
|
||||
this.focus();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives focus to this contextual link.
|
||||
*
|
||||
* Also closes + removes focus from every other contextual link.
|
||||
*
|
||||
* @return {Drupal.contextual.ContextualModelView}
|
||||
* The current contextual model view.
|
||||
*/
|
||||
focus() {
|
||||
const { modelId } = this;
|
||||
Drupal.contextual.instances.forEach((model) => {
|
||||
if (model.modelId !== modelId) {
|
||||
model.close().blur();
|
||||
}
|
||||
});
|
||||
window.clearTimeout(this.timer);
|
||||
this.hasFocus = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes focus from this contextual link, unless it is open.
|
||||
*
|
||||
* @return {Drupal.contextual.ContextualModelView}
|
||||
* The current contextual model view.
|
||||
*/
|
||||
blur() {
|
||||
if (!this.isOpen) {
|
||||
this.hasFocus = false;
|
||||
}
|
||||
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.ContextualModelView}
|
||||
* The current contextual model view.
|
||||
*/
|
||||
close() {
|
||||
this.isOpen = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current focus state.
|
||||
*
|
||||
* @return {boolean} the focus state.
|
||||
*/
|
||||
get hasFocus() {
|
||||
return this._hasFocus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current focus state.
|
||||
*
|
||||
* @param {boolean} value - new focus state
|
||||
*/
|
||||
set hasFocus(value) {
|
||||
this._hasFocus = value;
|
||||
this.$region.toggleClass('focus', this._hasFocus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current open state.
|
||||
*
|
||||
* @return {boolean} the open state.
|
||||
*/
|
||||
get isOpen() {
|
||||
return this._isOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current open state.
|
||||
*
|
||||
* @param {boolean} value - new open state
|
||||
*/
|
||||
set isOpen(value) {
|
||||
this._isOpen = value;
|
||||
// Nested contextual region handling: hide any nested contextual triggers.
|
||||
this.$region
|
||||
.closest('.contextual-region')
|
||||
.find('.contextual .trigger:not(:first)')
|
||||
.toggle(!this.isOpen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current locked state.
|
||||
*
|
||||
* @return {boolean} the locked state.
|
||||
*/
|
||||
get isLocked() {
|
||||
return this._isLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current locked state.
|
||||
*
|
||||
* @param {boolean} value - new locked state
|
||||
*/
|
||||
set isLocked(value) {
|
||||
if (value !== this._isLocked) {
|
||||
this._isLocked = value;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 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>';
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind Ajax contextual links when added.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The `drupalContextualLinkAdded` event.
|
||||
* @param {object} data
|
||||
* An object containing the data relevant to the event.
|
||||
*
|
||||
* @listens event:drupalContextualLinkAdded
|
||||
*/
|
||||
$(document).on('drupalContextualLinkAdded', (event, data) => {
|
||||
Drupal.ajax.bindAjaxLinks(data.$el[0]);
|
||||
});
|
||||
})(jQuery, Drupal, drupalSettings, window.JSON, window.sessionStorage);
|
||||
70
web/core/modules/contextual/js/contextual.toolbar.js
Normal file
70
web/core/modules/contextual/js/contextual.toolbar.js
Normal file
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* @file
|
||||
* Attaches behaviors for the Contextual module's edit toolbar tab.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
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.instances) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { contextualToolbar } = Drupal;
|
||||
|
||||
const viewOptions = {
|
||||
el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
|
||||
strings,
|
||||
};
|
||||
contextualToolbar.model = new Drupal.contextual.ContextualToolbarModelView(
|
||||
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 (once('contextualToolbar-init', 'body').length) {
|
||||
initContextualToolbar(context);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Namespace for the contextual toolbar.
|
||||
*
|
||||
* @namespace
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Drupal.contextualToolbar = {
|
||||
/**
|
||||
* The {@link Drupal.contextual.ContextualToolbarModelView} instance.
|
||||
*
|
||||
* @type {?Drupal.contextual.ContextualToolbarModelView}
|
||||
*/
|
||||
model: null,
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
@ -0,0 +1,175 @@
|
||||
(($, Drupal) => {
|
||||
Drupal.contextual.ContextualToolbarModelView = class {
|
||||
constructor(options) {
|
||||
this.strings = options.strings;
|
||||
this.isVisible = false;
|
||||
this._contextualCount = Drupal.contextual.instances.count;
|
||||
this.tabbingContext = null;
|
||||
this._isViewing =
|
||||
localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false';
|
||||
this.$el = options.el;
|
||||
|
||||
window.addEventListener('contextual-instances-added', () =>
|
||||
this.lockNewContextualLinks(),
|
||||
);
|
||||
window.addEventListener('contextual-instances-removed', () => {
|
||||
this.contextualCount = Drupal.contextual.instances.count;
|
||||
});
|
||||
|
||||
this.$el.on({
|
||||
click: () => {
|
||||
this.isViewing = !this.isViewing;
|
||||
},
|
||||
touchend: (event) => {
|
||||
event.preventDefault();
|
||||
event.target.click();
|
||||
},
|
||||
'click touchend': () => this.render(),
|
||||
});
|
||||
|
||||
$(document).on('keyup', (event) => this.onKeypress(event));
|
||||
this.manageTabbing(true);
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 announcement about
|
||||
// tabbing constraints can be raised if edit mode is enabled when the page
|
||||
// is loaded.
|
||||
if (!this.announcedOnce && event.keyCode === 9 && !this.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.isViewing = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the rendered representation of the current toolbar model view.
|
||||
*/
|
||||
render() {
|
||||
this.$el[0].classList.toggle('hidden', this.isVisible);
|
||||
const button = this.$el[0].querySelector('button');
|
||||
button.classList.toggle('is-active', !this.isViewing);
|
||||
button.setAttribute('aria-pressed', !this.isViewing);
|
||||
this.contextualCount = Drupal.contextual.instances.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically updates visibility of the view/edit mode toggle.
|
||||
*/
|
||||
updateVisibility() {
|
||||
this.isVisible = this.get('contextualCount') > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock newly added contextual links if edit mode is enabled.
|
||||
*/
|
||||
lockNewContextualLinks() {
|
||||
Drupal.contextual.instances.forEach((model) => {
|
||||
model.isLocked = !this.isViewing;
|
||||
});
|
||||
this.contextualCount = Drupal.contextual.instances.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits tabbing to the contextual links and edit mode toolbar tab.
|
||||
*
|
||||
* @param {boolean} init - true to initialize tabbing.
|
||||
*/
|
||||
manageTabbing(init = false) {
|
||||
let { tabbingContext } = this;
|
||||
// Always release an existing tabbing context.
|
||||
if (tabbingContext && !init) {
|
||||
// Only announce release when the context was active.
|
||||
if (tabbingContext.active) {
|
||||
Drupal.announce(this.strings.tabbingReleased);
|
||||
}
|
||||
tabbingContext.release();
|
||||
this.tabbingContext = null;
|
||||
}
|
||||
// Create a new tabbing context when edit mode is enabled.
|
||||
if (!this.isViewing) {
|
||||
tabbingContext = Drupal.tabbingManager.constrain(
|
||||
$('.contextual-toolbar-tab, .contextual'),
|
||||
);
|
||||
this.tabbingContext = tabbingContext;
|
||||
this.announceTabbingConstraint();
|
||||
this.announcedOnce = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Announces the current tabbing constraint.
|
||||
*/
|
||||
announceTabbingConstraint() {
|
||||
const { strings } = this;
|
||||
Drupal.announce(
|
||||
Drupal.formatString(strings.tabbingConstrained, {
|
||||
'@contextualsCount': Drupal.formatPlural(
|
||||
Drupal.contextual.instances.length,
|
||||
'@count contextual link',
|
||||
'@count contextual links',
|
||||
),
|
||||
}) + strings.pressEsc,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current viewing state.
|
||||
*
|
||||
* @return {boolean} the viewing state.
|
||||
*/
|
||||
get isViewing() {
|
||||
return this._isViewing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current viewing state.
|
||||
*
|
||||
* @param {boolean} value - new viewing state
|
||||
*/
|
||||
set isViewing(value) {
|
||||
this._isViewing = value;
|
||||
localStorage[!value ? 'setItem' : 'removeItem'](
|
||||
'Drupal.contextualToolbar.isViewing',
|
||||
'false',
|
||||
);
|
||||
|
||||
Drupal.contextual.instances.forEach((model) => {
|
||||
model.isLocked = !this.isViewing;
|
||||
});
|
||||
this.manageTabbing();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current contextual links count.
|
||||
*
|
||||
* @return {integer} the current contextual links count.
|
||||
*/
|
||||
get contextualCount() {
|
||||
return this._contextualCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current contextual links count.
|
||||
*
|
||||
* @param {integer} value - new contextual links count.
|
||||
*/
|
||||
set contextualCount(value) {
|
||||
if (value !== this._contextualCount) {
|
||||
this._contextualCount = value;
|
||||
this.updateVisibility();
|
||||
}
|
||||
}
|
||||
};
|
||||
})(jQuery, Drupal);
|
||||
Reference in New Issue
Block a user