206 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			206 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @file
							 | 
						||
| 
								 | 
							
								 * User permission page behaviors.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								(function ($, Drupal, debounce) {
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Shows checked and disabled checkboxes for inherited permissions.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @type {Drupal~behavior}
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @prop {Drupal~behaviorAttach} attach
							 | 
						||
| 
								 | 
							
								   *   Attaches functionality to the permissions table.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  Drupal.behaviors.permissions = {
							 | 
						||
| 
								 | 
							
								    attach() {
							 | 
						||
| 
								 | 
							
								      const [table] = once('permissions', 'table#permissions');
							 | 
						||
| 
								 | 
							
								      if (!table) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Create fake checkboxes. We use fake checkboxes instead of reusing
							 | 
						||
| 
								 | 
							
								      // the existing checkboxes here because new checkboxes don't alter the
							 | 
						||
| 
								 | 
							
								      // submitted form. If we'd automatically check existing checkboxes, the
							 | 
						||
| 
								 | 
							
								      // permission table would be polluted with redundant entries. This is
							 | 
						||
| 
								 | 
							
								      // deliberate, but desirable when we automatically check them.
							 | 
						||
| 
								 | 
							
								      const $fakeCheckbox = $(Drupal.theme('checkbox'))
							 | 
						||
| 
								 | 
							
								        .removeClass('form-checkbox')
							 | 
						||
| 
								 | 
							
								        .addClass('fake-checkbox js-fake-checkbox')
							 | 
						||
| 
								 | 
							
								        .attr({
							 | 
						||
| 
								 | 
							
								          disabled: 'disabled',
							 | 
						||
| 
								 | 
							
								          checked: 'checked',
							 | 
						||
| 
								 | 
							
								          title: Drupal.t(
							 | 
						||
| 
								 | 
							
								            'This permission is inherited from the authenticated user role.',
							 | 
						||
| 
								 | 
							
								          ),
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      const $wrapper = $('<div></div>').append($fakeCheckbox);
							 | 
						||
| 
								 | 
							
								      const fakeCheckboxHtml = $wrapper.html();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      /**
							 | 
						||
| 
								 | 
							
								       * Process each table row to create fake checkboxes.
							 | 
						||
| 
								 | 
							
								       *
							 | 
						||
| 
								 | 
							
								       * @param {object} object
							 | 
						||
| 
								 | 
							
								       * @param {HTMLElement} object.target
							 | 
						||
| 
								 | 
							
								       */
							 | 
						||
| 
								 | 
							
								      function tableRowProcessing({ target }) {
							 | 
						||
| 
								 | 
							
								        once('permission-checkbox', target).forEach((checkbox) => {
							 | 
						||
| 
								 | 
							
								          checkbox
							 | 
						||
| 
								 | 
							
								            .closest('tr')
							 | 
						||
| 
								 | 
							
								            .querySelectorAll(
							 | 
						||
| 
								 | 
							
								              'input[type="checkbox"]:not(.js-rid-anonymous, .js-rid-authenticated)',
							 | 
						||
| 
								 | 
							
								            )
							 | 
						||
| 
								 | 
							
								            .forEach((check) => {
							 | 
						||
| 
								 | 
							
								              check.classList.add('real-checkbox', 'js-real-checkbox');
							 | 
						||
| 
								 | 
							
								              check.insertAdjacentHTML('beforebegin', fakeCheckboxHtml);
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // An IntersectionObserver object is associated with each of the table
							 | 
						||
| 
								 | 
							
								      // rows to activate checkboxes interactively as users scroll the page
							 | 
						||
| 
								 | 
							
								      // up or down. This prevents processing all checkboxes on page load.
							 | 
						||
| 
								 | 
							
								      const checkedCheckboxObserver = new IntersectionObserver(
							 | 
						||
| 
								 | 
							
								        (entries, thisObserver) => {
							 | 
						||
| 
								 | 
							
								          entries
							 | 
						||
| 
								 | 
							
								            .filter((entry) => entry.isIntersecting)
							 | 
						||
| 
								 | 
							
								            .forEach((entry) => {
							 | 
						||
| 
								 | 
							
								              tableRowProcessing(entry);
							 | 
						||
| 
								 | 
							
								              thisObserver.unobserve(entry.target);
							 | 
						||
| 
								 | 
							
								            });
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        {
							 | 
						||
| 
								 | 
							
								          rootMargin: '50%',
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Select rows with checked authenticated role and attach an observer
							 | 
						||
| 
								 | 
							
								      // to each.
							 | 
						||
| 
								 | 
							
								      table
							 | 
						||
| 
								 | 
							
								        .querySelectorAll(
							 | 
						||
| 
								 | 
							
								          'tbody tr input[type="checkbox"].js-rid-authenticated:checked',
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        .forEach((checkbox) => checkedCheckboxObserver.observe(checkbox));
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // Create checkboxes only when necessary on click.
							 | 
						||
| 
								 | 
							
								      $(table).on(
							 | 
						||
| 
								 | 
							
								        'click.permissions',
							 | 
						||
| 
								 | 
							
								        'input[type="checkbox"].js-rid-authenticated',
							 | 
						||
| 
								 | 
							
								        tableRowProcessing,
							 | 
						||
| 
								 | 
							
								      );
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Filters the permission list table by a text input search string.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * Text search input: input.table-filter-text
							 | 
						||
| 
								 | 
							
								   * Target table:      input.table-filter-text[data-table]
							 | 
						||
| 
								 | 
							
								   * Source text:       .table-filter-text-source
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @type {Drupal~behavior}
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  Drupal.behaviors.tableFilterByText = {
							 | 
						||
| 
								 | 
							
								    attach(context, settings) {
							 | 
						||
| 
								 | 
							
								      const [input] = once('table-filter-text', 'input.table-filter-text');
							 | 
						||
| 
								 | 
							
								      if (!input) {
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const tableSelector = input.getAttribute('data-table');
							 | 
						||
| 
								 | 
							
								      const $table = $(tableSelector);
							 | 
						||
| 
								 | 
							
								      const $rows = $table.find('tbody tr');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      function hideEmptyPermissionHeader(index, row) {
							 | 
						||
| 
								 | 
							
								        const tdsWithModuleClass = row.querySelectorAll('td.module');
							 | 
						||
| 
								 | 
							
								        // Function to check if an element is visible (`display: block`).
							 | 
						||
| 
								 | 
							
								        function isVisible(element) {
							 | 
						||
| 
								 | 
							
								          return getComputedStyle(element).display !== 'none';
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (tdsWithModuleClass.length > 0) {
							 | 
						||
| 
								 | 
							
								          // Find the next visible sibling `<tr>`.
							 | 
						||
| 
								 | 
							
								          let nextVisibleSibling = row.nextElementSibling;
							 | 
						||
| 
								 | 
							
								          while (nextVisibleSibling && !isVisible(nextVisibleSibling)) {
							 | 
						||
| 
								 | 
							
								            nextVisibleSibling = nextVisibleSibling.nextElementSibling;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // Check if the next visible sibling has the "module" class in any of
							 | 
						||
| 
								 | 
							
								          // its `<td>` elements.
							 | 
						||
| 
								 | 
							
								          let nextVisibleSiblingHasModuleClass = false;
							 | 
						||
| 
								 | 
							
								          if (nextVisibleSibling) {
							 | 
						||
| 
								 | 
							
								            const nextSiblingTdsWithModuleClass =
							 | 
						||
| 
								 | 
							
								              nextVisibleSibling.querySelectorAll('td.module');
							 | 
						||
| 
								 | 
							
								            nextVisibleSiblingHasModuleClass =
							 | 
						||
| 
								 | 
							
								              nextSiblingTdsWithModuleClass.length > 0;
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // Check if this is the last visible row with class "module".
							 | 
						||
| 
								 | 
							
								          const isLastVisibleModuleRow =
							 | 
						||
| 
								 | 
							
								            !nextVisibleSibling || !isVisible(nextVisibleSibling);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // Hide the current row with class "module" if it meets the
							 | 
						||
| 
								 | 
							
								          // conditions.
							 | 
						||
| 
								 | 
							
								          $(row).toggle(
							 | 
						||
| 
								 | 
							
								            !nextVisibleSiblingHasModuleClass && !isLastVisibleModuleRow,
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      function filterPermissionList(e) {
							 | 
						||
| 
								 | 
							
								        const query = e.target.value;
							 | 
						||
| 
								 | 
							
								        if (query.length === 0) {
							 | 
						||
| 
								 | 
							
								          // Reset table when the textbox is cleared.
							 | 
						||
| 
								 | 
							
								          $rows.show();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        // Case insensitive expression to find query at the beginning of a word.
							 | 
						||
| 
								 | 
							
								        const re = new RegExp(`\\b${query}`, 'i');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        function showPermissionRow(index, row) {
							 | 
						||
| 
								 | 
							
								          const sources = row.querySelectorAll('.table-filter-text-source');
							 | 
						||
| 
								 | 
							
								          if (sources.length > 0) {
							 | 
						||
| 
								 | 
							
								            const textMatch = sources[0].textContent.search(re) !== -1;
							 | 
						||
| 
								 | 
							
								            $(row).closest('tr').toggle(textMatch);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        // Search over all rows.
							 | 
						||
| 
								 | 
							
								        $rows.show();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // Filter if the length of the query is at least 2 characters.
							 | 
						||
| 
								 | 
							
								        if (query.length >= 2) {
							 | 
						||
| 
								 | 
							
								          $rows.each(showPermissionRow);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          // Hide the empty header if they don't have any visible rows.
							 | 
						||
| 
								 | 
							
								          const visibleRows = $table.find('tbody tr:visible');
							 | 
						||
| 
								 | 
							
								          visibleRows.each(hideEmptyPermissionHeader);
							 | 
						||
| 
								 | 
							
								          const rowsWithoutEmptyModuleName = $table.find('tbody tr:visible');
							 | 
						||
| 
								 | 
							
								          // Find elements with class "permission" within visible rows.
							 | 
						||
| 
								 | 
							
								          const tdsWithModuleOrPermissionClass =
							 | 
						||
| 
								 | 
							
								            rowsWithoutEmptyModuleName.find('.permission');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								          Drupal.announce(
							 | 
						||
| 
								 | 
							
								            Drupal.formatPlural(
							 | 
						||
| 
								 | 
							
								              tdsWithModuleOrPermissionClass.length,
							 | 
						||
| 
								 | 
							
								              '1 permission is available in the modified list.',
							 | 
						||
| 
								 | 
							
								              '@count permissions are available in the modified list.',
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								          );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      function preventEnterKey(event) {
							 | 
						||
| 
								 | 
							
								        if (event.which === 13) {
							 | 
						||
| 
								 | 
							
								          event.preventDefault();
							 | 
						||
| 
								 | 
							
								          event.stopPropagation();
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      if ($table.length) {
							 | 
						||
| 
								 | 
							
								        $(input).on({
							 | 
						||
| 
								 | 
							
								          keyup: debounce(filterPermissionList, 200),
							 | 
						||
| 
								 | 
							
								          click: debounce(filterPermissionList, 200),
							 | 
						||
| 
								 | 
							
								          keydown: preventEnterKey,
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								})(jQuery, Drupal, Drupal.debounce);
							 |