upgrades core to 8.4.2

This commit is contained in:
Bachir Soussi Chiadmi
2017-11-14 16:09:54 +01:00
parent bd60eff9b3
commit c2b4e25be4
3504 changed files with 140306 additions and 38684 deletions
@@ -0,0 +1,238 @@
/**
* @file
* Set base styles for the settings tray.
*/
/* Set some global attributes. */
#drupal-off-canvas *,
#drupal-off-canvas *:not(div) {
background: #444;
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
color: #ddd;
}
/* Generic elements. */
#drupal-off-canvas a,
#drupal-off-canvas .link {
border-bottom: none;
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
font-size: inherit;
font-weight: normal;
color: #85bef4;
text-decoration: none;
transition: color .5s ease;
}
#drupal-off-canvas a:focus,
#drupal-off-canvas .link:focus,
#drupal-off-canvas a:hover,
#drupal-off-canvas .link:hover {
outline: none;
color: #46a0f5;
}
#drupal-off-canvas hr {
height: 1px;
background: #cccccc;
}
#drupal-off-canvas summary,
#drupal-off-canvas .fieldgroup:not(.form-composite) > legend {
font-weight: bold;
}
#drupal-off-canvas h1,
#drupal-off-canvas .heading-a {
display: block;
font-weight: bold;
font-size: 1.625em;
line-height: 1.875em;
}
#drupal-off-canvas h2,
#drupal-off-canvas .heading-b {
display: block;
font-weight: bold;
margin: 10px 0;
font-size: 1.385em;
}
#drupal-off-canvas h3,
#drupal-off-canvas .heading-c {
display: block;
font-weight: bold;
margin: 10px 0;
font-size: 1.231em;
}
#drupal-off-canvas h4,
#drupal-off-canvas .heading-d {
display: block;
font-weight: bold;
margin: 10px 0;
font-size: 1.154em;
}
#drupal-off-canvas h5,
#drupal-off-canvas .heading-e {
display: block;
font-weight: bold;
margin: 10px 0;
font-size: 1.077em;
}
#drupal-off-canvas h6,
#drupal-off-canvas .heading-f {
display: block;
font-weight: bold;
margin: 10px 0;
font-size: 1.077em;
}
#drupal-off-canvas p {
margin: 1em 0;
}
#drupal-off-canvas dl {
margin: 0 0 20px;
}
#drupal-off-canvas dl dd,
#drupal-off-canvas dl dl {
margin-left: 20px; /* LTR */
margin-bottom: 10px;
}
[dir="rtl"] #drupal-off-canvas dl dd,
[dir="rtl"] #drupal-off-canvas dl dl {
margin-right: 20px;
}
#drupal-off-canvas blockquote {
margin: 1em 40px;
}
#drupal-off-canvas address {
font-style: italic;
}
#drupal-off-canvas u,
#drupal-off-canvas ins {
text-decoration: underline;
}
#drupal-off-canvas s,
#drupal-off-canvas strike,
#drupal-off-canvas del {
text-decoration: line-through;
}
#drupal-off-canvas big {
font-size: larger;
}
#drupal-off-canvas small {
font-size: smaller;
}
#drupal-off-canvas sub {
vertical-align: sub;
font-size: smaller;
line-height: normal;
}
#drupal-off-canvas sup {
vertical-align: super;
font-size: smaller;
line-height: normal;
}
#drupal-off-canvas nobr {
white-space: nowrap;
}
#drupal-off-canvas abbr,
#drupal-off-canvas acronym {
border-bottom: dotted 1px;
background: transparent;
}
#drupal-off-canvas ul {
list-style-type: disc;
list-style-image: none;
}
[dir="rtl"] #drupal-off-canvas .messages__list {
margin-right: 0;
}
#drupal-off-canvas ol {
list-style-type: decimal;
}
#drupal-off-canvas ul li,
#drupal-off-canvas ol li {
display: block;
}
#drupal-off-canvas quote,
#drupal-off-canvas code {
margin: 20px 0;
}
#drupal-off-canvas pre {
margin: 20px 0;
white-space: pre-wrap;
}
/* Classes for hidden and visually hidden elements. See hidden.module.css. */
#drupal-off-canvas .hidden {
display: none;
}
#drupal-off-canvas .visually-hidden {
position: absolute !important;
clip: rect(1px, 1px, 1px, 1px);
overflow: hidden;
height: 1px;
width: 1px;
word-wrap: normal;
}
#drupal-off-canvas .visually-hidden.focusable:active,
#drupal-off-canvas .visually-hidden.focusable:focus {
position: static !important;
clip: auto;
overflow: visible;
height: auto;
width: auto;
}
#drupal-off-canvas .invisible {
visibility: hidden;
}
/* Some system classes. See system.admin.css. */
#drupal-off-canvas .panel {
padding: 5px 5px 15px;
}
#drupal-off-canvas .panel__description {
margin: 0 0 3px;
padding: 2px 0 3px 0;
}
#drupal-off-canvas .compact-link {
margin: 0 0 10px 0;
}
#drupal-off-canvas small .admin-link:before {
content: ' [';
}
#drupal-off-canvas small .admin-link:after {
content: ']';
}
/* Override jQuery UI */
#drupal-off-canvas .ui-widget-content a {
color: #85bef4 !important;
}
/* Message styles */
#drupal-off-canvas .messages {
background: no-repeat 10px 17px;
}
[dir="rtl"] #drupal-off-canvas .messages {
background-position: right 10px top 17px;
}
#drupal-off-canvas .messages abbr {
color: #444;
}
#drupal-off-canvas .messages--status {
background-color: #f3faef;
background-image: url(../../../misc/icons/73b355/check.svg);
color: #325e1c;
}
#drupal-off-canvas .messages--warning {
background-color: #fdf8ed;
background-image: url(../../../misc/icons/e29700/warning.svg);
color: #734c00;
}
#drupal-off-canvas .messages--error {
background-color: #fcf4f2;
background-image: url(../../../misc/icons/e32700/error.svg);
color: #a51b00;
}
#drupal-off-canvas .messages--error div[role="alert"] {
background: transparent;
color: inherit;
}
@@ -0,0 +1,118 @@
/**
* @file
* Visual styling for buttons in the off canvas tray.
*
* See seven/css/components/buttons.css
*/
#drupal-off-canvas button,
#drupal-off-canvas .button {
-webkit-appearance: none;
-moz-appearance: none;
margin: 0 0 10px;
padding: 0;
border: 0;
box-shadow: none;
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
line-height: normal;
text-transform: none;
text-decoration: none;
cursor: pointer;
}
#drupal-off-canvas button.link {
display: inline;
background: transparent;
font-size: 14px;
color: #85bef4;
transition: color .5s ease;
}
#drupal-off-canvas button.link:hover,
#drupal-off-canvas button.link:focus {
color: #46a0f5;
text-decoration: none;
}
#drupal-off-canvas input[type="submit"].button {
display: inline-block;
position: relative;
width: 100%;
height: auto;
padding: 4px 20px;
border: 0;
border-radius: 20em;
background: #777;
font-weight: 600;
font-size: 14px;
color: #f5f5f5;
text-align: center;
cursor: pointer;
transition: background .5s ease;
}
#drupal-off-canvas input[type="submit"].button:hover,
#drupal-off-canvas input[type="submit"].button:focus,
#drupal-off-canvas input[type="submit"].button:active {
border: 0;
color: #fff;
text-decoration: none;
outline: none;
z-index: 10;
}
#drupal-off-canvas input[type="submit"].button:focus,
#drupal-off-canvas input[type="submit"].button:active {
box-shadow: 0 3px 3px 2px rgba(0,0,0,0.1);
}
#drupal-off-canvas input[type="submit"].button--primary {
border: 0;
background: #277abd;
color: #fff;
margin-top: 15px;
}
#drupal-off-canvas input[type="submit"].button--primary:hover,
#drupal-off-canvas input[type="submit"].button--primary:focus,
#drupal-off-canvas input[type="submit"].button--primary:active {
background: #2b8bd8;
outline: none;
}
#drupal-off-canvas .button-action:before {
margin-left: -0.2em; /* LTR */
padding-right: 0.2em; /* LTR */
font-size: 14px;
line-height: 16px;
}
[dir="rtl"] #drupal-off-canvas .button-action:before {
margin-right: -0.2em;
margin-left: 0;
padding-right: 0;
padding-left: 0.2em;
}
#drupal-off-canvas .no-touchevents .button--small {
font-size: 13px;
padding: 2px 1em;
}
#drupal-off-canvas .button:disabled,
#drupal-off-canvas .button:disabled:active,
#drupal-off-canvas .button.is-disabled,
#drupal-off-canvas .button.is-disabled:active {
border: 0;
background: #555;
color: #5c5c5c;
font-weight: normal;
cursor: default;
}
#drupal-off-canvas .button--danger {
border-radius: 0;
color: #c72100;
font-weight: 400;
text-decoration: none;
}
#drupal-off-canvas .button--danger:hover,
#drupal-off-canvas .button--danger:focus,
#drupal-off-canvas .button--danger:active {
color: #ff2a00;
text-decoration: none;
text-shadow: none;
}
#drupal-off-canvas .button--danger:disabled,
#drupal-off-canvas .button--danger.is-disabled {
color: #737373;
cursor: default;
}
@@ -0,0 +1,57 @@
/**
* @file
* CSS for off-canvas dialog.
*
* @todo Move CSS into core dialog library https://www.drupal.org/node/2784443.
*/
/* Position the off-canvas dialog container outside the right of the viewport. */
.ui-dialog-off-canvas {
box-sizing: border-box;
height: 100%;
overflow: visible;
}
/* Wrap the form that's inside the off-canvas dialog. */
.ui-dialog-off-canvas .ui-dialog-content {
padding: 0 20px;
/* Prevent horizontal scrollbar. */
overflow-x: hidden;
overflow-y: auto;
}
[dir="rtl"] .ui-dialog-off-canvas .ui-dialog-content {
text-align: right;
}
/* Position the dialog-off-canvas tray container outside the right of the viewport. */
.ui-dialog-off-canvas {
box-sizing: border-box;
height: 100%;
overflow: visible;
}
/* Wrap the form that's inside the dialog-off-canvas tray. */
.ui-dialog-off-canvas #drupal-off-canvas {
padding: 0 20px;
/* Prevent horizontal scrollbar. */
overflow-x: hidden;
overflow-y: auto;
}
[dir="rtl"] .ui-dialog-off-canvas #drupal-off-canvas {
text-align: right;
}
/*
* Force the tray to be 100% width at the same breakpoint the dialog system uses
* to expand dialog widths.
*/
@media all and (max-width: 48em) { /* 768px */
.ui-dialog.ui-dialog-off-canvas {
width: 100% !important;
}
/* When tray is at 100% width stop the body from scrolling */
.js-tray-open {
height: 100%;
overflow-y: hidden;
}
}
@@ -0,0 +1,61 @@
/**
* @file
* Visual styling for summary and details in the Settings Tray module's off canvas tray.
*/
#drupal-off-canvas details,
#drupal-off-canvas summary {
display: block;
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
}
#drupal-off-canvas details,
#drupal-off-canvas summary,
#drupal-off-canvas .ui-dialog-content {
background: #474747;
color: #ddd;
}
#drupal-off-canvas summary a {
color: #ddd;
padding-top: 0;
padding-bottom: 0;
}
#drupal-off-canvas summary a:hover,
#drupal-off-canvas summary a:focus {
color: #fff;
}
#drupal-off-canvas details,
#drupal-off-canvas summary,
#drupal-off-canvas .details-wrapper {
border-width: 0;
/* Cancel out the padding of the parent. */
margin: 0 -20px;
padding: 0 20px;
}
#drupal-off-canvas summary {
text-shadow: none;
padding: 10px 20px;
font-size: 14px;
transition: all .5s ease;
}
#drupal-off-canvas summary:hover,
#drupal-off-canvas summary:focus {
background-color: #222;
outline: none;
}
#drupal-off-canvas details[open] {
padding-bottom: 10px;
}
#drupal-off-canvas details[open] > summary {
background-color: #333;
color: #eee;
}
#drupal-off-canvas details[open] > summary:hover {
background-color: #222;
color: #fff;
}
#drupal-off-canvas details .placeholder {
font: inherit;
color: inherit;
font-style: italic;
background: transparent;
}
@@ -0,0 +1,291 @@
/**
* @file
* Styles for dropbuttons in the off-canvas tray.
*/
#drupal-off-canvas .dropbutton-wrapper,
#drupal-off-canvas .dropbutton-widget {
-webkit-appearance: none;
-moz-appearance: none;
display: block;
position: static;
transition: none;
}
#drupal-off-canvas .dropbutton-widget {
margin: 0;
padding: 0;
border: 0;
background: #277abd;
border-radius: 1em;
font-weight: 600;
color: #fff;
text-transform: none;
text-decoration: none;
text-align: center;
line-height: normal;
cursor: pointer;
transition: background .5s ease;
}
#drupal-off-canvas .dropbutton-widget:hover {
background: #2b8bd8;
}
/*
* Style dropbutton single.
*/
#drupal-off-canvas .dropbutton-single .dropbutton-action a {
padding: 0;
/* Overlap icon for trigger. */
margin-top: -2em;
height: 2.2em;
cursor: pointer;
}
#drupal-off-canvas .dropbutton-single .dropbutton-action:hover,
#drupal-off-canvas .dropbutton-single .dropbutton-action:focus,
#drupal-off-canvas .dropbutton-single .dropbutton-action a:hover,
#drupal-off-canvas .dropbutton-single .dropbutton-action a:focus {
text-decoration: none;
outline: none;
}
#drupal-off-canvas .dropbutton-widget .dropbutton {
margin: 0;
overflow: hidden;
padding: 0;
}
#drupal-off-canvas .dropbutton li,
#drupal-off-canvas .dropbutton a {
display: block;
width: auto;
padding: 4px 0;
text-align: left;
color: #555;
outline: none;
}
#drupal-off-canvas .dropbutton li:hover,
#drupal-off-canvas .dropbutton li:focus,
#drupal-off-canvas .dropbutton a:hover,
#drupal-off-canvas .dropbutton a:focus {
background: transparent;
color: #333;
outline: none;
}
/*
* Style dropbutton multiple.
*/
#drupal-off-canvas .dropbutton-multiple .dropbutton-widget {
width: 2em;
height: 2em;
}
#drupal-off-canvas .dropbutton-multiple .dropbutton-widget:hover {
background-color: #2b8bd8;
}
/* Hide the other actions until the dropbutton is triggered. */
#drupal-off-canvas .dropbutton-multiple .dropbutton .secondary-action {
display: none;
}
/* The toggle to expand the button. */
#drupal-off-canvas .dropbutton-toggle {
position: absolute;
top: 0;
right: 0; /* LTR */
bottom: 0;
display: block;
width: 2em;
color: #fff;
text-indent: 110%;
white-space: nowrap;
}
#drupal-off-canvas .dropbutton-toggle button {
display: block;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
border: 0 solid transparent;
border-bottom-right-radius: 1em; /* LTR */
border-top-right-radius: 1em; /* LTR */
cursor: pointer;
}
#drupal-off-canvas .dropbutton-toggle button:hover,
#drupal-off-canvas .dropbutton-toggle button:focus {
outline: none;
}
/* The toggle arrow. */
#drupal-off-canvas .dropbutton-arrow {
position: absolute;
display: block;
height: 0;
width: 0;
margin-top: 0;
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
border-style: solid;
border-width: 0.3333em 0.3333em 0;
color: #fff;
line-height: 0;
overflow: hidden;
}
#drupal-off-canvas span.dropbutton-arrow {
top: 7px;
right: 7px; /* LTR */
background: transparent;
}
#drupal-off-canvas span.dropbutton-arrow:hover {
background: transparent;
}
#drupal-off-canvas .dropbutton-action > .js-form-submit.form-submit,
#drupal-off-canvas .dropbutton-toggle button {
position: relative;
text-shadow: none;
}
/*
* Dropbuttons when in a table cell.
*/
/* Make sure table cell doesn't collapse around absolute positioned dropbutton. */
#drupal-off-canvas td .dropbutton-single {
min-width: 2em;
}
#drupal-off-canvas td .dropbutton-multiple {
min-width: 2em;
padding-right: 0;
padding-left: 0;
margin-right: 0;
margin-left: 0;
border: 0;
}
#drupal-off-canvas td .dropbutton-multiple .dropbutton-action a,
#drupal-off-canvas td .dropbutton-multiple .dropbutton-action input,
#drupal-off-canvas td .dropbutton-multiple .dropbutton-action button {
width: auto;
padding: 0;
font-size: inherit;
}
#drupal-off-canvas td .dropbutton-wrapper {
margin-bottom: 0;
}
/* Push the widget to the right so text expands left. */
#drupal-off-canvas td .dropbutton-widget {
position: absolute;
right: 12px; /* LTR */
padding: 0;
background: #277abd none;
}
/* Push the wrapper to the right edge of the td. */
#drupal-off-canvas td .dropbutton-single,
#drupal-off-canvas td .dropbutton-multiple {
float: right; /* LTR */
padding-right: 0;
margin-right: 0;
max-width: initial;
min-width: initial;
position: relative;
}
#drupal-off-canvas td .dropbutton-widget .dropbutton {
margin: 0;
width: 2em;
height: 2em;
overflow: hidden;
background: transparent;
}
/* Push text out of the way. */
#drupal-off-canvas td .dropbutton-multiple li,
#drupal-off-canvas td .dropbutton-multiple a {
margin-left: -9999px;
background: transparent;
}
#drupal-off-canvas td .dropbutton-multiple.open .dropbutton li,
#drupal-off-canvas td .dropbutton-multiple.open .dropbutton a {
margin-left: 0;
width: auto;
color: #fff;
}
/* Collapse the button to a circle. */
#drupal-off-canvas td .dropbutton-toggle {
width: 2em;
height: 2em;
border-radius: 1em;
}
#drupal-off-canvas td .dropbutton-wrapper .dropbutton-widget .dropbutton-toggle button {
border: 0;
background: transparent;
}
/* Prevent list item from expanding it's container. */
#drupal-off-canvas td ul.dropbutton li.edit {
width: 2em;
height: 2em;
}
/* Make li text transparent above icon so it's clickable. */
#drupal-off-canvas td .dropbutton-single li.edit.dropbutton-action > a {
color: transparent;
z-index: 1;
}
/* Put pencil icon in place of hidden 'edit' text on single buttons. */
#drupal-off-canvas td .dropbutton-single .edit:before {
content: '.';
display: block;
color: transparent;
background: transparent url(../../../misc/icons/ffffff/pencil.svg) no-repeat center;
background-size: 14px;
}
/* Dropbutton when triggered expands to show secondary items. */
#drupal-off-canvas .dropbutton-multiple.open {
z-index: 100;
}
/* Create visual separation if there is an adjacent button. */
#drupal-off-canvas .dropbutton-multiple.open .dropbutton-widget {
box-shadow: 0 3px 3px 2px rgba(0,0,0,0.5);
}
/* Triggered dropbutton expands to show secondary items. */
#drupal-off-canvas .dropbutton-multiple.open,
#drupal-off-canvas .dropbutton-multiple.open .dropbutton-widget {
display: block;
width: auto;
height: auto;
max-width: none;
min-width: 0;
padding: 0;
overflow: visible;
}
/* Triggered dropbutton in td expands to show secondary items. */
#drupal-off-canvas td .dropbutton-multiple.open .dropbutton,
#drupal-off-canvas .dropbutton-multiple.open .dropbutton .secondary-action {
display: block;
width: auto;
height: auto;
padding-right: 1em; /* LTR */
}
[dir="rtl"] #drupal-off-canvas td .dropbutton-multiple.open .dropbutton {
padding-left: 1em;
padding-right: inherit;
}
#drupal-off-canvas .dropbutton-multiple.open .dropbutton li a {
padding: 2px 1em;
}
/* When open, the toggle arrow points upward. */
#drupal-off-canvas .dropbutton-multiple.open span.dropbutton-arrow {
border-bottom: 0.3333em solid;
border-top-color: transparent;
top: 2px;
}
@@ -0,0 +1,136 @@
/**
* @file
* Visual styling for forms in the Settings Tray module's off canvas tray.
*/
#drupal-off-canvas form {
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
color: #ddd;
}
#drupal-off-canvas input[type="checkbox"] {
-webkit-appearance: checkbox;
}
#drupal-off-canvas input[type="radio"] {
-webkit-appearance: radio;
}
#drupal-off-canvas select {
-webkit-appearance: menulist;
-moz-appearance: menulist;
}
#drupal-off-canvas option {
display: block;
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
}
#drupal-off-canvas label {
line-height: normal;
font-family: inherit;
font-size: 12px;
font-weight: bold;
color: #ddd;
}
#drupal-off-canvas .visually-hidden {
opacity: 0;
height: 0;
width: 0;
letter-spacing: -2em;
}
#drupal-off-canvas .description,
#drupal-off-canvas .form-item .description,
#drupal-off-canvas .details-description {
color: #ddd;
margin-top: 5px;
font-family: inherit;
font-size: 12px;
font-style: normal;
}
#drupal-off-canvas .details-wrapper .description {
color: #bbb;
}
#drupal-off-canvas .form-item {
margin-bottom: 10px;
margin-top: 10px;
}
/* Set size and position for all inputs. */
#drupal-off-canvas .form-select,
#drupal-off-canvas .form-text,
#drupal-off-canvas .form-tel,
#drupal-off-canvas .form-email,
#drupal-off-canvas .form-url,
#drupal-off-canvas .form-search,
#drupal-off-canvas .form-number,
#drupal-off-canvas .form-color,
#drupal-off-canvas .form-file,
#drupal-off-canvas .form-textarea,
#drupal-off-canvas .form-date,
#drupal-off-canvas .form-time {
box-sizing: border-box;
max-width: 100%;
padding: 6px;
margin: 5px 0 0 0;
border-width: 1px;
border-radius: 2px;
display: block;
font-family: inherit;
font-size: 14px;
color: #333;
line-height: 16px;
}
/* Reduce contrast for fields against dark background. */
#drupal-off-canvas .form-text,
#drupal-off-canvas .form-tel,
#drupal-off-canvas .form-email,
#drupal-off-canvas .form-url,
#drupal-off-canvas .form-search,
#drupal-off-canvas .form-number,
#drupal-off-canvas .form-color,
#drupal-off-canvas .form-file,
#drupal-off-canvas .form-textarea,
#drupal-off-canvas .form-date,
#drupal-off-canvas .form-time {
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .125);
background-color: #eee;
border-color: #333;
color: #595959;
}
#drupal-off-canvas .form-text:focus,
#drupal-off-canvas .form-tel:focus,
#drupal-off-canvas .form-email:focus,
#drupal-off-canvas .form-url:focus,
#drupal-off-canvas .form-search:focus,
#drupal-off-canvas .form-number:focus,
#drupal-off-canvas .form-color:focus,
#drupal-off-canvas .form-file:focus,
#drupal-off-canvas .form-textarea:focus,
#drupal-off-canvas .form-date:focus,
#drupal-off-canvas .form-time:focus {
border-color: #40b6ff;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .125), 0 0 8px #40b6ff;
background-color: #fff;
}
#drupal-off-canvas td .form-item,
#drupal-off-canvas td .form-select {
margin: 0;
}
#drupal-off-canvas .form-file {
margin-bottom: 5px;
width: 100%;
}
#drupal-off-canvas .form-actions {
text-align: center;
margin: 10px 0;
}
#drupal-off-canvas .ui-autocomplete {
background-color: white;
position: absolute;
top: 0;
left: 0;
cursor: default;
}
#drupal-off-canvas .ui-autocomplete li {
display: block;
}
#drupal-off-canvas .ui-autocomplete li a {
color: #595959 !important;
cursor: pointer;
padding: 5px;
}
@@ -0,0 +1,15 @@
/**
* @file
* Motion effects for off-canvas dialog.
*
* Motion effects are in a separate file so that they can be easily turned off
* to improve performance if desired.
*
* @todo Move motion effects file into a core Off-Canvas library and add a
* configuration option for browser rendering performance to disable this
* file: https://www.drupal.org/node/2784443.
*/
.dialog-off-canvas__main-canvas {
transition: all .7s ease;
}
@@ -0,0 +1,388 @@
/**
* @file
* Reset most HTML elements styles for the off-canvas dialog.
*
* This is a generic reset. Drupal-specific classes are reset in components.
*/
/**
* Do not include div in then initial overrides because including div will
* cause the need for many more overrides in this file.
*/
#drupal-off-canvas *:not(div),
#drupal-off-canvas *:not(svg *),
#drupal-off-canvas *:after,
#drupal-off-canvas *:before {
all: initial;
box-sizing: border-box;
text-shadow: none;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: initial;
}
/* Reset size and position on elements. */
#drupal-off-canvas a,
#drupal-off-canvas abbr,
#drupal-off-canvas acronym,
#drupal-off-canvas address,
#drupal-off-canvas applet,
#drupal-off-canvas article,
#drupal-off-canvas aside,
#drupal-off-canvas audio,
#drupal-off-canvas b,
#drupal-off-canvas big,
#drupal-off-canvas blockquote,
#drupal-off-canvas body,
#drupal-off-canvas canvas,
#drupal-off-canvas caption,
#drupal-off-canvas cite,
#drupal-off-canvas code,
#drupal-off-canvas dd,
#drupal-off-canvas del,
#drupal-off-canvas dfn,
#drupal-off-canvas dialog,
#drupal-off-canvas dl,
#drupal-off-canvas dt,
#drupal-off-canvas em,
#drupal-off-canvas embed,
#drupal-off-canvas fieldset,
#drupal-off-canvas figcaption,
#drupal-off-canvas figure,
#drupal-off-canvas footer,
#drupal-off-canvas form,
#drupal-off-canvas h1,
#drupal-off-canvas h2,
#drupal-off-canvas h3,
#drupal-off-canvas h4,
#drupal-off-canvas h5,
#drupal-off-canvas h6,
#drupal-off-canvas header,
#drupal-off-canvas hgroup,
#drupal-off-canvas hr,
#drupal-off-canvas html,
#drupal-off-canvas i,
#drupal-off-canvas iframe,
#drupal-off-canvas img,
#drupal-off-canvas ins,
#drupal-off-canvas kbd,
#drupal-off-canvas label,
#drupal-off-canvas legend,
#drupal-off-canvas li,
#drupal-off-canvas main,
#drupal-off-canvas mark,
#drupal-off-canvas menu,
#drupal-off-canvas meter,
#drupal-off-canvas nav,
#drupal-off-canvas object,
#drupal-off-canvas ol,
#drupal-off-canvas output,
#drupal-off-canvas p,
#drupal-off-canvas pre,
#drupal-off-canvas progress,
#drupal-off-canvas q,
#drupal-off-canvas rp,
#drupal-off-canvas rt,
#drupal-off-canvas s,
#drupal-off-canvas samp,
#drupal-off-canvas section,
#drupal-off-canvas small,
#drupal-off-canvas span,
#drupal-off-canvas strike,
#drupal-off-canvas strong,
#drupal-off-canvas sub,
#drupal-off-canvas sup,
#drupal-off-canvas table,
#drupal-off-canvas tbody,
#drupal-off-canvas td,
#drupal-off-canvas tfoot,
#drupal-off-canvas th,
#drupal-off-canvas thead,
#drupal-off-canvas time,
#drupal-off-canvas tr,
#drupal-off-canvas tt,
#drupal-off-canvas u,
#drupal-off-canvas ul,
#drupal-off-canvas var,
#drupal-off-canvas video,
#drupal-off-canvas xmp {
border: 0;
margin: 0;
padding: 0;
font-size: 100%;
}
/*
* Override the default (display: inline) for browsers that do not recognize HTML5 tags.
* IE8 (and lower) requires a shiv: http://ejohn.org/blog/html5-shiv
*/
#drupal-off-canvas article,
#drupal-off-canvas aside,
#drupal-off-canvas figcaption,
#drupal-off-canvas figure,
#drupal-off-canvas footer,
#drupal-off-canvas header,
#drupal-off-canvas hgroup,
#drupal-off-canvas main,
#drupal-off-canvas menu,
#drupal-off-canvas nav,
#drupal-off-canvas section {
display: block;
line-height: normal;
border-radius: 0;
}
/*
* Makes browsers agree.
* IE + Opera = font-weight: bold.
* Gecko + WebKit = font-weight: bolder.
*/
#drupal-off-canvas b,
#drupal-off-canvas strong {
font-weight: bold;
}
#drupal-off-canvas em,
#drupal-off-canvas i {
font-style: italic;
}
#drupal-off-canvas img {
color: transparent;
font-size: 0;
vertical-align: middle;
}
#drupal-off-canvas ul,
#drupal-off-canvas ol {
list-style: none;
}
/* reset table styling. */
#drupal-off-canvas table {
border-collapse: collapse;
border-spacing: 0;
}
#drupal-off-canvas table thead,
#drupal-off-canvas table tbody,
#drupal-off-canvas table tbody tr:nth-child(even),
#drupal-off-canvas table tbody tr:nth-child(odd),
#drupal-off-canvas table tfoot {
border: 0;
background: transparent none;
}
#drupal-off-canvas th,
#drupal-off-canvas td,
#drupal-off-canvas caption {
font-weight: normal;
}
#drupal-off-canvas q {
quotes: none;
}
#drupal-off-canvas q:before,
#drupal-off-canvas q:after {
content: none;
}
#drupal-off-canvas sub,
#drupal-off-canvas sup,
#drupal-off-canvas small {
font-size: 75%;
}
#drupal-off-canvas sub,
#drupal-off-canvas sup {
line-height: 0;
position: relative;
vertical-align: baseline;
}
#drupal-off-canvas sub {
bottom: -0.25em;
}
#drupal-off-canvas sup {
top: -0.5em;
}
/*
* For IE9. Without, occasionally draws shapes
* outside the boundaries of <svg> rectangle.
*/
#drupal-off-canvas svg {
overflow: hidden;
}
/* Specific resets for inputs. */
#drupal-off-canvas input[type="search"]::-webkit-search-decoration {
display: none;
}
#drupal-off-canvas input {
margin: 0;
padding: 0;
}
#drupal-off-canvas input[type="checkbox"],
#drupal-off-canvas input[type="radio"] {
position: static;
margin: 0;
}
#drupal-off-canvas input:invalid,
#drupal-off-canvas button:invalid,
#drupal-off-canvas select:invalid,
#drupal-off-canvas textarea:invalid,
#drupal-off-canvas input:focus,
#drupal-off-canvas button:focus,
#drupal-off-canvas select:focus,
#drupal-off-canvas textarea:focus,
#drupal-off-canvas input[type="file"]:focus,
#drupal-off-canvas input[type="file"]:active,
#drupal-off-canvas input[type="radio"]:focus,
#drupal-off-canvas input[type="radio"]:active,
#drupal-off-canvas input[type="checkbox"]:focus,
#drupal-off-canvas input[type="checkbox"]:active {
box-shadow: none;
z-index: 1;
}
#drupal-off-canvas input[role="button"] {
cursor: pointer;
}
#drupal-off-canvas button,
#drupal-off-canvas input[type="reset"],
#drupal-off-canvas input[type="submit"],
#drupal-off-canvas input[type="button"] {
-webkit-appearance: none;
-moz-appearance: none;
display: inline-block;
background-image: none;
border: 0;
outline: 0;
overflow: visible;
text-shadow: none;
text-decoration: none;
vertical-align: middle;
cursor: pointer;
}
#drupal-off-canvas button:hover,
#drupal-off-canvas input[type="reset"]:hover,
#drupal-off-canvas input[type="submit"]:hover,
#drupal-off-canvas input[type="button"]:hover {
background-image: none;
text-decoration: none;
}
#drupal-off-canvas button:active,
#drupal-off-canvas input[type="reset"]:active,
#drupal-off-canvas input[type="submit"]:active,
#drupal-off-canvas input[type="button"]:active {
background-image: none;
box-shadow: none;
border-color: grey;
}
#drupal-off-canvas button::-moz-focus-inner,
#drupal-off-canvas input[type="reset"]::-moz-focus-inner,
#drupal-off-canvas input[type="submit"]::-moz-focus-inner,
#drupal-off-canvas input[type="button"]::-moz-focus-inner {
border: 0;
padding: 0;
}
#drupal-off-canvas textarea,
#drupal-off-canvas select,
#drupal-off-canvas input[type="date"],
#drupal-off-canvas input[type="datetime"],
#drupal-off-canvas input[type="datetime-local"],
#drupal-off-canvas input[type="email"],
#drupal-off-canvas input[type="month"],
#drupal-off-canvas input[type="number"],
#drupal-off-canvas input[type="password"],
#drupal-off-canvas input[type="search"],
#drupal-off-canvas input[type="tel"],
#drupal-off-canvas input[type="text"],
#drupal-off-canvas input[type="time"],
#drupal-off-canvas input[type="url"],
#drupal-off-canvas input[type="week"] {
height: auto;
vertical-align: middle;
border-radius: 0;
}
#drupal-off-canvas textarea[disabled],
#drupal-off-canvas select[disabled],
#drupal-off-canvas input[type="date"][disabled],
#drupal-off-canvas input[type="datetime"][disabled],
#drupal-off-canvas input[type="datetime-local"][disabled],
#drupal-off-canvas input[type="email"][disabled],
#drupal-off-canvas input[type="month"][disabled],
#drupal-off-canvas input[type="number"][disabled],
#drupal-off-canvas input[type="password"][disabled],
#drupal-off-canvas input[type="search"][disabled],
#drupal-off-canvas input[type="tel"][disabled],
#drupal-off-canvas input[type="text"][disabled],
#drupal-off-canvas input[type="time"][disabled],
#drupal-off-canvas input[type="url"][disabled],
#drupal-off-canvas input[type="week"][disabled] {
background-color: grey;
}
#drupal-off-canvas input[type="hidden"] {
visibility: hidden;
}
#drupal-off-canvas button[disabled],
#drupal-off-canvas input[disabled],
#drupal-off-canvas select[disabled],
#drupal-off-canvas select[disabled] option,
#drupal-off-canvas select[disabled] optgroup,
#drupal-off-canvas textarea[disabled] {
box-shadow: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
#drupal-off-canvas input:placeholder,
#drupal-off-canvas textarea:placeholder {
color: grey;
}
#drupal-off-canvas textarea,
#drupal-off-canvas select[size],
#drupal-off-canvas select[multiple] {
height: auto;
}
#drupal-off-canvas select[size="0"],
#drupal-off-canvas select[size="1"] {
height: auto;
}
#drupal-off-canvas textarea {
min-height: 40px;
overflow: auto;
resize: vertical;
width: 100%;
}
#drupal-off-canvas optgroup {
color: black;
font-style: normal;
font-weight: normal;
}
#drupal-off-canvas optgroup::-moz-focus-inner {
border: 0;
padding: 0;
}
#drupal-off-canvas * button {
background: none;
border: 1px solid grey;
color: black;
padding: 0;
text-decoration: none;
overflow: visible;
vertical-align: middle;
width: auto;
}
#drupal-off-canvas * textarea,
#drupal-off-canvas * select,
#drupal-off-canvas *:not(div) textarea,
#drupal-off-canvas *:not(div) select {
background: white;
border: 1px solid grey;
color: black;
padding: 0;
vertical-align: top;
}
/* To standardize off-canvas selection color. */
#drupal-off-canvas ::-moz-selection,
#drupal-off-canvas ::selection {
background-color: rgba(175,175,175,0.5);
color: inherit;
}
@@ -0,0 +1,90 @@
/**
* @file
* Visual styling for tables in the Settings Tray module's off canvas tray.
*/
#drupal-off-canvas table * {
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
}
#drupal-off-canvas table {
display: table;
width: 100%;
min-width: calc(100% + 40px);
/* Cancel out the padding of the parent to make the table full width. */
margin: 0 -20px -10px -20px;
border: 0;
border-collapse: collapse;
font-size: 12px;
color: #ddd;
}
#drupal-off-canvas table thead {
display: table-header-group;
}
#drupal-off-canvas table tbody {
display: table-row-group;
}
#drupal-off-canvas tr {
display: table-row;
}
#drupal-off-canvas tr:hover td {
background-color: transparent;
}
#drupal-off-canvas td,
#drupal-off-canvas th {
display: table-cell;
height: auto;
width: auto;
padding: 2px 8px;
vertical-align: middle;
border-bottom: 1px solid #777;
background-color: transparent;
}
[dir="rtl"] #drupal-off-canvas th,
[dir="rtl"] #drupal-off-canvas td {
text-align: right;
}
#drupal-off-canvas th {
font-weight: bold;
color: #bbb;
}
#drupal-off-canvas th.checkbox,
#drupal-off-canvas td.checkbox {
width: 20px;
padding: 0;
text-align: center;
}
#drupal-off-canvas div.checkbox.menu-enabled {
position: static;
display: inline;
width: auto;
}
#drupal-off-canvas th:first-child,
#drupal-off-canvas td:first-child {
width: 150px;
}
/* For lack of a better class, using this to grab the operations th. */
#drupal-off-canvas .tabledrag-has-colspan {
text-align: right;
padding-right: 20px;
}
#drupal-off-canvas td {
padding: 6px 8px;
color: #ddd;
}
/* Hide overflow with ellipsis for links. */
#drupal-off-canvas td a {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background: transparent;
}
#drupal-off-canvas tr td:first-child,
#drupal-off-canvas tr th:first-child {
padding-left: 20px; /* LTR */
}
[dir="rtl"] #drupal-off-canvas tr td:first-child,
[dir="rtl"] #drupal-off-canvas tr th:first-child {
padding-right: 20px;
}
@@ -0,0 +1,122 @@
/**
* @file
* Table drag behavior for Settings Tray module.
*
* @see tabledrag.js
*/
#drupal-off-canvas .drag {
cursor: move;
}
#drupal-off-canvas tr.region-title {
font-weight: normal;
}
#drupal-off-canvas table .region-message {
color: #fff;
}
#drupal-off-canvas table .region-populated {
display: none;
}
#drupal-off-canvas .add-new .tabledrag-changed {
display: none;
}
#drupal-off-canvas .draggable a.tabledrag-handle {
background-image: none;
margin: 0 5px 0 0;
height: auto;
min-width: 20px;
padding: 0;
overflow: hidden;
float: left; /* LTR */
text-decoration: none;
cursor: move;
}
[dir="rtl"] #drupal-off-canvas .draggable a.tabledrag-handle {
float: right;
margin-right: 0;
margin-left: 5px;
}
#drupal-off-canvas a.tabledrag-handle .handle {
/* Use lighter drag icon against dark background. */
background-color: transparent;
background-image: url(../../../misc/icons/bebebe/move.svg);
background-repeat: no-repeat;
background-position: center;
height: auto;
margin: 0;
padding: 0;
width: auto;
}
#drupal-off-canvas .draggable a.tabledrag-handle:hover .handle,
#drupal-off-canvas .draggable a.tabledrag-handle:focus .handle {
background-image: url(../../../misc/icons/787878/move.svg);
text-decoration: none;
}
#drupal-off-canvas tr td {
transition: background .3s ease;
}
#drupal-off-canvas tr td abbr {
margin-left: 5px; /* LTR */
}
[dir="rtl"] #drupal-off-canvas tr td abbr {
margin-left: 0;
margin-right: 5px;
}
#drupal-off-canvas tr:hover td {
background: #222;
}
#drupal-off-canvas tr.drag td {
background: #111;
}
#drupal-off-canvas tr.drag-previous td {
background: #000;
}
#drupal-off-canvas tr.drag-previous:hover td {
background: #222;
}
body div.tabledrag-changed-warning {
margin-bottom: 0.5em;
font-size: 14px;
}
#drupal-off-canvas .touchevents .draggable td {
padding: 0 10px;
}
#drupal-off-canvas .touchevents .draggable .menu-item__link {
display: inline-block;
padding: 10px 0;
}
#drupal-off-canvas .touchevents a.tabledrag-handle {
height: 44px;
width: 40px;
}
#drupal-off-canvas .touchevents a.tabledrag-handle .handle {
background-position: 40% 19px; /* LTR */
height: 21px;
}
[dir="rtl"] #drupal-off-canvas .touch a.tabledrag-handle .handle {
background-position: right 40% top 19px;
}
#drupal-off-canvas .touchevents .draggable.drag a.tabledrag-handle .handle {
background-position: 50% -32px;
}
#drupal-off-canvas .tabledrag-toggle-weight-wrapper {
padding-top: 10px;
text-align: right; /* LTR */
}
[dir="rtl"] #drupal-off-canvas .tabledrag-toggle-weight-wrapper {
text-align: left;
}
#drupal-off-canvas .indentation {
float: left; /* LTR */
height: auto;
margin: 0 3px 0 -10px; /* LTR */
padding: 0 0 0 10px; /* LTR */
width: auto;
}
[dir="rtl"] #drupal-off-canvas .indentation {
float: right;
margin: 0 -10px 0 3px;
padding: 0 10px 0 0;
}
@@ -0,0 +1,93 @@
/**
* @file
* Styling for the off-canvas ui dialog. Including overrides for jQuery UI.
*/
/* Style the dialog-off-canvas container. */
.ui-dialog.ui-dialog-off-canvas {
background: #444;
border: 0 solid transparent;
border-radius: 0;
box-shadow: 0 0 4px 2px rgba(0, 0, 0, 0.3333);
padding: 0;
color: #ddd;
/* Layer the dialog just under the toolbar. */
z-index: 501;
}
/* Style the tray header. */
.ui-dialog.ui-dialog-off-canvas .ui-dialog-titlebar {
padding: 1em;
background: #2d2d2d;
border: 0;
border-bottom: 1px solid #000;
border-radius: 0;
font-weight: normal;
color: #fff;
}
/* Hide the default jQuery UI dialog close button. */
.ui-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close .ui-icon {
visibility: hidden;
}
.ui-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close {
background-image: url(../../../misc/icons/bebebe/ex.svg);
background-position: center center;
background-repeat: no-repeat;
background-color: transparent;
border: 0;
position: absolute;
right: 1em;
transition: background .5s ease;
}
.ui-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close:hover {
background-image: url(../../../misc/icons/ffffff/ex.svg);
}
[dir="rtl"] .ui-dialog.ui-dialog-off-canvas .ui-dialog-titlebar-close {
left: 1em;
right: auto;
}
.ui-dialog.ui-dialog-off-canvas .ui-dialog-title {
margin: 0;
/* Push the text away from the icon. */
padding-left: 30px; /* LTR */
padding-right: 0; /* LTR */
/* Ensure that long titles do not overlap the close button. */
max-width: 210px;
font-size: 16px;
font-family: "Lucida Grande", 'Lucida Sans Unicode', 'liberation sans', sans-serif;
text-align: left; /* LTR */
}
[dir="rtl"] .ui-dialog.ui-dialog-off-canvas .ui-dialog-title {
float: right;
text-align: right;
padding-left: 0;
padding-right: 30px;
}
.ui-dialog.ui-dialog-off-canvas .ui-dialog-title:before {
background: transparent url(../../../misc/icons/ffffff/pencil.svg) no-repeat scroll center center;
background-size: 100% auto;
content: '';
display: block;
height: 100%;
position: absolute;
left: 1em; /* LTR */
top: 0;
width: 20px;
}
[dir="rtl"] .ui-dialog.ui-dialog-off-canvas .ui-dialog-title:before {
left: auto;
right: 1em;
}
/* Override default styling from jQuery UI. */
#drupal-off-canvas .ui-state-default,
#drupal-off-canvas .ui-widget-content .ui-state-default,
#drupal-off-canvas .ui-widget-header .ui-state-default {
border: 0;
font-weight: normal;
font-size: 14px;
color: #333;
}
#drupal-off-canvas .ui-widget-content a {
color: #85bef4;
}
@@ -0,0 +1,23 @@
/**
* @file
* Styling for Settings Tray module.
*/
/*
* Position the edit toolbar tab.
* @todo Move changes into contextual module when Settings Tray is not
* experimental: https://www.drupal.org/node/2784591.
*/
.toolbar .toolbar-bar .contextual-toolbar-tab.toolbar-tab {
float: left;
}
[dir="rtl"] .toolbar .toolbar-bar .contextual-toolbar-tab.toolbar-tab {
float: right;
}
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode a,
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode input {
pointer-events: none;
}
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode .contextual-links a {
pointer-events: inherit;
}
@@ -0,0 +1,19 @@
/**
* @file
* Motion effects for Settings Tray module.
*
* Motion effects are in a separate file so that they can be easily turned off
* to improve performance if desired.
*/
/* Transition the edit icon in the toolbar. */
#toolbar-bar.button.toolbar-icon.toolbar-icon.toolbar-icon-edit:before {
transition: all .7s ease;
}
/* Transition the editables on the page, their contextual links and their hover states. */
.dialog-off-canvas__main-canvas .contextual,
.dialog-off-canvas__main-canvas .js-settings-tray-edit-mode .settings-tray-editable,
.dialog-off-canvas__main-canvas.js-tray-open .js-settings-tray-edit-mode .settings-tray-editable {
transition: all .7s ease;
}
@@ -0,0 +1,70 @@
/**
* @file
* Visual styling for Settings Tray module.
*/
/* @todo remove the @imports when we find a better way to load these styles last.
* https://www.drupal.org/node/1945262.
*/
/* Style the edit mode toolbar and tabs. */
#toolbar-bar.js-settings-tray-edit-mode {
background-image: linear-gradient(to bottom,#0c97ed,#1f86c7);
}
.js-settings-tray-edit-mode .toolbar-item:not(.toolbar-icon-edit) {
color: #999;
}
.js-settings-tray-edit-mode .toolbar-item:not(.toolbar-icon-edit) .is-active {
color: #333;
}
/* Style both the edit and editing states of the contextual links toggle tab. */
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item,
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item.is-active,
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item:focus {
background-color: #50a0e9;
background-image: linear-gradient(to bottom, #007bc6, #0071b8);
color: #ddd;
text-shadow: none;
font-weight: bold;
outline: none;
}
/* Make the hover of the inactive state the same as the active state. */
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item:hover,
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item.is-active {
background-image: linear-gradient(to bottom, #0c97ed, #1f86c7);
color: #fff;
}
/* Make the hover of the active state the same as the inactive state. */
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item.is-active:hover {
background-color: #2369a6;
background-image: linear-gradient(to bottom, #007bc6, #0071b8);
color: #fff;
}
/* Make the inactive icon grey. */
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item:before {
background-image: url(../../../misc/icons/bebebe/pencil.svg);
}
/* Make the active icon white. */
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item.is-active:before {
background-image: url(../../../misc/icons/ffffff/pencil.svg);
}
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item:hover:before {
background-image: url(../../../misc/icons/ffffff/pencil.svg);
}
.toolbar-tab > .toolbar-icon.toolbar-icon-edit.toolbar-item:hover > .toolbar-icon-edit:before {
background-image: url(../../../misc/icons/ffffff/pencil.svg);
}
.toolbar-tab > .button.toolbar-icon.toolbar-icon.toolbar-icon-edit:before {
background-image: url(../../../misc/icons/ffffff/pencil.svg);
}
/* Style the editables while in edit mode. */
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode .settings-tray-editable {
outline: 1px dashed rgba(0,0,0,0.5);
box-shadow: 0 0 0 1px rgba(255,255,255,0.7);
}
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode .settings-tray-editable:hover,
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode .settings-tray-editable.settings-tray-active-editable {
background-color: rgba(0,0,0,0.2);
}
@@ -0,0 +1,67 @@
/**
* @file
* Visual styling for the toolbar when Settings Tray module is enabled.
*/
/* @todo Move this into toolbar when module is not experimental:
* https://www.drupal.org/node/2784593.
*/
/* Style the edit mode toolbar and tabs. */
#toolbar-bar.js-settings-tray-edit-mode {
background-color: #fff;
}
#toolbar-bar.js-settings-tray-edit-mode .toolbar-item {
color: #999;
}
#toolbar-bar.js-settings-tray-edit-mode .toolbar-item .is-active {
color: #333;
}
/* Style both the edit and editing states of the contextual links toggle tab. */
.toolbar-icon-edit.toolbar-item {
background-color: #50a0e9;
background-image: linear-gradient(to bottom, #007bc6, #0071b8);
color: #eee;
text-shadow: 0 1px hsla(0, 0%, 0%, 0.5);
font-weight: 700;
-webkit-font-smoothing: antialiased;
}
.toolbar-icon-edit.toolbar-item.is-active {
background-color: #50a0e9;
background-image: linear-gradient(to bottom, #007bc6, #0071b8);
color: #eee;
text-shadow: 0 1px hsla(0, 0%, 0%, 0.5);
font-weight: 700;
-webkit-font-smoothing: antialiased;
}
.toolbar-tab:hover > .toolbar-icon-edit,
.toolbar-icon-edit:focus .toolbar-item {
background-color: #2369a6;
background-image: linear-gradient(to bottom, #0c97ed, #1f86c7);
border-color: #1e5c90;
color: #fff;
outline: none;
}
.toolbar-icon.toolbar-icon-edit.toolbar-item:before,
button.toolbar-icon.toolbar-icon-edit.toolbar-item:before {
background-image: url(../../../misc/icons/bebebe/pencil.svg);
}
.toolbar-icon.toolbar-icon-edit.toolbar-item:before:hover,
button.toolbar-icon.toolbar-icon-edit.toolbar-item:before:focus {
background-image: url(../../../misc/icons/ffffff/pencil.svg);
}
.toolbar-icon.toolbar-icon-edit.toolbar-item:hover > .toolbar-icon-edit:before {
background-image: url(../../../misc/icons/ffffff/pencil.svg);
}
#toolbar-bar.button.toolbar-icon.toolbar-icon.toolbar-icon-edit:before {
background-image: url(../../../misc/icons/ffffff/pencil.svg);
}
#toolbar-bar.js-settings-tray-edit-mode button.toolbar-icon.toolbar-icon-edit.toolbar-item.is-active {
background-image: none;
color: #fff;
}
#toolbar-bar.js-settings-tray-edit-mode button.toolbar-icon.toolbar-icon-edit.toolbar-item.is-active:hover {
background-image: linear-gradient(to bottom, #0094f0, #0e69be);
}
@@ -0,0 +1,270 @@
/**
* @file
* Drupal's off-canvas library.
*
* @todo This functionality should extracted into a new core library or a part
* of the current drupal.dialog.ajax library.
* https://www.drupal.org/node/2784443
*
* @private
*/
(($, Drupal, debounce, displace) => {
/**
* Off-canvas dialog implementation using jQuery Dialog.
*
* Transforms the regular dialogs created using Drupal.dialog when the dialog
* element equals '#drupal-off-canvas' into an side-loading dialog.
*
* @namespace
*/
Drupal.offCanvas = {
/**
* The minimum width to use body displace needs to match the width at which
* the tray will be %100 width. @see settings_tray.module.css
* @type {Number}
*/
minDisplaceWidth: 768,
/**
* Wrapper used to position off-canvas dialog.
* @type {jQuery}
*/
$mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
/**
* Determines if an element is an off-canvas dialog.
*
* @param {jQuery} $element
* The dialog element.
* @return {bool}
* True this is currently an off-canvas dialog.
*/
isOffCanvas($element) {
return $element.is('#drupal-off-canvas');
},
/**
* Remove off-canvas dialog events.
*
* @param {jQuery} $element
* The target element.
*/
removeOffCanvasEvents($element) {
$element.off('.off-canvas');
$(document).off('.off-canvas');
$(window).off('.off-canvas');
},
/**
* Handler fired before an off-canvas dialog has been opened.
* @param {Object} settings
* Settings related to the composition of the dialog.
* @return {undefined}
*/
beforeCreate({ settings, $element }) {
// Clean up previous dialog event handlers.
Drupal.offCanvas.removeOffCanvasEvents($element);
$('body').addClass('js-tray-open');
settings.dialogClass += ' ui-dialog-off-canvas';
// @see http://api.jqueryui.com/position/
settings.position = {
my: 'left top',
at: `${Drupal.offCanvas.getEdge()} top`,
of: window,
};
/**
* Applies initial height to dialog based on window height.
* @see http://api.jqueryui.com/dialog for all dialog options.
*/
settings.height = $(window).height();
},
/**
* Handler fired after an off-canvas dialog has been closed.
* @return {undefined}
*/
beforeClose({ $element }) {
$('body').removeClass('js-tray-open');
// Remove all *.off-canvas events
Drupal.offCanvas.removeOffCanvasEvents($element);
Drupal.offCanvas.$mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`, 0);
},
/**
* Handler fired when an off-canvas dialog has been opened.
* @param {jQuery} $element
* The off-canvas dialog element.
* @param {Object} settings
* Settings related to the composition of the dialog.
* @return {undefined}
*/
afterCreate({ $element, settings }) {
const eventData = { settings, $element, offCanvasDialog: this };
$element
.on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.handleDialogResize)
.on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.bodyPadding);
Drupal.offCanvas.getContainer($element).attr(`data-offset-${Drupal.offCanvas.getEdge()}`, '');
$(window)
.on('resize.off-canvas', eventData, debounce(Drupal.offCanvas.resetSize, 100))
.trigger('resize.off-canvas');
},
/**
* Toggle classes based on title existence.
* Called with Drupal.offCanvas.afterCreate.
* @param {Object} settings
* Settings related to the composition of the dialog.
* @return {undefined}
*/
render({ settings }) {
$('.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
},
/**
* Adjusts the dialog on resize.
*
* @param {jQuery.Event} event
* The event triggered.
* @param {object} event.data
* Data attached to the event.
*/
handleDialogResize(event) {
const $element = event.data.$element;
const $container = Drupal.offCanvas.getContainer($element);
const $offsets = $container.find('> :not(#drupal-off-canvas, .ui-resizable-handle)');
let offset = 0;
// Let scroll element take all the height available.
$element.css({ height: 'auto' });
const modalHeight = $container.height();
$offsets.each((i, e) => {
offset += $(e).outerHeight();
});
// Take internal padding into account.
const scrollOffset = $element.outerHeight() - $element.height();
$element.height(modalHeight - offset - scrollOffset);
},
/**
* Resets the size of the dialog.
*
* @param {jQuery.Event} event
* The event triggered.
* @param {object} event.data
* Data attached to the event.
*/
resetSize(event) {
const offsets = displace.offsets;
const $element = event.data.$element;
const container = Drupal.offCanvas.getContainer($element);
const topPosition = (offsets.top !== 0 ? `+${offsets.top}` : '');
const adjustedOptions = {
// @see http://api.jqueryui.com/position/
position: {
my: `${Drupal.offCanvas.getEdge()} top`,
at: `${Drupal.offCanvas.getEdge()} top${topPosition}`,
of: window,
},
};
container.css({
position: 'fixed',
height: `${$(window).height() - (offsets.top + offsets.bottom)}px`,
});
$element
.dialog('option', adjustedOptions)
.trigger('dialogContentResize.off-canvas');
},
/**
* Adjusts the body padding when the dialog is resized.
*
* @param {jQuery.Event} event
* The event triggered.
* @param {object} event.data
* Data attached to the event.
*/
bodyPadding(event) {
if ($('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth) {
return;
}
const $element = event.data.$element;
const $container = Drupal.offCanvas.getContainer($element);
const $mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper;
const width = $container.outerWidth();
const mainCanvasPadding = $mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`);
if (width !== mainCanvasPadding) {
$mainCanvasWrapper.css(`padding-${Drupal.offCanvas.getEdge()}`, `${width}px`);
$container.attr(`data-offset-${Drupal.offCanvas.getEdge()}`, width);
displace();
}
},
/**
* The HTML element that surrounds the dialog.
* @param {HTMLElement} $element
* The dialog element.
*
* @return {HTMLElement}
* The containing element.
*/
getContainer($element) {
return $element.dialog('widget');
},
/**
* The edge of the screen that the dialog should appear on.
*
* @return {string}
* The edge the tray will be shown on, left or right.
*/
getEdge() {
return document.documentElement.dir === 'rtl' ? 'left' : 'right';
},
};
/**
* Attaches off-canvas dialog behaviors.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attaches event listeners for off-canvas dialogs.
*/
Drupal.behaviors.offCanvasEvents = {
attach: () => {
$(window).once('off-canvas').on({
'dialog:beforecreate': (event, dialog, $element, settings) => {
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.beforeCreate({ dialog, $element, settings });
}
},
'dialog:aftercreate': (event, dialog, $element, settings) => {
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.render({ dialog, $element, settings });
Drupal.offCanvas.afterCreate({ $element, settings });
}
},
'dialog:beforeclose': (event, dialog, $element) => {
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.beforeClose({ dialog, $element });
}
},
});
},
};
})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
+149
View File
@@ -0,0 +1,149 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal, debounce, displace) {
Drupal.offCanvas = {
minDisplaceWidth: 768,
$mainCanvasWrapper: $('[data-off-canvas-main-canvas]'),
isOffCanvas: function isOffCanvas($element) {
return $element.is('#drupal-off-canvas');
},
removeOffCanvasEvents: function removeOffCanvasEvents($element) {
$element.off('.off-canvas');
$(document).off('.off-canvas');
$(window).off('.off-canvas');
},
beforeCreate: function beforeCreate(_ref) {
var settings = _ref.settings,
$element = _ref.$element;
Drupal.offCanvas.removeOffCanvasEvents($element);
$('body').addClass('js-tray-open');
settings.dialogClass += ' ui-dialog-off-canvas';
settings.position = {
my: 'left top',
at: Drupal.offCanvas.getEdge() + ' top',
of: window
};
settings.height = $(window).height();
},
beforeClose: function beforeClose(_ref2) {
var $element = _ref2.$element;
$('body').removeClass('js-tray-open');
Drupal.offCanvas.removeOffCanvasEvents($element);
Drupal.offCanvas.$mainCanvasWrapper.css('padding-' + Drupal.offCanvas.getEdge(), 0);
},
afterCreate: function afterCreate(_ref3) {
var $element = _ref3.$element,
settings = _ref3.settings;
var eventData = { settings: settings, $element: $element, offCanvasDialog: this };
$element.on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.handleDialogResize).on('dialogContentResize.off-canvas', eventData, Drupal.offCanvas.bodyPadding);
Drupal.offCanvas.getContainer($element).attr('data-offset-' + Drupal.offCanvas.getEdge(), '');
$(window).on('resize.off-canvas', eventData, debounce(Drupal.offCanvas.resetSize, 100)).trigger('resize.off-canvas');
},
render: function render(_ref4) {
var settings = _ref4.settings;
$('.ui-dialog-off-canvas, .ui-dialog-off-canvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
},
handleDialogResize: function handleDialogResize(event) {
var $element = event.data.$element;
var $container = Drupal.offCanvas.getContainer($element);
var $offsets = $container.find('> :not(#drupal-off-canvas, .ui-resizable-handle)');
var offset = 0;
$element.css({ height: 'auto' });
var modalHeight = $container.height();
$offsets.each(function (i, e) {
offset += $(e).outerHeight();
});
var scrollOffset = $element.outerHeight() - $element.height();
$element.height(modalHeight - offset - scrollOffset);
},
resetSize: function resetSize(event) {
var offsets = displace.offsets;
var $element = event.data.$element;
var container = Drupal.offCanvas.getContainer($element);
var topPosition = offsets.top !== 0 ? '+' + offsets.top : '';
var adjustedOptions = {
position: {
my: Drupal.offCanvas.getEdge() + ' top',
at: Drupal.offCanvas.getEdge() + ' top' + topPosition,
of: window
}
};
container.css({
position: 'fixed',
height: $(window).height() - (offsets.top + offsets.bottom) + 'px'
});
$element.dialog('option', adjustedOptions).trigger('dialogContentResize.off-canvas');
},
bodyPadding: function bodyPadding(event) {
if ($('body').outerWidth() < Drupal.offCanvas.minDisplaceWidth) {
return;
}
var $element = event.data.$element;
var $container = Drupal.offCanvas.getContainer($element);
var $mainCanvasWrapper = Drupal.offCanvas.$mainCanvasWrapper;
var width = $container.outerWidth();
var mainCanvasPadding = $mainCanvasWrapper.css('padding-' + Drupal.offCanvas.getEdge());
if (width !== mainCanvasPadding) {
$mainCanvasWrapper.css('padding-' + Drupal.offCanvas.getEdge(), width + 'px');
$container.attr('data-offset-' + Drupal.offCanvas.getEdge(), width);
displace();
}
},
getContainer: function getContainer($element) {
return $element.dialog('widget');
},
getEdge: function getEdge() {
return document.documentElement.dir === 'rtl' ? 'left' : 'right';
}
};
Drupal.behaviors.offCanvasEvents = {
attach: function attach() {
$(window).once('off-canvas').on({
'dialog:beforecreate': function dialogBeforecreate(event, dialog, $element, settings) {
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.beforeCreate({ dialog: dialog, $element: $element, settings: settings });
}
},
'dialog:aftercreate': function dialogAftercreate(event, dialog, $element, settings) {
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.render({ dialog: dialog, $element: $element, settings: settings });
Drupal.offCanvas.afterCreate({ $element: $element, settings: settings });
}
},
'dialog:beforeclose': function dialogBeforeclose(event, dialog, $element) {
if (Drupal.offCanvas.isOffCanvas($element)) {
Drupal.offCanvas.beforeClose({ dialog: dialog, $element: $element });
}
}
});
}
};
})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
@@ -0,0 +1,250 @@
/**
* @file
* Drupal's Settings Tray library.
*
* @private
*/
(function ($, Drupal) {
const blockConfigureSelector = '[data-settings-tray-edit]';
const toggleEditSelector = '[data-drupal-settingstray="toggle"]';
const itemsToToggleSelector = '[data-off-canvas-main-canvas], #toolbar-bar, [data-drupal-settingstray="editable"] a, [data-drupal-settingstray="editable"] button';
const contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button';
const quickEditItemSelector = '[data-quickedit-entity-id]';
/**
* Prevent default click events except contextual links.
*
* In edit mode the default action of click events is suppressed.
*
* @param {jQuery.Event} event
* The click event.
*/
function preventClick(event) {
// Do not prevent contextual links.
if ($(event.target).closest('.contextual-links').length) {
return;
}
event.preventDefault();
}
/**
* Close any active toolbar tray before entering edit mode.
*/
function closeToolbarTrays() {
$(Drupal.toolbar.models.toolbarModel.get('activeTab')).trigger('click');
}
/**
* Disables the QuickEdit module editor if open.
*/
function disableQuickEdit() {
$('.quickedit-toolbar button.action-cancel').trigger('click');
}
/**
* Closes/removes off-canvas.
*/
function closeOffCanvas() {
$('.ui-dialog-off-canvas .ui-dialog-titlebar-close').trigger('click');
}
/**
* Gets all items that should be toggled with class during edit mode.
*
* @return {jQuery}
* Items that should be toggled.
*/
function getItemsToToggle() {
return $(itemsToToggleSelector).not(contextualItemsSelector);
}
/**
* Helper to switch edit mode state.
*
* @param {boolean} editMode
* True enable edit mode, false disable edit mode.
*/
function setEditModeState(editMode) {
if (!document.querySelector('[data-off-canvas-main-canvas]')) {
throw new Error('data-off-canvas-main-canvas is missing from settings-tray-page-wrapper.html.twig');
}
editMode = !!editMode;
const $editButton = $(toggleEditSelector);
let $editables;
// Turn on edit mode.
if (editMode) {
$editButton.text(Drupal.t('Editing'));
closeToolbarTrays();
$editables = $('[data-drupal-settingstray="editable"]').once('settingstray');
if ($editables.length) {
// Use event capture to prevent clicks on links.
document.querySelector('[data-off-canvas-main-canvas]').addEventListener('click', preventClick, true);
// When a click occurs try and find the settings-tray edit link
// and click it.
$editables
.not(contextualItemsSelector)
.on('click.settingstray', (e) => {
// Contextual links are allowed to function in Edit mode.
if ($(e.target).closest('.contextual').length || !localStorage.getItem('Drupal.contextualToolbar.isViewing')) {
return;
}
$(e.currentTarget).find(blockConfigureSelector).trigger('click');
disableQuickEdit();
});
$(quickEditItemSelector)
.not(contextualItemsSelector)
.on('click.settingstray', (e) => {
/**
* For all non-contextual links or the contextual QuickEdit link
* close the off-canvas dialog.
*/
if (!$(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
closeOffCanvas();
}
// Do not trigger if target is quick edit link to avoid loop.
if ($(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
return;
}
$(e.currentTarget).find('li.quickedit a').trigger('click');
});
}
}
// Disable edit mode.
else {
$editables = $('[data-drupal-settingstray="editable"]').removeOnce('settingstray');
if ($editables.length) {
document.querySelector('[data-off-canvas-main-canvas]').removeEventListener('click', preventClick, true);
$editables.off('.settingstray');
$(quickEditItemSelector).off('.settingstray');
}
$editButton.text(Drupal.t('Edit'));
closeOffCanvas();
disableQuickEdit();
}
getItemsToToggle().toggleClass('js-settings-tray-edit-mode', editMode);
$('.edit-mode-inactive').toggleClass('visually-hidden', editMode);
}
/**
* Helper to check the state of the settings-tray mode.
*
* @todo don't use a class for this.
*
* @return {boolean}
* State of the settings-tray edit mode.
*/
function isInEditMode() {
return $('#toolbar-bar').hasClass('js-settings-tray-edit-mode');
}
/**
* Helper to toggle Edit mode.
*/
function toggleEditMode() {
setEditModeState(!isInEditMode());
}
/**
* Reacts to contextual links being added.
*
* @param {jQuery.Event} event
* The `drupalContextualLinkAdded` event.
* @param {object} data
* An object containing the data relevant to the event.
*
* @listens event:drupalContextualLinkAdded
*/
$(document).on('drupalContextualLinkAdded', (event, data) => {
// When the first contextual link is added to the page set Edit Mode.
$('body').once('settings_tray.edit_mode_init').each(() => {
const editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
if (editMode) {
setEditModeState(true);
}
});
/**
* Bind Ajax behaviors to all items showing the class.
* @todo Fix contextual links to work with use-ajax links in
* https://www.drupal.org/node/2764931.
*/
Drupal.attachBehaviors(data.$el[0]);
/**
* Bind a listener to all 'Quick edit' links for blocks. Click "Edit" button
* in toolbar to force Contextual Edit which starts Settings Tray edit
* mode also.
*/
data.$el.find(blockConfigureSelector)
.on('click.settingstray', () => {
if (!isInEditMode()) {
$(toggleEditSelector).trigger('click').trigger('click.settings_tray');
}
/**
* Always disable QuickEdit regardless of whether "EditMode" was just
* enabled.
*/
disableQuickEdit();
});
});
$(document).on('keyup.settingstray', (e) => {
if (isInEditMode() && e.keyCode === 27) {
Drupal.announce(
Drupal.t('Exited edit mode.'),
);
toggleEditMode();
}
});
/**
* Toggle the js-settings-tray-edit-mode class on items that we want to disable while in edit mode.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Toggle the js-settings-tray-edit-mode class.
*/
Drupal.behaviors.toggleEditMode = {
attach() {
$(toggleEditSelector).once('settingstray').on('click.settingstray', toggleEditMode);
// Find all Ajax instances that use the 'off_canvas' renderer.
Drupal.ajax.instances
// If there is an element and the renderer is 'off_canvas' then we want
// to add our changes.
.filter(instance => instance && $(instance.element).attr('data-dialog-renderer') === 'off_canvas')
// Loop through all Ajax instances that use the 'off_canvas' renderer to
// set active editable ID.
.forEach((instance) => {
// Check to make sure existing dialogOptions aren't overridden.
if (!('dialogOptions' in instance.options.data)) {
instance.options.data.dialogOptions = {};
}
instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id');
instance.progress = { type: 'fullscreen' };
});
},
};
// Manage Active editable class on opening and closing of the dialog.
$(window).on({
'dialog:beforecreate': (event, dialog, $element, settings) => {
if ($element.is('#drupal-off-canvas')) {
$('body .settings-tray-active-editable').removeClass('settings-tray-active-editable');
const $activeElement = $(`#${settings.settingsTrayActiveEditableId}`);
if ($activeElement.length) {
$activeElement.addClass('settings-tray-active-editable');
}
}
},
'dialog:beforeclose': (event, dialog, $element) => {
if ($element.is('#drupal-off-canvas')) {
$('body .settings-tray-active-editable').removeClass('settings-tray-active-editable');
}
},
});
}(jQuery, Drupal));
@@ -0,0 +1,154 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
(function ($, Drupal) {
var blockConfigureSelector = '[data-settings-tray-edit]';
var toggleEditSelector = '[data-drupal-settingstray="toggle"]';
var itemsToToggleSelector = '[data-off-canvas-main-canvas], #toolbar-bar, [data-drupal-settingstray="editable"] a, [data-drupal-settingstray="editable"] button';
var contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button';
var quickEditItemSelector = '[data-quickedit-entity-id]';
function preventClick(event) {
if ($(event.target).closest('.contextual-links').length) {
return;
}
event.preventDefault();
}
function closeToolbarTrays() {
$(Drupal.toolbar.models.toolbarModel.get('activeTab')).trigger('click');
}
function disableQuickEdit() {
$('.quickedit-toolbar button.action-cancel').trigger('click');
}
function closeOffCanvas() {
$('.ui-dialog-off-canvas .ui-dialog-titlebar-close').trigger('click');
}
function getItemsToToggle() {
return $(itemsToToggleSelector).not(contextualItemsSelector);
}
function setEditModeState(editMode) {
if (!document.querySelector('[data-off-canvas-main-canvas]')) {
throw new Error('data-off-canvas-main-canvas is missing from settings-tray-page-wrapper.html.twig');
}
editMode = !!editMode;
var $editButton = $(toggleEditSelector);
var $editables = void 0;
if (editMode) {
$editButton.text(Drupal.t('Editing'));
closeToolbarTrays();
$editables = $('[data-drupal-settingstray="editable"]').once('settingstray');
if ($editables.length) {
document.querySelector('[data-off-canvas-main-canvas]').addEventListener('click', preventClick, true);
$editables.not(contextualItemsSelector).on('click.settingstray', function (e) {
if ($(e.target).closest('.contextual').length || !localStorage.getItem('Drupal.contextualToolbar.isViewing')) {
return;
}
$(e.currentTarget).find(blockConfigureSelector).trigger('click');
disableQuickEdit();
});
$(quickEditItemSelector).not(contextualItemsSelector).on('click.settingstray', function (e) {
if (!$(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
closeOffCanvas();
}
if ($(e.target).parent().hasClass('contextual') || $(e.target).parent().hasClass('quickedit')) {
return;
}
$(e.currentTarget).find('li.quickedit a').trigger('click');
});
}
} else {
$editables = $('[data-drupal-settingstray="editable"]').removeOnce('settingstray');
if ($editables.length) {
document.querySelector('[data-off-canvas-main-canvas]').removeEventListener('click', preventClick, true);
$editables.off('.settingstray');
$(quickEditItemSelector).off('.settingstray');
}
$editButton.text(Drupal.t('Edit'));
closeOffCanvas();
disableQuickEdit();
}
getItemsToToggle().toggleClass('js-settings-tray-edit-mode', editMode);
$('.edit-mode-inactive').toggleClass('visually-hidden', editMode);
}
function isInEditMode() {
return $('#toolbar-bar').hasClass('js-settings-tray-edit-mode');
}
function toggleEditMode() {
setEditModeState(!isInEditMode());
}
$(document).on('drupalContextualLinkAdded', function (event, data) {
$('body').once('settings_tray.edit_mode_init').each(function () {
var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
if (editMode) {
setEditModeState(true);
}
});
Drupal.attachBehaviors(data.$el[0]);
data.$el.find(blockConfigureSelector).on('click.settingstray', function () {
if (!isInEditMode()) {
$(toggleEditSelector).trigger('click').trigger('click.settings_tray');
}
disableQuickEdit();
});
});
$(document).on('keyup.settingstray', function (e) {
if (isInEditMode() && e.keyCode === 27) {
Drupal.announce(Drupal.t('Exited edit mode.'));
toggleEditMode();
}
});
Drupal.behaviors.toggleEditMode = {
attach: function attach() {
$(toggleEditSelector).once('settingstray').on('click.settingstray', toggleEditMode);
Drupal.ajax.instances.filter(function (instance) {
return instance && $(instance.element).attr('data-dialog-renderer') === 'off_canvas';
}).forEach(function (instance) {
if (!('dialogOptions' in instance.options.data)) {
instance.options.data.dialogOptions = {};
}
instance.options.data.dialogOptions.settingsTrayActiveEditableId = $(instance.element).parents('.settings-tray-editable').attr('id');
instance.progress = { type: 'fullscreen' };
});
}
};
$(window).on({
'dialog:beforecreate': function dialogBeforecreate(event, dialog, $element, settings) {
if ($element.is('#drupal-off-canvas')) {
$('body .settings-tray-active-editable').removeClass('settings-tray-active-editable');
var $activeElement = $('#' + settings.settingsTrayActiveEditableId);
if ($activeElement.length) {
$activeElement.addClass('settings-tray-active-editable');
}
}
},
'dialog:beforeclose': function dialogBeforeclose(event, dialog, $element) {
if ($element.is('#drupal-off-canvas')) {
$('body .settings-tray-active-editable').removeClass('settings-tray-active-editable');
}
}
});
})(jQuery, Drupal);
@@ -0,0 +1,61 @@
<?php
/**
* @file
* Documentation for Settings Tray API.
*/
/**
* @defgroup settings_tray Settings Tray API
* @{
* Settings Tray API
*
* @section sec_api The API: the form in the Settings Tray
*
* By default, every block will show its built-in form in the Settings Tray.
* However, many blocks would benefit from a tailored form which either:
* - limits the form items displayed in the Settings Tray to only items that
* affect the content of the rendered block
* - adds additional form items to edit configuration that is rendered by the
* block. See \Drupal\settings_tray\Form\SystemBrandingOffCanvasForm which
* adds site name and slogan configuration.
*
* These can be used to provide a better experience, so that the Settings Tray
* only displays what the user will expect to change when editing the block.
*
* Each block plugin can specify which form to use in the Settings Tray dialog
* in its plugin annotation:
* @code
* forms = {
* "settings_tray" = "\Drupal\some_module\Form\MyBlockOffCanvasForm",
* },
* @endcode
*
* In some cases, a block's content is not configurable (for example, the title,
* main content, and help blocks). Such blocks can opt out of providing an
* off-canvas form:
* @code
* forms = {
* "settings_tray" = FALSE,
* },
* @endcode
*
* Finally, blocks that do not specify an off-canvas form using the annotation
* above will automatically have it set to their plugin class. For example, the
* "Powered by Drupal" block plugin
* (\Drupal\system\Plugin\Block\SystemPoweredByBlock) automatically gets
* this added to its annotation:
* @code
* forms = {
* "settings_tray" = "\Drupal\system\Plugin\Block\SystemPoweredByBlock",
* },
* @endcode
*
* Therefore, the entire Settings Tray API is just this annotation: it controls
* what the Settings Tray does for a given block.
*
* @see settings_tray_block_alter()
* @see \Drupal\Tests\settings_tray\Functional\SettingsTrayBlockTest::testPossibleAnnotations()
*
* @}
*/
@@ -0,0 +1,16 @@
name: 'Settings Tray'
type: module
description: 'Provides the ability to change the most common configuration from the Drupal front-end.'
package: Core (Experimental)
# version: VERSION
# core: 8.x
dependencies:
- block
- toolbar
- contextual
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
@@ -0,0 +1,24 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Settings Tray module.
*/
use Drupal\Core\Cache\Cache;
/**
* Implements hook_install().
*/
function settings_tray_install() {
// This module affects the rendering of blocks and of the page.
// @todo Remove in https://www.drupal.org/node/2783791.
Cache::invalidateTags(['rendered']);
// \Drupal\Core\Menu\ContextualLinkManager caches per-group definitions
// without associating the cache tag that would allow them to be cleared
// by its clearCachedDefinitions() implementation that is automatically
// invoked when modules are installed.
// @todo Remove when that is fixed in https://www.drupal.org/node/2773591.
\Drupal::service('cache.discovery')->deleteAll();
}
@@ -0,0 +1,45 @@
drupal.settings_tray:
version: VERSION
js:
js/settings_tray.js: {}
css:
component:
css/settings_tray.module.css: {}
css/settings_tray.motion.css: {}
css/settings_tray.toolbar.css: {}
theme:
# @todo Set the group higher than CSS_AGGREGATE_THEME so that it overrides
# both jQuery UI and Classy's dialog.css, remove in
# https://www.drupal.org/node/1945262.
css/settings_tray.theme.css: { group: 200 }
dependencies:
- core/jquery
- core/drupal
- core/jquery.once
- core/drupal.ajax
drupal.off_canvas:
version: VERSION
js:
js/off-canvas.js: {}
css:
base:
css/off-canvas.reset.css: {}
css/off-canvas.base.css: {}
css/off-canvas.css: {}
css/off-canvas.theme.css: { group: 200 }
component:
css/off-canvas.motion.css: {}
css/off-canvas.button.css: {}
css/off-canvas.form.css: {}
css/off-canvas.table.css: {}
css/off-canvas.details.css: {}
css/off-canvas.tabledrag.css: {}
css/off-canvas.dropbutton.css: {}
dependencies:
- core/jquery
- core/drupal
- core/drupal.ajax
- core/drupal.announce
- core/drupal.dialog
- core/drupal.dialog.ajax
@@ -0,0 +1,10 @@
settings_tray.block_configure:
title: 'Quick edit'
route_name: 'entity.block.off_canvas_form'
group: 'block'
options:
attributes:
class: ['use-ajax']
data-dialog-type: dialog
data-dialog-renderer: off_canvas
data-settings-tray-edit: true
@@ -0,0 +1,212 @@
<?php
/**
* @file
* Allows configuring blocks and other configuration from the site front-end.
*/
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\settings_tray\Block\BlockEntityOffCanvasForm;
use Drupal\settings_tray\Form\SystemBrandingOffCanvasForm;
use Drupal\settings_tray\Form\SystemMenuOffCanvasForm;
/**
* Implements hook_help().
*/
function settings_tray_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.settings_tray':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Settings Tray module provides an \'edit mode\' in which clicking on a block opens a slide-out tray which allows configuration to be altered without leaving the page.For more information, see the <a href=":settings-tray-documentation">online documentation for the Settings Tray module</a>.', [':settings-tray-documentation' => 'https://www.drupal.org/documentation/modules/settings_tray']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Editing blocks on the same page in the slide-out tray') . '</dt>';
$output .= '</dl>';
return ['#markup' => $output];
}
}
/**
* Implements hook_contextual_links_view_alter().
*
* Change Configure Blocks into off_canvas links.
*/
function settings_tray_contextual_links_view_alter(&$element, $items) {
if (isset($element['#links']['settings-trayblock-configure'])) {
// Place settings_tray link first.
$settings_tray_link = $element['#links']['settings-trayblock-configure'];
unset($element['#links']['settings-trayblock-configure']);
$element['#links'] = ['settings-trayblock-configure' => $settings_tray_link] + $element['#links'];
// If this is content block change title to avoid duplicate "Quick Edit".
if (isset($element['#links']['block-contentblock-edit'])) {
$element['#links']['settings-trayblock-configure']['title'] = t('Quick edit settings');
}
$element['#attached']['library'][] = 'settings_tray/drupal.off_canvas';
}
}
/**
* Implements hook_block_view_alter().
*/
function settings_tray_block_view_alter(array &$build) {
// Force a new 'data-contextual-id' attribute on blocks when this module is
// enabled so as not to reuse stale data cached client-side.
// @todo Remove when https://www.drupal.org/node/2773591 is fixed.
$build['#contextual_links']['settings_tray'] = [
'route_parameters' => [],
];
}
/**
* Implements hook_element_info_alter().
*/
function settings_tray_element_info_alter(&$type) {
if (isset($type['page'])) {
$type['page']['#theme_wrappers']['settings_tray_page_wrapper'] = ['#weight' => -1000];
}
}
/**
* Implements hook_theme().
*/
function settings_tray_theme() {
return [
'settings_tray_page_wrapper' => [
'variables' => ['children' => NULL],
],
];
}
/**
* Implements hook_entity_type_build().
*/
function settings_tray_entity_type_build(array &$entity_types) {
/* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
$entity_types['block']
->setFormClass('off_canvas', BlockEntityOffCanvasForm::class)
->setLinkTemplate('off_canvas-form', '/admin/structure/block/manage/{block}/off-canvas');
}
/**
* Implements hook_preprocess_HOOK() for block templates.
*/
function settings_tray_preprocess_block(&$variables) {
// Only blocks that have an settings_tray form will have a "Quick Edit" link.
// We could wait for the contextual links to be initialized on the client
// side, and then add the class and data- attribute below there (via
// JavaScript). But that would mean that it would be impossible to show
// Settings Tray's clickable regions immediately when the page loads. When
// latency is high, this will cause flicker.
// @see \Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck
/** @var \Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck $access_checker */
$access_checker = \Drupal::service('access_check.settings_tray.block.settings_tray_form');
/** @var \Drupal\Core\Block\BlockManagerInterface $block_plugin_manager */
$block_plugin_manager = \Drupal::service('plugin.manager.block');
/** @var \Drupal\Core\Block\BlockPluginInterface $block_plugin */
$block_plugin = $block_plugin_manager->createInstance($variables['plugin_id']);
if ($access_checker->accessBlockPlugin($block_plugin)->isAllowed()) {
// Add class and attributes to all blocks to allow Javascript to target.
$variables['attributes']['class'][] = 'settings-tray-editable';
$variables['attributes']['data-drupal-settingstray'] = 'editable';
}
}
/**
* Implements hook_toolbar_alter().
*
* Alters the 'contextual' toolbar tab if it exists (meaning the user is allowed
* to use contextual links) and if they can administer blocks.
*
* @todo Remove the "administer blocks" requirement in
* https://www.drupal.org/node/2822965.
*
* @see contextual_toolbar()
*/
function settings_tray_toolbar_alter(&$items) {
$items['contextual']['#cache']['contexts'][] = 'user.permissions';
if (isset($items['contextual']['tab']) && \Drupal::currentUser()->hasPermission('administer blocks')) {
$items['contextual']['#weight'] = -1000;
$items['contextual']['#attached']['library'][] = 'settings_tray/drupal.settings_tray';
$items['contextual']['tab']['#attributes']['data-drupal-settingstray'] = 'toggle';
// Set a class on items to mark whether they should be active in edit mode.
// @todo Create a dynamic method for modules to set their own items.
// https://www.drupal.org/node/2784589.
$edit_mode_items = ['contextual', 'block_place'];
foreach ($items as $key => $item) {
if (!in_array($key, $edit_mode_items) && (!isset($items[$key]['#wrapper_attributes']['class']) || !in_array('hidden', $items[$key]['#wrapper_attributes']['class']))) {
$items[$key]['#wrapper_attributes']['class'][] = 'edit-mode-inactive';
}
}
}
}
/**
* Implements hook_block_alter().
*
* Ensures every block plugin definition has an 'settings_tray' form specified.
*
* @see \Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck
*/
function settings_tray_block_alter(&$definitions) {
foreach ($definitions as &$definition) {
// If a block plugin already defines its own 'settings_tray' form, use that
// form instead of specifying one here.
if (isset($definition['forms']['settings_tray'])) {
continue;
}
switch ($definition['id']) {
// Use specialized forms for certain blocks that do not yet provide the
// form with their own annotation.
// @todo Move these into the corresponding block plugin annotations in
// https://www.drupal.org/node/2896356.
case 'system_menu_block':
$definition['forms']['settings_tray'] = SystemMenuOffCanvasForm::class;
break;
case 'system_branding_block':
$definition['forms']['settings_tray'] = SystemBrandingOffCanvasForm::class;
break;
// No off-canvas form for the page title block, despite it having
// contextual links: it's too confusing that you're editing configuration,
// not content, so the title itself cannot actually be changed.
// @todo Move these into the corresponding block plugin annotations in
// https://www.drupal.org/node/2896356.
case 'page_title_block':
$definition['forms']['settings_tray'] = FALSE;
break;
case 'system_main_block':
$definition['forms']['settings_tray'] = FALSE;
break;
case 'help_block':
$definition['forms']['settings_tray'] = FALSE;
break;
// Otherwise, use the block plugin's normal form rather than
// a custom form for Settings Tray.
default:
$definition['forms']['settings_tray'] = $definition['class'];
break;
}
}
}
/**
* Implements hook_css_alter().
*/
function settings_tray_css_alter(&$css, AttachedAssetsInterface $assets) {
// @todo Remove once conditional ordering is introduced in
// https://www.drupal.org/node/1945262.
$path = drupal_get_path('module', 'settings_tray') . '/css/settings_tray.theme.css';
if (isset($css[$path])) {
// Use 200 to come after CSS_AGGREGATE_THEME.
$css[$path]['group'] = 200;
}
}
@@ -0,0 +1,8 @@
entity.block.off_canvas_form:
path: '/admin/structure/block/manage/{block}/off-canvas'
defaults:
_entity_form: 'block.off_canvas'
_title_callback: '\Drupal\settings_tray\Block\BlockEntityOffCanvasForm::title'
requirements:
_permission: 'administer blocks'
_access_block_plugin_has_settings_tray_form: 'TRUE'
@@ -0,0 +1,11 @@
services:
main_content_renderer.off_canvas:
class: Drupal\settings_tray\Render\MainContent\OffCanvasRenderer
arguments: ['@title_resolver', '@renderer']
tags:
- { name: render.main_content_renderer, format: drupal_dialog.off_canvas }
access_check.settings_tray.block.settings_tray_form:
class: Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck
tags:
- { name: access_check, applies_to: _access_block_plugin_has_settings_tray_form }
@@ -0,0 +1,48 @@
<?php
namespace Drupal\settings_tray\Access;
use Drupal\block\BlockInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
use Drupal\Core\Routing\Access\AccessInterface;
/**
* Determines whether the requested block has a 'settings_tray' form.
*
* @internal
*/
class BlockPluginHasSettingsTrayFormAccessCheck implements AccessInterface {
/**
* Checks access for accessing a block's 'settings_tray' form.
*
* @param \Drupal\block\BlockInterface $block
* The block whose 'settings_tray' form is being accessed.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*/
public function access(BlockInterface $block) {
/** @var \Drupal\Core\Block\BlockPluginInterface $block_plugin */
$block_plugin = $block->getPlugin();
return $this->accessBlockPlugin($block_plugin);
}
/**
* Checks access for accessing a block plugin's 'settings_tray' form.
*
* @param \Drupal\Core\Block\BlockPluginInterface $block_plugin
* The block plugin whose 'settings_tray' form is being accessed.
*
* @return \Drupal\Core\Access\AccessResultInterface
* The access result.
*
* @see settings_tray_preprocess_block()
*/
public function accessBlockPlugin(BlockPluginInterface $block_plugin) {
return AccessResult::allowedIf($block_plugin instanceof PluginWithFormsInterface && $block_plugin->hasFormClass('settings_tray'));
}
}
@@ -0,0 +1,73 @@
<?php
namespace Drupal\settings_tray\Ajax;
use Drupal\Core\Ajax\OpenDialogCommand;
/**
* Defines an AJAX command to open content in a dialog in a off-canvas dialog.
*
* @ingroup ajax
*
* @internal
*/
class OpenOffCanvasDialogCommand extends OpenDialogCommand {
/**
* The dialog width to use if none is provided.
*/
const DEFAULT_DIALOG_WIDTH = 300;
/**
* Constructs an OpenOffCanvasDialogCommand object.
*
* The off-canvas dialog differs from the normal modal provided by
* OpenDialogCommand in that a off-canvas has built in positioning and
* behaviours. Drupal provides a built-in off-canvas dialog for this purpose,
* so the selector is hard-coded in the call to the parent constructor.
*
* @param string $title
* The title of the dialog.
* @param string|array $content
* The content that will be placed in the dialog, either a render array
* or an HTML string.
* @param array $dialog_options
* (optional) Settings to be passed to the dialog implementation. Any
* jQuery UI option can be used. See http://api.jqueryui.com/dialog.
* @param array|null $settings
* (optional) Custom settings that will be passed to the Drupal behaviors
* on the content of the dialog. If left empty, the settings will be
* populated automatically from the current request.
*/
public function __construct($title, $content, array $dialog_options = [], $settings = NULL) {
parent::__construct('#drupal-off-canvas', $title, $content, $dialog_options, $settings);
$this->dialogOptions['modal'] = FALSE;
$this->dialogOptions['autoResize'] = FALSE;
$this->dialogOptions['resizable'] = 'w';
$this->dialogOptions['draggable'] = FALSE;
$this->dialogOptions['drupalAutoButtons'] = FALSE;
// @todo drupal.ajax.js does not respect drupalAutoButtons properly, pass an
// empty set of buttons until https://www.drupal.org/node/2793343 is in.
$this->dialogOptions['buttons'] = [];
if (empty($dialog_options['dialogClass'])) {
$this->dialogOptions['dialogClass'] = 'ui-dialog-off-canvas';
}
// If no width option is provided then use the default width to avoid the
// dialog staying at the width of the previous instance when opened
// more than once, with different widths, on a single page.
if (!isset($this->dialogOptions['width'])) {
$this->dialogOptions['width'] = static::DEFAULT_DIALOG_WIDTH;
}
}
/**
* {@inheritdoc}
*/
public function render() {
$build = parent::render();
$build['effect'] = 'fade';
$build['speed'] = 1000;
return $build;
}
}
@@ -0,0 +1,194 @@
<?php
namespace Drupal\settings_tray\Block;
use Drupal\block\BlockForm;
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\RedirectCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
use Drupal\Core\Url;
/**
* Provides form for block instance forms when used in the off-canvas dialog.
*
* This form removes advanced sections of regular block form such as the
* visibility settings, machine ID and region.
*
* @internal
*/
class BlockEntityOffCanvasForm extends BlockForm {
/**
* Provides a title callback to get the block's admin label.
*
* @param \Drupal\block\BlockInterface $block
* The block entity.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The title.
*/
public function title(BlockInterface $block) {
// @todo Wrap "Configure " in <span class="visually-hidden"></span> once
// https://www.drupal.org/node/2359901 is fixed.
return $this->t('Configure @block', ['@block' => $block->getPlugin()->getPluginDefinition()['admin_label']]);
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$form = parent::form($form, $form_state);
// Create link to full block form.
$query = [];
if ($destination = $this->getRequest()->query->get('destination')) {
$query['destination'] = $destination;
}
$form['advanced_link'] = [
'#type' => 'link',
'#title' => $this->t('Advanced block options'),
'#url' => $this->entity->toUrl('edit-form', ['query' => $query]),
'#weight' => 1000,
];
// Remove the ID and region elements.
unset($form['id'], $form['region'], $form['settings']['admin_label']);
if (isset($form['settings']['label_display']) && isset($form['settings']['label'])) {
// Only show the label input if the label will be shown on the page.
$form['settings']['label_display']['#weight'] = -100;
$form['settings']['label']['#states']['visible'] = [
':input[name="settings[label_display]"]' => ['checked' => TRUE],
];
// Relabel to "Block title" because on the front-end this may be confused
// with page title.
$form['settings']['label']['#title'] = $this->t("Block title");
$form['settings']['label_display']['#title'] = $this->t("Display block title");
}
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$actions['submit']['#value'] = $this->t('Save @block', ['@block' => $this->entity->getPlugin()->getPluginDefinition()['admin_label']]);
$actions['delete']['#access'] = FALSE;
return $actions;
}
/**
* {@inheritdoc}
*/
protected function buildVisibilityInterface(array $form, FormStateInterface $form_state) {
// Do not display the visibility.
return [];
}
/**
* {@inheritdoc}
*/
protected function validateVisibility(array $form, FormStateInterface $form_state) {
// Intentionally empty.
}
/**
* {@inheritdoc}
*/
protected function submitVisibility(array $form, FormStateInterface $form_state) {
// Intentionally empty.
}
/**
* {@inheritdoc}
*/
protected function getPluginForm(BlockPluginInterface $block) {
if ($block instanceof PluginWithFormsInterface) {
return $this->pluginFormFactory->createInstance($block, 'settings_tray', 'configure');
}
return $block;
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$form['actions']['submit']['#ajax'] = [
'callback' => '::submitFormDialog',
];
$form['#attached']['library'][] = 'core/drupal.dialog.ajax';
// static::submitFormDialog() requires data-drupal-selector to be the same
// between the various Ajax requests. A bug in
// \Drupal\Core\Form\FormBuilder prevents that from happening unless
// $form['#id'] is also the same. Normally, #id is set to a unique HTML ID
// via Html::getUniqueId(), but here we bypass that in order to work around
// the data-drupal-selector bug. This is okay so long as we assume that this
// form only ever occurs once on a page.
// @todo Remove this workaround once https://www.drupal.org/node/2897377 is
// fixed.
$form['#id'] = Html::getId($form_state->getBuildInfo()['form_id']);
return $form;
}
/**
* Submit form dialog #ajax callback.
*
* @param array $form
* An associative array containing the structure of the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
* @return \Drupal\Core\Ajax\AjaxResponse
* An AJAX response that display validation error messages or redirects
* to a URL
*
* @todo Repalce this callback with generic trait in
* https://www.drupal.org/node/2896535.
*/
public function submitFormDialog(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
if ($form_state->hasAnyErrors()) {
$form['status_messages'] = [
'#type' => 'status_messages',
'#weight' => -1000,
];
$command = new ReplaceCommand('[data-drupal-selector="' . $form['#attributes']['data-drupal-selector'] . '"]', $form);
}
else {
if ($redirect_url = $this->getRedirectUrl()) {
$command = new RedirectCommand($redirect_url->setAbsolute()->toString());
}
else {
// Settings Tray always provides a destination.
throw new \Exception("No destination provided by Settings Tray form");
}
}
return $response->addCommand($command);
}
/**
* Gets the form's redirect URL from 'destination' provide in the request.
*
* @return \Drupal\Core\Url|null
* The redirect URL or NULL if dialog should just be closed.
*/
protected function getRedirectUrl() {
// \Drupal\Core\Routing\RedirectDestination::get() cannot be used directly
// because it will use <current> if 'destination' is not in the query
// string.
if ($this->getRequest()->query->has('destination') && $destination = $this->getRedirectDestination()->get()) {
return Url::fromUserInput('/' . $destination);
}
}
}
@@ -0,0 +1,106 @@
<?php
namespace Drupal\settings_tray\Form;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The off-canvas form handler for the SystemBrandingBlock.
*
* @see settings_tray_block_alter()
*
* @internal
*/
class SystemBrandingOffCanvasForm extends PluginFormBase implements ContainerInjectionInterface {
/**
* The block plugin.
*
* @var \Drupal\Core\Block\BlockPluginInterface
*/
protected $plugin;
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* SystemBrandingOffCanvasForm constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
*/
public function __construct(ConfigFactoryInterface $config_factory) {
$this->configFactory = $config_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = $this->plugin->buildConfigurationForm($form, $form_state);
$form['block_branding']['#type'] = 'details';
$form['block_branding']['#weight'] = 10;
// Unset links to Site Information form, we can make these changes here.
unset($form['block_branding']['use_site_name']['#description'], $form['block_branding']['use_site_slogan']['#description']);
$site_config = $this->configFactory->getEditable('system.site');
$form['site_information'] = [
'#type' => 'details',
'#title' => t('Site details'),
'#open' => TRUE,
];
$form['site_information']['site_name'] = [
'#type' => 'textfield',
'#title' => t('Site name'),
'#default_value' => $site_config->get('name'),
'#required' => TRUE,
];
$form['site_information']['site_slogan'] = [
'#type' => 'textfield',
'#title' => t('Slogan'),
'#default_value' => $site_config->get('slogan'),
'#description' => t("How this is used depends on your site's theme."),
];
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->plugin->validateConfigurationForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$site_info = $form_state->getValue('site_information');
$this->configFactory->getEditable('system.site')
->set('name', $site_info['site_name'])
->set('slogan', $site_info['site_slogan'])
->save();
$this->plugin->submitConfigurationForm($form, $form_state);
}
}
@@ -0,0 +1,153 @@
<?php
namespace Drupal\settings_tray\Form;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RedirectDestinationTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\system\MenuInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The off-canvas form handler for the SystemMenuBlock.
*
* @see settings_tray_block_alter()
*
* @internal
*/
class SystemMenuOffCanvasForm extends PluginFormBase implements ContainerInjectionInterface {
use StringTranslationTrait;
use RedirectDestinationTrait;
/**
* The plugin.
*
* @var \Drupal\Core\Block\BlockPluginInterface
*/
protected $plugin;
/**
* The menu entity that the block uses and that will be edited in this form.
*
* @var \Drupal\system\MenuInterface
*/
protected $menu;
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $menuStorage;
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* SystemMenuOffCanvasForm constructor.
*
* @param \Drupal\Core\Entity\EntityStorageInterface $menu_storage
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
*/
public function __construct(EntityStorageInterface $menu_storage, EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) {
$this->menuStorage = $menu_storage;
$this->entityTypeManager = $entity_type_manager;
$this->stringTranslation = $string_translation;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity_type.manager')->getStorage('menu'),
$container->get('entity_type.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = $this->plugin->buildConfigurationForm([], $form_state);
// Move the menu levels section to the bottom.
$form['menu_levels']['#weight'] = 100;
$form['entity_form'] = [
'#type' => 'details',
'#title' => $this->t('Edit menu %label', ['%label' => $this->menu->label()]),
'#open' => TRUE,
];
$form['entity_form'] += $this->getEntityForm($this->menu)->buildForm([], $form_state);
// Print the menu link titles as text instead of a link.
if (!empty($form['entity_form']['links']['links'])) {
foreach (Element::children($form['entity_form']['links']['links']) as $child) {
$title = $form['entity_form']['links']['links'][$child]['title'][1]['#title'];
$form['entity_form']['links']['links'][$child]['title'][1] = ['#markup' => $title];
}
}
// Change the header text.
$form['entity_form']['links']['links']['#header'][0] = $this->t('Link');
$form['entity_form']['links']['links']['#header'][1]['data'] = $this->t('On');
// Remove the label, ID, description, and buttons from the entity form.
unset($form['entity_form']['label'], $form['entity_form']['id'], $form['entity_form']['description'], $form['entity_form']['actions']);
// Since the overview form is further nested than expected, update the
// #parents. See \Drupal\menu_ui\MenuForm::form().
$form_state->set('menu_overview_form_parents', ['settings', 'entity_form', 'links']);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->plugin->validateConfigurationForm($form, $form_state);
$this->getEntityForm($this->menu)->validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
$this->plugin->submitConfigurationForm($form, $form_state);
$this->getEntityForm($this->menu)->submitForm($form, $form_state);
$this->menu->save();
}
/**
* Gets the entity form for this menu.
*
* @param \Drupal\system\MenuInterface $menu
* The menu entity.
*
* @return \Drupal\Core\Entity\EntityFormInterface
* The entity form.
*/
protected function getEntityForm(MenuInterface $menu) {
$entity_form = $this->entityTypeManager->getFormObject('menu', 'edit');
$entity_form->setEntity($menu);
return $entity_form;
}
/**
* {@inheritdoc}
*/
public function setPlugin(PluginInspectionInterface $plugin) {
$this->plugin = $plugin;
$this->menu = $this->menuStorage->load($this->plugin->getDerivativeId());
}
}
@@ -0,0 +1,63 @@
<?php
namespace Drupal\settings_tray\Render\MainContent;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Render\MainContent\DialogRenderer;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\settings_tray\Ajax\OpenOffCanvasDialogCommand;
use Symfony\Component\HttpFoundation\Request;
/**
* Default main content renderer for off-canvas dialog requests.
*
* @internal
*/
class OffCanvasRenderer extends DialogRenderer {
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new OffCanvasRenderer.
*
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(TitleResolverInterface $title_resolver, RendererInterface $renderer) {
parent::__construct($title_resolver);
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) {
$response = new AjaxResponse();
// First render the main content, because it might provide a title.
$content = $this->renderer->renderRoot($main_content);
// Attach the library necessary for using the OpenOffCanvasDialogCommand and
// set the attachments for this Ajax response.
$main_content['#attached']['library'][] = 'settings_tray/drupal.off_canvas';
$response->setAttachments($main_content['#attached']);
// If the main content doesn't provide a title, use the title resolver.
$title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject());
// Determine the title: use the title provided by the main content if any,
// otherwise get it from the routing information.
$options = $request->request->get('dialogOptions', []);
$response->addCommand(new OpenOffCanvasDialogCommand($title, $content, $options));
return $response;
}
}
@@ -0,0 +1,26 @@
{#
/**
* @file
* Default theme implementation for a page wrapper.
*
* For consistent wrapping to {{ page }} render in all themes. The
* "data-off-canvas-main-canvas" attribute is required by the off-canvas dialog.
* This is used by the settings_tray/drupal.off_canvas library to select the
* "main canvas" page element as opposed to the "off canvas" which is the tray
* itself. The "main canvas" element must be resized according to the width of
* the "off canvas" tray so that no portion of the "main canvas" is obstructed
* by the tray. The tray can vary in width when opened and can be resized by the
* user. The "data-off-canvas-main-canvas" attribute cannot be removed without
* breaking the off-canvas dialog functionality.
*
* Available variables:
* - children: Contains the child elements of the page.
*
* @ingroup themeable
*/
#}
{% if children %}
<div class="dialog-off-canvas__main-canvas" data-off-canvas-main-canvas >
{{ children }}
</div>
{% endif %}
@@ -0,0 +1,15 @@
name: 'Off-canvas tests'
type: module
description: 'Provides off-canvas test links.'
package: Testing
# version: VERSION
# core: 8.x
dependencies:
- block
- settings_tray
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
@@ -0,0 +1,29 @@
off_canvas_test.links:
path: '/off-canvas-test-links'
defaults:
_controller: '\Drupal\off_canvas_test\Controller\TestController::linksDisplay'
_title: 'Links'
requirements:
_access: 'TRUE'
off_canvas_test.thing1:
path: '/off-canvas-thing1'
defaults:
_controller: '\Drupal\off_canvas_test\Controller\TestController::thing1'
_title: 'Thing 1'
requirements:
_access: 'TRUE'
off_canvas_test.thing2:
path: '/off-canvas-thing2'
defaults:
_controller: '\Drupal\off_canvas_test\Controller\TestController::thing2'
requirements:
_access: 'TRUE'
off_canvas_test.dialog_links:
path: '/off-canvas-dialog-links'
defaults:
_controller: '\Drupal\off_canvas_test\Controller\TestController::otherDialogLinks'
requirements:
_access: 'TRUE'
@@ -0,0 +1,140 @@
<?php
namespace Drupal\off_canvas_test\Controller;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
/**
* Test controller for 2 different responses.
*/
class TestController {
/**
* Thing1.
*
* @return string
* Return Hello string.
*/
public function thing1() {
return [
'#type' => 'markup',
'#markup' => 'Thing 1 says hello',
];
}
/**
* Thing2.
*
* @return string
* Return Hello string.
*/
public function thing2() {
return [
'#type' => 'markup',
'#markup' => 'Thing 2 says hello',
];
}
/**
* Displays test links that will open in off-canvas dialog.
*
* @return array
* Render array with links.
*/
public function linksDisplay() {
return [
'off_canvas_link_1' => [
'#title' => 'Click Me 1!',
'#type' => 'link',
'#url' => Url::fromRoute('off_canvas_test.thing1'),
'#attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'off_canvas',
],
'#attached' => [
'library' => [
'settings_tray/drupal.settings_tray',
],
],
],
'off_canvas_link_2' => [
'#title' => 'Click Me 2!',
'#type' => 'link',
'#url' => Url::fromRoute('off_canvas_test.thing2'),
'#attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'off_canvas',
'data-dialog-options' => Json::encode([
'width' => 555,
]),
],
'#attached' => [
'library' => [
'settings_tray/drupal.settings_tray',
],
],
],
'other_dialog_links' => [
'#title' => 'Display more links!',
'#type' => 'link',
'#url' => Url::fromRoute('off_canvas_test.dialog_links'),
'#attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'off_canvas',
],
'#attached' => [
'library' => [
'settings_tray/drupal.settings_tray',
],
],
],
];
}
/**
* Displays dialogs links to be displayed inside the off-canvas dialog.
*
* This links are used to test opening a modal and another off_canvas link from
* inside the off-canvas dialog.
*
* @todo Update tests to check these links work in the off-canvas dialog.
* https://www.drupal.org/node/2790073
*
* @return array
* Render array with links.
*/
public function otherDialogLinks() {
return [
'#theme' => 'links',
'#links' => [
'modal_link' => [
'title' => 'Open modal!',
'url' => Url::fromRoute('off_canvas_test.thing2'),
'attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'modal',
],
],
'off_canvas_link' => [
'title' => 'Off_canvas link!',
'url' => Url::fromRoute('off_canvas_test.thing2'),
'attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'off_canvas',
],
],
],
'#attached' => [
'library' => [
'settings_tray/drupal.settings_tray',
],
],
];
}
}
@@ -0,0 +1,15 @@
name: 'Settings Tray Test'
type: module
description: 'Provides Settings Tray test functionality.'
package: Testing
# version: VERSION
# core: 8.x
dependencies:
- block
- settings_tray
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
@@ -0,0 +1,44 @@
<?php
namespace Drupal\settings_tray_test\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
/**
* @see \Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationIsClassBlock
*/
class SettingsTrayFormAnnotationIsClassBlockForm extends PluginFormBase {
/**
* The block plugin.
*
* @var \Drupal\Core\Block\BlockPluginInterface
*/
protected $plugin;
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
$form = $this->plugin->buildConfigurationForm($form, $form_state);
$form['some_setting'] = [
'#type' => 'select',
'#title' => t('Some setting'),
'#options' => [
'a' => 'A',
'b' => 'B',
],
'#required' => TRUE,
];
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {}
}
@@ -0,0 +1,27 @@
<?php
namespace Drupal\settings_tray_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Block that explicitly provides a "settings_tray" form class.
*
* @Block(
* id = "settings_tray_test_class",
* admin_label = "Settings Tray test block: forms[settings_tray]=class",
* forms = {
* "settings_tray" = "\Drupal\settings_tray_test\Form\SettingsTrayFormAnnotationIsClassBlockForm",
* },
* )
*/
class SettingsTrayFormAnnotationIsClassBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return ['#markup' => '<span>class</span>'];
}
}
@@ -0,0 +1,27 @@
<?php
namespace Drupal\settings_tray_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Block that explicitly provides no "settings_tray" form, thus opting out.
*
* @Block(
* id = "settings_tray_test_false",
* admin_label = "Settings Tray test block: forms[settings_tray]=FALSE",
* forms = {
* "settings_tray" = FALSE,
* },
* )
*/
class SettingsTrayFormAnnotationIsFalseBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return ['#markup' => '<span>FALSE</span>'];
}
}
@@ -0,0 +1,24 @@
<?php
namespace Drupal\settings_tray_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Block that does nothing explicit for Settings Tray.
*
* @Block(
* id = "settings_tray_test_none",
* admin_label = "Settings Tray test block: forms[settings_tray] is not specified",
* )
*/
class SettingsTrayFormAnnotationNoneBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return ['#markup' => '<span>none</span>'];
}
}
@@ -0,0 +1,33 @@
<?php
namespace Drupal\settings_tray_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a 'Block with validation error' test block.
*
* @Block(
* id = "settings_tray_test_validation",
* admin_label = @Translation("Block with validation error")
* )
*/
class ValidationErrorBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return ['#markup' => '<span>If I had more time this would be very witty :(.</span>'];
}
/**
* {@inheritdoc}
*/
public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
parent::validateConfigurationForm($form, $form_state);
$form_state->setError($form['label'], 'Sorry system error. Please save again.');
}
}
@@ -0,0 +1,27 @@
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode a,
.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode input {
pointer-events: inherit !important;
}
/**
* Remove all transitions for testing.
*/
* {
/* CSS transitions. */
-o-transition-property: none !important;
-moz-transition-property: none !important;
-ms-transition-property: none !important;
-webkit-transition-property: none !important;
transition-property: none !important;
/* CSS transforms. */
-o-transform: none !important;
-moz-transform: none !important;
-ms-transform: none !important;
-webkit-transform: none !important;
transform: none !important;
/* CSS animations. */
-webkit-animation: none !important;
-moz-animation: none !important;
-o-animation: none !important;
-ms-animation: none !important;
animation: none !important;
}
@@ -0,0 +1,14 @@
name: 'CSS Test fix'
type: module
description: 'Provides CSS fixes for tests.'
package: Testing
# version: VERSION
# core: 8.x
dependencies:
- settings_tray
# Information added by Drupal.org packaging script on 2017-11-03
version: '8.4.2'
core: '8.x'
project: 'drupal'
datestamp: 1509719929
@@ -0,0 +1,5 @@
drupal.css_fix:
version: VERSION
css:
theme:
css/css_fix.theme.css: {}
@@ -0,0 +1,16 @@
<?php
/**
* @file
* Module for attaching CSS during tests.
*
* CSS pointer-events properties cause testing errors.
*/
/**
* Implements hook_page_attachments().
*/
function settings_tray_test_css_page_attachments(array &$attachments) {
// Unconditionally attach an asset to the page.
$attachments['#attached']['library'][] = 'settings_tray_test_css/drupal.css_fix';
}
@@ -0,0 +1,63 @@
<?php
namespace Drupal\Tests\settings_tray\Functional;
use Drupal\ajax_test\Controller\AjaxTestController;
use Drupal\Component\Serialization\Json;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Tests\BrowserTestBase;
/**
* Performs tests on opening and manipulating dialogs via AJAX commands.
*
* @group settings_tray
*/
class OffCanvasDialogTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['ajax_test', 'settings_tray'];
/**
* Test sending AJAX requests to open and manipulate off-canvas dialog.
*/
public function testDialog() {
$this->drupalLogin($this->drupalCreateUser(['administer contact forms']));
// Ensure the elements render without notices or exceptions.
$this->drupalGet('ajax-test/dialog');
// Set up variables for this test.
$dialog_renderable = AjaxTestController::dialogContents();
$dialog_contents = \Drupal::service('renderer')->renderRoot($dialog_renderable);
$off_canvas_expected_response = [
'command' => 'openDialog',
'selector' => '#drupal-off-canvas',
'settings' => NULL,
'data' => $dialog_contents,
'dialogOptions' =>
[
'title' => 'AJAX Dialog & contents',
'modal' => FALSE,
'autoResize' => FALSE,
'resizable' => 'w',
'draggable' => FALSE,
'drupalAutoButtons' => FALSE,
'buttons' => [],
'dialogClass' => 'ui-dialog-off-canvas',
'width' => 300,
],
'effect' => 'fade',
'speed' => 1000,
];
// Emulate going to the JS version of the page and check the JSON response.
$ajax_result = $this->drupalGet('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog.off_canvas']]);
$ajax_result = Json::decode($ajax_result);
$this->assertEqual($off_canvas_expected_response, $ajax_result[3], 'off-canvas dialog JSON response matches.');
}
}
@@ -0,0 +1,109 @@
<?php
namespace Drupal\Tests\settings_tray\Functional;
use Drupal\block\Entity\Block;
use Drupal\Tests\BrowserTestBase;
/**
* Tests opening and saving block forms in the off-canvas dialog.
*
* @group settings_tray
*/
class SettingsTrayTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'settings_tray',
'settings_tray_test',
];
/**
* Gets the block CSS selector.
*
* @param \Drupal\block\Entity\Block $block
* The block.
*
* @return string
* The CSS selector.
*/
protected function getBlockSelector(Block $block) {
return '#block-' . $block->id();
}
/**
* Tests the 3 possible forms[settings_tray] annotations: class, FALSE, none.
*
* There is also functional JS test coverage to ensure that the two blocks
* that support Settings Tray (the "class" and "none" cases) do work
* correctly.
*
* @see SettingsTrayBlockFormTest::testBlocks()
*/
public function testPossibleAnnotations() {
$test_block_plugin_ids = [
// Block that explicitly provides an "settings_tray" form class.
'settings_tray_test_class',
// Block that explicitly provides no "settings_tray" form, thus opting out.
'settings_tray_test_false',
// Block that does nothing explicit for Settings Tray.
'settings_tray_test_none',
];
$placed_blocks = [];
foreach ($test_block_plugin_ids as $plugin_id) {
$placed_blocks[$plugin_id] = $this->placeBlock($plugin_id);
}
$this->drupalGet('');
$web_assert = $this->assertSession();
foreach ($placed_blocks as $plugin_id => $placed_block) {
$block_selector = $this->getBlockSelector($placed_block);
// All blocks are rendered.
$web_assert->elementExists('css', $block_selector);
// All blocks except 'settings_tray_test_false' are editable. For more
// detailed test coverage, which requires JS execution, see
// \Drupal\Tests\settings_tray\FunctionalJavascript\SettingsTrayBlockFormTest::testBlocks().
if ($plugin_id === 'settings_tray_test_false') {
$web_assert->elementNotExists('css', "{$block_selector}[data-drupal-settingstray=\"editable\"]");
}
else {
$web_assert->elementExists('css', "{$block_selector}[data-drupal-settingstray=\"editable\"]");
}
}
}
/**
* Tests that certain blocks opt out from Settings Tray.
*/
public function testOptOut() {
$web_assert = $this->assertSession();
$non_excluded_block = $this->placeBlock('system_powered_by_block');
$excluded_block_plugin_ids = ['page_title_block', 'system_main_block', 'settings_tray_test_false'];
$block_selectors = [];
// Place blocks that should be excluded.
foreach ($excluded_block_plugin_ids as $excluded_block_plugin_id) {
// The block HTML 'id' attribute will be "block-[block_id]".
$block_selectors[] = $this->getBlockSelector($this->placeBlock($excluded_block_plugin_id));
}
$this->drupalGet('');
// Assert that block has been marked as "editable" and contextual that
// should exist does.
$web_assert->elementExists('css', $this->getBlockSelector($non_excluded_block) . "[data-drupal-settingstray=\"editable\"]");
// Assert that each block that has a "forms[settings_tray] = FALSE" annotation:
// - is still rendered on the page
// - but is not marked as "editable" by settings_tray_preprocess_block()
// - and does not have the Settings Tray contextual link.
foreach ($block_selectors as $block_selector) {
$web_assert->elementExists('css', $block_selector);
$web_assert->elementNotExists('css', "{$block_selector}[data-drupal-settingstray=\"editable\"]");
$web_assert->elementNotExists('css', "$block_selector [data-settings-tray-edit]");
}
}
}
@@ -0,0 +1,123 @@
<?php
namespace Drupal\Tests\settings_tray\FunctionalJavascript;
/**
* Tests the off-canvas dialog functionality.
*
* @group settings_tray
*/
class OffCanvasTest extends SettingsTrayJavascriptTestBase {
/**
* {@inheritdoc}
*/
public static $modules = [
'block',
'system',
'toolbar',
'settings_tray',
'off_canvas_test',
];
/**
* Tests that non-contextual links will work with the off-canvas dialog.
*/
public function testOffCanvasLinks() {
// Test the same functionality on multiple themes.
foreach ($this->getTestThemes() as $theme) {
$this->enableTheme($theme);
$this->drupalGet('/off-canvas-test-links');
$page = $this->getSession()->getPage();
$web_assert = $this->assertSession();
// Make sure off-canvas dialog is on page when first loaded.
$web_assert->elementNotExists('css', '#drupal-off-canvas');
// Check opening and closing with two separate links.
// Make sure tray updates to new content.
// Check the first link again to make sure the empty title class is
// removed.
foreach (['1', '2', '1'] as $link_index) {
// Click the first test like that should open the page.
$page->clickLink("Click Me $link_index!");
$this->waitForOffCanvasToOpen();
// Check that the canvas is not on the page.
$web_assert->elementExists('css', '#drupal-off-canvas');
// Check that response text is on page.
$web_assert->pageTextContains("Thing $link_index says hello");
$off_canvas_tray = $this->getTray();
// Check that tray is visible.
$this->assertEquals(TRUE, $off_canvas_tray->isVisible());
$header_text = $off_canvas_tray->find('css', '.ui-dialog-title')->getText();
$tray_text = $off_canvas_tray->findById('drupal-off-canvas')->getText();
$this->assertEquals("Thing $link_index says hello", $tray_text);
if ($link_index == '2') {
// Check no title behavior.
$web_assert->elementExists('css', '.ui-dialog-empty-title');
$this->assertEquals("\xc2\xa0", $header_text);
$style = $page->find('css', '.ui-dialog-off-canvas')->getAttribute('style');
$this->assertTrue(strstr($style, 'width: 555px;') !== FALSE, 'Dialog width respected.');
$page->clickLink("Click Me 1!");
$this->waitForOffCanvasToOpen();
$style = $page->find('css', '.ui-dialog-off-canvas')->getAttribute('style');
$this->assertTrue(strstr($style, 'width: 555px;') === FALSE, 'Dialog width reset to default.');
}
else {
// Check that header is correct.
$this->assertEquals("Thing $link_index", $header_text);
$web_assert->elementNotExists('css', '.ui-dialog-empty-title');
}
}
}
}
/**
* Tests the body displacement behaves differently at a narrow width.
*/
public function testNarrowWidth() {
$narrow_width_breakpoint = 768;
$offset = 20;
$height = 800;
$page = $this->getSession()->getPage();
$web_assert = $this->assertSession();
// Test the same functionality on multiple themes.
foreach ($this->getTestThemes() as $theme) {
$this->enableTheme($theme);
// Testing at the wider width.
$this->getSession()->resizeWindow($narrow_width_breakpoint + $offset, $height);
$this->drupalGet('/off-canvas-test-links');
$this->assertFalse($page->find('css', '.dialog-off-canvas__main-canvas')->hasAttribute('style'), 'Body not padded on wide page load.');
$page->clickLink("Click Me 1!");
$this->waitForOffCanvasToOpen();
// Check that the main canvas is padded when page is not narrow width and
// tray is open.
$web_assert->elementAttributeContains('css', '.dialog-off-canvas__main-canvas', 'style', 'padding-right');
// Testing at the narrower width.
$this->getSession()->resizeWindow($narrow_width_breakpoint - $offset, $height);
$this->drupalGet('/off-canvas-test-links');
$this->assertFalse($page->find('css', '.dialog-off-canvas__main-canvas')->hasAttribute('style'), 'Body not padded on narrow page load.');
$page->clickLink("Click Me 1!");
$this->waitForOffCanvasToOpen();
$this->assertFalse($page->find('css', '.dialog-off-canvas__main-canvas')->hasAttribute('style'), 'Body not padded on narrow page with tray open.');
}
}
/**
* {@inheritdoc}
*/
protected function getTestThemes() {
// Add 'seven' theme. Setting Tray "Edit Mode" will not work with 'seven'
// because it removes all contextual links the off-canvas dialog should.
return array_merge(parent::getTestThemes(), ['seven']);
}
}
@@ -0,0 +1,568 @@
<?php
namespace Drupal\Tests\settings_tray\FunctionalJavascript;
use Drupal\block\Entity\Block;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationIsClassBlock;
use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationNoneBlock;
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
use Drupal\user\Entity\Role;
/**
* Testing opening and saving block forms in the off-canvas dialog.
*
* @group settings_tray
*/
class SettingsTrayBlockFormTest extends SettingsTrayJavascriptTestBase {
use ContextualLinkClickTrait;
const TOOLBAR_EDIT_LINK_SELECTOR = '#toolbar-bar div.contextual-toolbar-tab button';
const LABEL_INPUT_SELECTOR = 'input[data-drupal-selector="edit-settings-label"]';
/**
* {@inheritdoc}
*/
public static $modules = [
'node',
'block',
'system',
'breakpoint',
'toolbar',
'contextual',
'settings_tray',
'quickedit',
'search',
'block_content',
'settings_tray_test',
// Add test module to override CSS pointer-events properties because they
// cause test failures.
'settings_tray_test_css',
'settings_tray_test',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->createBlockContentType('basic', TRUE);
$block_content = $this->createBlockContent('Custom Block', 'basic', TRUE);
$user = $this->createUser([
'administer blocks',
'access contextual links',
'access toolbar',
'administer nodes',
'access in-place editing',
'search content',
]);
$this->drupalLogin($user);
$this->placeBlock('block_content:' . $block_content->uuid(), ['id' => 'custom']);
}
/**
* Tests opening off-canvas dialog by click blocks and elements in the blocks.
*
* @dataProvider providerTestBlocks
*/
public function testBlocks($theme, $block_plugin, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item) {
$web_assert = $this->assertSession();
$page = $this->getSession()->getPage();
$this->enableTheme($theme);
$block = $this->placeBlock($block_plugin);
$block_selector = str_replace('_', '-', $this->getBlockSelector($block));
$block_id = $block->id();
$this->drupalGet('user');
$link = $page->find('css', "$block_selector .contextual-links li a");
$this->assertEquals('Quick edit', $link->getText(), "'Quick edit' is the first contextual link for the block.");
$this->assertContains("/admin/structure/block/manage/$block_id/off-canvas?destination=user/2", $link->getAttribute('href'));
if (isset($toolbar_item)) {
// Check that you can open a toolbar tray and it will be closed after
// entering edit mode.
if ($element = $page->find('css', "#toolbar-administration a.is-active")) {
// If a tray was open from page load close it.
$element->click();
$this->waitForNoElement("#toolbar-administration a.is-active");
}
$page->find('css', $toolbar_item)->click();
$this->assertElementVisibleAfterWait('css', "{$toolbar_item}.is-active");
}
$this->enableEditMode();
if (isset($toolbar_item)) {
$this->waitForNoElement("{$toolbar_item}.is-active");
}
$this->openBlockForm($block_selector);
switch ($block_plugin) {
case 'system_powered_by_block':
// Confirm "Display Title" is not checked.
$web_assert->checkboxNotChecked('settings[label_display]');
// Confirm Title is not visible.
$this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
$page->checkField('settings[label_display]');
$this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
// Fill out form, save the form.
$page->fillField('settings[label]', $new_page_text);
break;
case 'system_branding_block':
// Fill out form, save the form.
$page->fillField('settings[site_information][site_name]', $new_page_text);
break;
case 'settings_tray_test_class':
$web_assert->elementExists('css', '[data-drupal-selector="edit-settings-some-setting"]');
break;
}
if (isset($new_page_text)) {
$page->pressButton($button_text);
// Make sure the changes are present.
$new_page_text_locator = "$block_selector $label_selector:contains($new_page_text)";
$this->assertElementVisibleAfterWait('css', $new_page_text_locator);
// The page is loaded with the new change but make sure page is
// completely loaded.
$this->assertPageLoadComplete();
}
$this->openBlockForm($block_selector);
$this->disableEditMode();
// Canvas should close when editing module is closed.
$this->waitForOffCanvasToClose();
$this->enableEditMode();
// Open block form by clicking a element inside the block.
// This confirms that default action for links and form elements is
// suppressed.
$this->openBlockForm("$block_selector {$element_selector}", $block_selector);
$web_assert->elementTextContains('css', '.contextual-toolbar-tab button', 'Editing');
$web_assert->elementAttributeContains('css', '.dialog-off-canvas__main-canvas', 'class', 'js-settings-tray-edit-mode');
// Simulate press the Escape key.
$this->getSession()->executeScript('jQuery("body").trigger(jQuery.Event("keyup", { keyCode: 27 }));');
$this->waitForOffCanvasToClose();
$this->getSession()->wait(100);
$this->assertEditModeDisabled();
$web_assert->elementTextContains('css', '#drupal-live-announce', 'Exited edit mode.');
$web_assert->elementTextNotContains('css', '.contextual-toolbar-tab button', 'Editing');
$web_assert->elementAttributeNotContains('css', '.dialog-off-canvas__main-canvas', 'class', 'js-settings-tray-edit-mode');
}
/**
* Dataprovider for testBlocks().
*/
public function providerTestBlocks() {
$blocks = [];
foreach ($this->getTestThemes() as $theme) {
$blocks += [
"$theme: block-powered" => [
'theme' => $theme,
'block_plugin' => 'system_powered_by_block',
'new_page_text' => 'Can you imagine anyone showing the label on this block',
'element_selector' => 'span a',
'label_selector' => 'h2',
'button_text' => 'Save Powered by Drupal',
'toolbar_item' => '#toolbar-item-user',
],
"$theme: block-branding" => [
'theme' => $theme,
'block_plugin' => 'system_branding_block',
'new_page_text' => 'The site that will live a very short life',
'element_selector' => "a[rel='home']:last-child",
'label_selector' => "a[rel='home']:last-child",
'button_text' => 'Save Site branding',
'toolbar_item' => '#toolbar-item-administration',
],
"$theme: block-search" => [
'theme' => $theme,
'block_plugin' => 'search_form_block',
'new_page_text' => NULL,
'element_selector' => '#edit-submit',
'label_selector' => 'h2',
'button_text' => 'Save Search form',
'toolbar_item' => NULL,
],
// This is the functional JS test coverage accompanying
// \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
"$theme: " . SettingsTrayFormAnnotationIsClassBlock::class => [
'theme' => $theme,
'block_plugin' => 'settings_tray_test_class',
'new_page_text' => NULL,
'element_selector' => 'span',
'label_selector' => NULL,
'button_text' => NULL,
'toolbar_item' => NULL,
],
// This is the functional JS test coverage accompanying
// \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
"$theme: " . SettingsTrayFormAnnotationNoneBlock::class => [
'theme' => $theme,
'block_plugin' => 'settings_tray_test_none',
'new_page_text' => NULL,
'element_selector' => 'span',
'label_selector' => NULL,
'button_text' => NULL,
'toolbar_item' => NULL,
],
];
}
return $blocks;
}
/**
* Enables edit mode by pressing edit button in the toolbar.
*/
protected function enableEditMode() {
$this->pressToolbarEditButton();
$this->assertEditModeEnabled();
}
/**
* Disables edit mode by pressing edit button in the toolbar.
*/
protected function disableEditMode() {
$this->assertSession()->assertWaitOnAjaxRequest();
$this->pressToolbarEditButton();
$this->assertEditModeDisabled();
}
/**
* Asserts that Off-Canvas block form is valid.
*/
protected function assertOffCanvasBlockFormIsValid() {
$web_assert = $this->assertSession();
// Confirm that Block title display label has been changed.
$web_assert->elementTextContains('css', '.form-item-settings-label-display label', 'Display block title');
// Confirm Block title label is shown if checkbox is checked.
if ($this->getSession()->getPage()->find('css', 'input[name="settings[label_display]"]')->isChecked()) {
$this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
$web_assert->elementTextContains('css', '.form-item-settings-label label', 'Block title');
}
else {
$this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
}
// Check that common block form elements exist.
$web_assert->elementExists('css', static::LABEL_INPUT_SELECTOR);
$web_assert->elementExists('css', 'input[data-drupal-selector="edit-settings-label-display"]');
// Check that advanced block form elements do not exist.
$web_assert->elementNotExists('css', 'input[data-drupal-selector="edit-visibility-request-path-pages"]');
$web_assert->elementNotExists('css', 'select[data-drupal-selector="edit-region"]');
}
/**
* Open block form by clicking the element found with a css selector.
*
* @param string $block_selector
* A css selector selects the block or an element within it.
* @param string $contextual_link_container
* The element that contains the contextual links. If none provide the
* $block_selector will be used.
*/
protected function openBlockForm($block_selector, $contextual_link_container = '') {
if (!$contextual_link_container) {
$contextual_link_container = $block_selector;
}
// Ensure that contextual link element is present because this is required
// to open the off-canvas dialog in edit mode.
$contextual_link = $this->assertSession()->waitForElement('css', "$contextual_link_container .contextual-links a");
$this->assertNotEmpty($contextual_link);
// When page first loads Edit Mode is not triggered until first contextual
// link is added.
$this->assertElementVisibleAfterWait('css', '.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode');
// Ensure that all other Ajax activity is completed.
$this->assertSession()->assertWaitOnAjaxRequest();
$this->click($block_selector);
$this->waitForOffCanvasToOpen();
$this->assertOffCanvasBlockFormIsValid();
}
/**
* Tests QuickEdit links behavior.
*/
public function testQuickEditLinks() {
$quick_edit_selector = '#quickedit-entity-toolbar';
$node_selector = '[data-quickedit-entity-id="node/1"]';
$body_selector = '[data-quickedit-field-id="node/1/body/en/full"]';
$web_assert = $this->assertSession();
// Create a Content type and two test nodes.
$this->createContentType(['type' => 'page']);
$auth_role = Role::load(Role::AUTHENTICATED_ID);
$this->grantPermissions($auth_role, [
'edit any page content',
'access content',
]);
$node = $this->createNode(
[
'title' => 'Page One',
'type' => 'page',
'body' => [
[
'value' => 'Regular NODE body for the test.',
'format' => 'plain_text',
],
],
]
);
$page = $this->getSession()->getPage();
$block_plugin = 'system_powered_by_block';
foreach ($this->getTestThemes() as $theme) {
$this->enableTheme($theme);
$block = $this->placeBlock($block_plugin);
$block_selector = str_replace('_', '-', $this->getBlockSelector($block));
// Load the same page twice.
foreach ([1, 2] as $page_load_times) {
$this->drupalGet('node/' . $node->id());
// The 2nd page load we should already be in edit mode.
if ($page_load_times == 1) {
$this->enableEditMode();
}
// In Edit mode clicking field should open QuickEdit toolbar.
$page->find('css', $body_selector)->click();
$this->assertElementVisibleAfterWait('css', $quick_edit_selector);
$this->disableEditMode();
// Exiting Edit mode should close QuickEdit toolbar.
$web_assert->elementNotExists('css', $quick_edit_selector);
// When not in Edit mode QuickEdit toolbar should not open.
$page->find('css', $body_selector)->click();
$web_assert->elementNotExists('css', $quick_edit_selector);
$this->enableEditMode();
$this->openBlockForm($block_selector);
$page->find('css', $body_selector)->click();
$this->assertElementVisibleAfterWait('css', $quick_edit_selector);
// Off-canvas dialog should be closed when opening QuickEdit toolbar.
$this->waitForOffCanvasToClose();
$this->openBlockForm($block_selector);
// QuickEdit toolbar should be closed when opening Off-canvas dialog.
$web_assert->elementNotExists('css', $quick_edit_selector);
}
// Check using contextual links to invoke QuickEdit and open the tray.
$this->drupalGet('node/' . $node->id());
$web_assert->assertWaitOnAjaxRequest();
$this->disableEditMode();
// Open QuickEdit toolbar before going into Edit mode.
$this->clickContextualLink($node_selector, "Quick edit");
$this->assertElementVisibleAfterWait('css', $quick_edit_selector);
// Open off-canvas and enter Edit mode via contextual link.
$this->clickContextualLink($block_selector, "Quick edit");
$this->waitForOffCanvasToOpen();
// QuickEdit toolbar should be closed when opening off-canvas dialog.
$web_assert->elementNotExists('css', $quick_edit_selector);
// Open QuickEdit toolbar via contextual link while in Edit mode.
$this->clickContextualLink($node_selector, "Quick edit", FALSE);
$this->waitForOffCanvasToClose();
$this->assertElementVisibleAfterWait('css', $quick_edit_selector);
$this->disableEditMode();
}
}
/**
* Tests enabling and disabling Edit Mode.
*/
public function testEditModeEnableDisable() {
foreach ($this->getTestThemes() as $theme) {
$this->enableTheme($theme);
$block = $this->placeBlock('system_powered_by_block');
foreach (['contextual_link', 'toolbar_link'] as $enable_option) {
$this->drupalGet('user');
$this->assertEditModeDisabled();
switch ($enable_option) {
// Enable Edit mode.
case 'contextual_link':
$this->clickContextualLink($this->getBlockSelector($block), "Quick edit");
$this->waitForOffCanvasToOpen();
$this->assertEditModeEnabled();
break;
case 'toolbar_link':
$this->enableEditMode();
break;
}
$this->disableEditMode();
// Make another page request to ensure Edit mode is still disabled.
$this->drupalGet('user');
$this->assertEditModeDisabled();
// Make sure on this page request it also re-enables and disables
// correctly.
$this->enableEditMode();
$this->disableEditMode();
}
}
}
/**
* Assert that edit mode has been properly enabled.
*/
protected function assertEditModeEnabled() {
$web_assert = $this->assertSession();
// No contextual triggers should be hidden.
$web_assert->elementNotExists('css', '.contextual .trigger.visually-hidden');
// The toolbar edit button should read "Editing".
$web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Editing');
// The main canvas element should have the "js-settings-tray-edit-mode" class.
$web_assert->elementExists('css', '.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode');
}
/**
* Assert that edit mode has been properly disabled.
*/
protected function assertEditModeDisabled() {
$web_assert = $this->assertSession();
// Contextual triggers should be hidden.
$web_assert->elementExists('css', '.contextual .trigger.visually-hidden');
// No contextual triggers should be not hidden.
$web_assert->elementNotExists('css', '.contextual .trigger:not(.visually-hidden)');
// The toolbar edit button should read "Edit".
$web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Edit');
// The main canvas element should NOT have the "js-settings-tray-edit-mode"
// class.
$web_assert->elementNotExists('css', '.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode');
}
/**
* Press the toolbar Edit button provided by the contextual module.
*/
protected function pressToolbarEditButton() {
$this->assertSession()->waitForElement('css', '[data-contextual-id] .contextual-links a');
$edit_button = $this->getSession()
->getPage()
->find('css', static::TOOLBAR_EDIT_LINK_SELECTOR);
$edit_button->press();
}
/**
* Creates a custom block.
*
* @param bool|string $title
* (optional) Title of block. When no value is given uses a random name.
* Defaults to FALSE.
* @param string $bundle
* (optional) Bundle name. Defaults to 'basic'.
* @param bool $save
* (optional) Whether to save the block. Defaults to TRUE.
*
* @return \Drupal\block_content\Entity\BlockContent
* Created custom block.
*/
protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) {
$title = $title ?: $this->randomName();
$block_content = BlockContent::create([
'info' => $title,
'type' => $bundle,
'langcode' => 'en',
'body' => [
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'format' => 'plain_text',
],
]);
if ($block_content && $save === TRUE) {
$block_content->save();
}
return $block_content;
}
/**
* Creates a custom block type (bundle).
*
* @param string $label
* The block type label.
* @param bool $create_body
* Whether or not to create the body field.
*
* @return \Drupal\block_content\Entity\BlockContentType
* Created custom block type.
*/
protected function createBlockContentType($label, $create_body = FALSE) {
$bundle = BlockContentType::create([
'id' => $label,
'label' => $label,
'revision' => FALSE,
]);
$bundle->save();
if ($create_body) {
block_content_add_body_field($bundle->id());
}
return $bundle;
}
/**
* Tests that contextual links in custom blocks are changed.
*
* "Quick edit" is quickedit.module link.
* "Quick edit settings" is settings_tray.module link.
*/
public function testCustomBlockLinks() {
$this->drupalGet('user');
$page = $this->getSession()->getPage();
$links = $page->findAll('css', "#block-custom .contextual-links li a");
$link_labels = [];
/** @var \Behat\Mink\Element\NodeElement $link */
foreach ($links as $link) {
$link_labels[$link->getAttribute('href')] = $link->getText();
}
$href = array_search('Quick edit', $link_labels);
$this->assertEquals('', $href);
$href = array_search('Quick edit settings', $link_labels);
$this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/off-canvas?destination=user/2') !== FALSE);
}
/**
* Gets the block CSS selector.
*
* @param \Drupal\block\Entity\Block $block
* The block.
*
* @return string
* The CSS selector.
*/
public function getBlockSelector(Block $block) {
return '#block-' . $block->id();
}
/**
* Determines if the label input is visible.
*
* @return bool
* TRUE if the label is visible, FALSE if it is not.
*/
protected function isLabelInputVisible() {
return $this->getSession()->getPage()->find('css', static::LABEL_INPUT_SELECTOR)->isVisible();
}
/**
* Test that validation errors appear in the off-canvas dialog.
*/
public function testValidationMessages() {
$page = $this->getSession()->getPage();
$web_assert = $this->assertSession();
foreach ($this->getTestThemes() as $theme) {
$this->enableTheme($theme);
$block = $this->placeBlock('settings_tray_test_validation');
$this->drupalGet('user');
$this->enableEditMode();
$this->openBlockForm($this->getBlockSelector($block));
$page->pressButton('Save Block with validation error');
$web_assert->assertWaitOnAjaxRequest();
// The settings_tray_test_validation test plugin form always has a
// validation error.
$web_assert->elementContains('css', '#drupal-off-canvas', 'Sorry system error. Please save again');
$this->disableEditMode();
$block->delete();
}
}
}
@@ -0,0 +1,126 @@
<?php
namespace Drupal\Tests\settings_tray\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
/**
* Base class contains common test functionality for the Settings Tray module.
*/
abstract class SettingsTrayJavascriptTestBase extends JavascriptTestBase {
/**
* {@inheritdoc}
*/
protected function drupalGet($path, array $options = [], array $headers = []) {
$return = parent::drupalGet($path, $options, $headers);
$this->assertPageLoadComplete();
return $return;
}
/**
* Assert the page is completely loaded.
*
* Ajax requests may happen after page loads. Also for users who have access
* to contextual links the contextual link placeholders will be filled after
* the page is received.
*/
protected function assertPageLoadComplete() {
$this->assertSession()->assertWaitOnAjaxRequest();
if ($this->loggedInUser && $this->loggedInUser->hasPermission('access contextual links')) {
$this->assertAllContextualLinksLoaded();
}
}
/**
* Assert all contextual link areas have be loaded.
*
* Contextual link placeholders will be filled after
* the page is received.
*
* @todo Move this function to https://www.drupal.org/node/2821724.
*/
protected function assertAllContextualLinksLoaded() {
$this->waitForNoElement('[data-contextual-id]:empty');
}
/**
* Enables a theme.
*
* @param string $theme
* The theme.
*/
protected function enableTheme($theme) {
// Enable the theme.
\Drupal::service('theme_installer')->install([$theme]);
$theme_config = \Drupal::configFactory()->getEditable('system.theme');
$theme_config->set('default', $theme);
$theme_config->save();
}
/**
* Waits for off-canvas dialog to open.
*/
protected function waitForOffCanvasToOpen() {
$web_assert = $this->assertSession();
$web_assert->assertWaitOnAjaxRequest();
$this->assertElementVisibleAfterWait('css', '#drupal-off-canvas');
}
/**
* Waits for off-canvas dialog to close.
*/
protected function waitForOffCanvasToClose() {
$this->waitForNoElement('#drupal-off-canvas');
}
/**
* Gets the off-canvas dialog element.
*
* @return \Behat\Mink\Element\NodeElement|null
*/
protected function getTray() {
$tray = $this->getSession()->getPage()->find('css', '.ui-dialog[aria-describedby="drupal-off-canvas"]');
$this->assertEquals(FALSE, empty($tray), 'The tray was found.');
return $tray;
}
/**
* Waits for an element to be removed from the page.
*
* @param string $selector
* CSS selector.
* @param int $timeout
* (optional) Timeout in milliseconds, defaults to 10000.
*/
protected function waitForNoElement($selector, $timeout = 10000) {
$condition = "(typeof jQuery !== 'undefined' && jQuery('$selector').length === 0)";
$this->assertJsCondition($condition, $timeout);
}
/**
* Get themes to test.
*
* @return string[]
* Theme names to test.
*/
protected function getTestThemes() {
return ['bartik', 'stark', 'classy', 'stable'];
}
/**
* Asserts the specified selector is visible after a wait.
*
* @param string $selector
* The selector engine name. See ElementInterface::findAll() for the
* supported selectors.
* @param string|array $locator
* The selector locator.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 10000.
*/
protected function assertElementVisibleAfterWait($selector, $locator, $timeout = 10000) {
$this->assertNotEmpty($this->assertSession()->waitForElementVisible($selector, $locator, $timeout));
}
}
@@ -0,0 +1,96 @@
<?php
namespace Drupal\Tests\settings_tray\Unit\Access;
use Drupal\block\BlockInterface;
use Drupal\Core\Access\AccessResultAllowed;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Access\AccessResultNeutral;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
use Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\settings_tray\Access\BlockPluginHasSettingsTrayFormAccessCheck
* @group settings_tray
*/
class BlockPluginHasSettingsTrayFormAccessCheckTest extends UnitTestCase {
/**
* @covers ::access
* @covers ::accessBlockPlugin
* @dataProvider providerTestAccess
*/
public function testAccess($with_forms, array $plugin_definition, AccessResultInterface $expected_access_result) {
$block_plugin = $this->prophesize()->willImplement(BlockPluginInterface::class);
if ($with_forms) {
$block_plugin->willImplement(PluginWithFormsInterface::class);
$block_plugin->hasFormClass(Argument::type('string'))->will(function ($arguments) use ($plugin_definition) {
return !empty($plugin_definition['forms'][$arguments[0]]);
});
}
$block = $this->prophesize(BlockInterface::class);
$block->getPlugin()->willReturn($block_plugin->reveal());
$access_check = new BlockPluginHasSettingsTrayFormAccessCheck();
$this->assertEquals($expected_access_result, $access_check->access($block->reveal()));
$this->assertEquals($expected_access_result, $access_check->accessBlockPlugin($block_plugin->reveal()));
}
/**
* Provides test data for ::testAccess().
*/
public function providerTestAccess() {
$annotation_forms_settings_tray_class = [
'forms' => [
'settings_tray' => $this->randomMachineName(),
],
];
$annotation_forms_settings_tray_not_set = [];
$annotation_forms_settings_tray_false = [
'forms' => [
'settings_tray' => FALSE,
],
];
return [
'block plugin with forms, forms[settings_tray] set to class' => [
TRUE,
$annotation_forms_settings_tray_class,
new AccessResultAllowed(),
],
'block plugin with forms, forms[settings_tray] not set' => [
TRUE,
$annotation_forms_settings_tray_not_set,
new AccessResultNeutral(),
],
'block plugin with forms, forms[settings_tray] set to FALSE' => [
TRUE,
$annotation_forms_settings_tray_false,
new AccessResultNeutral(),
],
// In practice, all block plugins extend BlockBase, which means they all
// implement PluginWithFormsInterface, but this may change in the future.
// This ensures Settings Tray will continue to work correctly.
'block plugin without forms, forms[settings_tray] set to class' => [
FALSE,
$annotation_forms_settings_tray_class,
new AccessResultNeutral(),
],
'block plugin without forms, forms[settings_tray] not set' => [
FALSE,
$annotation_forms_settings_tray_not_set,
new AccessResultNeutral(),
],
'block plugin without forms, forms[settings_tray] set to FALSE' => [
FALSE,
$annotation_forms_settings_tray_false,
new AccessResultNeutral(),
],
];
}
}
@@ -0,0 +1,43 @@
<?php
namespace Drupal\Tests\settings_tray\Unit\Ajax;
use Drupal\settings_tray\Ajax\OpenOffCanvasDialogCommand;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\settings_tray\Ajax\OpenOffCanvasDialogCommand
* @group settings_tray
*/
class OpenOffCanvasDialogCommandTest extends UnitTestCase {
/**
* @covers ::render
*/
public function testRender() {
$command = new OpenOffCanvasDialogCommand('Title', '<p>Text!</p>', ['url' => 'example']);
$expected = [
'command' => 'openDialog',
'selector' => '#drupal-off-canvas',
'settings' => NULL,
'data' => '<p>Text!</p>',
'dialogOptions' => [
'url' => 'example',
'title' => 'Title',
'modal' => FALSE,
'autoResize' => FALSE,
'resizable' => 'w',
'draggable' => FALSE,
'drupalAutoButtons' => FALSE,
'buttons' => [],
'dialogClass' => 'ui-dialog-off-canvas',
'width' => 300,
],
'effect' => 'fade',
'speed' => 1000,
];
$this->assertEquals($expected, $command->render());
}
}