diff --git a/core/.eslintrc.passing.json b/core/.eslintrc.passing.json index 3f42d326c6c0a5e92a528ee42a7817a170bb280d..13fcf79c77c3b4964d621d7812623176f6e4177c 100644 --- a/core/.eslintrc.passing.json +++ b/core/.eslintrc.passing.json @@ -1,7 +1,6 @@ { "extends": "./.eslintrc.json", "rules": { - "no-use-before-define": "off", "no-shadow": "off", "no-new": "off", "no-continue": "off", diff --git a/core/misc/autocomplete.es6.js b/core/misc/autocomplete.es6.js index 9beaf02af3c1825187ab90d35c4e750c1adfb74a..79ed666ee115d3908168ee5197db4f8d143f8f85 100644 --- a/core/misc/autocomplete.es6.js +++ b/core/misc/autocomplete.es6.js @@ -122,6 +122,9 @@ response(suggestions); } + // Get the desired term and construct the autocomplete URL for it. + const term = autocomplete.extractLastTerm(request.term); + /** * Transforms the data object into an array and update autocomplete results. * @@ -135,9 +138,6 @@ showSuggestions(data); } - // Get the desired term and construct the autocomplete URL for it. - const term = autocomplete.extractLastTerm(request.term); - // Check if the term is already cached. if (autocomplete.cache[elementId].hasOwnProperty(term)) { showSuggestions(autocomplete.cache[elementId][term]); diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js index aa3311d08d3d37d185a204abf0a1499538a1f4e5..52ddfd2ab4825329cc5fbcc77e30d0c4df924bfb 100644 --- a/core/misc/autocomplete.js +++ b/core/misc/autocomplete.js @@ -73,14 +73,14 @@ response(suggestions); } + var term = autocomplete.extractLastTerm(request.term); + function sourceCallbackHandler(data) { autocomplete.cache[elementId][term] = data; showSuggestions(data); } - var term = autocomplete.extractLastTerm(request.term); - if (autocomplete.cache[elementId].hasOwnProperty(term)) { showSuggestions(autocomplete.cache[elementId][term]); } else { diff --git a/core/misc/dialog/dialog.es6.js b/core/misc/dialog/dialog.es6.js index 545afc34ca79dd0f96ed32adc6c8c9ba7ddaa93d..26b9107bd988851e0737005b80cb5d67287c312c 100644 --- a/core/misc/dialog/dialog.es6.js +++ b/core/misc/dialog/dialog.es6.js @@ -65,13 +65,6 @@ const dialog = { open: false, returnValue: undef, - show() { - openDialog({ modal: false }); - }, - showModal() { - openDialog({ modal: true }); - }, - close: closeDialog, }; function openDialog(settings) { @@ -91,6 +84,14 @@ $(window).trigger('dialog:afterclose', [dialog, $element]); } + dialog.show = () => { + openDialog({ modal: false }); + }; + dialog.showModal = () => { + openDialog({ modal: true }); + }; + dialog.close = closeDialog; + return dialog; }; }(jQuery, Drupal, drupalSettings)); diff --git a/core/misc/dialog/dialog.js b/core/misc/dialog/dialog.js index f25c0683e34e9e9ee0ceb943284a353687cd928a..9bd8b554e4e1e590fcf46623779be18f03fd0207 100644 --- a/core/misc/dialog/dialog.js +++ b/core/misc/dialog/dialog.js @@ -23,15 +23,7 @@ var $element = $(element); var dialog = { open: false, - returnValue: undef, - show: function show() { - openDialog({ modal: false }); - }, - showModal: function showModal() { - openDialog({ modal: true }); - }, - - close: closeDialog + returnValue: undef }; function openDialog(settings) { @@ -51,6 +43,14 @@ $(window).trigger('dialog:afterclose', [dialog, $element]); } + dialog.show = function () { + openDialog({ modal: false }); + }; + dialog.showModal = function () { + openDialog({ modal: true }); + }; + dialog.close = closeDialog; + return dialog; }; })(jQuery, Drupal, drupalSettings); \ No newline at end of file diff --git a/core/misc/dialog/dialog.position.es6.js b/core/misc/dialog/dialog.position.es6.js index d2d35a326339c96f24d6128b904c40132ad8b507..939fbd849bbe2627c0d184dbf1c46c36decb0d34 100644 --- a/core/misc/dialog/dialog.position.es6.js +++ b/core/misc/dialog/dialog.position.es6.js @@ -13,6 +13,31 @@ // autoResize option will turn off resizable and draggable. drupalSettings.dialog = $.extend({ autoResize: true, maxHeight: '95%' }, drupalSettings.dialog); + /** + * Position the dialog's center at the center of displace.offsets boundaries. + * + * @function Drupal.dialog~resetPosition + * + * @param {object} options + * Options object. + * + * @return {object} + * Altered options object. + */ + function resetPosition(options) { + const offsets = displace.offsets; + const left = offsets.left - offsets.right; + const top = offsets.top - offsets.bottom; + + const leftString = `${(left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2))}px`; + const topString = `${(top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2))}px`; + options.position = { + my: `center${left !== 0 ? leftString : ''} center${top !== 0 ? topString : ''}`, + of: window, + }; + return options; + } + /** * Resets the current options for positioning. * @@ -61,31 +86,6 @@ .trigger('dialogContentResize'); } - /** - * Position the dialog's center at the center of displace.offsets boundaries. - * - * @function Drupal.dialog~resetPosition - * - * @param {object} options - * Options object. - * - * @return {object} - * Altered options object. - */ - function resetPosition(options) { - const offsets = displace.offsets; - const left = offsets.left - offsets.right; - const top = offsets.top - offsets.bottom; - - const leftString = `${(left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2))}px`; - const topString = `${(top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2))}px`; - options.position = { - my: `center${left !== 0 ? leftString : ''} center${top !== 0 ? topString : ''}`, - of: window, - }; - return options; - } - $(window).on({ 'dialog:aftercreate': function (event, dialog, $element, settings) { const autoResize = debounce(resetSize, 20); diff --git a/core/misc/dialog/dialog.position.js b/core/misc/dialog/dialog.position.js index 7ff530f580e67947b8fa197a52560768e22bc608..843bf3b249ef95b78c5b720bdf881a874ae69787 100644 --- a/core/misc/dialog/dialog.position.js +++ b/core/misc/dialog/dialog.position.js @@ -8,6 +8,20 @@ (function ($, Drupal, drupalSettings, debounce, displace) { drupalSettings.dialog = $.extend({ autoResize: true, maxHeight: '95%' }, drupalSettings.dialog); + function resetPosition(options) { + var offsets = displace.offsets; + var left = offsets.left - offsets.right; + var top = offsets.top - offsets.bottom; + + var leftString = (left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2)) + 'px'; + var topString = (top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2)) + 'px'; + options.position = { + my: 'center' + (left !== 0 ? leftString : '') + ' center' + (top !== 0 ? topString : ''), + of: window + }; + return options; + } + function resetSize(event) { var positionOptions = ['width', 'height', 'minWidth', 'minHeight', 'maxHeight', 'maxWidth', 'position']; var adjustedOptions = {}; @@ -37,20 +51,6 @@ event.data.$element.dialog('option', adjustedOptions).trigger('dialogContentResize'); } - function resetPosition(options) { - var offsets = displace.offsets; - var left = offsets.left - offsets.right; - var top = offsets.top - offsets.bottom; - - var leftString = (left > 0 ? '+' : '-') + Math.abs(Math.round(left / 2)) + 'px'; - var topString = (top > 0 ? '+' : '-') + Math.abs(Math.round(top / 2)) + 'px'; - options.position = { - my: 'center' + (left !== 0 ? leftString : '') + ' center' + (top !== 0 ? topString : ''), - of: window - }; - return options; - } - $(window).on({ 'dialog:aftercreate': function dialogAftercreate(event, dialog, $element, settings) { var autoResize = debounce(resetSize, 20); diff --git a/core/misc/displace.es6.js b/core/misc/displace.es6.js index 6ac09f4e6aae63778a7e40ef991f0d7fd4e21c4d..4a87edc3d12245746a8c3d75c01c5a164b781d3e 100644 --- a/core/misc/displace.es6.js +++ b/core/misc/displace.es6.js @@ -38,64 +38,56 @@ }; /** - * Registers a resize handler on the window. - * - * @type {Drupal~behavior} - */ - Drupal.behaviors.drupalDisplace = { - attach() { - // Mark this behavior as processed on the first pass. - if (this.displaceProcessed) { - return; - } - this.displaceProcessed = true; - - $(window).on('resize.drupalDisplace', debounce(displace, 200)); - }, - }; - - /** - * Informs listeners of the current offset dimensions. - * - * @function Drupal.displace - * - * @prop {Drupal~displaceOffset} offsets + * Calculates displacement for element based on its dimensions and placement. * - * @param {bool} [broadcast] - * When true or undefined, causes the recalculated offsets values to be - * broadcast to listeners. + * @param {HTMLElement} el + * The jQuery element whose dimensions and placement will be measured. * - * @return {Drupal~displaceOffset} - * An object whose keys are the for sides an element -- top, right, bottom - * and left. The value of each key is the viewport displacement distance for - * that edge. + * @param {string} edge + * The name of the edge of the viewport that the element is associated + * with. * - * @fires event:drupalViewportOffsetChange + * @return {number} + * The viewport displacement distance for the requested edge. */ - function displace(broadcast) { - offsets = calculateOffsets(); - Drupal.displace.offsets = offsets; - if (typeof broadcast === 'undefined' || broadcast) { - $(document).trigger('drupalViewportOffsetChange', offsets); - } - return offsets; - } + function getRawOffset(el, edge) { + const $el = $(el); + const documentElement = document.documentElement; + let displacement = 0; + const horizontal = (edge === 'left' || edge === 'right'); + // Get the offset of the element itself. + let placement = $el.offset()[horizontal ? 'left' : 'top']; + // Subtract scroll distance from placement to get the distance + // to the edge of the viewport. + placement -= window[`scroll${horizontal ? 'X' : 'Y'}`] || document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] || 0; + // Find the displacement value according to the edge. + switch (edge) { + // Left and top elements displace as a sum of their own offset value + // plus their size. + case 'top': + // Total displacement is the sum of the elements placement and size. + displacement = placement + $el.outerHeight(); + break; - /** - * Determines the viewport offsets. - * - * @return {Drupal~displaceOffset} - * An object whose keys are the for sides an element -- top, right, bottom - * and left. The value of each key is the viewport displacement distance for - * that edge. - */ - function calculateOffsets() { - return { - top: calculateOffset('top'), - right: calculateOffset('right'), - bottom: calculateOffset('bottom'), - left: calculateOffset('left'), - }; + case 'left': + // Total displacement is the sum of the elements placement and size. + displacement = placement + $el.outerWidth(); + break; + + // Right and bottom elements displace according to their left and + // top offset. Their size isn't important. + case 'bottom': + displacement = documentElement.clientHeight - placement; + break; + + case 'right': + displacement = documentElement.clientWidth - placement; + break; + + default: + displacement = 0; + } + return displacement; } /** @@ -142,58 +134,66 @@ } /** - * Calculates displacement for element based on its dimensions and placement. + * Determines the viewport offsets. * - * @param {HTMLElement} el - * The jQuery element whose dimensions and placement will be measured. + * @return {Drupal~displaceOffset} + * An object whose keys are the for sides an element -- top, right, bottom + * and left. The value of each key is the viewport displacement distance for + * that edge. + */ + function calculateOffsets() { + return { + top: calculateOffset('top'), + right: calculateOffset('right'), + bottom: calculateOffset('bottom'), + left: calculateOffset('left'), + }; + } + + /** + * Informs listeners of the current offset dimensions. * - * @param {string} edge - * The name of the edge of the viewport that the element is associated - * with. + * @function Drupal.displace * - * @return {number} - * The viewport displacement distance for the requested edge. + * @prop {Drupal~displaceOffset} offsets + * + * @param {bool} [broadcast] + * When true or undefined, causes the recalculated offsets values to be + * broadcast to listeners. + * + * @return {Drupal~displaceOffset} + * An object whose keys are the for sides an element -- top, right, bottom + * and left. The value of each key is the viewport displacement distance for + * that edge. + * + * @fires event:drupalViewportOffsetChange */ - function getRawOffset(el, edge) { - const $el = $(el); - const documentElement = document.documentElement; - let displacement = 0; - const horizontal = (edge === 'left' || edge === 'right'); - // Get the offset of the element itself. - let placement = $el.offset()[horizontal ? 'left' : 'top']; - // Subtract scroll distance from placement to get the distance - // to the edge of the viewport. - placement -= window[`scroll${horizontal ? 'X' : 'Y'}`] || document.documentElement[`scroll${horizontal ? 'Left' : 'Top'}`] || 0; - // Find the displacement value according to the edge. - switch (edge) { - // Left and top elements displace as a sum of their own offset value - // plus their size. - case 'top': - // Total displacement is the sum of the elements placement and size. - displacement = placement + $el.outerHeight(); - break; - - case 'left': - // Total displacement is the sum of the elements placement and size. - displacement = placement + $el.outerWidth(); - break; - - // Right and bottom elements displace according to their left and - // top offset. Their size isn't important. - case 'bottom': - displacement = documentElement.clientHeight - placement; - break; - - case 'right': - displacement = documentElement.clientWidth - placement; - break; - - default: - displacement = 0; + function displace(broadcast) { + offsets = calculateOffsets(); + Drupal.displace.offsets = offsets; + if (typeof broadcast === 'undefined' || broadcast) { + $(document).trigger('drupalViewportOffsetChange', offsets); } - return displacement; + return offsets; } + /** + * Registers a resize handler on the window. + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.drupalDisplace = { + attach() { + // Mark this behavior as processed on the first pass. + if (this.displaceProcessed) { + return; + } + this.displaceProcessed = true; + + $(window).on('resize.drupalDisplace', debounce(displace, 200)); + }, + }; + /** * Assign the displace function to a property of the Drupal global object. * diff --git a/core/misc/displace.js b/core/misc/displace.js index 777aee478d75a2a7e742bce579d575e183bf1b8c..9192fdb3a1ca46061e969d76c6ed1943d638771f 100644 --- a/core/misc/displace.js +++ b/core/misc/displace.js @@ -13,58 +13,6 @@ left: 0 }; - Drupal.behaviors.drupalDisplace = { - attach: function attach() { - if (this.displaceProcessed) { - return; - } - this.displaceProcessed = true; - - $(window).on('resize.drupalDisplace', debounce(displace, 200)); - } - }; - - function displace(broadcast) { - offsets = calculateOffsets(); - Drupal.displace.offsets = offsets; - if (typeof broadcast === 'undefined' || broadcast) { - $(document).trigger('drupalViewportOffsetChange', offsets); - } - return offsets; - } - - function calculateOffsets() { - return { - top: calculateOffset('top'), - right: calculateOffset('right'), - bottom: calculateOffset('bottom'), - left: calculateOffset('left') - }; - } - - function calculateOffset(edge) { - var edgeOffset = 0; - var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']'); - var n = displacingElements.length; - for (var i = 0; i < n; i++) { - var el = displacingElements[i]; - - if (el.style.display === 'none') { - continue; - } - - var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10); - - if (isNaN(displacement)) { - displacement = getRawOffset(el, edge); - } - - edgeOffset = Math.max(edgeOffset, displacement); - } - - return edgeOffset; - } - function getRawOffset(el, edge) { var $el = $(el); var documentElement = document.documentElement; @@ -98,6 +46,58 @@ return displacement; } + function calculateOffset(edge) { + var edgeOffset = 0; + var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']'); + var n = displacingElements.length; + for (var i = 0; i < n; i++) { + var el = displacingElements[i]; + + if (el.style.display === 'none') { + continue; + } + + var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10); + + if (isNaN(displacement)) { + displacement = getRawOffset(el, edge); + } + + edgeOffset = Math.max(edgeOffset, displacement); + } + + return edgeOffset; + } + + function calculateOffsets() { + return { + top: calculateOffset('top'), + right: calculateOffset('right'), + bottom: calculateOffset('bottom'), + left: calculateOffset('left') + }; + } + + function displace(broadcast) { + offsets = calculateOffsets(); + Drupal.displace.offsets = offsets; + if (typeof broadcast === 'undefined' || broadcast) { + $(document).trigger('drupalViewportOffsetChange', offsets); + } + return offsets; + } + + Drupal.behaviors.drupalDisplace = { + attach: function attach() { + if (this.displaceProcessed) { + return; + } + this.displaceProcessed = true; + + $(window).on('resize.drupalDisplace', debounce(displace, 200)); + } + }; + Drupal.displace = displace; $.extend(Drupal.displace, { offsets: offsets, diff --git a/core/misc/dropbutton/dropbutton.es6.js b/core/misc/dropbutton/dropbutton.es6.js index 6fe2b67db7be68849fec587c0b814f9367baea6a..73ff363feb4eb3d1818269c4fab880fc692384d5 100644 --- a/core/misc/dropbutton/dropbutton.es6.js +++ b/core/misc/dropbutton/dropbutton.es6.js @@ -4,45 +4,6 @@ */ (function ($, Drupal) { - /** - * Process elements with the .dropbutton class on page load. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches dropButton behaviors. - */ - Drupal.behaviors.dropButton = { - attach(context, settings) { - const $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton'); - if ($dropbuttons.length) { - // Adds the delegated handler that will toggle dropdowns on click. - const $body = $('body').once('dropbutton-click'); - if ($body.length) { - $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler); - } - // Initialize all buttons. - const il = $dropbuttons.length; - for (let i = 0; i < il; i++) { - DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton)); - } - } - }, - }; - - /** - * Delegated callback for opening and closing dropbutton secondary actions. - * - * @function Drupal.DropButton~dropbuttonClickHandler - * - * @param {jQuery.Event} e - * The event triggered. - */ - function dropbuttonClickHandler(e) { - e.preventDefault(); - $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); - } - /** * A DropButton presents an HTML list as a button with a primary action. * @@ -127,6 +88,45 @@ } } + /** + * Delegated callback for opening and closing dropbutton secondary actions. + * + * @function Drupal.DropButton~dropbuttonClickHandler + * + * @param {jQuery.Event} e + * The event triggered. + */ + function dropbuttonClickHandler(e) { + e.preventDefault(); + $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); + } + + /** + * Process elements with the .dropbutton class on page load. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches dropButton behaviors. + */ + Drupal.behaviors.dropButton = { + attach(context, settings) { + const $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton'); + if ($dropbuttons.length) { + // Adds the delegated handler that will toggle dropdowns on click. + const $body = $('body').once('dropbutton-click'); + if ($body.length) { + $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler); + } + // Initialize all buttons. + const il = $dropbuttons.length; + for (let i = 0; i < il; i++) { + DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton)); + } + } + }, + }; + /** * Extend the DropButton constructor. */ diff --git a/core/misc/dropbutton/dropbutton.js b/core/misc/dropbutton/dropbutton.js index 448484d336543486b7a0954366466c141b257e2a..166eab5845258c349aca0308457eef476681e3ef 100644 --- a/core/misc/dropbutton/dropbutton.js +++ b/core/misc/dropbutton/dropbutton.js @@ -6,28 +6,6 @@ **/ (function ($, Drupal) { - Drupal.behaviors.dropButton = { - attach: function attach(context, settings) { - var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton'); - if ($dropbuttons.length) { - var $body = $('body').once('dropbutton-click'); - if ($body.length) { - $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler); - } - - var il = $dropbuttons.length; - for (var i = 0; i < il; i++) { - DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton)); - } - } - } - }; - - function dropbuttonClickHandler(e) { - e.preventDefault(); - $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); - } - function DropButton(dropbutton, settings) { var options = $.extend({ title: Drupal.t('List additional actions') }, settings); var $dropbutton = $(dropbutton); @@ -60,6 +38,28 @@ } } + function dropbuttonClickHandler(e) { + e.preventDefault(); + $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); + } + + Drupal.behaviors.dropButton = { + attach: function attach(context, settings) { + var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton'); + if ($dropbuttons.length) { + var $body = $('body').once('dropbutton-click'); + if ($body.length) { + $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler); + } + + var il = $dropbuttons.length; + for (var i = 0; i < il; i++) { + DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton)); + } + } + } + }; + $.extend(DropButton, { dropbuttons: [] }); diff --git a/core/misc/states.es6.js b/core/misc/states.es6.js index 2274874f9050bcb3efb947de3c85924f6e5396e9..358277963321c879bf4fc15b197fd7b51410ce47 100644 --- a/core/misc/states.es6.js +++ b/core/misc/states.es6.js @@ -22,6 +22,44 @@ Drupal.states = states; + /** + * Inverts a (if it's not undefined) when invertState is true. + * + * @function Drupal.states~invert + * + * @param {*} a + * The value to maybe invert. + * @param {bool} invertState + * Whether to invert state or not. + * + * @return {bool} + * The result. + */ + function invert(a, invertState) { + return (invertState && typeof a !== 'undefined') ? !a : a; + } + + /** + * Compares two values while ignoring undefined values. + * + * @function Drupal.states~compare + * + * @param {*} a + * Value a. + * @param {*} b + * Value b. + * + * @return {bool} + * The comparison result. + */ + function compare(a, b) { + if (a === b) { + return typeof a === 'undefined' ? a : true; + } + + return typeof a === 'undefined' || typeof b === 'undefined'; + } + /** * Attaches the states. * @@ -642,47 +680,4 @@ } } }); - - /** - * These are helper functions implementing addition "operators" and don't - * implement any logic that is particular to states. - */ - - /** - * Inverts a (if it's not undefined) when invertState is true. - * - * @function Drupal.states~invert - * - * @param {*} a - * The value to maybe invert. - * @param {bool} invertState - * Whether to invert state or not. - * - * @return {bool} - * The result. - */ - function invert(a, invertState) { - return (invertState && typeof a !== 'undefined') ? !a : a; - } - - /** - * Compares two values while ignoring undefined values. - * - * @function Drupal.states~compare - * - * @param {*} a - * Value a. - * @param {*} b - * Value b. - * - * @return {bool} - * The comparison result. - */ - function compare(a, b) { - if (a === b) { - return typeof a === 'undefined' ? a : true; - } - - return typeof a === 'undefined' || typeof b === 'undefined'; - } }(jQuery, Drupal)); diff --git a/core/misc/states.js b/core/misc/states.js index 2d1d84df314e398a27a9e93be5a85b3d9f751b56..a8c58021780aef4919bced1d30dcb970c255fda0 100644 --- a/core/misc/states.js +++ b/core/misc/states.js @@ -12,6 +12,18 @@ Drupal.states = states; + function invert(a, invertState) { + return invertState && typeof a !== 'undefined' ? !a : a; + } + + function _compare2(a, b) { + if (a === b) { + return typeof a === 'undefined' ? a : true; + } + + return typeof a === 'undefined' || typeof b === 'undefined'; + } + Drupal.behaviors.states = { attach: function attach(context, settings) { var $states = $(context).find('[data-drupal-states]'); @@ -345,16 +357,4 @@ } } }); - - function invert(a, invertState) { - return invertState && typeof a !== 'undefined' ? !a : a; - } - - function _compare2(a, b) { - if (a === b) { - return typeof a === 'undefined' ? a : true; - } - - return typeof a === 'undefined' || typeof b === 'undefined'; - } })(jQuery, Drupal); \ No newline at end of file diff --git a/core/misc/tabbingmanager.es6.js b/core/misc/tabbingmanager.es6.js index 075dda5177d0c911ec15cc442e83a06ffb97ff16..5183d8bca6c08fa42db5f5835fd716ec25d505f9 100644 --- a/core/misc/tabbingmanager.es6.js +++ b/core/misc/tabbingmanager.es6.js @@ -46,6 +46,61 @@ this.stack = []; } + /** + * Stores a set of tabbable elements. + * + * This constraint can be removed with the release() method. + * + * @constructor Drupal~TabbingContext + * + * @param {object} options + * A set of initiating values + * @param {number} options.level + * The level in the TabbingManager's stack of this tabbingContext. + * @param {jQuery} options.$tabbableElements + * The DOM elements that should be reachable via the tab key when this + * tabbingContext is active. + * @param {jQuery} options.$disabledElements + * The DOM elements that should not be reachable via the tab key when this + * tabbingContext is active. + * @param {bool} options.released + * A released tabbingContext can never be activated again. It will be + * cleaned up when the TabbingManager unwinds its stack. + * @param {bool} options.active + * When true, the tabbable elements of this tabbingContext will be reachable + * via the tab key and the disabled elements will not. Only one + * tabbingContext can be active at a time. + */ + function TabbingContext(options) { + $.extend(this, /** @lends Drupal~TabbingContext# */{ + + /** + * @type {?number} + */ + level: null, + + /** + * @type {jQuery} + */ + $tabbableElements: $(), + + /** + * @type {jQuery} + */ + $disabledElements: $(), + + /** + * @type {bool} + */ + released: false, + + /** + * @type {bool} + */ + active: false, + }, options); + } + /** * Add public methods to the TabbingManager class. */ @@ -242,61 +297,6 @@ }, }); - /** - * Stores a set of tabbable elements. - * - * This constraint can be removed with the release() method. - * - * @constructor Drupal~TabbingContext - * - * @param {object} options - * A set of initiating values - * @param {number} options.level - * The level in the TabbingManager's stack of this tabbingContext. - * @param {jQuery} options.$tabbableElements - * The DOM elements that should be reachable via the tab key when this - * tabbingContext is active. - * @param {jQuery} options.$disabledElements - * The DOM elements that should not be reachable via the tab key when this - * tabbingContext is active. - * @param {bool} options.released - * A released tabbingContext can never be activated again. It will be - * cleaned up when the TabbingManager unwinds its stack. - * @param {bool} options.active - * When true, the tabbable elements of this tabbingContext will be reachable - * via the tab key and the disabled elements will not. Only one - * tabbingContext can be active at a time. - */ - function TabbingContext(options) { - $.extend(this, /** @lends Drupal~TabbingContext# */{ - - /** - * @type {?number} - */ - level: null, - - /** - * @type {jQuery} - */ - $tabbableElements: $(), - - /** - * @type {jQuery} - */ - $disabledElements: $(), - - /** - * @type {bool} - */ - released: false, - - /** - * @type {bool} - */ - active: false, - }, options); - } - /** * Add public methods to the TabbingContext class. */ diff --git a/core/misc/tabbingmanager.js b/core/misc/tabbingmanager.js index 2d0b92716d96656617b6bffe15a0ca70d6a78b48..c912c92684e8e92bf62d2672c9f872c70cc82cdd 100644 --- a/core/misc/tabbingmanager.js +++ b/core/misc/tabbingmanager.js @@ -10,6 +10,20 @@ this.stack = []; } + function TabbingContext(options) { + $.extend(this, { + level: null, + + $tabbableElements: $(), + + $disabledElements: $(), + + released: false, + + active: false + }, options); + } + $.extend(TabbingManager.prototype, { constrain: function constrain(elements) { var il = this.stack.length; @@ -109,20 +123,6 @@ } }); - function TabbingContext(options) { - $.extend(this, { - level: null, - - $tabbableElements: $(), - - $disabledElements: $(), - - released: false, - - active: false - }, options); - } - $.extend(TabbingContext.prototype, { release: function release() { if (!this.released) { diff --git a/core/misc/tableheader.es6.js b/core/misc/tableheader.es6.js index a5aa6635d34468c747f6e7ea9ef7c153e3f6cced..289e3fcba7b67ad1c6241d74e6ad8aecbadc2e6c 100644 --- a/core/misc/tableheader.es6.js +++ b/core/misc/tableheader.es6.js @@ -5,21 +5,66 @@ (function ($, Drupal, displace) { /** - * Attaches sticky table headers. + * Constructor for the tableHeader object. Provides sticky table headers. * - * @type {Drupal~behavior} + * TableHeader will make the current table header stick to the top of the page + * if the table is very long. * - * @prop {Drupal~behaviorAttach} attach - * Attaches the sticky table header behavior. + * @constructor Drupal.TableHeader + * + * @param {HTMLElement} table + * DOM object for the table to add a sticky header to. + * + * @listens event:columnschange */ - Drupal.behaviors.tableHeader = { - attach(context) { - $(window).one('scroll.TableHeaderInit', { context }, tableHeaderInitHandler); - }, - }; + function TableHeader(table) { + const $table = $(table); - function scrollValue(position) { - return document.documentElement[position] || document.body[position]; + /** + * @name Drupal.TableHeader#$originalTable + * + * @type {HTMLElement} + */ + this.$originalTable = $table; + + /** + * @type {jQuery} + */ + this.$originalHeader = $table.children('thead'); + + /** + * @type {jQuery} + */ + this.$originalHeaderCells = this.$originalHeader.find('> tr > th'); + + /** + * @type {null|bool} + */ + this.displayWeight = null; + this.$originalTable.addClass('sticky-table'); + this.tableHeight = $table[0].clientHeight; + this.tableOffset = this.$originalTable.offset(); + + // React to columns change to avoid making checks in the scroll callback. + this.$originalTable.on('columnschange', { tableHeader: this }, (e, display) => { + const tableHeader = e.data.tableHeader; + if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) { + tableHeader.recalculateSticky(); + } + tableHeader.displayWeight = display; + }); + + // Create and display sticky header. + this.createSticky(); + } + + // Helper method to loop through tables and execute a method. + function forTables(method, arg) { + const tables = TableHeader.tables; + const il = tables.length; + for (let i = 0; i < il; i++) { + tables[i][method](arg); + } } // Select and initialize sticky table headers. @@ -32,13 +77,22 @@ forTables('onScroll'); } - // Helper method to loop through tables and execute a method. - function forTables(method, arg) { - const tables = TableHeader.tables; - const il = tables.length; - for (let i = 0; i < il; i++) { - tables[i][method](arg); - } + /** + * Attaches sticky table headers. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches the sticky table header behavior. + */ + Drupal.behaviors.tableHeader = { + attach(context) { + $(window).one('scroll.TableHeaderInit', { context }, tableHeaderInitHandler); + }, + }; + + function scrollValue(position) { + return document.documentElement[position] || document.body[position]; } function tableHeaderResizeHandler(e) { @@ -89,60 +143,6 @@ 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler, }); - /** - * Constructor for the tableHeader object. Provides sticky table headers. - * - * TableHeader will make the current table header stick to the top of the page - * if the table is very long. - * - * @constructor Drupal.TableHeader - * - * @param {HTMLElement} table - * DOM object for the table to add a sticky header to. - * - * @listens event:columnschange - */ - function TableHeader(table) { - const $table = $(table); - - /** - * @name Drupal.TableHeader#$originalTable - * - * @type {HTMLElement} - */ - this.$originalTable = $table; - - /** - * @type {jQuery} - */ - this.$originalHeader = $table.children('thead'); - - /** - * @type {jQuery} - */ - this.$originalHeaderCells = this.$originalHeader.find('> tr > th'); - - /** - * @type {null|bool} - */ - this.displayWeight = null; - this.$originalTable.addClass('sticky-table'); - this.tableHeight = $table[0].clientHeight; - this.tableOffset = this.$originalTable.offset(); - - // React to columns change to avoid making checks in the scroll callback. - this.$originalTable.on('columnschange', { tableHeader: this }, (e, display) => { - const tableHeader = e.data.tableHeader; - if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) { - tableHeader.recalculateSticky(); - } - tableHeader.displayWeight = display; - }); - - // Create and display sticky header. - this.createSticky(); - } - /** * Store the state of TableHeader. */ diff --git a/core/misc/tableheader.js b/core/misc/tableheader.js index bce54ec7d43d9cf9d6bbac087ba22a455b9fa088..1fd60086db89dc1a0516c7d2c3944f725c9d9926 100644 --- a/core/misc/tableheader.js +++ b/core/misc/tableheader.js @@ -6,14 +6,37 @@ **/ (function ($, Drupal, displace) { - Drupal.behaviors.tableHeader = { - attach: function attach(context) { - $(window).one('scroll.TableHeaderInit', { context: context }, tableHeaderInitHandler); - } - }; + function TableHeader(table) { + var $table = $(table); - function scrollValue(position) { - return document.documentElement[position] || document.body[position]; + this.$originalTable = $table; + + this.$originalHeader = $table.children('thead'); + + this.$originalHeaderCells = this.$originalHeader.find('> tr > th'); + + this.displayWeight = null; + this.$originalTable.addClass('sticky-table'); + this.tableHeight = $table[0].clientHeight; + this.tableOffset = this.$originalTable.offset(); + + this.$originalTable.on('columnschange', { tableHeader: this }, function (e, display) { + var tableHeader = e.data.tableHeader; + if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) { + tableHeader.recalculateSticky(); + } + tableHeader.displayWeight = display; + }); + + this.createSticky(); + } + + function forTables(method, arg) { + var tables = TableHeader.tables; + var il = tables.length; + for (var i = 0; i < il; i++) { + tables[i][method](arg); + } } function tableHeaderInitHandler(e) { @@ -25,12 +48,14 @@ forTables('onScroll'); } - function forTables(method, arg) { - var tables = TableHeader.tables; - var il = tables.length; - for (var i = 0; i < il; i++) { - tables[i][method](arg); + Drupal.behaviors.tableHeader = { + attach: function attach(context) { + $(window).one('scroll.TableHeaderInit', { context: context }, tableHeaderInitHandler); } + }; + + function scrollValue(position) { + return document.documentElement[position] || document.body[position]; } function tableHeaderResizeHandler(e) { @@ -57,31 +82,6 @@ 'drupalViewportOffsetChange.TableHeader': tableHeaderOffsetChangeHandler }); - function TableHeader(table) { - var $table = $(table); - - this.$originalTable = $table; - - this.$originalHeader = $table.children('thead'); - - this.$originalHeaderCells = this.$originalHeader.find('> tr > th'); - - this.displayWeight = null; - this.$originalTable.addClass('sticky-table'); - this.tableHeight = $table[0].clientHeight; - this.tableOffset = this.$originalTable.offset(); - - this.$originalTable.on('columnschange', { tableHeader: this }, function (e, display) { - var tableHeader = e.data.tableHeader; - if (tableHeader.displayWeight === null || tableHeader.displayWeight !== display) { - tableHeader.recalculateSticky(); - } - tableHeader.displayWeight = display; - }); - - this.createSticky(); - } - $.extend(TableHeader, { tables: [] }); diff --git a/core/misc/tableresponsive.es6.js b/core/misc/tableresponsive.es6.js index 8d1ae0115e77f647a8a02592cce076e42eedee34..6030b6e37c06f5c0df95ca752bccc9fb6939f2d5 100644 --- a/core/misc/tableresponsive.es6.js +++ b/core/misc/tableresponsive.es6.js @@ -4,26 +4,6 @@ */ (function ($, Drupal, window) { - /** - * Attach the tableResponsive function to {@link Drupal.behaviors}. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches tableResponsive functionality. - */ - Drupal.behaviors.tableResponsive = { - attach(context, settings) { - const $tables = $(context).find('table.responsive-enabled').once('tableresponsive'); - if ($tables.length) { - const il = $tables.length; - for (let i = 0; i < il; i++) { - TableResponsive.tables.push(new TableResponsive($tables[i])); - } - } - }, - }; - /** * The TableResponsive object optimizes table presentation for screen size. * @@ -60,6 +40,26 @@ .trigger('resize.tableresponsive'); } + /** + * Attach the tableResponsive function to {@link Drupal.behaviors}. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches tableResponsive functionality. + */ + Drupal.behaviors.tableResponsive = { + attach(context, settings) { + const $tables = $(context).find('table.responsive-enabled').once('tableresponsive'); + if ($tables.length) { + const il = $tables.length; + for (let i = 0; i < il; i++) { + TableResponsive.tables.push(new TableResponsive($tables[i])); + } + } + }, + }; + /** * Extend the TableResponsive function with a list of managed tables. */ diff --git a/core/misc/tableresponsive.js b/core/misc/tableresponsive.js index d4fa6315cc11dfca2a2b0ccc45447f12b16e187d..0de5eee23cd66e3627d2fc0c4ea92eeb9229c41e 100644 --- a/core/misc/tableresponsive.js +++ b/core/misc/tableresponsive.js @@ -6,18 +6,6 @@ **/ (function ($, Drupal, window) { - Drupal.behaviors.tableResponsive = { - attach: function attach(context, settings) { - var $tables = $(context).find('table.responsive-enabled').once('tableresponsive'); - if ($tables.length) { - var il = $tables.length; - for (var i = 0; i < il; i++) { - TableResponsive.tables.push(new TableResponsive($tables[i])); - } - } - } - }; - function TableResponsive(table) { this.table = table; this.$table = $(table); @@ -33,6 +21,18 @@ $(window).on('resize.tableresponsive', $.proxy(this, 'eventhandlerEvaluateColumnVisibility')).trigger('resize.tableresponsive'); } + Drupal.behaviors.tableResponsive = { + attach: function attach(context, settings) { + var $tables = $(context).find('table.responsive-enabled').once('tableresponsive'); + if ($tables.length) { + var il = $tables.length; + for (var i = 0; i < il; i++) { + TableResponsive.tables.push(new TableResponsive($tables[i])); + } + } + } + }; + $.extend(TableResponsive, { tables: [] }); diff --git a/core/modules/big_pipe/js/big_pipe.es6.js b/core/modules/big_pipe/js/big_pipe.es6.js index df72915a8ed674a6e73a505839d929421ecc2c01..afaa6aa67bb75a43d0218bd3132e974c2bb8f466 100644 --- a/core/modules/big_pipe/js/big_pipe.es6.js +++ b/core/modules/big_pipe/js/big_pipe.es6.js @@ -4,6 +4,28 @@ */ (function ($, Drupal, drupalSettings) { + /** + * Maps textContent of <script type="application/vnd.drupal-ajax"> to an AJAX response. + * + * @param {string} content + * The text content of a <script type="application/vnd.drupal-ajax"> DOM node. + * @return {Array|boolean} + * The parsed Ajax response containing an array of Ajax commands, or false in + * case the DOM node hasn't fully arrived yet. + */ + function mapTextContentToAjaxResponse(content) { + if (content === '') { + return false; + } + + try { + return JSON.parse(content); + } + catch (e) { + return false; + } + } + /** * Executes Ajax commands in <script type="application/vnd.drupal-ajax"> tag. * @@ -46,27 +68,13 @@ } } - /** - * Maps textContent of <script type="application/vnd.drupal-ajax"> to an AJAX response. - * - * @param {string} content - * The text content of a <script type="application/vnd.drupal-ajax"> DOM node. - * @return {Array|boolean} - * The parsed Ajax response containing an array of Ajax commands, or false in - * case the DOM node hasn't fully arrived yet. - */ - function mapTextContentToAjaxResponse(content) { - if (content === '') { - return false; - } + // The frequency with which to check for newly arrived BigPipe placeholders. + // Hence 50 ms means we check 20 times per second. Setting this to 100 ms or + // more would cause the user to see content appear noticeably slower. + const interval = drupalSettings.bigPipeInterval || 50; - try { - return JSON.parse(content); - } - catch (e) { - return false; - } - } + // The internal ID to contain the watcher service. + let timeoutID; /** * Processes a streamed HTML document receiving placeholder replacements. @@ -109,13 +117,6 @@ }, interval); } - // The frequency with which to check for newly arrived BigPipe placeholders. - // Hence 50 ms means we check 20 times per second. Setting this to 100 ms or - // more would cause the user to see content appear noticeably slower. - const interval = drupalSettings.bigPipeInterval || 50; - // The internal ID to contain the watcher service. - let timeoutID; - bigPipeProcess(); // If something goes wrong, make sure everything is cleaned up and has had a diff --git a/core/modules/big_pipe/js/big_pipe.js b/core/modules/big_pipe/js/big_pipe.js index b8a25f77cc8987832ec660ba87dea22329419b83..e386191ac1b99fce7e188ecc06eb885c455b0703 100644 --- a/core/modules/big_pipe/js/big_pipe.js +++ b/core/modules/big_pipe/js/big_pipe.js @@ -6,6 +6,18 @@ **/ (function ($, Drupal, drupalSettings) { + function mapTextContentToAjaxResponse(content) { + if (content === '') { + return false; + } + + try { + return JSON.parse(content); + } catch (e) { + return false; + } + } + function bigPipeProcessPlaceholderReplacement(index, placeholderReplacement) { var placeholderId = placeholderReplacement.getAttribute('data-big-pipe-replacement-for-placeholder-with-id'); var content = this.textContent.trim(); @@ -28,17 +40,9 @@ } } - function mapTextContentToAjaxResponse(content) { - if (content === '') { - return false; - } + var interval = drupalSettings.bigPipeInterval || 50; - try { - return JSON.parse(content); - } catch (e) { - return false; - } - } + var timeoutID = void 0; function bigPipeProcessDocument(context) { if (!context.querySelector('script[data-big-pipe-event="start"]')) { @@ -65,10 +69,6 @@ }, interval); } - var interval = drupalSettings.bigPipeInterval || 50; - - var timeoutID = void 0; - bigPipeProcess(); $(window).on('load', function () { diff --git a/core/modules/ckeditor/js/ckeditor.admin.es6.js b/core/modules/ckeditor/js/ckeditor.admin.es6.js index 4cf6a63e44be1750eb3554fb4da850cd5e49bf57..687169f5764aa1275a2da43baf80493e3b98212d 100644 --- a/core/modules/ckeditor/js/ckeditor.admin.es6.js +++ b/core/modules/ckeditor/js/ckeditor.admin.es6.js @@ -215,6 +215,7 @@ * Closes the dialog when the user cancels or supplies valid data. */ function shutdown() { + // eslint-disable-next-line no-use-before-define dialog.close(action); // The processing marker can be deleted since the dialog has been @@ -346,6 +347,7 @@ $(event.target).remove(); }, }); + // A modal dialog is used because the user must provide a button group // name or cancel the button placement before taking any other action. dialog.showModal(); diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js index ac4fba44e4647482a3c4e408c4ef054006d5b977..94f92e6b904644c198a2014cd31ff5724c98ee60 100644 --- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js +++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.es6.js @@ -14,6 +14,77 @@ */ (function ($, Drupal, CKEDITOR) { + /** + * Gets the focused widget, if of the type specific for this plugin. + * + * @param {CKEDITOR.editor} editor + * A CKEditor instance. + * + * @return {?CKEDITOR.plugins.widget} + * The focused image2 widget instance, or null. + */ + function getFocusedWidget(editor) { + const widget = editor.widgets.focused; + + if (widget && widget.name === 'image') { + return widget; + } + + return null; + } + + /** + * Integrates the drupalimage widget with the drupallink plugin. + * + * Makes images linkable. + * + * @param {CKEDITOR.editor} editor + * A CKEditor instance. + */ + function linkCommandIntegrator(editor) { + // Nothing to integrate with if the drupallink plugin is not loaded. + if (!editor.plugins.drupallink) { + return; + } + + // Override default behaviour of 'drupalunlink' command. + editor.getCommand('drupalunlink').on('exec', function (evt) { + const widget = getFocusedWidget(editor); + + // Override 'drupalunlink' only when link truly belongs to the widget. If + // wrapped inline widget in a link, let default unlink work. + // @see https://dev.ckeditor.com/ticket/11814 + if (!widget || !widget.parts.link) { + return; + } + + widget.setData('link', null); + + // Selection (which is fake) may not change if unlinked image in focused + // widget, i.e. if captioned image. Let's refresh command state manually + // here. + this.refresh(editor, editor.elementPath()); + + evt.cancel(); + }); + + // Override default refresh of 'drupalunlink' command. + editor.getCommand('drupalunlink').on('refresh', function (evt) { + const widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + // Note that widget may be wrapped in a link, which + // does not belong to that widget (#11814). + this.setState(widget.data.link || widget.wrapper.getAscendant('a') ? + CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED); + + evt.cancel(); + }); + } + CKEDITOR.plugins.add('drupalimage', { requires: 'image2', icons: 'drupalimage', @@ -289,77 +360,6 @@ return CKEDITOR.plugins.drupallink.getLinkAttributes; }; - /** - * Integrates the drupalimage widget with the drupallink plugin. - * - * Makes images linkable. - * - * @param {CKEDITOR.editor} editor - * A CKEditor instance. - */ - function linkCommandIntegrator(editor) { - // Nothing to integrate with if the drupallink plugin is not loaded. - if (!editor.plugins.drupallink) { - return; - } - - // Override default behaviour of 'drupalunlink' command. - editor.getCommand('drupalunlink').on('exec', function (evt) { - const widget = getFocusedWidget(editor); - - // Override 'drupalunlink' only when link truly belongs to the widget. If - // wrapped inline widget in a link, let default unlink work. - // @see https://dev.ckeditor.com/ticket/11814 - if (!widget || !widget.parts.link) { - return; - } - - widget.setData('link', null); - - // Selection (which is fake) may not change if unlinked image in focused - // widget, i.e. if captioned image. Let's refresh command state manually - // here. - this.refresh(editor, editor.elementPath()); - - evt.cancel(); - }); - - // Override default refresh of 'drupalunlink' command. - editor.getCommand('drupalunlink').on('refresh', function (evt) { - const widget = getFocusedWidget(editor); - - if (!widget) { - return; - } - - // Note that widget may be wrapped in a link, which - // does not belong to that widget (#11814). - this.setState(widget.data.link || widget.wrapper.getAscendant('a') ? - CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED); - - evt.cancel(); - }); - } - - /** - * Gets the focused widget, if of the type specific for this plugin. - * - * @param {CKEDITOR.editor} editor - * A CKEditor instance. - * - * @return {?CKEDITOR.plugins.widget} - * The focused image2 widget instance, or null. - */ - function getFocusedWidget(editor) { - const widget = editor.widgets.focused; - - if (widget && widget.name === 'image') { - return widget; - } - - return null; - } - // Expose an API for other plugins to interact with drupalimage widgets. CKEDITOR.plugins.drupalimage = { getFocusedWidget, diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js index b1751c28e0c55080d043afa3dcc62e7eee6850bf..8176a72a524e5ee856f5ffc5023b5fd767cd844b 100644 --- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js @@ -6,6 +6,48 @@ **/ (function ($, Drupal, CKEDITOR) { + function getFocusedWidget(editor) { + var widget = editor.widgets.focused; + + if (widget && widget.name === 'image') { + return widget; + } + + return null; + } + + function linkCommandIntegrator(editor) { + if (!editor.plugins.drupallink) { + return; + } + + editor.getCommand('drupalunlink').on('exec', function (evt) { + var widget = getFocusedWidget(editor); + + if (!widget || !widget.parts.link) { + return; + } + + widget.setData('link', null); + + this.refresh(editor, editor.elementPath()); + + evt.cancel(); + }); + + editor.getCommand('drupalunlink').on('refresh', function (evt) { + var widget = getFocusedWidget(editor); + + if (!widget) { + return; + } + + this.setState(widget.data.link || widget.wrapper.getAscendant('a') ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED); + + evt.cancel(); + }); + } + CKEDITOR.plugins.add('drupalimage', { requires: 'image2', icons: 'drupalimage', @@ -204,48 +246,6 @@ return CKEDITOR.plugins.drupallink.getLinkAttributes; }; - function linkCommandIntegrator(editor) { - if (!editor.plugins.drupallink) { - return; - } - - editor.getCommand('drupalunlink').on('exec', function (evt) { - var widget = getFocusedWidget(editor); - - if (!widget || !widget.parts.link) { - return; - } - - widget.setData('link', null); - - this.refresh(editor, editor.elementPath()); - - evt.cancel(); - }); - - editor.getCommand('drupalunlink').on('refresh', function (evt) { - var widget = getFocusedWidget(editor); - - if (!widget) { - return; - } - - this.setState(widget.data.link || widget.wrapper.getAscendant('a') ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED); - - evt.cancel(); - }); - } - - function getFocusedWidget(editor) { - var widget = editor.widgets.focused; - - if (widget && widget.name === 'image') { - return widget; - } - - return null; - } - CKEDITOR.plugins.drupalimage = { getFocusedWidget: getFocusedWidget }; diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js index 1d51bcb82d486240307929e1e0a0d7476458dd8f..140a18420703ea6450bc014809733526abfac371 100644 --- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js +++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js @@ -11,6 +11,36 @@ */ (function (CKEDITOR) { + /** + * Finds an element by its name. + * + * Function will check first the passed element itself and then all its + * children in DFS order. + * + * @param {CKEDITOR.htmlParser.element} element + * The element to search. + * @param {string} name + * The element name to search for. + * + * @return {?CKEDITOR.htmlParser.element} + * The found element, or null. + */ + function findElementByName(element, name) { + if (element.name === name) { + return element; + } + + let found = null; + element.forEach((el) => { + if (el.name === name) { + found = el; + // Stop here. + return false; + } + }, CKEDITOR.NODE_ELEMENT); + return found; + } + CKEDITOR.plugins.add('drupalimagecaption', { requires: 'drupalimage', @@ -263,34 +293,4 @@ } }, }); - - /** - * Finds an element by its name. - * - * Function will check first the passed element itself and then all its - * children in DFS order. - * - * @param {CKEDITOR.htmlParser.element} element - * The element to search. - * @param {string} name - * The element name to search for. - * - * @return {?CKEDITOR.htmlParser.element} - * The found element, or null. - */ - function findElementByName(element, name) { - if (element.name === name) { - return element; - } - - let found = null; - element.forEach((el) => { - if (el.name === name) { - found = el; - // Stop here. - return false; - } - }, CKEDITOR.NODE_ELEMENT); - return found; - } }(CKEDITOR)); diff --git a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js index c9c9427327023947c4cff89f6310622b28a46372..d19f3b3adc5282f17b8939f82a6464673479580d 100644 --- a/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.js @@ -6,6 +6,22 @@ **/ (function (CKEDITOR) { + function findElementByName(element, name) { + if (element.name === name) { + return element; + } + + var found = null; + element.forEach(function (el) { + if (el.name === name) { + found = el; + + return false; + } + }, CKEDITOR.NODE_ELEMENT); + return found; + } + CKEDITOR.plugins.add('drupalimagecaption', { requires: 'drupalimage', @@ -192,20 +208,4 @@ } } }); - - function findElementByName(element, name) { - if (element.name === name) { - return element; - } - - var found = null; - element.forEach(function (el) { - if (el.name === name) { - found = el; - - return false; - } - }, CKEDITOR.NODE_ELEMENT); - return found; - } })(CKEDITOR); \ No newline at end of file diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js index 8dddd2e1844082d5abb400cc0b1095442114141f..42b8565f81e072a4067d38fd0d8dfdc48755c90c 100644 --- a/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.es6.js @@ -54,6 +54,42 @@ }; } + /** + * Get the surrounding link element of current selection. + * + * The following selection will all return the link element. + * + * @example + * <a href="#">li^nk</a> + * <a href="#">[link]</a> + * text[<a href="#">link]</a> + * <a href="#">li[nk</a>] + * [<b><a href="#">li]nk</a></b>] + * [<a href="#"><b>li]nk</b></a> + * + * @param {CKEDITOR.editor} editor + * The CKEditor editor object + * + * @return {?HTMLElement} + * The selected link element, or null. + * + */ + function getSelectedLink(editor) { + const selection = editor.getSelection(); + const selectedElement = selection.getSelectedElement(); + if (selectedElement && selectedElement.is('a')) { + return selectedElement; + } + + const range = selection.getRanges(true)[0]; + + if (range) { + range.shrink(CKEDITOR.SHRINK_TEXT); + return editor.elementPath(range.getCommonAncestor()).contains('a', 1); + } + return null; + } + CKEDITOR.plugins.add('drupallink', { icons: 'drupallink,drupalunlink', hidpi: true, @@ -248,42 +284,6 @@ }, }); - /** - * Get the surrounding link element of current selection. - * - * The following selection will all return the link element. - * - * @example - * <a href="#">li^nk</a> - * <a href="#">[link]</a> - * text[<a href="#">link]</a> - * <a href="#">li[nk</a>] - * [<b><a href="#">li]nk</a></b>] - * [<a href="#"><b>li]nk</b></a> - * - * @param {CKEDITOR.editor} editor - * The CKEditor editor object - * - * @return {?HTMLElement} - * The selected link element, or null. - * - */ - function getSelectedLink(editor) { - const selection = editor.getSelection(); - const selectedElement = selection.getSelectedElement(); - if (selectedElement && selectedElement.is('a')) { - return selectedElement; - } - - const range = selection.getRanges(true)[0]; - - if (range) { - range.shrink(CKEDITOR.SHRINK_TEXT); - return editor.elementPath(range.getCommonAncestor()).contains('a', 1); - } - return null; - } - // Expose an API for other plugins to interact with drupallink widgets. // (Compatible with the official CKEditor link plugin's API: // http://dev.ckeditor.com/ticket/13885.) diff --git a/core/modules/ckeditor/js/plugins/drupallink/plugin.js b/core/modules/ckeditor/js/plugins/drupallink/plugin.js index d53f49a345ec2a1670fe8c675b0552863a954947..3e5e0dbd1bef057f83e7589b0c0f4194d6bc35cb 100644 --- a/core/modules/ckeditor/js/plugins/drupallink/plugin.js +++ b/core/modules/ckeditor/js/plugins/drupallink/plugin.js @@ -49,6 +49,22 @@ }; } + function getSelectedLink(editor) { + var selection = editor.getSelection(); + var selectedElement = selection.getSelectedElement(); + if (selectedElement && selectedElement.is('a')) { + return selectedElement; + } + + var range = selection.getRanges(true)[0]; + + if (range) { + range.shrink(CKEDITOR.SHRINK_TEXT); + return editor.elementPath(range.getCommonAncestor()).contains('a', 1); + } + return null; + } + CKEDITOR.plugins.add('drupallink', { icons: 'drupallink,drupalunlink', hidpi: true, @@ -216,22 +232,6 @@ } }); - function getSelectedLink(editor) { - var selection = editor.getSelection(); - var selectedElement = selection.getSelectedElement(); - if (selectedElement && selectedElement.is('a')) { - return selectedElement; - } - - var range = selection.getRanges(true)[0]; - - if (range) { - range.shrink(CKEDITOR.SHRINK_TEXT); - return editor.elementPath(range.getCommonAncestor()).contains('a', 1); - } - return null; - } - CKEDITOR.plugins.drupallink = { parseLinkAttributes: parseAttributes, getLinkAttributes: getAttributes diff --git a/core/modules/color/color.es6.js b/core/modules/color/color.es6.js index c4f923a02d4fbdf87dd9ead63878b09f79e39532..01939a1f65aadac774fc11f89b617c947f64b71f 100644 --- a/core/modules/color/color.es6.js +++ b/core/modules/color/color.es6.js @@ -40,37 +40,6 @@ // Build a preview. const height = []; const width = []; - // Loop through all defined gradients. - Object.keys(settings.gradients || {}).forEach((i) => { - // Add element to display the gradient. - $('.color-preview').once('color').append(`<div id="gradient-${i}"></div>`); - const gradient = $(`.color-preview #gradient-${i}`); - // Add height of current gradient to the list (divided by 10). - height.push(parseInt(gradient.css('height'), 10) / 10); - // Add width of current gradient to the list (divided by 10). - width.push(parseInt(gradient.css('width'), 10) / 10); - // Add rows (or columns for horizontal gradients). - // Each gradient line should have a height (or width for horizontal - // gradients) of 10px (because we divided the height/width by 10 - // above). - for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) { - gradient.append('<div class="gradient-line"></div>'); - } - }); - - // Set up colorScheme selector. - form.find('#edit-scheme').on('change', function () { - const schemes = settings.color.schemes; - const colorScheme = this.options[this.selectedIndex].value; - if (colorScheme !== '' && schemes[colorScheme]) { - // Get colors of active scheme. - colors = schemes[colorScheme]; - Object.keys(colors || {}).forEach((fieldName) => { - callback($(`#edit-palette-${fieldName}`), colors[fieldName], false, true); - }); - preview(); - } - }); /** * Renders the preview. @@ -79,6 +48,15 @@ Drupal.color.callback(context, settings, form, farb, height, width); } + /** + * Resets the color scheme selector. + */ + function resetScheme() { + form.find('#edit-scheme').each(function () { + this.selectedIndex = this.options.length - 1; + }); + } + /** * Shifts a given color, using a reference pair (ref in HSL). * @@ -193,14 +171,37 @@ } } - /** - * Resets the color scheme selector. - */ - function resetScheme() { - form.find('#edit-scheme').each(function () { - this.selectedIndex = this.options.length - 1; - }); - } + // Loop through all defined gradients. + Object.keys(settings.gradients || {}).forEach((i) => { + // Add element to display the gradient. + $('.color-preview').once('color').append(`<div id="gradient-${i}"></div>`); + const gradient = $(`.color-preview #gradient-${i}`); + // Add height of current gradient to the list (divided by 10). + height.push(parseInt(gradient.css('height'), 10) / 10); + // Add width of current gradient to the list (divided by 10). + width.push(parseInt(gradient.css('width'), 10) / 10); + // Add rows (or columns for horizontal gradients). + // Each gradient line should have a height (or width for horizontal + // gradients) of 10px (because we divided the height/width by 10 + // above). + for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) { + gradient.append('<div class="gradient-line"></div>'); + } + }); + + // Set up colorScheme selector. + form.find('#edit-scheme').on('change', function () { + const schemes = settings.color.schemes; + const colorScheme = this.options[this.selectedIndex].value; + if (colorScheme !== '' && schemes[colorScheme]) { + // Get colors of active scheme. + colors = schemes[colorScheme]; + Object.keys(colors || {}).forEach((fieldName) => { + callback($(`#edit-palette-${fieldName}`), colors[fieldName], false, true); + }); + preview(); + } + }); /** * Focuses Farbtastic on a particular field. diff --git a/core/modules/color/color.js b/core/modules/color/color.js index 5cc0b9be12d94d407f4ca737336601b70796e1ed..c92a0df36dcb78ac499b13b14b687e0aeab5f734 100644 --- a/core/modules/color/color.js +++ b/core/modules/color/color.js @@ -32,35 +32,16 @@ var height = []; var width = []; - Object.keys(settings.gradients || {}).forEach(function (i) { - $('.color-preview').once('color').append('<div id="gradient-' + i + '"></div>'); - var gradient = $('.color-preview #gradient-' + i); - - height.push(parseInt(gradient.css('height'), 10) / 10); - - width.push(parseInt(gradient.css('width'), 10) / 10); - - for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) { - gradient.append('<div class="gradient-line"></div>'); - } - }); - - form.find('#edit-scheme').on('change', function () { - var schemes = settings.color.schemes; - var colorScheme = this.options[this.selectedIndex].value; - if (colorScheme !== '' && schemes[colorScheme]) { - colors = schemes[colorScheme]; - Object.keys(colors || {}).forEach(function (fieldName) { - callback($('#edit-palette-' + fieldName), colors[fieldName], false, true); - }); - preview(); - } - }); - function preview() { Drupal.color.callback(context, settings, form, farb, height, width); } + function resetScheme() { + form.find('#edit-scheme').each(function () { + this.selectedIndex = this.options.length - 1; + }); + } + function shiftColor(given, ref1, ref2) { var d = void 0; @@ -130,11 +111,30 @@ } } - function resetScheme() { - form.find('#edit-scheme').each(function () { - this.selectedIndex = this.options.length - 1; - }); - } + Object.keys(settings.gradients || {}).forEach(function (i) { + $('.color-preview').once('color').append('<div id="gradient-' + i + '"></div>'); + var gradient = $('.color-preview #gradient-' + i); + + height.push(parseInt(gradient.css('height'), 10) / 10); + + width.push(parseInt(gradient.css('width'), 10) / 10); + + for (j = 0; j < (settings.gradients[i].direction === 'vertical' ? height[i] : width[i]); ++j) { + gradient.append('<div class="gradient-line"></div>'); + } + }); + + form.find('#edit-scheme').on('change', function () { + var schemes = settings.color.schemes; + var colorScheme = this.options[this.selectedIndex].value; + if (colorScheme !== '' && schemes[colorScheme]) { + colors = schemes[colorScheme]; + Object.keys(colors || {}).forEach(function (fieldName) { + callback($('#edit-palette-' + fieldName), colors[fieldName], false, true); + }); + preview(); + } + }); function focus(e) { var input = e.target; diff --git a/core/modules/comment/js/comment-new-indicator.es6.js b/core/modules/comment/js/comment-new-indicator.es6.js index dd1fadf3ad76765b5a6df2181bdee9daae3e239f..791853e6eed826b3a666070ac747e378fb44b683 100644 --- a/core/modules/comment/js/comment-new-indicator.es6.js +++ b/core/modules/comment/js/comment-new-indicator.es6.js @@ -7,46 +7,6 @@ */ (function ($, Drupal, window) { - /** - * Renders "new" comment indicators wherever necessary. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches "new" comment indicators behavior. - */ - Drupal.behaviors.commentNewIndicator = { - attach(context) { - // Collect all "new" comment indicator placeholders (and their - // corresponding node IDs) newer than 30 days ago that have not already - // been read after their last comment timestamp. - const nodeIDs = []; - const $placeholders = $(context) - .find('[data-comment-timestamp]') - .once('history') - .filter(function () { - const $placeholder = $(this); - const commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10); - const nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); - if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) { - nodeIDs.push(nodeID); - return true; - } - - return false; - }); - - if ($placeholders.length === 0) { - return; - } - - // Fetch the node read timestamps from the server. - Drupal.history.fetchTimestamps(nodeIDs, () => { - processCommentNewIndicators($placeholders); - }); - }, - }; - /** * Processes the markup for "new comment" indicators. * @@ -88,4 +48,44 @@ } }); } + + /** + * Renders "new" comment indicators wherever necessary. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches "new" comment indicators behavior. + */ + Drupal.behaviors.commentNewIndicator = { + attach(context) { + // Collect all "new" comment indicator placeholders (and their + // corresponding node IDs) newer than 30 days ago that have not already + // been read after their last comment timestamp. + const nodeIDs = []; + const $placeholders = $(context) + .find('[data-comment-timestamp]') + .once('history') + .filter(function () { + const $placeholder = $(this); + const commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10); + const nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); + if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) { + nodeIDs.push(nodeID); + return true; + } + + return false; + }); + + if ($placeholders.length === 0) { + return; + } + + // Fetch the node read timestamps from the server. + Drupal.history.fetchTimestamps(nodeIDs, () => { + processCommentNewIndicators($placeholders); + }); + }, + }; }(jQuery, Drupal, window)); diff --git a/core/modules/comment/js/comment-new-indicator.js b/core/modules/comment/js/comment-new-indicator.js index bc7f55ec37df19121f1a1b0489085c3e44b06c46..88587d5724c31c45b86a6232094f729ba777f550 100644 --- a/core/modules/comment/js/comment-new-indicator.js +++ b/core/modules/comment/js/comment-new-indicator.js @@ -6,31 +6,6 @@ **/ (function ($, Drupal, window) { - Drupal.behaviors.commentNewIndicator = { - attach: function attach(context) { - var nodeIDs = []; - var $placeholders = $(context).find('[data-comment-timestamp]').once('history').filter(function () { - var $placeholder = $(this); - var commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10); - var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); - if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) { - nodeIDs.push(nodeID); - return true; - } - - return false; - }); - - if ($placeholders.length === 0) { - return; - } - - Drupal.history.fetchTimestamps(nodeIDs, function () { - processCommentNewIndicators($placeholders); - }); - } - }; - function processCommentNewIndicators($placeholders) { var isFirstNewComment = true; var newCommentString = Drupal.t('new'); @@ -57,4 +32,29 @@ } }); } + + Drupal.behaviors.commentNewIndicator = { + attach: function attach(context) { + var nodeIDs = []; + var $placeholders = $(context).find('[data-comment-timestamp]').once('history').filter(function () { + var $placeholder = $(this); + var commentTimestamp = parseInt($placeholder.attr('data-comment-timestamp'), 10); + var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); + if (Drupal.history.needsServerCheck(nodeID, commentTimestamp)) { + nodeIDs.push(nodeID); + return true; + } + + return false; + }); + + if ($placeholders.length === 0) { + return; + } + + Drupal.history.fetchTimestamps(nodeIDs, function () { + processCommentNewIndicators($placeholders); + }); + } + }; })(jQuery, Drupal, window); \ No newline at end of file diff --git a/core/modules/comment/js/node-new-comments-link.es6.js b/core/modules/comment/js/node-new-comments-link.es6.js index a2bdc35b9d7272b4950f55f303a27b73eacf0486..0f084d4cb3db18ac4dfc1292794972de098f9d16 100644 --- a/core/modules/comment/js/node-new-comments-link.es6.js +++ b/core/modules/comment/js/node-new-comments-link.es6.js @@ -7,51 +7,6 @@ */ (function ($, Drupal, drupalSettings) { - /** - * Render "X new comments" links wherever necessary. - * - * @type {Drupal~behavior} - * - * @prop {Drupal~behaviorAttach} attach - * Attaches new comment links behavior. - */ - Drupal.behaviors.nodeNewCommentsLink = { - attach(context) { - // Collect all "X new comments" node link placeholders (and their - // corresponding node IDs) newer than 30 days ago that have not already - // been read after their last comment timestamp. - const nodeIDs = []; - const $placeholders = $(context) - .find('[data-history-node-last-comment-timestamp]') - .once('history') - .filter(function () { - const $placeholder = $(this); - const lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10); - const nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); - if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) { - nodeIDs.push(nodeID); - // Hide this placeholder link until it is certain we'll need it. - hide($placeholder); - return true; - } - - // Remove this placeholder link from the DOM because we won't need - // it. - remove($placeholder); - return false; - }); - - if ($placeholders.length === 0) { - return; - } - - // Perform an AJAX request to retrieve node read timestamps. - Drupal.history.fetchTimestamps(nodeIDs, () => { - processNodeNewCommentLinks($placeholders); - }); - }, - }; - /** * Hides a "new comment" element. * @@ -173,4 +128,49 @@ }); } } + + /** + * Render "X new comments" links wherever necessary. + * + * @type {Drupal~behavior} + * + * @prop {Drupal~behaviorAttach} attach + * Attaches new comment links behavior. + */ + Drupal.behaviors.nodeNewCommentsLink = { + attach(context) { + // Collect all "X new comments" node link placeholders (and their + // corresponding node IDs) newer than 30 days ago that have not already + // been read after their last comment timestamp. + const nodeIDs = []; + const $placeholders = $(context) + .find('[data-history-node-last-comment-timestamp]') + .once('history') + .filter(function () { + const $placeholder = $(this); + const lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10); + const nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); + if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) { + nodeIDs.push(nodeID); + // Hide this placeholder link until it is certain we'll need it. + hide($placeholder); + return true; + } + + // Remove this placeholder link from the DOM because we won't need + // it. + remove($placeholder); + return false; + }); + + if ($placeholders.length === 0) { + return; + } + + // Perform an AJAX request to retrieve node read timestamps. + Drupal.history.fetchTimestamps(nodeIDs, () => { + processNodeNewCommentLinks($placeholders); + }); + }, + }; }(jQuery, Drupal, drupalSettings)); diff --git a/core/modules/comment/js/node-new-comments-link.js b/core/modules/comment/js/node-new-comments-link.js index 1396018a561872b3a47ff5ac8993b0c3d03e9c39..b7439940f61ac81bc5739ca3ef73af8ea865bcdc 100644 --- a/core/modules/comment/js/node-new-comments-link.js +++ b/core/modules/comment/js/node-new-comments-link.js @@ -6,34 +6,6 @@ **/ (function ($, Drupal, drupalSettings) { - Drupal.behaviors.nodeNewCommentsLink = { - attach: function attach(context) { - var nodeIDs = []; - var $placeholders = $(context).find('[data-history-node-last-comment-timestamp]').once('history').filter(function () { - var $placeholder = $(this); - var lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10); - var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); - if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) { - nodeIDs.push(nodeID); - - hide($placeholder); - return true; - } - - remove($placeholder); - return false; - }); - - if ($placeholders.length === 0) { - return; - } - - Drupal.history.fetchTimestamps(nodeIDs, function () { - processNodeNewCommentLinks($placeholders); - }); - } - }; - function hide($placeholder) { return $placeholder.closest('.comment-new-comments').prev().addClass('last').end().hide(); } @@ -90,4 +62,32 @@ }); } } + + Drupal.behaviors.nodeNewCommentsLink = { + attach: function attach(context) { + var nodeIDs = []; + var $placeholders = $(context).find('[data-history-node-last-comment-timestamp]').once('history').filter(function () { + var $placeholder = $(this); + var lastCommentTimestamp = parseInt($placeholder.attr('data-history-node-last-comment-timestamp'), 10); + var nodeID = $placeholder.closest('[data-history-node-id]').attr('data-history-node-id'); + if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) { + nodeIDs.push(nodeID); + + hide($placeholder); + return true; + } + + remove($placeholder); + return false; + }); + + if ($placeholders.length === 0) { + return; + } + + Drupal.history.fetchTimestamps(nodeIDs, function () { + processNodeNewCommentLinks($placeholders); + }); + } + }; })(jQuery, Drupal, drupalSettings); \ No newline at end of file diff --git a/core/modules/contextual/js/contextual.es6.js b/core/modules/contextual/js/contextual.es6.js index 701ed126cb0749e9a4a933447d7916e0a6aab82a..8e05f87ae3ac1a710dbe67c27587fd1e662753cc 100644 --- a/core/modules/contextual/js/contextual.es6.js +++ b/core/modules/contextual/js/contextual.es6.js @@ -29,6 +29,46 @@ 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.css({ top: $nestedContextual.position().top + height }); + } + } + /** * Initializes a contextual link: updates its DOM, sets up model and views. * @@ -89,46 +129,6 @@ 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. * diff --git a/core/modules/contextual/js/contextual.js b/core/modules/contextual/js/contextual.js index ed210d070d57916a4f75614c1b0c395c8c864db9..7e6d000d407e25d67236355da810f518ae6ab15f 100644 --- a/core/modules/contextual/js/contextual.js +++ b/core/modules/contextual/js/contextual.js @@ -26,6 +26,29 @@ storage.setItem('Drupal.contextual.permissionsHash', permissionsHash); } + function adjustIfNestedAndOverlapping($contextual) { + var $contextuals = $contextual.parents('.contextual-region').eq(-1).find('.contextual'); + + if ($contextuals.length <= 1) { + return; + } + + var firstTop = $contextuals.eq(0).offset().top; + var secondTop = $contextuals.eq(1).offset().top; + if (firstTop === secondTop) { + var $nestedContextual = $contextuals.eq(1); + + var height = 0; + var $trigger = $nestedContextual.find('.trigger'); + + $trigger.removeClass('visually-hidden'); + height = $nestedContextual.height(); + $trigger.addClass('visually-hidden'); + + $nestedContextual.css({ top: $nestedContextual.position().top + height }); + } + } + function initContextual($contextual, html) { var $region = $contextual.closest('.contextual-region'); var contextual = Drupal.contextual; @@ -61,29 +84,6 @@ adjustIfNestedAndOverlapping($contextual); } - function adjustIfNestedAndOverlapping($contextual) { - var $contextuals = $contextual.parents('.contextual-region').eq(-1).find('.contextual'); - - if ($contextuals.length <= 1) { - return; - } - - var firstTop = $contextuals.eq(0).offset().top; - var secondTop = $contextuals.eq(1).offset().top; - if (firstTop === secondTop) { - var $nestedContextual = $contextuals.eq(1); - - var height = 0; - var $trigger = $nestedContextual.find('.trigger'); - - $trigger.removeClass('visually-hidden'); - height = $nestedContextual.height(); - $trigger.addClass('visually-hidden'); - - $nestedContextual.css({ top: $nestedContextual.position().top + height }); - } - } - Drupal.behaviors.contextual = { attach: function attach(context) { var $context = $(context); diff --git a/core/modules/editor/js/editor.admin.es6.js b/core/modules/editor/js/editor.admin.es6.js index 5b7eac78b2238e3a1bdd29b79f946523201b915a..7307bb0d31534f6a05aadc60d952c135505dd392 100644 --- a/core/modules/editor/js/editor.admin.es6.js +++ b/core/modules/editor/js/editor.admin.es6.js @@ -88,6 +88,20 @@ * Whether the given feature is allowed by the current filters. */ featureIsAllowedByFilters(feature) { + /** + * Provided a section of a feature or filter rule, checks if no property + * values are defined for all properties: attributes, classes and styles. + * + * @param {object} section + * The section to check. + * + * @return {bool} + * Returns true if the section has empty properties, false otherwise. + */ + function emptyProperties(section) { + return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0; + } + /** * Generate the universe U of possible values that can result from the * feature's rules' requirements. @@ -183,23 +197,10 @@ } /** - * Provided a section of a feature or filter rule, checks if no property - * values are defined for all properties: attributes, classes and styles. - * - * @param {object} section - * The section to check. - * - * @return {bool} - * Returns true if the section has empty properties, false otherwise. - */ - function emptyProperties(section) { - return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0; - } - - /** - * Calls findPropertyValueOnTag on the given tag for every property value - * that is listed in the "propertyValues" parameter. Supports the wildcard - * tag. + * Finds out if a specific property value (potentially containing + * wildcards) exists on the given tag. When the "allowing" parameter + * equals true, the universe will be updated if that specific property + * value exists. Returns true if found, false otherwise. * * @param {object} universe * The universe to check. @@ -207,24 +208,50 @@ * The tag to look for. * @param {string} property * The property to check. - * @param {Array} propertyValues - * Values of the property to check. + * @param {string} propertyValue + * The property value to check. * @param {bool} allowing * Whether to update the universe or not. * * @return {bool} * Returns true if found, false otherwise. */ - function findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing) { - // Detect the wildcard case. - if (tag === '*') { - return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing); + function findPropertyValueOnTag(universe, tag, property, propertyValue, allowing) { + // If the tag does not exist in the universe, then it definitely can't + // have this specific property value. + if (!_.has(universe, tag)) { + return false; } + const key = `${property}:${propertyValue}`; + + // Track whether a tag was touched by a filter rule that allows specific + // property values on this particular tag. + // @see generateUniverseFromFeatureRequirements + if (allowing) { + universe[tag].touchedByAllowedPropertyRule = true; + } + + // The simple case: no wildcard in property value. + if (_.indexOf(propertyValue, '*') === -1) { + if (_.has(universe, tag) && _.has(universe[tag], key)) { + if (allowing) { + universe[tag][key] = true; + } + return true; + } + return false; + } + // The complex case: wildcard in property value. + let atLeastOneFound = false; - _.each(propertyValues, (propertyValue) => { - if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) { + const regex = key.replace(/\*/g, '[^ ]*'); + _.each(_.keys(universe[tag]), (key) => { + if (key.match(regex)) { atLeastOneFound = true; + if (allowing) { + universe[tag][key] = true; + } } }); return atLeastOneFound; @@ -248,6 +275,7 @@ function findPropertyValuesOnAllTags(universe, property, propertyValues, allowing) { let atLeastOneFound = false; _.each(_.keys(universe), (tag) => { + // eslint-disable-next-line no-use-before-define if (findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing)) { atLeastOneFound = true; } @@ -256,10 +284,9 @@ } /** - * Finds out if a specific property value (potentially containing - * wildcards) exists on the given tag. When the "allowing" parameter - * equals true, the universe will be updated if that specific property - * value exists. Returns true if found, false otherwise. + * Calls findPropertyValueOnTag on the given tag for every property value + * that is listed in the "propertyValues" parameter. Supports the wildcard + * tag. * * @param {object} universe * The universe to check. @@ -267,55 +294,49 @@ * The tag to look for. * @param {string} property * The property to check. - * @param {string} propertyValue - * The property value to check. + * @param {Array} propertyValues + * Values of the property to check. * @param {bool} allowing * Whether to update the universe or not. * * @return {bool} * Returns true if found, false otherwise. */ - function findPropertyValueOnTag(universe, tag, property, propertyValue, allowing) { - // If the tag does not exist in the universe, then it definitely can't - // have this specific property value. - if (!_.has(universe, tag)) { - return false; - } - - const key = `${property}:${propertyValue}`; - - // Track whether a tag was touched by a filter rule that allows specific - // property values on this particular tag. - // @see generateUniverseFromFeatureRequirements - if (allowing) { - universe[tag].touchedByAllowedPropertyRule = true; - } - - // The simple case: no wildcard in property value. - if (_.indexOf(propertyValue, '*') === -1) { - if (_.has(universe, tag) && _.has(universe[tag], key)) { - if (allowing) { - universe[tag][key] = true; - } - return true; - } - return false; + function findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing) { + // Detect the wildcard case. + if (tag === '*') { + return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing); } - // The complex case: wildcard in property value. let atLeastOneFound = false; - const regex = key.replace(/\*/g, '[^ ]*'); - _.each(_.keys(universe[tag]), (key) => { - if (key.match(regex)) { + _.each(propertyValues, (propertyValue) => { + if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) { atLeastOneFound = true; - if (allowing) { - universe[tag][key] = true; - } } }); return atLeastOneFound; } + /** + * Calls deleteFromUniverseIfAllowed for all tags in the universe. + * + * @param {object} universe + * The universe to delete from. + * + * @return {bool} + * Whether something was deleted from the universe. + */ + function deleteAllTagsFromUniverseIfAllowed(universe) { + let atLeastOneDeleted = false; + _.each(_.keys(universe), (tag) => { + // eslint-disable-next-line no-use-before-define + if (deleteFromUniverseIfAllowed(universe, tag)) { + atLeastOneDeleted = true; + } + }); + return atLeastOneDeleted; + } + /** * Deletes a tag from the universe if the tag itself and each of its * properties are marked as allowed. @@ -340,25 +361,6 @@ return false; } - /** - * Calls deleteFromUniverseIfAllowed for all tags in the universe. - * - * @param {object} universe - * The universe to delete from. - * - * @return {bool} - * Whether something was deleted from the universe. - */ - function deleteAllTagsFromUniverseIfAllowed(universe) { - let atLeastOneDeleted = false; - _.each(_.keys(universe), (tag) => { - if (deleteFromUniverseIfAllowed(universe, tag)) { - atLeastOneDeleted = true; - } - }); - return atLeastOneDeleted; - } - /** * Checks if any filter rule forbids either a tag or a tag property value * that exists in the universe. diff --git a/core/modules/editor/js/editor.admin.js b/core/modules/editor/js/editor.admin.js index 4a1014096eed5321dd939219c86d3cd01fb81c37..3d88c936686ee9928af979bef90cf33adb932a17 100644 --- a/core/modules/editor/js/editor.admin.js +++ b/core/modules/editor/js/editor.admin.js @@ -17,6 +17,10 @@ $(document).trigger('drupalEditorFeatureModified', feature); }, featureIsAllowedByFilters: function featureIsAllowedByFilters(feature) { + function emptyProperties(section) { + return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0; + } + function generateUniverseFromFeatureRequirements(feature) { var properties = ['attributes', 'styles', 'classes']; var universe = {}; @@ -51,34 +55,6 @@ return universe; } - function emptyProperties(section) { - return section.attributes.length === 0 && section.classes.length === 0 && section.styles.length === 0; - } - - function findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing) { - if (tag === '*') { - return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing); - } - - var atLeastOneFound = false; - _.each(propertyValues, function (propertyValue) { - if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) { - atLeastOneFound = true; - } - }); - return atLeastOneFound; - } - - function findPropertyValuesOnAllTags(universe, property, propertyValues, allowing) { - var atLeastOneFound = false; - _.each(_.keys(universe), function (tag) { - if (findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing)) { - atLeastOneFound = true; - } - }); - return atLeastOneFound; - } - function findPropertyValueOnTag(universe, tag, property, propertyValue, allowing) { if (!_.has(universe, tag)) { return false; @@ -114,15 +90,28 @@ return atLeastOneFound; } - function deleteFromUniverseIfAllowed(universe, tag) { + function findPropertyValuesOnAllTags(universe, property, propertyValues, allowing) { + var atLeastOneFound = false; + _.each(_.keys(universe), function (tag) { + if (findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing)) { + atLeastOneFound = true; + } + }); + return atLeastOneFound; + } + + function findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing) { if (tag === '*') { - return deleteAllTagsFromUniverseIfAllowed(universe); - } - if (_.has(universe, tag) && _.every(_.omit(universe[tag], 'touchedByAllowedPropertyRule'))) { - delete universe[tag]; - return true; + return findPropertyValuesOnAllTags(universe, property, propertyValues, allowing); } - return false; + + var atLeastOneFound = false; + _.each(propertyValues, function (propertyValue) { + if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) { + atLeastOneFound = true; + } + }); + return atLeastOneFound; } function deleteAllTagsFromUniverseIfAllowed(universe) { @@ -135,6 +124,17 @@ return atLeastOneDeleted; } + function deleteFromUniverseIfAllowed(universe, tag) { + if (tag === '*') { + return deleteAllTagsFromUniverseIfAllowed(universe); + } + if (_.has(universe, tag) && _.every(_.omit(universe[tag], 'touchedByAllowedPropertyRule'))) { + delete universe[tag]; + return true; + } + return false; + } + function anyForbiddenFilterRuleMatches(universe, filterStatus) { var properties = ['attributes', 'styles', 'classes']; diff --git a/core/modules/editor/js/editor.es6.js b/core/modules/editor/js/editor.es6.js index b87e9141fb62f28193153398558e55fa44feceb4..f3fe38956870e7a28bbbfcfc89c78957567c78af 100644 --- a/core/modules/editor/js/editor.es6.js +++ b/core/modules/editor/js/editor.es6.js @@ -20,6 +20,46 @@ return $(`#${fieldId}`).get(0); } + /** + * Filter away XSS attack vectors when switching text formats. + * + * @param {HTMLElement} field + * The textarea DOM element. + * @param {object} format + * The text format that's being activated, from + * drupalSettings.editor.formats. + * @param {string} originalFormatID + * The text format ID of the original text format. + * @param {function} callback + * A callback to be called (with no parameters) after the field's value has + * been XSS filtered. + */ + function filterXssWhenSwitching(field, format, originalFormatID, callback) { + // A text editor that already is XSS-safe needs no additional measures. + if (format.editor.isXssSafe) { + callback(field, format); + } + // Otherwise, ensure XSS safety: let the server XSS filter this value. + else { + $.ajax({ + url: Drupal.url(`editor/filter_xss/${format.format}`), + type: 'POST', + data: { + value: field.value, + original_format_id: originalFormatID, + }, + dataType: 'json', + success(xssFilteredValue) { + // If the server returns false, then no XSS filtering is needed. + if (xssFilteredValue !== false) { + field.value = xssFilteredValue; + } + callback(field, format); + }, + }); + } + } + /** * Changes the text editor on a text area. * @@ -271,44 +311,4 @@ } } }; - - /** - * Filter away XSS attack vectors when switching text formats. - * - * @param {HTMLElement} field - * The textarea DOM element. - * @param {object} format - * The text format that's being activated, from - * drupalSettings.editor.formats. - * @param {string} originalFormatID - * The text format ID of the original text format. - * @param {function} callback - * A callback to be called (with no parameters) after the field's value has - * been XSS filtered. - */ - function filterXssWhenSwitching(field, format, originalFormatID, callback) { - // A text editor that already is XSS-safe needs no additional measures. - if (format.editor.isXssSafe) { - callback(field, format); - } - // Otherwise, ensure XSS safety: let the server XSS filter this value. - else { - $.ajax({ - url: Drupal.url(`editor/filter_xss/${format.format}`), - type: 'POST', - data: { - value: field.value, - original_format_id: originalFormatID, - }, - dataType: 'json', - success(xssFilteredValue) { - // If the server returns false, then no XSS filtering is needed. - if (xssFilteredValue !== false) { - field.value = xssFilteredValue; - } - callback(field, format); - }, - }); - } - } }(jQuery, Drupal, drupalSettings)); diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js index 30177497dddc09fb0f28357a60dacad73512967d..6448953f190acd372154c2ec9d194fb1c189984d 100644 --- a/core/modules/editor/js/editor.js +++ b/core/modules/editor/js/editor.js @@ -12,6 +12,28 @@ return $('#' + fieldId).get(0); } + function filterXssWhenSwitching(field, format, originalFormatID, callback) { + if (format.editor.isXssSafe) { + callback(field, format); + } else { + $.ajax({ + url: Drupal.url('editor/filter_xss/' + format.format), + type: 'POST', + data: { + value: field.value, + original_format_id: originalFormatID + }, + dataType: 'json', + success: function success(xssFilteredValue) { + if (xssFilteredValue !== false) { + field.value = xssFilteredValue; + } + callback(field, format); + } + }); + } + } + function changeTextEditor(field, newFormatID) { var previousFormatID = field.getAttribute('data-editor-active-text-format'); @@ -168,26 +190,4 @@ } } }; - - function filterXssWhenSwitching(field, format, originalFormatID, callback) { - if (format.editor.isXssSafe) { - callback(field, format); - } else { - $.ajax({ - url: Drupal.url('editor/filter_xss/' + format.format), - type: 'POST', - data: { - value: field.value, - original_format_id: originalFormatID - }, - dataType: 'json', - success: function success(xssFilteredValue) { - if (xssFilteredValue !== false) { - field.value = xssFilteredValue; - } - callback(field, format); - } - }); - } - } })(jQuery, Drupal, drupalSettings); \ No newline at end of file diff --git a/core/modules/quickedit/js/editors/formEditor.es6.js b/core/modules/quickedit/js/editors/formEditor.es6.js index c12308403d9c9581ad8a98100119ea1cdd46aa5a..f2eb3f03239509a451146720c66cef4e4287a5e7 100644 --- a/core/modules/quickedit/js/editors/formEditor.es6.js +++ b/core/modules/quickedit/js/editors/formEditor.es6.js @@ -189,17 +189,17 @@ const editorModel = this.model; const fieldModel = this.fieldModel; - function cleanUpAjax() { - Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax); - formSaveAjax = null; - } - // Create an AJAX object for the form associated with the field. let formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({ nocssjs: false, other_view_modes: fieldModel.findOtherViewModes(), }, $submit); + function cleanUpAjax() { + Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax); + formSaveAjax = null; + } + // Successfully saved. formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) { cleanUpAjax(); diff --git a/core/modules/quickedit/js/editors/formEditor.js b/core/modules/quickedit/js/editors/formEditor.js index 8a872edc57ef94b770f1083e7e2d932c4050dc1d..1ef1fa4077a541aa283b0d99d95a8b8e4ac857cc 100644 --- a/core/modules/quickedit/js/editors/formEditor.js +++ b/core/modules/quickedit/js/editors/formEditor.js @@ -122,16 +122,16 @@ var editorModel = this.model; var fieldModel = this.fieldModel; - function cleanUpAjax() { - Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax); - formSaveAjax = null; - } - var formSaveAjax = Drupal.quickedit.util.form.ajaxifySaving({ nocssjs: false, other_view_modes: fieldModel.findOtherViewModes() }, $submit); + function cleanUpAjax() { + Drupal.quickedit.util.form.unajaxifySaving(formSaveAjax); + formSaveAjax = null; + } + formSaveAjax.commands.quickeditFieldFormSaved = function (ajax, response, status) { cleanUpAjax(); diff --git a/core/modules/quickedit/js/quickedit.es6.js b/core/modules/quickedit/js/quickedit.es6.js index ba742033fcac818a83753b7ec07bed0a066c7ca4..eaacc3b6c51dc595c0e305d5f22355f54191055c 100644 --- a/core/modules/quickedit/js/quickedit.es6.js +++ b/core/modules/quickedit/js/quickedit.es6.js @@ -60,249 +60,6 @@ */ const entityInstancesTracker = {}; - /** - * - * @type {Drupal~behavior} - */ - Drupal.behaviors.quickedit = { - attach(context) { - // Initialize the Quick Edit app once per page load. - $('body').once('quickedit-init').each(initQuickEdit); - - // Find all in-place editable fields, if any. - const $fields = $(context).find('[data-quickedit-field-id]').once('quickedit'); - if ($fields.length === 0) { - return; - } - - // Process each entity element: identical entities that appear multiple - // times will get a numeric identifier, starting at 0. - $(context).find('[data-quickedit-entity-id]').once('quickedit').each((index, entityElement) => { - processEntity(entityElement); - }); - - // Process each field element: queue to be used or to fetch metadata. - // When a field is being rerendered after editing, it will be processed - // immediately. New fields will be unable to be processed immediately, - // but will instead be queued to have their metadata fetched, which occurs - // below in fetchMissingMetaData(). - $fields.each((index, fieldElement) => { - processField(fieldElement); - }); - - // Entities and fields on the page have been detected, try to set up the - // contextual links for those entities that already have the necessary - // meta- data in the client-side cache. - contextualLinksQueue = _.filter(contextualLinksQueue, contextualLink => !initializeEntityContextualLink(contextualLink)); - - // Fetch metadata for any fields that are queued to retrieve it. - fetchMissingMetadata((fieldElementsWithFreshMetadata) => { - // Metadata has been fetched, reprocess fields whose metadata was - // missing. - _.each(fieldElementsWithFreshMetadata, processField); - - // Metadata has been fetched, try to set up more contextual links now. - contextualLinksQueue = _.filter(contextualLinksQueue, contextualLink => !initializeEntityContextualLink(contextualLink)); - }); - }, - detach(context, settings, trigger) { - if (trigger === 'unload') { - deleteContainedModelsAndQueues($(context)); - } - }, - }; - - /** - * - * @namespace - */ - Drupal.quickedit = { - - /** - * A {@link Drupal.quickedit.AppView} instance. - */ - app: null, - - /** - * @type {object} - * - * @prop {Array.<Drupal.quickedit.EntityModel>} entities - * @prop {Array.<Drupal.quickedit.FieldModel>} fields - */ - collections: { - // All in-place editable entities (Drupal.quickedit.EntityModel) on the - // page. - entities: null, - // All in-place editable fields (Drupal.quickedit.FieldModel) on the page. - fields: null, - }, - - /** - * In-place editors will register themselves in this object. - * - * @namespace - */ - editors: {}, - - /** - * Per-field metadata that indicates whether in-place editing is allowed, - * which in-place editor should be used, etc. - * - * @namespace - */ - metadata: { - - /** - * Check if a field exists in storage. - * - * @param {string} fieldID - * The field id to check. - * - * @return {bool} - * Whether it was found or not. - */ - has(fieldID) { - return storage.getItem(this._prefixFieldID(fieldID)) !== null; - }, - - /** - * Add metadata to a field id. - * - * @param {string} fieldID - * The field ID to add data to. - * @param {object} metadata - * Metadata to add. - */ - add(fieldID, metadata) { - storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata)); - }, - - /** - * Get a key from a field id. - * - * @param {string} fieldID - * The field ID to check. - * @param {string} [key] - * The key to check. If empty, will return all metadata. - * - * @return {object|*} - * The value for the key, if defined. Otherwise will return all metadata - * for the specified field id. - * - */ - get(fieldID, key) { - const metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID))); - return (typeof key === 'undefined') ? metadata : metadata[key]; - }, - - /** - * Prefix the field id. - * - * @param {string} fieldID - * The field id to prefix. - * - * @return {string} - * A prefixed field id. - */ - _prefixFieldID(fieldID) { - return `Drupal.quickedit.metadata.${fieldID}`; - }, - - /** - * Unprefix the field id. - * - * @param {string} fieldID - * The field id to unprefix. - * - * @return {string} - * An unprefixed field id. - */ - _unprefixFieldID(fieldID) { - // Strip "Drupal.quickedit.metadata.", which is 26 characters long. - return fieldID.substring(26); - }, - - /** - * Intersection calculation. - * - * @param {Array} fieldIDs - * An array of field ids to compare to prefix field id. - * - * @return {Array} - * The intersection found. - */ - intersection(fieldIDs) { - const prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID); - const intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage)); - return _.map(intersection, this._unprefixFieldID); - }, - }, - }; - - // Clear the Quick Edit metadata cache whenever the current user's set of - // permissions changes. - const permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash'); - const permissionsHashValue = storage.getItem(permissionsHashKey); - const permissionsHash = drupalSettings.user.permissionsHash; - if (permissionsHashValue !== permissionsHash) { - if (typeof permissionsHash === 'string') { - _.chain(storage).keys().each((key) => { - if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') { - storage.removeItem(key); - } - }); - } - storage.setItem(permissionsHashKey, permissionsHash); - } - - /** - * Detect contextual links on entities annotated by quickedit. - * - * Queue contextual links to be processed. - * - * @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) => { - if (data.$region.is('[data-quickedit-entity-id]')) { - // If the contextual link is cached on the client side, an entity instance - // will not yet have been assigned. So assign one. - if (!data.$region.is('[data-quickedit-entity-instance-id]')) { - data.$region.once('quickedit'); - processEntity(data.$region.get(0)); - } - const contextualLink = { - entityID: data.$region.attr('data-quickedit-entity-id'), - entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'), - el: data.$el[0], - region: data.$region[0], - }; - // Set up contextual links for this, otherwise queue it to be set up - // later. - if (!initializeEntityContextualLink(contextualLink)) { - contextualLinksQueue.push(contextualLink); - } - } - }); - - /** - * Extracts the entity ID from a field ID. - * - * @param {string} fieldID - * A field ID: a string of the format - * `<entity type>/<id>/<field name>/<language>/<view mode>`. - * - * @return {string} - * An entity ID: a string of the format `<entity type>/<id>`. - */ - function extractEntityID(fieldID) { - return fieldID.split('/').slice(0, 2).join('/'); - } - /** * Initialize the Quick Edit app. * @@ -345,79 +102,16 @@ } /** - * Fetch the field's metadata; queue or initialize it (if EntityModel exists). + * Initialize a field; create FieldModel. * * @param {HTMLElement} fieldElement - * A Drupal Field API field's DOM element with a data-quickedit-field-id - * attribute. - */ - function processField(fieldElement) { - const metadata = Drupal.quickedit.metadata; - const fieldID = fieldElement.getAttribute('data-quickedit-field-id'); - const entityID = extractEntityID(fieldID); - // Figure out the instance ID by looking at the ancestor - // [data-quickedit-entity-id] element's data-quickedit-entity-instance-id - // attribute. - const entityElementSelector = `[data-quickedit-entity-id="${entityID}"]`; - const $entityElement = $(entityElementSelector); - - // If there are no elements returned from `entityElementSelector` - // throw an error. Check the browser console for this message. - if (!$entityElement.length) { - throw new Error(`Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id="${fieldID}"]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id="${entityID}"]. This is typically caused by the theme's template for this entity type forgetting to print the attributes.`); - } - let entityElement = $(fieldElement).closest($entityElement); - - // In the case of a full entity view page, the entity title is rendered - // outside of "the entity DOM node": it's rendered as the page title. So in - // this case, we find the lowest common parent element (deepest in the tree) - // and consider that the entity element. - if (entityElement.length === 0) { - const $lowestCommonParent = $entityElement.parents().has(fieldElement).first(); - entityElement = $lowestCommonParent.find($entityElement); - } - const entityInstanceID = entityElement - .get(0) - .getAttribute('data-quickedit-entity-instance-id'); - - // Early-return if metadata for this field is missing. - if (!metadata.has(fieldID)) { - fieldsMetadataQueue.push({ - el: fieldElement, - fieldID, - entityID, - entityInstanceID, - }); - return; - } - // Early-return if the user is not allowed to in-place edit this field. - if (metadata.get(fieldID, 'access') !== true) { - return; - } - - // If an EntityModel for this field already exists (and hence also a "Quick - // edit" contextual link), then initialize it immediately. - if (Drupal.quickedit.collections.entities.findWhere({ entityID, entityInstanceID })) { - initializeField(fieldElement, fieldID, entityID, entityInstanceID); - } - // Otherwise: queue the field. It is now available to be set up when its - // corresponding entity becomes in-place editable. - else { - fieldsAvailableQueue.push({ el: fieldElement, fieldID, entityID, entityInstanceID }); - } - } - - /** - * Initialize a field; create FieldModel. - * - * @param {HTMLElement} fieldElement - * The field's DOM element. - * @param {string} fieldID - * The field's ID. - * @param {string} entityID - * The field's entity's ID. - * @param {string} entityInstanceID - * The field's entity's instance ID. + * The field's DOM element. + * @param {string} fieldID + * The field's ID. + * @param {string} entityID + * The field's entity's ID. + * @param {string} entityInstanceID + * The field's entity's instance ID. */ function initializeField(fieldElement, fieldID, entityID, entityInstanceID) { const entity = Drupal.quickedit.collections.entities.findWhere({ @@ -441,44 +135,6 @@ Drupal.quickedit.collections.fields.add(field); } - /** - * Fetches metadata for fields whose metadata is missing. - * - * Fields whose metadata is missing are tracked at fieldsMetadataQueue. - * - * @param {function} callback - * A callback function that receives field elements whose metadata will just - * have been fetched. - */ - function fetchMissingMetadata(callback) { - if (fieldsMetadataQueue.length) { - const fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID'); - const fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el'); - let entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true); - // Ensure we only request entityIDs for which we don't have metadata yet. - entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs)); - fieldsMetadataQueue = []; - - $.ajax({ - url: Drupal.url('quickedit/metadata'), - type: 'POST', - data: { - 'fields[]': fieldIDs, - 'entities[]': entityIDs, - }, - dataType: 'json', - success(results) { - // Store the metadata. - _.each(results, (fieldMetadata, fieldID) => { - Drupal.quickedit.metadata.add(fieldID, fieldMetadata); - }); - - callback(fieldElementsWithoutMetadata); - }, - }); - } - } - /** * Loads missing in-place editor's attachments (JavaScript and CSS files). * @@ -629,6 +285,83 @@ return false; } + /** + * Extracts the entity ID from a field ID. + * + * @param {string} fieldID + * A field ID: a string of the format + * `<entity type>/<id>/<field name>/<language>/<view mode>`. + * + * @return {string} + * An entity ID: a string of the format `<entity type>/<id>`. + */ + function extractEntityID(fieldID) { + return fieldID.split('/').slice(0, 2).join('/'); + } + + /** + * Fetch the field's metadata; queue or initialize it (if EntityModel exists). + * + * @param {HTMLElement} fieldElement + * A Drupal Field API field's DOM element with a data-quickedit-field-id + * attribute. + */ + function processField(fieldElement) { + const metadata = Drupal.quickedit.metadata; + const fieldID = fieldElement.getAttribute('data-quickedit-field-id'); + const entityID = extractEntityID(fieldID); + // Figure out the instance ID by looking at the ancestor + // [data-quickedit-entity-id] element's data-quickedit-entity-instance-id + // attribute. + const entityElementSelector = `[data-quickedit-entity-id="${entityID}"]`; + const $entityElement = $(entityElementSelector); + + // If there are no elements returned from `entityElementSelector` + // throw an error. Check the browser console for this message. + if (!$entityElement.length) { + throw new Error(`Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id="${fieldID}"]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id="${entityID}"]. This is typically caused by the theme's template for this entity type forgetting to print the attributes.`); + } + let entityElement = $(fieldElement).closest($entityElement); + + // In the case of a full entity view page, the entity title is rendered + // outside of "the entity DOM node": it's rendered as the page title. So in + // this case, we find the lowest common parent element (deepest in the tree) + // and consider that the entity element. + if (entityElement.length === 0) { + const $lowestCommonParent = $entityElement.parents().has(fieldElement).first(); + entityElement = $lowestCommonParent.find($entityElement); + } + const entityInstanceID = entityElement + .get(0) + .getAttribute('data-quickedit-entity-instance-id'); + + // Early-return if metadata for this field is missing. + if (!metadata.has(fieldID)) { + fieldsMetadataQueue.push({ + el: fieldElement, + fieldID, + entityID, + entityInstanceID, + }); + return; + } + // Early-return if the user is not allowed to in-place edit this field. + if (metadata.get(fieldID, 'access') !== true) { + return; + } + + // If an EntityModel for this field already exists (and hence also a "Quick + // edit" contextual link), then initialize it immediately. + if (Drupal.quickedit.collections.entities.findWhere({ entityID, entityInstanceID })) { + initializeField(fieldElement, fieldID, entityID, entityInstanceID); + } + // Otherwise: queue the field. It is now available to be set up when its + // corresponding entity becomes in-place editable. + else { + fieldsAvailableQueue.push({ el: fieldElement, fieldID, entityID, entityInstanceID }); + } + } + /** * Delete models and queue items that are contained within a given context. * @@ -683,4 +416,271 @@ fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement); }); } + + /** + * Fetches metadata for fields whose metadata is missing. + * + * Fields whose metadata is missing are tracked at fieldsMetadataQueue. + * + * @param {function} callback + * A callback function that receives field elements whose metadata will just + * have been fetched. + */ + function fetchMissingMetadata(callback) { + if (fieldsMetadataQueue.length) { + const fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID'); + const fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el'); + let entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true); + // Ensure we only request entityIDs for which we don't have metadata yet. + entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs)); + fieldsMetadataQueue = []; + + $.ajax({ + url: Drupal.url('quickedit/metadata'), + type: 'POST', + data: { + 'fields[]': fieldIDs, + 'entities[]': entityIDs, + }, + dataType: 'json', + success(results) { + // Store the metadata. + _.each(results, (fieldMetadata, fieldID) => { + Drupal.quickedit.metadata.add(fieldID, fieldMetadata); + }); + + callback(fieldElementsWithoutMetadata); + }, + }); + } + } + + /** + * + * @type {Drupal~behavior} + */ + Drupal.behaviors.quickedit = { + attach(context) { + // Initialize the Quick Edit app once per page load. + $('body').once('quickedit-init').each(initQuickEdit); + + // Find all in-place editable fields, if any. + const $fields = $(context).find('[data-quickedit-field-id]').once('quickedit'); + if ($fields.length === 0) { + return; + } + + // Process each entity element: identical entities that appear multiple + // times will get a numeric identifier, starting at 0. + $(context).find('[data-quickedit-entity-id]').once('quickedit').each((index, entityElement) => { + processEntity(entityElement); + }); + + // Process each field element: queue to be used or to fetch metadata. + // When a field is being rerendered after editing, it will be processed + // immediately. New fields will be unable to be processed immediately, + // but will instead be queued to have their metadata fetched, which occurs + // below in fetchMissingMetaData(). + $fields.each((index, fieldElement) => { + processField(fieldElement); + }); + + // Entities and fields on the page have been detected, try to set up the + // contextual links for those entities that already have the necessary + // meta- data in the client-side cache. + contextualLinksQueue = _.filter(contextualLinksQueue, contextualLink => !initializeEntityContextualLink(contextualLink)); + + // Fetch metadata for any fields that are queued to retrieve it. + fetchMissingMetadata((fieldElementsWithFreshMetadata) => { + // Metadata has been fetched, reprocess fields whose metadata was + // missing. + _.each(fieldElementsWithFreshMetadata, processField); + + // Metadata has been fetched, try to set up more contextual links now. + contextualLinksQueue = _.filter(contextualLinksQueue, contextualLink => !initializeEntityContextualLink(contextualLink)); + }); + }, + detach(context, settings, trigger) { + if (trigger === 'unload') { + deleteContainedModelsAndQueues($(context)); + } + }, + }; + + /** + * + * @namespace + */ + Drupal.quickedit = { + + /** + * A {@link Drupal.quickedit.AppView} instance. + */ + app: null, + + /** + * @type {object} + * + * @prop {Array.<Drupal.quickedit.EntityModel>} entities + * @prop {Array.<Drupal.quickedit.FieldModel>} fields + */ + collections: { + // All in-place editable entities (Drupal.quickedit.EntityModel) on the + // page. + entities: null, + // All in-place editable fields (Drupal.quickedit.FieldModel) on the page. + fields: null, + }, + + /** + * In-place editors will register themselves in this object. + * + * @namespace + */ + editors: {}, + + /** + * Per-field metadata that indicates whether in-place editing is allowed, + * which in-place editor should be used, etc. + * + * @namespace + */ + metadata: { + + /** + * Check if a field exists in storage. + * + * @param {string} fieldID + * The field id to check. + * + * @return {bool} + * Whether it was found or not. + */ + has(fieldID) { + return storage.getItem(this._prefixFieldID(fieldID)) !== null; + }, + + /** + * Add metadata to a field id. + * + * @param {string} fieldID + * The field ID to add data to. + * @param {object} metadata + * Metadata to add. + */ + add(fieldID, metadata) { + storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata)); + }, + + /** + * Get a key from a field id. + * + * @param {string} fieldID + * The field ID to check. + * @param {string} [key] + * The key to check. If empty, will return all metadata. + * + * @return {object|*} + * The value for the key, if defined. Otherwise will return all metadata + * for the specified field id. + * + */ + get(fieldID, key) { + const metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID))); + return (typeof key === 'undefined') ? metadata : metadata[key]; + }, + + /** + * Prefix the field id. + * + * @param {string} fieldID + * The field id to prefix. + * + * @return {string} + * A prefixed field id. + */ + _prefixFieldID(fieldID) { + return `Drupal.quickedit.metadata.${fieldID}`; + }, + + /** + * Unprefix the field id. + * + * @param {string} fieldID + * The field id to unprefix. + * + * @return {string} + * An unprefixed field id. + */ + _unprefixFieldID(fieldID) { + // Strip "Drupal.quickedit.metadata.", which is 26 characters long. + return fieldID.substring(26); + }, + + /** + * Intersection calculation. + * + * @param {Array} fieldIDs + * An array of field ids to compare to prefix field id. + * + * @return {Array} + * The intersection found. + */ + intersection(fieldIDs) { + const prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID); + const intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage)); + return _.map(intersection, this._unprefixFieldID); + }, + }, + }; + + // Clear the Quick Edit metadata cache whenever the current user's set of + // permissions changes. + const permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash'); + const permissionsHashValue = storage.getItem(permissionsHashKey); + const permissionsHash = drupalSettings.user.permissionsHash; + if (permissionsHashValue !== permissionsHash) { + if (typeof permissionsHash === 'string') { + _.chain(storage).keys().each((key) => { + if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') { + storage.removeItem(key); + } + }); + } + storage.setItem(permissionsHashKey, permissionsHash); + } + + /** + * Detect contextual links on entities annotated by quickedit. + * + * Queue contextual links to be processed. + * + * @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) => { + if (data.$region.is('[data-quickedit-entity-id]')) { + // If the contextual link is cached on the client side, an entity instance + // will not yet have been assigned. So assign one. + if (!data.$region.is('[data-quickedit-entity-instance-id]')) { + data.$region.once('quickedit'); + processEntity(data.$region.get(0)); + } + const contextualLink = { + entityID: data.$region.attr('data-quickedit-entity-id'), + entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'), + el: data.$el[0], + region: data.$region[0], + }; + // Set up contextual links for this, otherwise queue it to be set up + // later. + if (!initializeEntityContextualLink(contextualLink)) { + contextualLinksQueue.push(contextualLink); + } + } + }); }(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage)); diff --git a/core/modules/quickedit/js/quickedit.js b/core/modules/quickedit/js/quickedit.js index 4548c013a33f2b8d7001dbb4738441e5d05cdce2..1c677aa1e904b2d4c2af3405a428a86633824d34 100644 --- a/core/modules/quickedit/js/quickedit.js +++ b/core/modules/quickedit/js/quickedit.js @@ -20,115 +20,6 @@ var entityInstancesTracker = {}; - Drupal.behaviors.quickedit = { - attach: function attach(context) { - $('body').once('quickedit-init').each(initQuickEdit); - - var $fields = $(context).find('[data-quickedit-field-id]').once('quickedit'); - if ($fields.length === 0) { - return; - } - - $(context).find('[data-quickedit-entity-id]').once('quickedit').each(function (index, entityElement) { - processEntity(entityElement); - }); - - $fields.each(function (index, fieldElement) { - processField(fieldElement); - }); - - contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) { - return !initializeEntityContextualLink(contextualLink); - }); - - fetchMissingMetadata(function (fieldElementsWithFreshMetadata) { - _.each(fieldElementsWithFreshMetadata, processField); - - contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) { - return !initializeEntityContextualLink(contextualLink); - }); - }); - }, - detach: function detach(context, settings, trigger) { - if (trigger === 'unload') { - deleteContainedModelsAndQueues($(context)); - } - } - }; - - Drupal.quickedit = { - app: null, - - collections: { - entities: null, - - fields: null - }, - - editors: {}, - - metadata: { - has: function has(fieldID) { - return storage.getItem(this._prefixFieldID(fieldID)) !== null; - }, - add: function add(fieldID, metadata) { - storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata)); - }, - get: function get(fieldID, key) { - var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID))); - return typeof key === 'undefined' ? metadata : metadata[key]; - }, - _prefixFieldID: function _prefixFieldID(fieldID) { - return 'Drupal.quickedit.metadata.' + fieldID; - }, - _unprefixFieldID: function _unprefixFieldID(fieldID) { - return fieldID.substring(26); - }, - intersection: function intersection(fieldIDs) { - var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID); - var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage)); - return _.map(intersection, this._unprefixFieldID); - } - } - }; - - var permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash'); - var permissionsHashValue = storage.getItem(permissionsHashKey); - var permissionsHash = drupalSettings.user.permissionsHash; - if (permissionsHashValue !== permissionsHash) { - if (typeof permissionsHash === 'string') { - _.chain(storage).keys().each(function (key) { - if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') { - storage.removeItem(key); - } - }); - } - storage.setItem(permissionsHashKey, permissionsHash); - } - - $(document).on('drupalContextualLinkAdded', function (event, data) { - if (data.$region.is('[data-quickedit-entity-id]')) { - if (!data.$region.is('[data-quickedit-entity-instance-id]')) { - data.$region.once('quickedit'); - processEntity(data.$region.get(0)); - } - var contextualLink = { - entityID: data.$region.attr('data-quickedit-entity-id'), - entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'), - el: data.$el[0], - region: data.$region[0] - }; - - if (!initializeEntityContextualLink(contextualLink)) { - contextualLinksQueue.push(contextualLink); - } - } - }); - - function extractEntityID(fieldID) { - return fieldID.split('/').slice(0, 2).join('/'); - } - function initQuickEdit(bodyElement) { Drupal.quickedit.collections.entities = new Drupal.quickedit.EntityCollection(); Drupal.quickedit.collections.fields = new Drupal.quickedit.FieldCollection(); @@ -153,46 +44,6 @@ entityElement.setAttribute('data-quickedit-entity-instance-id', entityInstanceID); } - function processField(fieldElement) { - var metadata = Drupal.quickedit.metadata; - var fieldID = fieldElement.getAttribute('data-quickedit-field-id'); - var entityID = extractEntityID(fieldID); - - var entityElementSelector = '[data-quickedit-entity-id="' + entityID + '"]'; - var $entityElement = $(entityElementSelector); - - if (!$entityElement.length) { - throw new Error('Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id="' + fieldID + '"]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id="' + entityID + '"]. This is typically caused by the theme\'s template for this entity type forgetting to print the attributes.'); - } - var entityElement = $(fieldElement).closest($entityElement); - - if (entityElement.length === 0) { - var $lowestCommonParent = $entityElement.parents().has(fieldElement).first(); - entityElement = $lowestCommonParent.find($entityElement); - } - var entityInstanceID = entityElement.get(0).getAttribute('data-quickedit-entity-instance-id'); - - if (!metadata.has(fieldID)) { - fieldsMetadataQueue.push({ - el: fieldElement, - fieldID: fieldID, - entityID: entityID, - entityInstanceID: entityInstanceID - }); - return; - } - - if (metadata.get(fieldID, 'access') !== true) { - return; - } - - if (Drupal.quickedit.collections.entities.findWhere({ entityID: entityID, entityInstanceID: entityInstanceID })) { - initializeField(fieldElement, fieldID, entityID, entityInstanceID); - } else { - fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID }); - } - } - function initializeField(fieldElement, fieldID, entityID, entityInstanceID) { var entity = Drupal.quickedit.collections.entities.findWhere({ entityID: entityID, @@ -213,34 +64,6 @@ Drupal.quickedit.collections.fields.add(field); } - function fetchMissingMetadata(callback) { - if (fieldsMetadataQueue.length) { - var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID'); - var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el'); - var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true); - - entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs)); - fieldsMetadataQueue = []; - - $.ajax({ - url: Drupal.url('quickedit/metadata'), - type: 'POST', - data: { - 'fields[]': fieldIDs, - 'entities[]': entityIDs - }, - dataType: 'json', - success: function success(results) { - _.each(results, function (fieldMetadata, fieldID) { - Drupal.quickedit.metadata.add(fieldID, fieldMetadata); - }); - - callback(fieldElementsWithoutMetadata); - } - }); - } - } - function loadMissingEditors(callback) { var loadedEditors = _.keys(Drupal.quickedit.editors); var missingEditors = []; @@ -338,6 +161,50 @@ return false; } + function extractEntityID(fieldID) { + return fieldID.split('/').slice(0, 2).join('/'); + } + + function processField(fieldElement) { + var metadata = Drupal.quickedit.metadata; + var fieldID = fieldElement.getAttribute('data-quickedit-field-id'); + var entityID = extractEntityID(fieldID); + + var entityElementSelector = '[data-quickedit-entity-id="' + entityID + '"]'; + var $entityElement = $(entityElementSelector); + + if (!$entityElement.length) { + throw new Error('Quick Edit could not associate the rendered entity field markup (with [data-quickedit-field-id="' + fieldID + '"]) with the corresponding rendered entity markup: no parent DOM node found with [data-quickedit-entity-id="' + entityID + '"]. This is typically caused by the theme\'s template for this entity type forgetting to print the attributes.'); + } + var entityElement = $(fieldElement).closest($entityElement); + + if (entityElement.length === 0) { + var $lowestCommonParent = $entityElement.parents().has(fieldElement).first(); + entityElement = $lowestCommonParent.find($entityElement); + } + var entityInstanceID = entityElement.get(0).getAttribute('data-quickedit-entity-instance-id'); + + if (!metadata.has(fieldID)) { + fieldsMetadataQueue.push({ + el: fieldElement, + fieldID: fieldID, + entityID: entityID, + entityInstanceID: entityInstanceID + }); + return; + } + + if (metadata.get(fieldID, 'access') !== true) { + return; + } + + if (Drupal.quickedit.collections.entities.findWhere({ entityID: entityID, entityInstanceID: entityInstanceID })) { + initializeField(fieldElement, fieldID, entityID, entityInstanceID); + } else { + fieldsAvailableQueue.push({ el: fieldElement, fieldID: fieldID, entityID: entityID, entityInstanceID: entityInstanceID }); + } + } + function deleteContainedModelsAndQueues($context) { $context.find('[data-quickedit-entity-id]').addBack('[data-quickedit-entity-id]').each(function (index, entityElement) { var entityModel = Drupal.quickedit.collections.entities.findWhere({ el: entityElement }); @@ -371,4 +238,137 @@ fieldsAvailableQueue = _.filter(fieldsAvailableQueue, hasOtherFieldElement); }); } + + function fetchMissingMetadata(callback) { + if (fieldsMetadataQueue.length) { + var fieldIDs = _.pluck(fieldsMetadataQueue, 'fieldID'); + var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el'); + var entityIDs = _.uniq(_.pluck(fieldsMetadataQueue, 'entityID'), true); + + entityIDs = _.difference(entityIDs, Drupal.quickedit.metadata.intersection(entityIDs)); + fieldsMetadataQueue = []; + + $.ajax({ + url: Drupal.url('quickedit/metadata'), + type: 'POST', + data: { + 'fields[]': fieldIDs, + 'entities[]': entityIDs + }, + dataType: 'json', + success: function success(results) { + _.each(results, function (fieldMetadata, fieldID) { + Drupal.quickedit.metadata.add(fieldID, fieldMetadata); + }); + + callback(fieldElementsWithoutMetadata); + } + }); + } + } + + Drupal.behaviors.quickedit = { + attach: function attach(context) { + $('body').once('quickedit-init').each(initQuickEdit); + + var $fields = $(context).find('[data-quickedit-field-id]').once('quickedit'); + if ($fields.length === 0) { + return; + } + + $(context).find('[data-quickedit-entity-id]').once('quickedit').each(function (index, entityElement) { + processEntity(entityElement); + }); + + $fields.each(function (index, fieldElement) { + processField(fieldElement); + }); + + contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) { + return !initializeEntityContextualLink(contextualLink); + }); + + fetchMissingMetadata(function (fieldElementsWithFreshMetadata) { + _.each(fieldElementsWithFreshMetadata, processField); + + contextualLinksQueue = _.filter(contextualLinksQueue, function (contextualLink) { + return !initializeEntityContextualLink(contextualLink); + }); + }); + }, + detach: function detach(context, settings, trigger) { + if (trigger === 'unload') { + deleteContainedModelsAndQueues($(context)); + } + } + }; + + Drupal.quickedit = { + app: null, + + collections: { + entities: null, + + fields: null + }, + + editors: {}, + + metadata: { + has: function has(fieldID) { + return storage.getItem(this._prefixFieldID(fieldID)) !== null; + }, + add: function add(fieldID, metadata) { + storage.setItem(this._prefixFieldID(fieldID), JSON.stringify(metadata)); + }, + get: function get(fieldID, key) { + var metadata = JSON.parse(storage.getItem(this._prefixFieldID(fieldID))); + return typeof key === 'undefined' ? metadata : metadata[key]; + }, + _prefixFieldID: function _prefixFieldID(fieldID) { + return 'Drupal.quickedit.metadata.' + fieldID; + }, + _unprefixFieldID: function _unprefixFieldID(fieldID) { + return fieldID.substring(26); + }, + intersection: function intersection(fieldIDs) { + var prefixedFieldIDs = _.map(fieldIDs, this._prefixFieldID); + var intersection = _.intersection(prefixedFieldIDs, _.keys(sessionStorage)); + return _.map(intersection, this._unprefixFieldID); + } + } + }; + + var permissionsHashKey = Drupal.quickedit.metadata._prefixFieldID('permissionsHash'); + var permissionsHashValue = storage.getItem(permissionsHashKey); + var permissionsHash = drupalSettings.user.permissionsHash; + if (permissionsHashValue !== permissionsHash) { + if (typeof permissionsHash === 'string') { + _.chain(storage).keys().each(function (key) { + if (key.substring(0, 26) === 'Drupal.quickedit.metadata.') { + storage.removeItem(key); + } + }); + } + storage.setItem(permissionsHashKey, permissionsHash); + } + + $(document).on('drupalContextualLinkAdded', function (event, data) { + if (data.$region.is('[data-quickedit-entity-id]')) { + if (!data.$region.is('[data-quickedit-entity-instance-id]')) { + data.$region.once('quickedit'); + processEntity(data.$region.get(0)); + } + var contextualLink = { + entityID: data.$region.attr('data-quickedit-entity-id'), + entityInstanceID: data.$region.attr('data-quickedit-entity-instance-id'), + el: data.$el[0], + region: data.$region[0] + }; + + if (!initializeEntityContextualLink(contextualLink)) { + contextualLinksQueue.push(contextualLink); + } + } + }); })(jQuery, _, Backbone, Drupal, drupalSettings, window.JSON, window.sessionStorage); \ No newline at end of file diff --git a/core/modules/toolbar/js/toolbar.menu.es6.js b/core/modules/toolbar/js/toolbar.menu.es6.js index 03fd41f371dff0e8c61d48fa195f8217852dd6cd..1c06d8deff05343f6ca58335be1803a462f1d5a0 100644 --- a/core/modules/toolbar/js/toolbar.menu.es6.js +++ b/core/modules/toolbar/js/toolbar.menu.es6.js @@ -20,6 +20,30 @@ handleClose: Drupal.t('Collapse'), }; + /** + * Toggle the open/close state of a list is a menu. + * + * @param {jQuery} $item + * The li item to be toggled. + * + * @param {Boolean} switcher + * A flag that forces toggleClass to add or a remove a class, rather than + * simply toggling its presence. + */ + function toggleList($item, switcher) { + const $toggle = $item.children('.toolbar-box').children('.toolbar-handle'); + switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open'); + // Toggle the item open state. + $item.toggleClass('open', switcher); + // Twist the toggle. + $toggle.toggleClass('open', switcher); + // Adjust the toggle text. + $toggle + .find('.action') + // Expand Structure, Collapse Structure. + .text((switcher) ? ui.handleClose : ui.handleOpen); + } + /** * Handle clicks from the disclosure button on an item with sub-items. * @@ -56,30 +80,6 @@ event.stopPropagation(); } - /** - * Toggle the open/close state of a list is a menu. - * - * @param {jQuery} $item - * The li item to be toggled. - * - * @param {Boolean} switcher - * A flag that forces toggleClass to add or a remove a class, rather than - * simply toggling its presence. - */ - function toggleList($item, switcher) { - const $toggle = $item.children('.toolbar-box').children('.toolbar-handle'); - switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open'); - // Toggle the item open state. - $item.toggleClass('open', switcher); - // Twist the toggle. - $toggle.toggleClass('open', switcher); - // Adjust the toggle text. - $toggle - .find('.action') - // Expand Structure, Collapse Structure. - .text((switcher) ? ui.handleClose : ui.handleOpen); - } - /** * Add markup to the menu elements. * diff --git a/core/modules/toolbar/js/toolbar.menu.js b/core/modules/toolbar/js/toolbar.menu.js index 45cfafbca87c36e0cdc3bf14f53a87d6044b093a..23f1076ce1ee5f4cceef01ffcb9e1ebfa78ecd0e 100644 --- a/core/modules/toolbar/js/toolbar.menu.js +++ b/core/modules/toolbar/js/toolbar.menu.js @@ -14,6 +14,17 @@ handleClose: Drupal.t('Collapse') }; + function toggleList($item, switcher) { + var $toggle = $item.children('.toolbar-box').children('.toolbar-handle'); + switcher = typeof switcher !== 'undefined' ? switcher : !$item.hasClass('open'); + + $item.toggleClass('open', switcher); + + $toggle.toggleClass('open', switcher); + + $toggle.find('.action').text(switcher ? ui.handleClose : ui.handleOpen); + } + function toggleClickHandler(event) { var $toggle = $(event.target); var $item = $toggle.closest('li'); @@ -32,17 +43,6 @@ event.stopPropagation(); } - function toggleList($item, switcher) { - var $toggle = $item.children('.toolbar-box').children('.toolbar-handle'); - switcher = typeof switcher !== 'undefined' ? switcher : !$item.hasClass('open'); - - $item.toggleClass('open', switcher); - - $toggle.toggleClass('open', switcher); - - $toggle.find('.action').text(switcher ? ui.handleClose : ui.handleOpen); - } - function initItems($menu) { var options = { class: 'toolbar-icon toolbar-handle', diff --git a/core/modules/tracker/js/tracker-history.es6.js b/core/modules/tracker/js/tracker-history.es6.js index 13090008d65e663550408c120c488e0c8db3971c..a80fa1d714be64ffb762fc3a3f0691371320b33f 100644 --- a/core/modules/tracker/js/tracker-history.es6.js +++ b/core/modules/tracker/js/tracker-history.es6.js @@ -4,6 +4,59 @@ * May only be loaded for authenticated users, with the History module enabled. */ (function ($, Drupal, window) { + function processNodeNewIndicators($placeholders) { + const newNodeString = Drupal.t('new'); + const updatedNodeString = Drupal.t('updated'); + + $placeholders.each((index, placeholder) => { + const timestamp = parseInt(placeholder.getAttribute('data-history-node-timestamp'), 10); + const nodeID = placeholder.getAttribute('data-history-node-id'); + const lastViewTimestamp = Drupal.history.getLastRead(nodeID); + + if (timestamp > lastViewTimestamp) { + const message = (lastViewTimestamp === 0) ? newNodeString : updatedNodeString; + $(placeholder).append(`<span class="marker">${message}</span>`); + } + }); + } + + function processNewRepliesIndicators($placeholders) { + // Figure out which placeholders need the "x new" replies links. + const placeholdersToUpdate = {}; + $placeholders.each((index, placeholder) => { + const timestamp = parseInt(placeholder.getAttribute('data-history-node-last-comment-timestamp'), 10); + const nodeID = placeholder.previousSibling.previousSibling.getAttribute('data-history-node-id'); + const lastViewTimestamp = Drupal.history.getLastRead(nodeID); + + // Queue this placeholder's "X new" replies link to be downloaded from the + // server. + if (timestamp > lastViewTimestamp) { + placeholdersToUpdate[nodeID] = placeholder; + } + }); + + // Perform an AJAX request to retrieve node view timestamps. + const nodeIDs = Object.keys(placeholdersToUpdate); + if (nodeIDs.length === 0) { + return; + } + $.ajax({ + url: Drupal.url('comments/render_new_comments_node_links'), + type: 'POST', + data: { 'node_ids[]': nodeIDs }, + dataType: 'json', + success(results) { + Object.keys(results || {}).forEach((nodeID) => { + if (placeholdersToUpdate.hasOwnProperty(nodeID)) { + const url = results[nodeID].first_new_comment_link; + const text = Drupal.formatPlural(results[nodeID].new_comment_count, '1 new', '@count new'); + $(placeholdersToUpdate[nodeID]).append(`<br /><a href="${url}">${text}</a>`); + } + }); + }, + }); + } + /** * Render "new" and "updated" node indicators, as well as "X new" replies links. */ @@ -60,57 +113,4 @@ }); }, }; - - function processNodeNewIndicators($placeholders) { - const newNodeString = Drupal.t('new'); - const updatedNodeString = Drupal.t('updated'); - - $placeholders.each((index, placeholder) => { - const timestamp = parseInt(placeholder.getAttribute('data-history-node-timestamp'), 10); - const nodeID = placeholder.getAttribute('data-history-node-id'); - const lastViewTimestamp = Drupal.history.getLastRead(nodeID); - - if (timestamp > lastViewTimestamp) { - const message = (lastViewTimestamp === 0) ? newNodeString : updatedNodeString; - $(placeholder).append(`<span class="marker">${message}</span>`); - } - }); - } - - function processNewRepliesIndicators($placeholders) { - // Figure out which placeholders need the "x new" replies links. - const placeholdersToUpdate = {}; - $placeholders.each((index, placeholder) => { - const timestamp = parseInt(placeholder.getAttribute('data-history-node-last-comment-timestamp'), 10); - const nodeID = placeholder.previousSibling.previousSibling.getAttribute('data-history-node-id'); - const lastViewTimestamp = Drupal.history.getLastRead(nodeID); - - // Queue this placeholder's "X new" replies link to be downloaded from the - // server. - if (timestamp > lastViewTimestamp) { - placeholdersToUpdate[nodeID] = placeholder; - } - }); - - // Perform an AJAX request to retrieve node view timestamps. - const nodeIDs = Object.keys(placeholdersToUpdate); - if (nodeIDs.length === 0) { - return; - } - $.ajax({ - url: Drupal.url('comments/render_new_comments_node_links'), - type: 'POST', - data: { 'node_ids[]': nodeIDs }, - dataType: 'json', - success(results) { - Object.keys(results || {}).forEach((nodeID) => { - if (placeholdersToUpdate.hasOwnProperty(nodeID)) { - const url = results[nodeID].first_new_comment_link; - const text = Drupal.formatPlural(results[nodeID].new_comment_count, '1 new', '@count new'); - $(placeholdersToUpdate[nodeID]).append(`<br /><a href="${url}">${text}</a>`); - } - }); - }, - }); - } }(jQuery, Drupal, window)); diff --git a/core/modules/tracker/js/tracker-history.js b/core/modules/tracker/js/tracker-history.js index a147eb8a63641fbce5bd37c85da221a9108a37c4..30a500fe708f154295fb4b1795f6d5bf8d54885c 100644 --- a/core/modules/tracker/js/tracker-history.js +++ b/core/modules/tracker/js/tracker-history.js @@ -6,49 +6,6 @@ **/ (function ($, Drupal, window) { - Drupal.behaviors.trackerHistory = { - attach: function attach(context) { - var nodeIDs = []; - var $nodeNewPlaceholders = $(context).find('[data-history-node-timestamp]').once('history').filter(function () { - var nodeTimestamp = parseInt(this.getAttribute('data-history-node-timestamp'), 10); - var nodeID = this.getAttribute('data-history-node-id'); - if (Drupal.history.needsServerCheck(nodeID, nodeTimestamp)) { - nodeIDs.push(nodeID); - return true; - } - - return false; - }); - - var $newRepliesPlaceholders = $(context).find('[data-history-node-last-comment-timestamp]').once('history').filter(function () { - var lastCommentTimestamp = parseInt(this.getAttribute('data-history-node-last-comment-timestamp'), 10); - var nodeTimestamp = parseInt(this.previousSibling.previousSibling.getAttribute('data-history-node-timestamp'), 10); - - if (lastCommentTimestamp === nodeTimestamp) { - return false; - } - var nodeID = this.previousSibling.previousSibling.getAttribute('data-history-node-id'); - if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) { - if (nodeIDs.indexOf(nodeID) === -1) { - nodeIDs.push(nodeID); - } - return true; - } - - return false; - }); - - if ($nodeNewPlaceholders.length === 0 && $newRepliesPlaceholders.length === 0) { - return; - } - - Drupal.history.fetchTimestamps(nodeIDs, function () { - processNodeNewIndicators($nodeNewPlaceholders); - processNewRepliesIndicators($newRepliesPlaceholders); - }); - } - }; - function processNodeNewIndicators($placeholders) { var newNodeString = Drupal.t('new'); var updatedNodeString = Drupal.t('updated'); @@ -97,4 +54,47 @@ } }); } + + Drupal.behaviors.trackerHistory = { + attach: function attach(context) { + var nodeIDs = []; + var $nodeNewPlaceholders = $(context).find('[data-history-node-timestamp]').once('history').filter(function () { + var nodeTimestamp = parseInt(this.getAttribute('data-history-node-timestamp'), 10); + var nodeID = this.getAttribute('data-history-node-id'); + if (Drupal.history.needsServerCheck(nodeID, nodeTimestamp)) { + nodeIDs.push(nodeID); + return true; + } + + return false; + }); + + var $newRepliesPlaceholders = $(context).find('[data-history-node-last-comment-timestamp]').once('history').filter(function () { + var lastCommentTimestamp = parseInt(this.getAttribute('data-history-node-last-comment-timestamp'), 10); + var nodeTimestamp = parseInt(this.previousSibling.previousSibling.getAttribute('data-history-node-timestamp'), 10); + + if (lastCommentTimestamp === nodeTimestamp) { + return false; + } + var nodeID = this.previousSibling.previousSibling.getAttribute('data-history-node-id'); + if (Drupal.history.needsServerCheck(nodeID, lastCommentTimestamp)) { + if (nodeIDs.indexOf(nodeID) === -1) { + nodeIDs.push(nodeID); + } + return true; + } + + return false; + }); + + if ($nodeNewPlaceholders.length === 0 && $newRepliesPlaceholders.length === 0) { + return; + } + + Drupal.history.fetchTimestamps(nodeIDs, function () { + processNodeNewIndicators($nodeNewPlaceholders); + processNewRepliesIndicators($newRepliesPlaceholders); + }); + } + }; })(jQuery, Drupal, window); \ No newline at end of file diff --git a/core/themes/seven/js/responsive-details.es6.js b/core/themes/seven/js/responsive-details.es6.js index 8d5c46565a28fe79d7ec27635a289a54c60146e1..78530d8ea63f37bc1611804520ff4ecc2074ac41 100644 --- a/core/themes/seven/js/responsive-details.es6.js +++ b/core/themes/seven/js/responsive-details.es6.js @@ -20,6 +20,8 @@ return; } + const $summaries = $details.find('> summary'); + function detailsToggle(matches) { if (matches) { $details.attr('open', true); @@ -43,7 +45,6 @@ detailsToggle(event.matches); } - const $summaries = $details.find('> summary'); const mql = window.matchMedia('(min-width:48em)'); mql.addListener(handleDetailsMQ); detailsToggle(mql.matches); diff --git a/core/themes/seven/js/responsive-details.js b/core/themes/seven/js/responsive-details.js index 62ad4b2b35fc6a006b70a60923d4e1b675c8d159..3c62e3cef6af47dfb1d0190b9aa1af256d99fe86 100644 --- a/core/themes/seven/js/responsive-details.js +++ b/core/themes/seven/js/responsive-details.js @@ -14,6 +14,8 @@ return; } + var $summaries = $details.find('> summary'); + function detailsToggle(matches) { if (matches) { $details.attr('open', true); @@ -31,7 +33,6 @@ detailsToggle(event.matches); } - var $summaries = $details.find('> summary'); var mql = window.matchMedia('(min-width:48em)'); mql.addListener(handleDetailsMQ); detailsToggle(mql.matches);