diff --git a/workbench_tabs/.gitignore b/workbench_tabs/.gitignore new file mode 100644 index 0000000..57872d0 --- /dev/null +++ b/workbench_tabs/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/workbench_tabs/README.md b/workbench_tabs/README.md new file mode 100644 index 0000000..d5f2484 --- /dev/null +++ b/workbench_tabs/README.md @@ -0,0 +1,9 @@ +# Workbench Tabs + +A module to provide local task tabs and messages as part of the core Toolbar, so that custom themes don't need to provide these administrative elements. + +## Configuration + +* Enable the module +* Grant the "Use Workbench Tabs" permission to one or more roles +* Optionally disable the primary and secondary tabs blocks for the admin theme, so that there are not duplicate task tabs on admin pages diff --git a/workbench_tabs/css/workbench-tabs.css b/workbench_tabs/css/workbench-tabs.css new file mode 100644 index 0000000..25af39c --- /dev/null +++ b/workbench_tabs/css/workbench-tabs.css @@ -0,0 +1,143 @@ +/** + * @file workbench-tabs.css + * CSS for displaying local task tabs below the core toolbar. + */ + +/* Container */ +.workbench-tabs { + background-color: #FFFFFF; +} + +/* Tabs Container */ +.workbench-tabs__tabs { + position: relative; + min-height: 33px; + background-color: #F5F5F5; + box-shadow: 0px 2px 10px #888888 inset; +} + +/* Tabs lists */ +.workbench-tabs__tabs > ul { + list-style: none; + margin: 0; + padding: 0; + text-align: right; +} + +/* Tabs Lists Items */ +.workbench-tabs__tabs > ul > li { + display: inline-block; +} + +/* Primary Tabs default */ +.primary-tabs > li > a { + display: inline-block; + padding: 10px 25px; +} + +/* Primary Tabs hover and focus */ +.primary-tabs > li > a:active, +.primary-tabs > li > a:hover, +.primary-tabs > li > a:focus { + background-color: #FFFFFF; + text-decoration: none; + color: #0032a0; /*Drupal Link color*/ + transition: all 0.15s ease-out; +} + +/* Primary Tabs Active */ +.primary-tabs > li > a.is-active { + background-color: #FFFFFF; + color: #0C2240; /*Drupal hover color*/ + box-shadow: 2px 2px 2px #888888; +} + +/* Secondary Tabs List (ul) */ +.secondary-tabs { + display: block; + border-top: 1px solid #b5b5b5; + background-color: #EBEBEB; + box-shadow: 0px 2px 10px rgba(181, 181, 181, 0.5) inset; +} + +/* Secondary Tabs default link */ +.secondary-tabs > li > a { + display: inline-block; + padding: 5px 15px; +} + +/* Secondary Tabs hover */ +.secondary-tabs > li > a:active, +.secondary-tabs > li > a:hover, +.secondary-tabs > li > a:focus { + background-color: rgba(181, 181, 181, 0.5); + color: #0032a0; /*Drupal Link color*/ +} + +/* Secondary Tabs Active */ +.secondary-tabs > li > a.is-active { + background-color: rgba(181, 181, 181, 0.5); + color: #0C2240; /*Drupal hover color*/ +} + +/* Messages Container */ +.workbench-tabs__message { + padding: 10px 20px; +} + +/* Messages Container, closed */ +.workbench-tabs__message.is-closed { + display: none; + padding: 10px 20px; +} + +/* Zap toolbar link styling within the message area. */ +.workbench-tabs.toolbar .workbench-tabs__message a { + display: inline; + line-height: inherit; +} + +/* Open/Close Messages Trigger */ +.workbench-tabs__trigger { + position: absolute; + top: 0; + left: 20px; + display: inline-block; + border-left: 1px solid #b5b5b5; + border-right: 1px solid #b5b5b5; + padding: 8px 25px; + color: #0032a0; +} + +/* Place "Hide" in for messaging when the drawer is open */ +.workbench-tabs__trigger > .show { + display: none; +} +.workbench-tabs__trigger > .hide { + display: inline; +} + +/* Place "Show" in for messaging when the drawer is closed */ +.workbench-tabs__trigger.is-closed > .show { + display: inline; +} +.workbench-tabs__trigger.is-closed > .hide { + display: none; +} + +/* add in an arrow indicator to the message */ +.workbench-tabs__trigger:after { + content: url('../images/icon-arrow.png'); + position: relative; + top: 2px; + display: inline-block; + margin-left: 10px; +} + +/* Rotate the arrow indicator when the drawer is closed */ +.workbench-tabs__trigger.is-closed:after { + -ms-transform: rotate(180deg); /* IE 9 */ + -webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */ + transform: rotate(180deg); + top: -.5px; +} diff --git a/workbench_tabs/images/icon-arrow.png b/workbench_tabs/images/icon-arrow.png new file mode 100644 index 0000000..8c88c58 Binary files /dev/null and b/workbench_tabs/images/icon-arrow.png differ diff --git a/workbench_tabs/js/workbench_tabs_trigger.js b/workbench_tabs/js/workbench_tabs_trigger.js new file mode 100644 index 0000000..905878a --- /dev/null +++ b/workbench_tabs/js/workbench_tabs_trigger.js @@ -0,0 +1,92 @@ +/** + * @file + * Collapse and expand the workbench_tabs messages. + */ + +(function ($) { + + 'use strict'; + + var $messageTrigger = $('.workbench-tabs__trigger'); + var $messageContents = $('.workbench-tabs__message'); + + var messageHeight = $messageContents.outerHeight(true); + var messagesOpen = messageHeight > 0; + + Drupal.behaviors.workbenchTabs = {}; + + Drupal.behaviors.workbenchTabs.attach = function() { + // Open/Close functionality for rail navigation. + $messageTrigger.once('workbenchTabsMessagesButtonClick').click(function(e) { + e.preventDefault(); + Drupal.behaviors.workbenchTabs.toggleMessagesVisual(); + }); + + // Lose focus on the trigger when the mouse leaves. Using .blur() in the + // click handler breaks menus for users who are tabbing through. + $messageTrigger.once('workbenchTabsMessagesButtonMouseout').mouseout(function() { + $(this).blur(); + }); + + // Close the drawer when we scroll past it. + $(window).once('workbenchTabsMessagesScroll').on('scroll', function() { + if (messagesOpen && $(window).scrollTop() > messageHeight) { + + // Reevaluate message height because user interactions can change it, + // but we don't want to calculate this on every scroll event. + messageHeight = $messageContents.outerHeight(true); + if ($(window).scrollTop() > messageHeight) { + Drupal.behaviors.workbenchTabs.closeMessages(); + + // Scroll to the top of the page to prevent a jump. + $(window).scrollTop(0); + } + } + }); + }; + + Drupal.behaviors.workbenchTabs.toggleMessagesVisual = function() { + $messageContents.slideToggle('slow', function() { + messagesOpen = $messageContents.is(':visible'); + Drupal.behaviors.workbenchTabs.toggleMessages(messagesOpen); + }); + } + + /** + * @param bool state + * Force opening or closing the messages. + * - true: open messages + * - false: close messages + */ + Drupal.behaviors.workbenchTabs.toggleMessages = function(state) { + if (state === true || state === false) { + messagesOpen = !state; + } + else { + messagesOpen = $messageContents.is(':visible'); + } + + if (messagesOpen) { + Drupal.behaviors.workbenchTabs.closeMessages(); + } + else { + Drupal.behaviors.workbenchTabs.openMessages(); + } + } + + Drupal.behaviors.workbenchTabs.closeMessages = function() { + $messageTrigger.addClass('is-closed'); + $messageContents.addClass('is-closed'); + messagesOpen = false; + } + + Drupal.behaviors.workbenchTabs.openMessages = function() { + $messageTrigger.removeClass('is-closed'); + $messageContents + .removeClass('is-closed') + .attr('style', ''); + messageHeight = $messageContents.outerHeight(true); + messagesOpen = true; + } + +})(jQuery); diff --git a/workbench_tabs/src/Element/LocalTasks.php b/workbench_tabs/src/Element/LocalTasks.php new file mode 100644 index 0000000..061e42c --- /dev/null +++ b/workbench_tabs/src/Element/LocalTasks.php @@ -0,0 +1,59 @@ + [[$class, 'preRenderLocalTasks']], + '#theme' => 'workbench_tabs_menu_local_tasks', + '#primary' => '', + '#secondary' => '', + ]; + } + + /** + * @param array $element + * A renderable array. + * + * @return array + * A renderable array. + */ + public static function preRenderLocalTasks($element) { + // This is a workaround for entity pages whose routes have been taken over by + // Page Manager, which breaks local tasks by messing with route names. + $route_name = \Drupal::routeMatch()->getRouteName(); + if (preg_match('/^entity\.[^\.]+\.canonical/', $route_name, $matches)) { + $route_name = $matches[0]; + } + + /** @var \Drupal\Core\Menu\LocalTaskManagerInterface $manager */ + $manager = \Drupal::service('plugin.manager.menu.local_task'); + + foreach (['#primary', '#secondary'] as $i => $key) { + $tabs = $manager->getLocalTasks($route_name, $i); + + foreach (Element::getVisibleChildren($tabs['tabs']) as $tab_key) { + $element[$key][$tab_key] = $tabs['tabs'][$tab_key]; + $element[$key][$tab_key]['#theme'] = 'workbench_tabs_menu_local_task'; + } + } + + return $element; + } + +} diff --git a/workbench_tabs/src/Element/StatusMessages.php b/workbench_tabs/src/Element/StatusMessages.php new file mode 100644 index 0000000..c238c53 --- /dev/null +++ b/workbench_tabs/src/Element/StatusMessages.php @@ -0,0 +1,53 @@ + [[$class, 'renderMessages']], + '#message_type' => NULL, + '#clear_queue' => TRUE, + ]; + } + + /** + * Clear the message queue when rendering the messages. + */ + public static function renderMessages($element) { + $messages = drupal_get_messages($element['#message_type'], $element['#clear_queue']); + + if (!empty($messages)) { + $element = [ + '#theme' => 'workbench_tabs_status_messages', + '#message_list' => $messages, + '#status_headings' => [ + 'status' => t('Status message'), + 'error' => t('Error message'), + 'warning' => t('Warning message'), + ], + ] + $element; + } + + return $element; + } + +} diff --git a/workbench_tabs/templates/workbench-tabs-menu-local-task.html.twig b/workbench_tabs/templates/workbench-tabs-menu-local-task.html.twig new file mode 100644 index 0000000..2b15eaa --- /dev/null +++ b/workbench_tabs/templates/workbench-tabs-menu-local-task.html.twig @@ -0,0 +1,19 @@ +{# +/** + * @file + * Theme override for a local task link. + * + * This is a copy of stable/templates/navigation/menu-local-task.html.twig + * + * Available variables: + * - attributes: HTML attributes for the wrapper element. + * - is_active: Whether the task item is an active tab. + * - link: A rendered link element. + * + * Note: This template renders the content for each task item in + * menu-local-tasks.html.twig. + * + * @see template_preprocess_menu_local_task() + */ +#} +{{ link }} diff --git a/workbench_tabs/templates/workbench-tabs-menu-local-tasks.html.twig b/workbench_tabs/templates/workbench-tabs-menu-local-tasks.html.twig new file mode 100644 index 0000000..844c8db --- /dev/null +++ b/workbench_tabs/templates/workbench-tabs-menu-local-tasks.html.twig @@ -0,0 +1,23 @@ +{# +/** + * @file + * Theme override to display primary and secondary local tasks. + * + * This is a copy of stable/templates/navigation/menu-local-tasks.html.twig + * + * Available variables: + * - primary: HTML list items representing primary tasks. + * - secondary: HTML list items representing primary tasks. + * + * Each item in these variables (primary and secondary) can be individually + * themed in menu-local-task.html.twig. + */ +#} +{% if primary %} +

