diff --git a/core/includes/form.inc b/core/includes/form.inc index 513615c..3684578 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -3337,6 +3337,52 @@ function form_process_actions($element, &$form_state) { } /** + * #pre_render callback for #type 'actions'. + * + * This callback iterates over all child elements of the #type 'actions' + * container to look for elements with a #dropbutton property, so as to group + * those elements into dropbuttons. As such, it works similar to #group, but is + * specialized for dropbuttons. + * + * The value of #dropbutton denotes the dropbutton to group the child element + * into. For example, two different values of 'foo' and 'bar' on child elements + * would generate two separate dropbuttons, which each contain the corresponding + * buttons. + * + * @param array $element + * The #type 'actions' element to process. + * + * @return array + * The processed #type 'actions' element, including individual buttons grouped + * into new #type 'dropbutton' elements. + */ +function form_pre_render_actions_dropbutton(array $element) { + $dropbuttons = array(); + foreach (element_children($element, TRUE) as $key) { + if (isset($element[$key]['#dropbutton'])) { + $dropbutton = $element[$key]['#dropbutton']; + // If there is no dropbutton for this button group yet, create one. + if (!isset($dropbuttons[$dropbutton])) { + $dropbuttons[$dropbutton] = array( + '#type' => 'dropbutton', + ); + } + // Add this button to the corresponding dropbutton. + // @todo Change #type 'dropbutton' to be based on theme_item_list() + // instead of theme_links() to avoid this preemptive rendering. + $button = drupal_render($element[$key]); + $dropbuttons[$dropbutton]['#links'][$key] = array( + 'title' => $button, + 'html' => TRUE, + ); + } + } + // @todo For now, all dropbuttons appear first. Consider to invent a more + // fancy sorting/injection algorithm here. + return $dropbuttons + $element; +} + +/** * #process callback for #pattern form element property. * * @param $element diff --git a/core/includes/theme.inc b/core/includes/theme.inc index a38dbe0..6f27936 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1811,11 +1811,11 @@ function theme_links($variables) { // Merge in default array properties into $link. $link += array( 'html' => FALSE, - 'attributes' => array(), ); - $item = ''; - $item .= ($link['html'] ? $link['title'] : check_plain($link['title'])); - $item .= ''; + $item = ($link['html'] ? $link['title'] : check_plain($link['title'])); + if (isset($link['attributes'])) { + $item = '' . $item . ''; + } } $output .= ' $class)) . '>'; @@ -1844,24 +1844,6 @@ function theme_dropbutton_wrapper($variables) { } /** - * Returns HTML for wrapping a dropbutton list. - * - * Use this function if the dropbutton contains submit buttons. These elements - * need to have a #prefix and #suffix element that wraps those into an
  • - * element. - * - * @param array $variables - * An associative array containing: - * - element: An associative array containing the properties and children of - * the dropbutton list. Properties used: #children. - */ -function theme_dropbutton_list_wrapper($variables) { - if (!empty($variables['element']['#children'])) { - return '
      ' . $variables['element']['#children'] . '
    '; - } -} - -/** * Returns HTML for an image. * * @param $variables @@ -3172,9 +3154,6 @@ function drupal_common_theme() { 'dropbutton_wrapper' => array( 'render element' => 'element', ), - 'dropbutton_list_wrapper' => array( - 'render element' => 'element', - ), 'image' => array( // HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft // allows the alt attribute to be omitted in some cases. Therefore, diff --git a/core/misc/dropbutton/dropbutton.base.css b/core/misc/dropbutton/dropbutton.base.css index 9c1a3ef..35e9692 100644 --- a/core/misc/dropbutton/dropbutton.base.css +++ b/core/misc/dropbutton/dropbutton.base.css @@ -22,13 +22,21 @@ .js .dropbutton-widget { max-width: 100%; } - @media screen and (max-width:600px) { .js .dropbutton-wrapper { width: 100%; } } +/* Splitbuttons */ +.form-actions .dropbutton-wrapper { + float: left; +} +.js .form-actions .dropbutton-widget { + position: static; +} + + .js .dropbutton-widget { position: absolute; } @@ -79,7 +87,7 @@ text-indent: 110%; top: 0; white-space: nowrap; - width: 2.08em; + width: 2em; } .dropbutton-toggle button { background: none; diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php index a7354ee..af7d354 100644 --- a/core/modules/node/lib/Drupal/node/NodeFormController.php +++ b/core/modules/node/lib/Drupal/node/NodeFormController.php @@ -274,89 +274,6 @@ public function form(array $form, array &$form_state, EntityInterface $node) { return parent::form($form, $form_state, $node); } - /** - * Overrides Drupal\entity\EntityFormController::actionsElement(). - */ - protected function actionsElement(array $form, array &$form_state) { - $element = parent::actionsElement($form, $form_state); - $node = $this->getEntity($form_state); - - // Because some of the 'links' are actually submit buttons, we have to - // manually wrap each item in
  • and the whole list in
      . The - //
        is added with a #theme_wrappers function. - $element['operations'] = array( - '#type' => 'operations', - '#subtype' => 'node', - '#attached' => array ( - 'css' => array( - drupal_get_path('module', 'node') . '/node.admin.css', - ), - ), - ); - - $element['operations']['actions'] = array( - '#theme_wrappers' => array('dropbutton_list_wrapper') - ); - - // Depending on the state of the node (published or unpublished) and - // whether the current user has the permission to change the status, the - // labels and order of the buttons will vary. - if (user_access('administer nodes')) { - $element['operations']['actions']['publish'] = array( - '#type' => 'submit', - '#value' => t('Save and publish'), - '#submit' => array(array($this, 'publish'), array($this, 'submit'), array($this, 'save')), - '#validate' => array(array($this, 'validate')), - '#button_type' => $node->status ? 'primary' : '', - '#weight' => 0, - '#prefix' => '
      • ', - '#suffix' => '
      • ', - ); - $element['operations']['actions']['unpublish'] = array( - '#type' => 'submit', - '#value' => t('Save as unpublished'), - '#submit' => array(array($this, 'unpublish'), array($this, 'submit'), array($this, 'save')), - '#validate' => array(array($this, 'validate')), - '#button_type' => empty($node->status) ? 'primary' : '', - '#weight' => $node->status ? 1 : -1, - '#prefix' => '
      • ', - "#suffix" => '
      • ', - ); - - if (!empty($node->nid)) { - if ($node->status) { - $publish_label = t('Save and keep published'); - $unpublish_label = t('Save and unpublish'); - } - else { - $publish_label = t('Save and publish'); - $unpublish_label = t('Save and keep unpublished'); - } - $element['operations']['actions']['publish']['#value'] = $publish_label; - $element['operations']['actions']['unpublish']['#value'] = $unpublish_label; - } - } - // The user has no permission to change the status of the node. Just - // show a save button without the 'publish' or 'unpublish' callback in - // the #submit definition. - else { - $element['operations']['actions']['save'] = array( - '#type' => 'submit', - '#value' => t('Save'), - '#submit' => array(array($this, 'submit'), array($this, 'save')), - '#validate' => array(array($this, 'validate')), - '#button_type' => 'primary', - '#weight' => 1, - '#prefix' => '
      • ', - "#suffix" => '
      • ', - ); - } - - unset($element['submit']); - - return $element; - } - /* * Overrides Drupal\Core\Entity\EntityFormController::actions(). */ @@ -365,9 +282,63 @@ protected function actions(array $form, array &$form_state) { $node = $this->getEntity($form_state); $preview_mode = variable_get('node_preview_' . $node->type, DRUPAL_OPTIONAL); + $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])); + + // If saving is an option, privileged users get dedicated form submit + // buttons to adjust the publishing status while saving in one go. + // @todo This adjustment makes it close to impossible for contributed + // modules to integrate with "the Save operation" of this form. Modules + // need a way to plug themselves into 1) the ::submit() step, and + // 2) the ::save() step, both decoupled from the pressed form button. + if ($element['submit']['#access'] && user_access('administer nodes')) { + // isNew | prev status » default & publish label & unpublish label + // 1 | 1 » publish & Save and publish & Save as unpublished + // 1 | 0 » unpublish & Save and publish & Save as unpublished + // 0 | 1 » publish & Save and keep published & Save and unpublish + // 0 | 0 » unpublish & Save and keep unpublished & Save and publish + + // Add a "Publish" button. + $element['publish'] = $element['submit']; + $element['publish']['#dropbutton'] = 'save'; + if ($node->isNew()) { + $element['publish']['#value'] = t('Save and publish'); + } + else { + $element['publish']['#value'] = $node->status ? t('Save and keep published') : t('Save and publish'); + } + $element['publish']['#weight'] = 0; + array_unshift($element['publish']['#submit'], array($this, 'publish')); + + // Add a "Unpublish" button. + $element['unpublish'] = $element['submit']; + $element['unpublish']['#dropbutton'] = 'save'; + if ($node->isNew()) { + $element['unpublish']['#value'] = t('Save as unpublished'); + } + else { + $element['unpublish']['#value'] = !$node->status ? t('Save and keep unpublished') : t('Save and unpublish'); + } + $element['unpublish']['#weight'] = 10; + array_unshift($element['unpublish']['#submit'], array($this, 'unpublish')); + + // If already published, the 'publish' button is primary. + if ($node->status) { + unset($element['unpublish']['#button_type']); + } + // Otherwise, the 'unpublish' button is primary and should come first. + else { + unset($element['publish']['#button_type']); + $element['unpublish']['#weight'] = -10; + } + + // Remove the "Save" button. + $element['submit']['#access'] = FALSE; + } + $element['preview'] = array( '#access' => $preview_mode != DRUPAL_DISABLED, '#value' => t('Preview'), + '#weight' => 20, '#validate' => array( array($this, 'validate'), ), @@ -377,8 +348,8 @@ protected function actions(array $form, array &$form_state) { ), ); - $element['submit']['#access'] = $preview_mode != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])); $element['delete']['#access'] = node_access('delete', $node); + $element['delete']['#weight'] = 100; return $element; } @@ -473,7 +444,7 @@ public function preview(array $form, array &$form_state) { */ public function publish(array $form, array &$form_state) { $node = $this->getEntity($form_state); - $node->status = TRUE; + $node->status = 1; return $node; } @@ -487,7 +458,7 @@ public function publish(array $form, array &$form_state) { */ public function unpublish(array $form, array &$form_state) { $node = $this->getEntity($form_state); - $node->status = FALSE; + $node->status = 0; return $node; } diff --git a/core/modules/node/node.admin.css b/core/modules/node/node.admin.css index 9ef08e1..101a38d 100644 --- a/core/modules/node/node.admin.css +++ b/core/modules/node/node.admin.css @@ -9,130 +9,3 @@ .revision-current { background: #ffc; } - -/** - * Node form dropbuttons. - */ -.form-actions .dropbutton-wrapper { - float: left; - margin-right: 1em; -} - -.form-actions .dropbutton-wrapper .dropbutton-widget { - position: static; -} - -.form-actions .dropbutton-wrapper li a, -.form-actions .dropbutton-wrapper input { - padding: 5px 17px 6px 17px; - margin-bottom: 0em; - border: medium; - border-radius: 0; - background: none; -} - -.form-actions .dropbutton-wrapper input:hover { - background: none; - border: none; -} - -.form-actions .button { - background: #fefefe; - background-image: -webkit-linear-gradient(top, #fefefe, #e0e0e0); - background-image: -moz-linear-gradient(top, #fefefe, #e0e0e0); - background-image: -o-linear-gradient(top, #fefefe, #e0e0e0); - background-image: linear-gradient(to bottom, #fefefe, #e0e0e0); - border: 1px solid #c8c8c8; - border-radius: 3px; - text-decoration: none; - padding: 6px 17px 6px 17px; - margin-left: 0; -} - -.form-actions .button:focus, -.form-actions .button:hover { - background: #fefefe; - background-image: -webkit-linear-gradient(top, #fefefe, #eaeaea); - background-image: -moz-linear-gradient(top, #fefefe, #eaeaea); - background-image: -o-linear-gradient(top, #fefefe, #eaeaea); - background-image: linear-gradient(to bottom, #fefefe, #eaeaea); - -webkit-box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); - box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); - color: #2e2e2e; - text-decoration: none; -} -.form-actions .button:active { - border: 1px solid #c8c8c8; - background: #fefefe; - background-image: -webkit-linear-gradient(top, #eaeaea, #fefefe); - background-image: -moz-linear-gradient(top, #eaeaea, #fefefe); - background-image: -o-linear-gradient(top, #eaeaea, #fefefe); - background-image: linear-gradient(to bottom, #eaeaea, #fefefe); - -webkit-box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); - box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); - color: #2e2e2e; - text-decoration: none; - text-shadow: none; -} - -/* Delete button */ -.form-actions .button-danger { - color: #c72100; - background: none; - border: none; - float: right; - margin-right: 0; - margin-left: 0; - padding-right: 0; - padding-left: 0; -} -.form-actions .button-danger:hover, -.form-actions .button-danger:focus { - color: #ff2a00; - background: none; - border: none; - text-decoration: underline; -} -.form-actions .button-danger:active { - color: #ff2a00; - background: none; - border: none; - text-decoration: underline; -} - - -/** - * Form edit action theming - */ -.js .form-actions .dropbutton-widget { - background-color: #50a0e9; - background-image: -moz-linear-gradient(-90deg, #50a0e9, #4481dc); - background-image: -o-linear-gradient(-90deg, #50a0e9, #4481dc); - background-image: -webkit-linear-gradient(-90deg, #50a0e9, #4481dc); - background-image: linear-gradient(180deg, #50a0e9, #4481dc); - border-radius: 3px; - border: 1px solid #3974ae; -} -.js .form-actions .dropbutton-widget .dropbutton li { - border-top: 1px solid rgba(255, 255, 255, 0.5); - border-top-left-radius: 3px; -} -.js .form-actions .dropbutton-widget .dropbutton .dropbutton-toggle { - border-top-left-radius: 0px; - border-top-right-radius: 3px; - top: 1px; -} -.js .form-actions .dropbutton-widget .dropbutton .secondary-action { - border-top: 1px solid rgba(255, 255, 255, 0.3); - border-top-left-radius: 0px; -} -.js .form-actions .dropbutton-widget .button { - color: #ffffff; - text-shadow: 1px 1px 1px rgba(31, 83, 131, 0.8); -} -.js .form-actions .dropbutton-multiple.open .dropbutton-action:hover { - background-color: #50a0e9; -} - - - diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc index 10a12c6..7c360a4 100644 --- a/core/modules/node/node.admin.inc +++ b/core/modules/node/node.admin.inc @@ -434,9 +434,6 @@ function node_admin_nodes() { '#title' => t('Update options'), '#attributes' => array('class' => array('container-inline')), '#access' => $admin_access, - '#attached' => array ( - 'css' => array(drupal_get_path('module', 'node') . '/css/node-admin.theme.css'), - ), ); $options = array(); foreach (module_invoke_all('node_operations') as $operation => $array) { diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 17015c6..e580a84 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -576,7 +576,7 @@ function system_element_info() { '#theme_wrappers' => array('container'), ); $types['actions'] = array( - '#process' => array('form_process_actions', 'form_process_container'), + '#process' => array('form_pre_render_actions_dropbutton', 'form_process_actions', 'form_process_container'), '#weight' => 100, '#theme_wrappers' => array('container'), ); diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css index a72d686..376c130 100644 --- a/core/themes/seven/style.css +++ b/core/themes/seven/style.css @@ -1529,3 +1529,114 @@ details.fieldset-no-legend { .entity-meta details .summary { display: none; /* Hide JS summaries. @todo Rethink summaries. */ } + + +/** + * Node form dropbuttons. + */ +.form-actions .dropbutton-wrapper li a, +.form-actions .dropbutton-wrapper input { + padding: 5px 17px 6px 17px; + margin-bottom: 0em; + border: medium; + border-radius: 0; + background: none; +} +.form-actions .dropbutton-wrapper input:hover { + background: none; + border: none; +} +.form-actions .button { + background: #fefefe; + background-image: -webkit-linear-gradient(top, #fefefe, #e0e0e0); + background-image: -moz-linear-gradient(top, #fefefe, #e0e0e0); + background-image: -o-linear-gradient(top, #fefefe, #e0e0e0); + background-image: linear-gradient(to bottom, #fefefe, #e0e0e0); + border: 1px solid #c8c8c8; + border-radius: 3px; + text-decoration: none; + padding: 6px 17px 6px 17px; + margin-left: 0; +} +.form-actions .button:focus, +.form-actions .button:hover { + background: #fefefe; + background-image: -webkit-linear-gradient(top, #fefefe, #eaeaea); + background-image: -moz-linear-gradient(top, #fefefe, #eaeaea); + background-image: -o-linear-gradient(top, #fefefe, #eaeaea); + background-image: linear-gradient(to bottom, #fefefe, #eaeaea); + -webkit-box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); + box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); + color: #2e2e2e; + text-decoration: none; +} +.form-actions .button:active { + border: 1px solid #c8c8c8; + background: #fefefe; + background-image: -webkit-linear-gradient(top, #eaeaea, #fefefe); + background-image: -moz-linear-gradient(top, #eaeaea, #fefefe); + background-image: -o-linear-gradient(top, #eaeaea, #fefefe); + background-image: linear-gradient(to bottom, #eaeaea, #fefefe); + -webkit-box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); + box-shadow: 1px 1px 3px rgba(50, 50, 50, 0.1); + color: #2e2e2e; + text-decoration: none; + text-shadow: none; +} +/* Delete button */ +.form-actions .button-danger { + color: #c72100; + background: none; + border: none; + float: right; + margin-right: 0; + margin-left: 0; + padding-right: 0; + padding-left: 0; +} +.form-actions .button-danger:hover, +.form-actions .button-danger:focus { + color: #ff2a00; + background: none; + border: none; + text-decoration: underline; +} +.form-actions .button-danger:active { + color: #ff2a00; + background: none; + border: none; + text-decoration: underline; +} + +/** + * Form edit action theming + */ +.js .form-actions .dropbutton-widget { + background-color: #50a0e9; + background-image: -moz-linear-gradient(-90deg, #50a0e9, #4481dc); + background-image: -o-linear-gradient(-90deg, #50a0e9, #4481dc); + background-image: -webkit-linear-gradient(-90deg, #50a0e9, #4481dc); + background-image: linear-gradient(180deg, #50a0e9, #4481dc); + border-radius: 3px; + border: 1px solid #3974ae; +} +.js .form-actions .dropbutton-widget .dropbutton li { + border-top: 1px solid rgba(255, 255, 255, 0.5); + border-top-left-radius: 3px; +} +.js .form-actions .dropbutton-widget .dropbutton .dropbutton-toggle { + border-top-left-radius: 0px; + border-top-right-radius: 3px; + top: 1px; +} +.js .form-actions .dropbutton-widget .dropbutton .secondary-action { + border-top: 1px solid rgba(255, 255, 255, 0.3); + border-top-left-radius: 0px; +} +.js .form-actions .dropbutton-widget .button { + color: #ffffff; + text-shadow: 1px 1px 1px rgba(31, 83, 131, 0.8); +} +.js .form-actions .dropbutton-multiple.open .dropbutton-action:hover { + background-color: #50a0e9; +}