{{ 'Primary tabs'|t }}

+ +{% endif %} +{% if secondary %} +

{{ 'Secondary tabs'|t }}

+ +{% endif %} diff --git a/workbench_tabs/templates/workbench-tabs-status-messages.html.twig b/workbench_tabs/templates/workbench-tabs-status-messages.html.twig new file mode 100644 index 0000000..9c9dbd2 --- /dev/null +++ b/workbench_tabs/templates/workbench-tabs-status-messages.html.twig @@ -0,0 +1,58 @@ +{# +/** + * @file + * Theme override for status messages. + * + * This is a copy of classy/templates/misc/status-messages.html.twig + * + * Displays status, error, and warning messages, grouped by type. + * + * An invisible heading identifies the messages for assistive technology. + * Sighted users see a colored box. See http://www.w3.org/TR/WCAG-TECHS/H69.html + * for info. + * + * Add an ARIA label to the contentinfo area so that assistive technology + * user agents will better describe this landmark. + * + * Available variables: + * - message_list: List of messages to be displayed, grouped by type. + * - status_headings: List of all status types. + * - display: (optional) May have a value of 'status' or 'error' when only + * displaying messages of that specific type. + * - attributes: HTML attributes for the element, including: + * - class: HTML classes. + */ +#} +{{ attach_library('classy/messages') }} +{% block messages %} +{% for type, messages in message_list %} + {% + set classes = [ + 'messages', + 'messages--' ~ type, + ] + %} +
+ {% if type == 'error' %} +
+ {% endif %} + {% if status_headings[type] %} +

{{ status_headings[type] }}

+ {% endif %} + {% if messages|length > 1 %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+ {% else %} + {{ messages|first }} + {% endif %} + {% if type == 'error' %} +
+ {% endif %} +
+ {# Remove type specific classes. #} + {{ attributes.removeClass(classes) }} +{% endfor %} +{% endblock messages %} diff --git a/workbench_tabs/templates/workbench-tabs.html.twig b/workbench_tabs/templates/workbench-tabs.html.twig new file mode 100644 index 0000000..94c01ed --- /dev/null +++ b/workbench_tabs/templates/workbench-tabs.html.twig @@ -0,0 +1,35 @@ +{# +/** + * @file + * Markup for the Workbench Tabs. + * + * Available variables: + * - messages: Status messages + * - tabs: Local task tabs + * + * @see workbench_tabs_page_top() + * @see template_preprocess_workbench_tabs() + * + * @ingroup themeable + */ +#} + +{% if (has_messages or has_tabs) %} + +{{ attach_library('workbench_tabs/workbench_tabs') }} + +
+ {% if (messages) %} +
{{ messages }}
+ {% endif %} + + +
+ +{% endif %} diff --git a/workbench_tabs/workbench_tabs.info.yml b/workbench_tabs/workbench_tabs.info.yml new file mode 100644 index 0000000..0a40fc9 --- /dev/null +++ b/workbench_tabs/workbench_tabs.info.yml @@ -0,0 +1,5 @@ +name: Workbench Tabs +type: module +description: "Display Drupal's local task tabs and status messages in a consistent location on all pages." +package: Workbench +core: 8.x diff --git a/workbench_tabs/workbench_tabs.libraries.yml b/workbench_tabs/workbench_tabs.libraries.yml new file mode 100644 index 0000000..a9863ad --- /dev/null +++ b/workbench_tabs/workbench_tabs.libraries.yml @@ -0,0 +1,10 @@ +workbench_tabs: + version: 1.x + css: + component: + css/workbench-tabs.css: {} + js: + js/workbench_tabs_trigger.js: {} + dependencies: + - core/jquery + - core/jquery.once diff --git a/workbench_tabs/workbench_tabs.module b/workbench_tabs/workbench_tabs.module new file mode 100644 index 0000000..a20b243 --- /dev/null +++ b/workbench_tabs/workbench_tabs.module @@ -0,0 +1,77 @@ + [ + 'render element' => 'element', + ], + 'workbench_tabs_menu_local_task' => [ + 'render element' => 'element', + ], + 'workbench_tabs_menu_local_tasks' => [ + 'variables' => ['primary' => NULL, 'secondary' => NULL], + ], + 'workbench_tabs_status_messages' => [ + 'variables' => ['status_headings' => [], 'message_list' => NULL], + ], + ]; +} + +/** + * Preprocess variables for the workbench_tabs_menu_local_task template. + * + * Wraps the core menu_local_task preprocess function. + * + * @ingroup themeable + */ +function template_preprocess_workbench_tabs_menu_local_task(&$variables) { + template_preprocess_menu_local_task($variables); +} + +/** + * Preprocess variables for the workbench tabs template. + * + * @ingroup themeable + */ +function template_preprocess_workbench_tabs(&$variables) { + $element = $variables['element']; + + $variables['messages'] = Drupal::service('renderer')->render($element['messages']); + $variables['tabs'] = Drupal::service('renderer')->render($element['tabs']); + + $variables['has_messages'] = !empty($variables['messages']); + $variables['has_tabs'] = !empty($element['tabs']['#primary']); +} + +/** + * Implements hook_page_top(). + */ +function workbench_tabs_page_top(array &$page_top) { + $account = \Drupal::currentUser(); + + $page_top['workbench_tabs'] = [ + '#theme' => 'workbench_tabs', + '#access' => $account->hasPermission('use workbench_tabs'), + ]; + + $page_top['workbench_tabs']['messages'] = [ + '#type' => 'workbench_tabs_status_messages', + '#cache' => [ + 'contexts' => [ + 'url.path', + 'user.roles', + ], + ], + ]; + + $page_top['workbench_tabs']['tabs'] = [ + '#type' => 'workbench_tabs_local_tasks', + ]; +} diff --git a/workbench_tabs/workbench_tabs.permissions.yml b/workbench_tabs/workbench_tabs.permissions.yml new file mode 100644 index 0000000..75e91f4 --- /dev/null +++ b/workbench_tabs/workbench_tabs.permissions.yml @@ -0,0 +1,3 @@ +use workbench_tabs: + title: 'Use Workbench Tabs' + description: 'View the status messages and local task tabs as Workbench Tabs.'