Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
23
web/core/modules/outside_in/css/offcanvas.css
Normal file
23
web/core/modules/outside_in/css/offcanvas.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* @file
|
||||
* CSS for Offcanvas tray.
|
||||
*
|
||||
* @todo Move CSS into core dialog library https://www.drupal.org/node/2784443.
|
||||
*/
|
||||
/* Position the dialog-offcanvas tray container outside the right of the viewport. */
|
||||
.ui-dialog-offcanvas {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Wrap the form that's inside the dialog-offcanvas tray. */
|
||||
.ui-dialog-offcanvas .ui-dialog-content {
|
||||
padding: 0 20px;
|
||||
/* Prevent horizontal scrollbar. */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-offcanvas .ui-dialog-content {
|
||||
text-align: right;
|
||||
}
|
32
web/core/modules/outside_in/css/offcanvas.motion.css
Normal file
32
web/core/modules/outside_in/css/offcanvas.motion.css
Normal file
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* @file
|
||||
* Motion effects for off-canvas tray 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.
|
||||
*/
|
||||
|
||||
/* Transition the dialog-offcanvas tray container, with 2s delay to match main canvas speed. */
|
||||
.ui-dialog-offcanvas .ui-dialog-content {
|
||||
-webkit-transition: all .7s ease 2s;
|
||||
-moz-transition: all .7s ease 2s;
|
||||
transition: all .7s ease 2s;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.ui-dialog-offcanvas .ui-dialog-content {
|
||||
-webkit-transition: all .7s ease;
|
||||
-moz-transition: all .7s ease;
|
||||
transition: all .7s ease;
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-offcanvas__main-canvas {
|
||||
-webkit-transition: all .7s ease;
|
||||
-moz-transition: all .7s ease;
|
||||
transition: all .7s ease;
|
||||
}
|
52
web/core/modules/outside_in/css/outside_in.details.css
Normal file
52
web/core/modules/outside_in/css/outside_in.details.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @file
|
||||
* Visual styling for summary and details in the Settings Tray module's off canvas tray.
|
||||
*/
|
||||
|
||||
.ui-dialog-outside-in details,
|
||||
.ui-dialog-outside-in summary,
|
||||
.ui-dialog-outside-in .ui-dialog-content {
|
||||
background: #474747;
|
||||
color: #ddd;
|
||||
}
|
||||
.ui-dialog-outside-in summary a {
|
||||
color: #ddd;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ui-dialog-outside-in summary a:hover,
|
||||
.ui-dialog-outside-in summary a:focus {
|
||||
color: #fff;
|
||||
}
|
||||
.ui-dialog-outside-in details,
|
||||
.ui-dialog-outside-in summary,
|
||||
.ui-dialog-outside-in .details-wrapper {
|
||||
border-width: 0;
|
||||
/* Cancel out the padding of the parent. */
|
||||
margin: 0 -20px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
.ui-dialog-outside-in summary {
|
||||
text-shadow: none;
|
||||
outline: none;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
.ui-dialog-outside-in summary:hover,
|
||||
.ui-dialog-outside-in summary:focus {
|
||||
background-color: #222;
|
||||
outline: none;
|
||||
}
|
||||
.ui-dialog-outside-in details[open] {
|
||||
background-color: #333;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.ui-dialog-outside-in details[open] > summary {
|
||||
background-color: #333;
|
||||
color: #eee;
|
||||
}
|
||||
.ui-dialog-outside-in details[open] > summary:hover {
|
||||
background-color: #222;
|
||||
color: #fff;
|
||||
}
|
110
web/core/modules/outside_in/css/outside_in.form.css
Normal file
110
web/core/modules/outside_in/css/outside_in.form.css
Normal file
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* @file
|
||||
* Visual styling for forms in the Settings Tray module's off canvas tray.
|
||||
*/
|
||||
|
||||
.ui-dialog-outside-in label {
|
||||
line-height: normal;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
color: #ddd;
|
||||
}
|
||||
.ui-dialog-outside-in .description,
|
||||
.ui-dialog-outside-in .form-item .description,
|
||||
.ui-dialog-outside-in .details-description {
|
||||
color: #ddd;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
}
|
||||
.ui-dialog-outside-in .details-wrapper .description {
|
||||
color: #bbb;
|
||||
}
|
||||
.ui-dialog-outside-in .form-item {
|
||||
margin-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
/* Set size and position for all inputs. */
|
||||
.ui-dialog-outside-in .form-select,
|
||||
.ui-dialog-outside-in .form-text,
|
||||
.ui-dialog-outside-in .form-tel,
|
||||
.ui-dialog-outside-in .form-email,
|
||||
.ui-dialog-outside-in .form-url,
|
||||
.ui-dialog-outside-in .form-search,
|
||||
.ui-dialog-outside-in .form-number,
|
||||
.ui-dialog-outside-in .form-color,
|
||||
.ui-dialog-outside-in .form-file,
|
||||
.ui-dialog-outside-in .form-textarea,
|
||||
.ui-dialog-outside-in .form-date,
|
||||
.ui-dialog-outside-in .form-time {
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
padding: 6px;
|
||||
margin-top: 5px;
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
margin-left: 0;
|
||||
border-width: 1px;
|
||||
border-radius: 2px;
|
||||
outline: 0;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
/* Reduce contrast for fields against dark backround. */
|
||||
.ui-dialog-outside-in .form-text,
|
||||
.ui-dialog-outside-in .form-tel,
|
||||
.ui-dialog-outside-in .form-email,
|
||||
.ui-dialog-outside-in .form-url,
|
||||
.ui-dialog-outside-in .form-search,
|
||||
.ui-dialog-outside-in .form-number,
|
||||
.ui-dialog-outside-in .form-color,
|
||||
.ui-dialog-outside-in .form-file,
|
||||
.ui-dialog-outside-in .form-textarea,
|
||||
.ui-dialog-outside-in .form-date,
|
||||
.ui-dialog-outside-in .form-time {
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .125);
|
||||
background-color: #eee;
|
||||
border-color: #333;
|
||||
color: #595959;
|
||||
}
|
||||
.ui-dialog-outside-in .form-text:focus,
|
||||
.ui-dialog-outside-in .form-tel:focus,
|
||||
.ui-dialog-outside-in .form-email:focus,
|
||||
.ui-dialog-outside-in .form-url:focus,
|
||||
.ui-dialog-outside-in .form-search:focus,
|
||||
.ui-dialog-outside-in .form-number:focus,
|
||||
.ui-dialog-outside-in .form-color:focus,
|
||||
.ui-dialog-outside-in .form-file:focus,
|
||||
.ui-dialog-outside-in .form-textarea:focus,
|
||||
.ui-dialog-outside-in .form-date:focus,
|
||||
.ui-dialog-outside-in .form-time:focus {
|
||||
border-color: #40b6ff;
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .125), 0 0 8px #40b6ff;
|
||||
background-color: #fff;
|
||||
}
|
||||
.ui-dialog-outside-in input[type="checkbox"],
|
||||
.ui-dialog-outside-in .checkbox,
|
||||
.ui-dialog-outside-in input[type="radio"],
|
||||
.ui-dialog-outside-in .radio {
|
||||
position: static;
|
||||
margin: 0;
|
||||
}
|
||||
.ui-dialog-outside-in td .checkbox {
|
||||
display: table-cell;
|
||||
line-height: normal;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.ui-dialog-outside-in .form-checkbox,
|
||||
.ui-dialog-outside-in .form-radio {
|
||||
/* Add contrast for dark background. */
|
||||
box-shadow: 0 0 2px 1px #000;
|
||||
}
|
||||
.ui-dialog-outside-in input[type="radio"] {
|
||||
/* Add full circular radius. */
|
||||
border-radius: 50%;
|
||||
}
|
||||
.ui-dialog-outside-in .form-actions {
|
||||
text-align: center;
|
||||
}
|
38
web/core/modules/outside_in/css/outside_in.module.css
Normal file
38
web/core/modules/outside_in/css/outside_in.module.css
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
#main-canvas.js-outside-in-edit-mode a,
|
||||
#main-canvas.js-outside-in-edit-mode input {
|
||||
pointer-events: none;
|
||||
}
|
||||
#main-canvas.js-outside-in-edit-mode .contextual-links a {
|
||||
pointer-events: inherit;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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-offcanvas {
|
||||
width: 100% !important;
|
||||
}
|
||||
/* When tray is at 100% width stop the body from scrolling */
|
||||
.js-tray-open {
|
||||
height: 100%;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
44
web/core/modules/outside_in/css/outside_in.motion.css
Normal file
44
web/core/modules/outside_in/css/outside_in.motion.css
Normal file
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
/* Transition the edit icon in the toolbar. */
|
||||
#toolbar-bar.button.toolbar-icon.toolbar-icon.toolbar-icon-edit:before {
|
||||
-webkit-transition: all .7s ease;
|
||||
-moz-transition: all .7s ease;
|
||||
transition: all .7s ease;
|
||||
}
|
||||
|
||||
/* Transition the editables on the page, their contextual links and their hover states. */
|
||||
.dialog-offcanvas__main-canvas .contextual,
|
||||
.dialog-offcanvas__main-canvas .js-outside-in-edit-mode .outside-in-editable,
|
||||
.dialog-offcanvas__main-canvas.js-tray-open .js-outside-in-edit-mode .outside-in-editable {
|
||||
-webkit-transition: all .7s ease;
|
||||
-moz-transition: all .7s ease;
|
||||
transition: all .7s ease;
|
||||
}
|
||||
|
||||
/* Transition the position of the toolbar. */
|
||||
.toolbar-fixed,
|
||||
.toolbar-tray-open {
|
||||
-webkit-transition: all .5s ease;
|
||||
-moz-transition: all .5s ease;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
|
||||
/* Transition the administration tray.
|
||||
#toolbar-administration,
|
||||
#toolbar-administration * {
|
||||
-webkit-transition: all .7s ease;
|
||||
-moz-transition: all .7s ease;
|
||||
transition: all .7s ease;
|
||||
}*/
|
72
web/core/modules/outside_in/css/outside_in.table.css
Normal file
72
web/core/modules/outside_in/css/outside_in.table.css
Normal file
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @file
|
||||
* Visual styling for tables in the Settings Tray module's off canvas tray.
|
||||
*/
|
||||
|
||||
.ui-dialog-outside-in table {
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
min-width: 300px;
|
||||
margin-top: 0;
|
||||
/* Cancel out the padding of the parent to make the table full width and flush to the bottom. */
|
||||
margin-right: -20px;
|
||||
margin-left: -20px;
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
.ui-dialog-outside-in tr th {
|
||||
padding: 2px 4px;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
border-collapse: collapse;
|
||||
font-size: 12px;
|
||||
color: #bbb;
|
||||
text-align: left; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in tr th {
|
||||
text-align: right;
|
||||
}
|
||||
.ui-dialog-outside-in tr,
|
||||
.ui-dialog-outside-in tr td {
|
||||
padding: 2px 4px;
|
||||
height: 35px;
|
||||
vertical-align: middle;
|
||||
text-align: left; /* LTR */
|
||||
border: 0px;
|
||||
border-style: solid;
|
||||
border-color: #777;
|
||||
border-bottom-width: 1px;
|
||||
background: none;
|
||||
background-color: transparent;
|
||||
font-size: 12px;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in tr th,
|
||||
[dir="rtl"] .ui-dialog-outside-in tr td {
|
||||
text-align: right;
|
||||
}
|
||||
.ui-dialog-outside-in td a {
|
||||
display: block;
|
||||
max-width: 120px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.ui-dialog.ui-dialog-outside-in tr td:first-child,
|
||||
.ui-dialog.ui-dialog-outside-in tr th:first-child {
|
||||
padding-left: 20px; /* LTR */
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog.ui-dialog-outside-in tr td:first-child,
|
||||
[dir="rtl"] .ui-dialog.ui-dialog-outside-in tr th:first-child {
|
||||
padding-right: 20px;
|
||||
}
|
||||
.ui-dialog-outside-in tr.odd,
|
||||
.ui-dialog-outside-in tr.even {
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
td.checkbox,
|
||||
th.checkbox {
|
||||
text-align: center;
|
||||
width: 20px;
|
||||
}
|
||||
|
104
web/core/modules/outside_in/css/outside_in.tabledrag.css
Normal file
104
web/core/modules/outside_in/css/outside_in.tabledrag.css
Normal file
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* @file
|
||||
* Table drag behavior for Settings Tray module.
|
||||
*
|
||||
* @see tabledrag.js
|
||||
*/
|
||||
|
||||
|
||||
/* Because base font sizes will vary widely across themes, to maintain consistency outside-in uses only pixels. */
|
||||
|
||||
.ui-dialog-outside-in body.drag {
|
||||
cursor: move;
|
||||
}
|
||||
.ui-dialog-outside-in tr.region-title {
|
||||
font-weight: normal;
|
||||
}
|
||||
.ui-dialog-outside-in tr.region-message {
|
||||
color: #fff;
|
||||
}
|
||||
.ui-dialog-outside-in tr.region-populated {
|
||||
display: none;
|
||||
}
|
||||
.ui-dialog-outside-in tr.add-new .tabledrag-changed {
|
||||
display: none;
|
||||
}
|
||||
.ui-dialog-outside-in .draggable a.tabledrag-handle {
|
||||
background-image: none;
|
||||
margin: 0;
|
||||
margin-left: 0; /* LTR */
|
||||
margin-right: 5px; /* LTR */
|
||||
height: auto;
|
||||
min-width: 20px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
float: left; /* LTR */
|
||||
text-decoration: none;
|
||||
cursor: move;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .draggable a.tabledrag-handle {
|
||||
float: right;
|
||||
margin-right: 0;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.ui-dialog-outside-in a.tabledrag-handle .handle {
|
||||
/* Use lighter drag icon against dark background. */
|
||||
background-image: url(../../../misc/icons/bebebe/move.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
.ui-dialog-outside-in .draggable a.tabledrag-handle:hover .handle,
|
||||
.ui-dialog-outside-in .draggable a.tabledrag-handle:focus .handle {
|
||||
background-image: url(../../../misc/icons/787878/move.svg);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.ui-dialog-outside-in .touchevents .draggable td {
|
||||
padding: 0 10px;
|
||||
}
|
||||
.ui-dialog-outside-in .touchevents .draggable .menu-item__link {
|
||||
display: inline-block;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.ui-dialog-outside-in .touchevents a.tabledrag-handle {
|
||||
height: 44px;
|
||||
width: 40px;
|
||||
}
|
||||
.ui-dialog-outside-in .touchevents a.tabledrag-handle .handle {
|
||||
background-position: 40% 19px; /* LTR */
|
||||
height: 21px;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .touch a.tabledrag-handle .handle {
|
||||
background-position: right 40% top 19px;
|
||||
}
|
||||
.ui-dialog-outside-in .touchevents .draggable.drag a.tabledrag-handle .handle {
|
||||
background-position: 50% -32px;
|
||||
}
|
||||
.ui-dialog-outside-in .tabledrag-toggle-weight-wrapper {
|
||||
text-align: right; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .tabledrag-toggle-weight-wrapper {
|
||||
text-align: left;
|
||||
}
|
||||
.ui-dialog-outside-in .indentation {
|
||||
float: left; /* LTR */
|
||||
height: auto;
|
||||
margin: 0 3px 0 -10px; /* LTR */
|
||||
padding: 0 0 0 10px; /* LTR */
|
||||
width: auto;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .indentation {
|
||||
float: right;
|
||||
margin: 0 -10px 0 3px;
|
||||
padding: 0 10px 0 0;
|
||||
}
|
||||
.ui-dialog-outside-in tr.drag {
|
||||
background-color: #555;
|
||||
}
|
||||
.ui-dialog-outside-in tr.drag-previous {
|
||||
background-color: #000;
|
||||
}
|
326
web/core/modules/outside_in/css/outside_in.theme.css
Normal file
326
web/core/modules/outside_in/css/outside_in.theme.css
Normal file
|
@ -0,0 +1,326 @@
|
|||
/**
|
||||
* @file
|
||||
* Visual styling for Settings Tray module.
|
||||
*/
|
||||
|
||||
/* @todo Move this into toolbar when this module is no longer experimental:
|
||||
* https://www.drupal.org/node/2784593.
|
||||
*/
|
||||
|
||||
/* Style the edit mode toolbar and tabs. */
|
||||
#toolbar-bar.js-outside-in-edit-mode {
|
||||
background-color: #fff;
|
||||
}
|
||||
.js-outside-in-edit-mode .toolbar-item:not(.toolbar-icon-edit) {
|
||||
color: #999;
|
||||
}
|
||||
.js-outside-in-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-offcanvas__main-canvas.js-outside-in-edit-mode .outside-in-editable {
|
||||
outline: 1px dashed rgba(0,0,0,0.5);
|
||||
box-shadow: 0 0 0 1px rgba(255,255,255,0.7);
|
||||
}
|
||||
.dialog-offcanvas__main-canvas.js-outside-in-edit-mode .outside-in-editable:hover,
|
||||
.dialog-offcanvas__main-canvas.js-outside-in-edit-mode .outside-in-editable.outside-in-active-editable {
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
/* Style the dialog-offcanvas container. */
|
||||
.ui-dialog-outside-in {
|
||||
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 content in the tray. */
|
||||
.ui-dialog-outside-in p,
|
||||
.ui-dialog-outside-in h1,
|
||||
.ui-dialog-outside-in h2,
|
||||
.ui-dialog-outside-in h3,
|
||||
.ui-dialog-outside-in h4,
|
||||
.ui-dialog-outside-in h5,
|
||||
.ui-dialog-outside-in h6,
|
||||
.ui-dialog-outside-in pre,
|
||||
.ui-dialog-outside-in legend,
|
||||
.ui-dialog-outside-in cite,
|
||||
.ui-dialog-outside-in span,
|
||||
.ui-dialog-outside-in summary,
|
||||
.ui-dialog-outside-in details,
|
||||
.ui-dialog-outside-in .form-item {
|
||||
color: #ddd;
|
||||
font-family: "Lucida Grande", 'Lucida Sans Unicode','liberation sans', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
background-color: transparent;
|
||||
text-shadow: none;
|
||||
}
|
||||
.ui-dialog-outside-in a,
|
||||
.ui-dialog-outside-in .link {
|
||||
border-bottom: none;
|
||||
font-family: "Lucida Grande", 'Lucida Sans Unicode','liberation sans', sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #85bef4;
|
||||
text-decoration: none;
|
||||
transition: color .5s ease;
|
||||
}
|
||||
.ui-dialog-outside-in a:focus,
|
||||
.ui-dialog-outside-in .link:focus,
|
||||
.ui-dialog-outside-in a:hover,
|
||||
.ui-dialog-outside-in .link:hover {
|
||||
outline: none;
|
||||
color: #46a0f5;
|
||||
}
|
||||
|
||||
/* Style the tray header. */
|
||||
.ui-dialog-outside-in .ui-dialog-titlebar {
|
||||
padding: 20px;
|
||||
background: #2d2d2d;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #000;
|
||||
border-radius: 0;
|
||||
font-weight: normal;
|
||||
color: #fff;
|
||||
}
|
||||
.ui-dialog-outside-in .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: 20px;
|
||||
-moz-transition: all .5s ease;
|
||||
transition: background .5s ease;
|
||||
}
|
||||
.ui-dialog-outside-in .ui-dialog-titlebar-close:hover {
|
||||
background-image: url(../../../misc/icons/ffffff/ex.svg);
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .ui-dialog-titlebar-close {
|
||||
left: 20px;
|
||||
right: auto;
|
||||
}
|
||||
.ui-dialog-outside-in .ui-dialog-title {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
/* Push the text away from the icon. */
|
||||
padding-left: 30px; /* LTR */
|
||||
padding-right: 0px; /* LTR */
|
||||
/* Ensure that long titles do not overlap the close button. */
|
||||
max-width: 210px;
|
||||
text-align: left; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .ui-dialog-title {
|
||||
text-align: right;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.ui-dialog-outside-in .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: 20px; /* LTR */
|
||||
top: 0;
|
||||
width: 20px;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .ui-dialog-title:before {
|
||||
left: auto;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
/* Override default styling from jquery UI. */
|
||||
.ui-state-default,
|
||||
.ui-widget-content .ui-state-default,
|
||||
.ui-widget-header .ui-state-default {
|
||||
border: 0;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
/* Hide the defauld Jquery UI dialog close button. */
|
||||
.ui-dialog-outside-in .ui-icon-closethick {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Groups below here with todos to move to new component need to load last. */
|
||||
|
||||
/**
|
||||
* Visual styling for buttons in the Settings Tray module's off canvas tray.
|
||||
* @todo Move to its own component:
|
||||
* https://www.drupal.org/node/1945262.
|
||||
*/
|
||||
|
||||
.ui-dialog-outside-in button.link {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.ui-dialog-outside-in .button {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin: 1em auto;
|
||||
padding: 6px 1em;
|
||||
background: #7b7b7b;
|
||||
border-radius: 1em;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
.ui-dialog-outside-in .button:hover,
|
||||
.ui-dialog-outside-in .button:focus {
|
||||
background: #888;
|
||||
}
|
||||
.ui-dialog-outside-in .button--primary {
|
||||
background: #277abd none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
.ui-dialog-outside-in .button--primary:hover,
|
||||
.ui-dialog-outside-in .button--primary:focus {
|
||||
background: #2b8bd8;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Visual styling for dropbutton in the Settings Tray module's off canvas tray.
|
||||
* @todo Move to its own component:
|
||||
* https://www.drupal.org/node/1945262.
|
||||
*/
|
||||
|
||||
.ui-dialog-outside-in .dropbutton-widget {
|
||||
background: #7b7b7b none;
|
||||
border: 0;
|
||||
border-radius: 1em;
|
||||
color: #eee;
|
||||
transition: background .5s ease;
|
||||
}
|
||||
.ui-dialog-outside-in .dropbutton-widget:hover {
|
||||
box-shadow: 0 2px 2px 1px rgba(0,0,0,0.5);
|
||||
}
|
||||
.ui-dialog-outside-in .dropbutton-toggle button {
|
||||
background: #7b7b7b none;
|
||||
border-bottom-right-radius: 1em; /* LTR */
|
||||
border-top-right-radius: 1em; /* LTR */
|
||||
border-style: solid;
|
||||
border-color: #333;
|
||||
border-left-width: 1px; /* LTR */
|
||||
transition: background .5s ease;
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .dropbutton-toggle button {
|
||||
border-radius: 0;
|
||||
border-bottom-left-radius: 1em;
|
||||
border-top-left-radius: 1em;
|
||||
border-width: 0;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
.ui-dialog-outside-in .dropbutton .dropbutton-action:hover,
|
||||
.ui-dialog-outside-in .dropbutton a:hover {
|
||||
background: #6b6b6b none;
|
||||
border-bottom-left-radius: 1em; /* LTR */
|
||||
border-top-left-radius: 1em; /* LTR */
|
||||
}
|
||||
[dir="rtl"] .ui-dialog-outside-in .dropbutton .dropbutton-action:hover,
|
||||
[dir="rtl"] .ui-dialog-outside-in .dropbutton a:hover {
|
||||
border-radius: 0;
|
||||
border-bottom-right-radius: 1em;
|
||||
border-top-right-radius: 1em;
|
||||
}
|
||||
.ui-dialog-outside-in .dropbutton a {
|
||||
padding: 0.1em 0.8em;
|
||||
color: #eee;
|
||||
font-size: 90%;
|
||||
line-height: 1.8;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
.ui-dialog-outside-in .dropbutton:hover a {
|
||||
color: #fff;
|
||||
}
|
||||
/* Make an arrow out of borders with some fancy CSS. */
|
||||
.ui-dialog-outside-in span.dropbutton-arrow {
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-right-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 0.3333em 0.3333em 0;
|
||||
display: block;
|
||||
height: 0;
|
||||
line-height: 0;
|
||||
position: absolute;
|
||||
right: 40%;
|
||||
top: 50%;
|
||||
margin-top: -0.1666em;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
}
|
||||
.js .ui-dialog-outside-in .dropbutton-toggle .dropbutton-arrow:hover {
|
||||
background: transparent;
|
||||
}
|
||||
.ui-dialog-outside-in td .dropbutton-multiple {
|
||||
padding-right: 0;
|
||||
}
|
||||
[dir="rtl"].ui-dialog-outside-in td .dropbutton-multiple {
|
||||
padding-left: 0;
|
||||
}
|
||||
.ui-dialog-outside-in td .dropbutton-multiple .dropbutton {
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
}
|
||||
.ui-dialog-outside-in td .dropbutton .secondary-action {
|
||||
border-top-color: #000;
|
||||
}
|
70
web/core/modules/outside_in/css/outside_in.toolbar.css
Normal file
70
web/core/modules/outside_in/css/outside_in.toolbar.css
Normal file
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* @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-outside-in-edit-mode {
|
||||
background-color: #fff;
|
||||
}
|
||||
#toolbar-bar.js-outside-in-edit-mode .toolbar-item {
|
||||
color: #999;
|
||||
}
|
||||
#toolbar-bar.js-outside-in-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: -webkit-linear-gradient(top, #007bc6, #0071b8);
|
||||
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: -webkit-linear-gradient(top, #007bc6, #0071b8);
|
||||
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: -webkit-linear-gradient(top, #0c97ed, #1f86c7);
|
||||
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-outside-in-edit-mode button.toolbar-icon.toolbar-icon-edit.toolbar-item.is-active {
|
||||
background-image: none;
|
||||
color: #fff;
|
||||
}
|
||||
#toolbar-bar.js-outside-in-edit-mode button.toolbar-icon.toolbar-icon-edit.toolbar-item.is-active:hover {
|
||||
background-image: linear-gradient(to bottom, #0094f0, #0e69be);
|
||||
}
|
147
web/core/modules/outside_in/js/offcanvas.js
Normal file
147
web/core/modules/outside_in/js/offcanvas.js
Normal file
|
@ -0,0 +1,147 @@
|
|||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
(function ($, Drupal, debounce, displace) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// The minimum width to use body displace needs to match the width at which
|
||||
// the tray will be %100 width. @see outside_in.module.css
|
||||
var minDisplaceWidth = 768;
|
||||
|
||||
/**
|
||||
* The edge of the screen that the dialog should appear on.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
var edge = document.documentElement.dir === 'rtl' ? 'left' : 'right';
|
||||
|
||||
var $mainCanvasWrapper = $('[data-offcanvas-main-canvas]');
|
||||
|
||||
/**
|
||||
* Resets the size of the dialog.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered.
|
||||
*/
|
||||
function resetSize(event) {
|
||||
var offsets = displace.offsets;
|
||||
var $element = event.data.$element;
|
||||
var $widget = $element.dialog('widget');
|
||||
|
||||
var adjustedOptions = {
|
||||
// @see http://api.jqueryui.com/position/
|
||||
position: {
|
||||
my: edge + ' top',
|
||||
at: edge + ' top' + (offsets.top !== 0 ? '+' + offsets.top : ''),
|
||||
of: window
|
||||
}
|
||||
};
|
||||
|
||||
$widget.css({
|
||||
position: 'fixed',
|
||||
height: ($(window).height() - (offsets.top + offsets.bottom)) + 'px'
|
||||
});
|
||||
|
||||
$element
|
||||
.dialog('option', adjustedOptions)
|
||||
.trigger('dialogContentResize.offcanvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the dialog on resize.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered.
|
||||
*/
|
||||
function handleDialogResize(event) {
|
||||
var $element = event.data.$element;
|
||||
var $widget = $element.dialog('widget');
|
||||
|
||||
var $offsets = $widget.find('> :not(#drupal-offcanvas, .ui-resizable-handle)');
|
||||
var offset = 0;
|
||||
var modalHeight;
|
||||
|
||||
// Let scroll element take all the height available.
|
||||
$element.css({height: 'auto'});
|
||||
modalHeight = $widget.height();
|
||||
$offsets.each(function () { offset += $(this).outerHeight(); });
|
||||
|
||||
// Take internal padding into account.
|
||||
var scrollOffset = $element.outerHeight() - $element.height();
|
||||
$element.height(modalHeight - offset - scrollOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the body padding when the dialog is resized.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
* The event triggered.
|
||||
*/
|
||||
function bodyPadding(event) {
|
||||
if ($('body').outerWidth() < minDisplaceWidth) {
|
||||
return;
|
||||
}
|
||||
var $element = event.data.$element;
|
||||
var $widget = $element.dialog('widget');
|
||||
|
||||
var width = $widget.outerWidth();
|
||||
var mainCanvasPadding = $mainCanvasWrapper.css('padding-' + edge);
|
||||
if (width !== mainCanvasPadding) {
|
||||
$mainCanvasWrapper.css('padding-' + edge, width + 'px');
|
||||
$widget.attr('data-offset-' + edge, width);
|
||||
displace();
|
||||
}
|
||||
}
|
||||
|
||||
$(window).on({
|
||||
'dialog:aftercreate': function (event, dialog, $element, settings) {
|
||||
if ($element.is('#drupal-offcanvas')) {
|
||||
var eventData = {settings: settings, $element: $element};
|
||||
$('.ui-dialog-offcanvas, .ui-dialog-offcanvas .ui-dialog-titlebar').toggleClass('ui-dialog-empty-title', !settings.title);
|
||||
|
||||
$element
|
||||
.on('dialogresize.offcanvas', eventData, debounce(bodyPadding, 100))
|
||||
.on('dialogContentResize.offcanvas', eventData, handleDialogResize)
|
||||
.on('dialogContentResize.offcanvas', eventData, debounce(bodyPadding, 100))
|
||||
.trigger('dialogresize.offcanvas');
|
||||
|
||||
$element.dialog('widget').attr('data-offset-' + edge, '');
|
||||
|
||||
$(window)
|
||||
.on('resize.offcanvas scroll.offcanvas', eventData, debounce(resetSize, 100))
|
||||
.trigger('resize.offcanvas');
|
||||
}
|
||||
},
|
||||
'dialog:beforecreate': function (event, dialog, $element, settings) {
|
||||
if ($element.is('#drupal-offcanvas')) {
|
||||
$('body').addClass('js-tray-open');
|
||||
// @see http://api.jqueryui.com/position/
|
||||
settings.position = {
|
||||
my: 'left top',
|
||||
at: edge + ' top',
|
||||
of: window
|
||||
};
|
||||
settings.dialogClass += ' ui-dialog-offcanvas';
|
||||
// Applies initial height to dialog based on window height.
|
||||
// See http://api.jqueryui.com/dialog for all dialog options.
|
||||
settings.height = $(window).height();
|
||||
}
|
||||
},
|
||||
'dialog:beforeclose': function (event, dialog, $element) {
|
||||
if ($element.is('#drupal-offcanvas')) {
|
||||
$('body').removeClass('js-tray-open');
|
||||
$(document).off('.offcanvas');
|
||||
$(window).off('.offcanvas');
|
||||
$mainCanvasWrapper.css('padding-' + edge, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, Drupal, Drupal.debounce, Drupal.displace);
|
262
web/core/modules/outside_in/js/outside_in.js
Normal file
262
web/core/modules/outside_in/js/outside_in.js
Normal file
|
@ -0,0 +1,262 @@
|
|||
/**
|
||||
* @file
|
||||
* Drupal's Settings Tray library.
|
||||
*/
|
||||
|
||||
(function ($, Drupal) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var blockConfigureSelector = '[data-outside-in-edit]';
|
||||
var toggleEditSelector = '[data-drupal-outsidein="toggle"]';
|
||||
var itemsToToggleSelector = '[data-offcanvas-main-canvas], #toolbar-bar, [data-drupal-outsidein="editable"] a, [data-drupal-outsidein="editable"] button';
|
||||
var contextualItemsSelector = '[data-contextual-id] a, [data-contextual-id] button';
|
||||
var quickEditItemSelector = '[data-quickedit-entity-id]';
|
||||
|
||||
/**
|
||||
* 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', function (event, data) {
|
||||
// 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.outsidein', function () {
|
||||
if (!isInEditMode()) {
|
||||
$(toggleEditSelector).trigger('click.outsidein');
|
||||
}
|
||||
// Always disable QuickEdit regardless of whether "EditMode" was just enabled.
|
||||
disableQuickEdit();
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('keyup.outsidein', function (e) {
|
||||
if (isInEditMode() && e.keyCode === 27) {
|
||||
Drupal.announce(
|
||||
Drupal.t('Exited edit mode.')
|
||||
);
|
||||
toggleEditMode();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 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 check the state of the outside-in mode.
|
||||
*
|
||||
* @todo don't use a class for this.
|
||||
*
|
||||
* @return {boolean}
|
||||
* State of the outside-in edit mode.
|
||||
*/
|
||||
function isInEditMode() {
|
||||
return $('#toolbar-bar').hasClass('js-outside-in-edit-mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to toggle Edit mode.
|
||||
*/
|
||||
function toggleEditMode() {
|
||||
setEditModeState(!isInEditMode());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 offcanvas.
|
||||
*/
|
||||
function closeOffCanvas() {
|
||||
$('.ui-dialog-offcanvas .ui-dialog-titlebar-close').trigger('click');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to switch edit mode state.
|
||||
*
|
||||
* @param {boolean} editMode
|
||||
* True enable edit mode, false disable edit mode.
|
||||
*/
|
||||
function setEditModeState(editMode) {
|
||||
editMode = !!editMode;
|
||||
var $editButton = $(toggleEditSelector);
|
||||
var $editables;
|
||||
// Turn on edit mode.
|
||||
if (editMode) {
|
||||
$editButton.text(Drupal.t('Editing'));
|
||||
closeToolbarTrays();
|
||||
|
||||
$editables = $('[data-drupal-outsidein="editable"]').once('outsidein');
|
||||
if ($editables.length) {
|
||||
// Use event capture to prevent clicks on links.
|
||||
document.querySelector('[data-offcanvas-main-canvas]').addEventListener('click', preventClick, true);
|
||||
|
||||
// When a click occurs try and find the outside-in edit link
|
||||
// and click it.
|
||||
$editables
|
||||
.not(contextualItemsSelector)
|
||||
.on('click.outsidein', function (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.outsidein', function (e) {
|
||||
// For all non-contextual links or the contextual QuickEdit link close the off-canvas tray.
|
||||
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-outsidein="editable"]').removeOnce('outsidein');
|
||||
if ($editables.length) {
|
||||
document.querySelector('[data-offcanvas-main-canvas]').removeEventListener('click', preventClick, true);
|
||||
$editables.off('.outsidein');
|
||||
$(quickEditItemSelector).off('.outsidein');
|
||||
}
|
||||
|
||||
$editButton.text(Drupal.t('Edit'));
|
||||
closeOffCanvas();
|
||||
disableQuickEdit();
|
||||
}
|
||||
getItemsToToggle().toggleClass('js-outside-in-edit-mode', editMode);
|
||||
$('.edit-mode-inactive').toggleClass('visually-hidden', editMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches contextual's edit toolbar tab behavior.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Attaches contextual toolbar behavior on a contextualToolbar-init event.
|
||||
*/
|
||||
Drupal.behaviors.outsideInEdit = {
|
||||
attach: function () {
|
||||
var editMode = localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false';
|
||||
if (editMode) {
|
||||
setEditModeState(true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle the js-outside-edit-mode class on items that we want to disable while in edit mode.
|
||||
*
|
||||
* @type {Drupal~behavior}
|
||||
*
|
||||
* @prop {Drupal~behaviorAttach} attach
|
||||
* Toggle the js-outside-edit-mode class.
|
||||
*/
|
||||
Drupal.behaviors.toggleEditMode = {
|
||||
attach: function () {
|
||||
|
||||
$(toggleEditSelector).once('outsidein').on('click.outsidein', toggleEditMode);
|
||||
|
||||
var search = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog';
|
||||
var replace = Drupal.ajax.WRAPPER_FORMAT + '=drupal_dialog_offcanvas';
|
||||
// Loop through all Ajax links and change the format to dialog-offcanvas when
|
||||
// needed.
|
||||
Drupal.ajax.instances
|
||||
.filter(function (instance) {
|
||||
var hasElement = instance && !!instance.element;
|
||||
var rendererOffcanvas = false;
|
||||
var wrapperOffcanvas = false;
|
||||
if (hasElement) {
|
||||
rendererOffcanvas = $(instance.element).attr('data-dialog-renderer') === 'offcanvas';
|
||||
wrapperOffcanvas = instance.options.url.indexOf('drupal_dialog_offcanvas') === -1;
|
||||
}
|
||||
return hasElement && rendererOffcanvas && wrapperOffcanvas;
|
||||
})
|
||||
.forEach(function (instance) {
|
||||
// @todo Move logic for data-dialog-renderer attribute into ajax.js
|
||||
// https://www.drupal.org/node/2784443
|
||||
instance.options.url = instance.options.url.replace(search, replace);
|
||||
// Check to make sure existing dialogOptions aren't overridden.
|
||||
if (!('dialogOptions' in instance.options.data)) {
|
||||
instance.options.data.dialogOptions = {};
|
||||
}
|
||||
instance.options.data.dialogOptions.outsideInActiveEditableId = $(instance.element).parents('.outside-in-editable').attr('id');
|
||||
instance.progress = {type: 'fullscreen'};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Manage Active editable class on opening and closing of the dialog.
|
||||
$(window).on({
|
||||
'dialog:beforecreate': function (event, dialog, $element, settings) {
|
||||
if ($element.is('#drupal-offcanvas')) {
|
||||
$('body .outside-in-active-editable').removeClass('outside-in-active-editable');
|
||||
var $activeElement = $('#' + settings.outsideInActiveEditableId);
|
||||
if ($activeElement.length) {
|
||||
$activeElement.addClass('outside-in-active-editable');
|
||||
settings.dialogClass += ' ui-dialog-outside-in';
|
||||
}
|
||||
}
|
||||
},
|
||||
'dialog:beforeclose': function (event, dialog, $element) {
|
||||
if ($element.is('#drupal-offcanvas')) {
|
||||
$('body .outside-in-active-editable').removeClass('outside-in-active-editable');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
})(jQuery, Drupal);
|
10
web/core/modules/outside_in/outside_in.info.yml
Normal file
10
web/core/modules/outside_in/outside_in.info.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
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
|
24
web/core/modules/outside_in/outside_in.install
Normal file
24
web/core/modules/outside_in/outside_in.install
Normal file
|
@ -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 outside_in_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();
|
||||
}
|
33
web/core/modules/outside_in/outside_in.libraries.yml
Normal file
33
web/core/modules/outside_in/outside_in.libraries.yml
Normal file
|
@ -0,0 +1,33 @@
|
|||
drupal.outside_in:
|
||||
version: VERSION
|
||||
js:
|
||||
js/outside_in.js: {}
|
||||
css:
|
||||
component:
|
||||
css/outside_in.module.css: {}
|
||||
css/outside_in.motion.css: {}
|
||||
css/outside_in.form.css: {}
|
||||
css/outside_in.table.css: {}
|
||||
css/outside_in.details.css: {}
|
||||
css/outside_in.tabledrag.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/outside_in.theme.css: { group: 200 }
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
- core/jquery.once
|
||||
- core/drupal.ajax
|
||||
drupal.off_canvas:
|
||||
version: VERSION
|
||||
js:
|
||||
js/offcanvas.js: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
- core/drupal.ajax
|
||||
- core/drupal.announce
|
||||
- core/drupal.dialog
|
||||
- core/drupal.dialog.ajax
|
|
@ -0,0 +1,4 @@
|
|||
outside_in.block_configure:
|
||||
title: 'Quick edit'
|
||||
route_name: 'entity.block.offcanvas_form'
|
||||
group: 'block'
|
156
web/core/modules/outside_in/outside_in.module
Normal file
156
web/core/modules/outside_in/outside_in.module
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?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\outside_in\Block\BlockEntityOffCanvasForm;
|
||||
use Drupal\outside_in\Form\SystemBrandingOffCanvasForm;
|
||||
use Drupal\outside_in\Form\SystemMenuOffCanvasForm;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function outside_in_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.outside_in':
|
||||
$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=":outside-in-documentation">online documentation for the Settings Tray module</a>.', [':outside-in-documentation' => 'https://www.drupal.org/documentation/modules/outside_in']) . '</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 offcanvas links.
|
||||
*/
|
||||
function outside_in_contextual_links_view_alter(&$element, $items) {
|
||||
if (isset($element['#links']['outside-inblock-configure'])) {
|
||||
$element['#links']['outside-inblock-configure']['attributes'] = [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'offcanvas',
|
||||
'data-outside-in-edit' => TRUE,
|
||||
];
|
||||
|
||||
$element['#attached']['library'][] = 'outside_in/drupal.off_canvas';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_view_alter().
|
||||
*/
|
||||
function outside_in_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']['outside_in'] = [
|
||||
'route_parameters' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_element_info_alter().
|
||||
*/
|
||||
function outside_in_element_info_alter(&$type) {
|
||||
if (isset($type['page'])) {
|
||||
$type['page']['#theme_wrappers']['outside_in_page_wrapper'] = ['#weight' => -1000];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function outside_in_theme() {
|
||||
return [
|
||||
'outside_in_page_wrapper' => [
|
||||
'variables' => ['children' => NULL],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_build().
|
||||
*/
|
||||
function outside_in_entity_type_build(array &$entity_types) {
|
||||
/* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
|
||||
$entity_types['block']
|
||||
->setFormClass('offcanvas', BlockEntityOffCanvasForm::class)
|
||||
->setLinkTemplate('offcanvas-form', '/admin/structure/block/manage/{block}/offcanvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_HOOK() for block templates.
|
||||
*/
|
||||
function outside_in_preprocess_block(&$variables) {
|
||||
// The main system block does not contain the block contextual links.
|
||||
$variables['#cache']['contexts'][] = 'outside_in_is_applied';
|
||||
if ($variables['plugin_id'] !== 'system_main_block' && \Drupal::service('outside_in.manager')->isApplicable()) {
|
||||
// Add class and attributes to all blocks to allow Javascript to target.
|
||||
$variables['attributes']['class'][] = 'outside-in-editable';
|
||||
$variables['attributes']['data-drupal-outsidein'] = 'editable';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_toolbar_alter().
|
||||
*
|
||||
* Includes outside_library if Edit link is in toolbar.
|
||||
*/
|
||||
function outside_in_toolbar_alter(&$items) {
|
||||
$items['contextual']['#cache']['contexts'][] = 'outside_in_is_applied';
|
||||
if (isset($items['contextual']['tab']) && \Drupal::service('outside_in.manager')->isApplicable()) {
|
||||
$items['contextual']['#weight'] = -1000;
|
||||
$items['contextual']['#attached']['library'][] = 'outside_in/drupal.outside_in';
|
||||
$items['contextual']['tab']['#attributes']['data-drupal-outsidein'] = '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().
|
||||
*/
|
||||
function outside_in_block_alter(&$definitions) {
|
||||
if (!empty($definitions['system_branding_block'])) {
|
||||
$definitions['system_branding_block']['forms']['offcanvas'] = SystemBrandingOffCanvasForm::class;
|
||||
}
|
||||
|
||||
// Since menu blocks use derivatives, check the definition ID instead of
|
||||
// relying on the plugin ID.
|
||||
foreach ($definitions as &$definition) {
|
||||
if ($definition['id'] === 'system_menu_block') {
|
||||
$definition['forms']['offcanvas'] = SystemMenuOffCanvasForm::class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_css_alter().
|
||||
*/
|
||||
function outside_in_css_alter(&$css, AttachedAssetsInterface $assets) {
|
||||
// @todo Remove once conditional ordering is introduced in
|
||||
// https://www.drupal.org/node/1945262.
|
||||
$path = drupal_get_path('module', 'outside_in') . '/css/outside_in.theme.css';
|
||||
if (isset($css[$path])) {
|
||||
// Use 200 to come after CSS_AGGREGATE_THEME.
|
||||
$css[$path]['group'] = 200;
|
||||
}
|
||||
}
|
7
web/core/modules/outside_in/outside_in.routing.yml
Normal file
7
web/core/modules/outside_in/outside_in.routing.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
entity.block.offcanvas_form:
|
||||
path: '/admin/structure/block/manage/{block}/offcanvas'
|
||||
defaults:
|
||||
_entity_form: 'block.offcanvas'
|
||||
_title_callback: '\Drupal\outside_in\Block\BlockEntityOffCanvasForm::title'
|
||||
requirements:
|
||||
_permission: 'administer blocks'
|
16
web/core/modules/outside_in/outside_in.services.yml
Normal file
16
web/core/modules/outside_in/outside_in.services.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
services:
|
||||
main_content_renderer.off_canvas:
|
||||
class: Drupal\outside_in\Render\MainContent\OffCanvasRender
|
||||
arguments: ['@title_resolver', '@renderer']
|
||||
tags:
|
||||
- { name: render.main_content_renderer, format: drupal_dialog_offcanvas }
|
||||
|
||||
outside_in.manager:
|
||||
class: Drupal\outside_in\OutsideInManager
|
||||
arguments: ['@router.admin_context', '@current_route_match', '@current_user']
|
||||
|
||||
cache_context.outside_in_is_applied:
|
||||
class: Drupal\outside_in\Cache\Context\OutsideInCacheContext
|
||||
arguments: ['@outside_in.manager']
|
||||
tags:
|
||||
- { name: cache.context}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in\Ajax;
|
||||
|
||||
use Drupal\Core\Ajax\OpenDialogCommand;
|
||||
|
||||
/**
|
||||
* Defines an AJAX command to open content in a dialog in a off-canvas tray.
|
||||
*
|
||||
* @ingroup ajax
|
||||
*/
|
||||
class OpenOffCanvasDialogCommand extends OpenDialogCommand {
|
||||
|
||||
/**
|
||||
* 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 tray 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-offcanvas', $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'] = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build = parent::render();
|
||||
$build['effect'] = 'fade';
|
||||
$build['speed'] = 1000;
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in\Block;
|
||||
|
||||
use Drupal\block\BlockForm;
|
||||
use Drupal\block\BlockInterface;
|
||||
use Drupal\Core\Block\BlockPluginInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\PluginWithFormsInterface;
|
||||
|
||||
/**
|
||||
* Provides form for block instance forms when used in the off-canvas tray.
|
||||
*
|
||||
* This form removes advanced sections of regular block form such as the
|
||||
* visibility settings, machine ID and region.
|
||||
*/
|
||||
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 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']);
|
||||
|
||||
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, 'offcanvas', 'configure');
|
||||
}
|
||||
return $block;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in\Cache\Context;
|
||||
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Cache\Context\CacheContextInterface;
|
||||
use Drupal\outside_in\OutsideInManagerInterface;
|
||||
|
||||
/**
|
||||
* Defines the OutsideInCacheContext service, for "Outside-In or not" caching.
|
||||
*
|
||||
* Cache context ID: 'outside_in_is_applied'.
|
||||
*/
|
||||
class OutsideInCacheContext implements CacheContextInterface {
|
||||
|
||||
/**
|
||||
* The Outside-In manager.
|
||||
*
|
||||
* @var \Drupal\outside_in\OutsideInManagerInterface
|
||||
*/
|
||||
protected $outsideInManager;
|
||||
|
||||
/**
|
||||
* OutsideInCacheContext constructor.
|
||||
*
|
||||
* @param \Drupal\outside_in\OutsideInManagerInterface $outside_in_manager
|
||||
* The Outside-In manager.
|
||||
*/
|
||||
public function __construct(OutsideInManagerInterface $outside_in_manager) {
|
||||
$this->outsideInManager = $outside_in_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getLabel() {
|
||||
return t('Settings Tray');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getContext() {
|
||||
return $this->outsideInManager->isApplicable() ? '1' : '0';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCacheableMetadata() {
|
||||
return new CacheableMetadata();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in\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 outside_in_block_alter()
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
149
web/core/modules/outside_in/src/Form/SystemMenuOffCanvasForm.php
Normal file
149
web/core/modules/outside_in/src/Form/SystemMenuOffCanvasForm.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in\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 outside_in_block_alter()
|
||||
*/
|
||||
class SystemMenuOffCanvasForm extends PluginFormBase implements ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
use RedirectDestinationTrait;
|
||||
|
||||
/**
|
||||
* The plugin.
|
||||
*
|
||||
* @var \Drupal\Core\Block\BlockPluginInterface
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* @var \Drupal\system\MenuInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* @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', array('%label' => $this->entity->label())),
|
||||
'#open' => TRUE,
|
||||
];
|
||||
$form['entity_form'] += $this->getEntityForm($this->entity)->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->entity)->validateForm($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
|
||||
$this->plugin->submitConfigurationForm($form, $form_state);
|
||||
$this->getEntityForm($this->entity)->submitForm($form, $form_state);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the entity form for this menu.
|
||||
*
|
||||
* @param \Drupal\system\MenuInterface $entity
|
||||
* The menu entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityFormInterface
|
||||
* The entity form.
|
||||
*/
|
||||
protected function getEntityForm(MenuInterface $entity) {
|
||||
$entity_form = $this->entityTypeManager->getFormObject('menu', 'edit');
|
||||
$entity_form->setEntity($entity);
|
||||
return $entity_form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPlugin(PluginInspectionInterface $plugin) {
|
||||
$this->plugin = $plugin;
|
||||
$this->entity = $this->menuStorage->load($this->plugin->getDerivativeId());
|
||||
}
|
||||
|
||||
}
|
66
web/core/modules/outside_in/src/OutsideInManager.php
Normal file
66
web/core/modules/outside_in/src/OutsideInManager.php
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in;
|
||||
|
||||
use Drupal\Core\Routing\AdminContext;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Manages information related to Settings Tray.
|
||||
*/
|
||||
class OutsideInManager implements OutsideInManagerInterface {
|
||||
|
||||
/**
|
||||
* The admin context service.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\AdminContext
|
||||
*/
|
||||
protected $adminContext;
|
||||
|
||||
/**
|
||||
* The current route match.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\RouteMatchInterface
|
||||
*/
|
||||
protected $routeMatch;
|
||||
|
||||
/**
|
||||
* The current account.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* OutsideInManager constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\AdminContext $admin_context
|
||||
* The admin context service.
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* The current route match.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The current account.
|
||||
*/
|
||||
public function __construct(AdminContext $admin_context, RouteMatchInterface $route_match, AccountInterface $account) {
|
||||
$this->adminContext = $admin_context;
|
||||
$this->routeMatch = $route_match;
|
||||
$this->account = $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isApplicable() {
|
||||
// Remove on Admin routes.
|
||||
$is_admin_route = $this->adminContext->isAdminRoute();
|
||||
|
||||
// Remove on Block Demo page.
|
||||
$is_admin_demo_route = $this->routeMatch->getRouteName() === 'block.admin_demo';
|
||||
|
||||
// @todo Check if there is actually a different admin theme.
|
||||
// https://www.drupal.org/node/2784853
|
||||
return $this->account->hasPermission('administer blocks') && !$is_admin_route && !$is_admin_demo_route;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in;
|
||||
|
||||
/**
|
||||
* Provides an interface for managing information related to Outside-In.
|
||||
*/
|
||||
interface OutsideInManagerInterface {
|
||||
|
||||
/**
|
||||
* Determines if the Settings Tray logic should be run on the current page.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the Settings Tray logic should be run.
|
||||
*/
|
||||
public function isApplicable();
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in\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\outside_in\Ajax\OpenOffCanvasDialogCommand;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Default main content renderer for offcanvas dialog requests.
|
||||
*/
|
||||
class OffCanvasRender extends DialogRenderer {
|
||||
|
||||
/**
|
||||
* The renderer.
|
||||
*
|
||||
* @var \Drupal\Core\Render\RendererInterface
|
||||
*/
|
||||
protected $renderer;
|
||||
|
||||
/**
|
||||
* Constructs a new OffCanvasRender.
|
||||
*
|
||||
* @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'][] = 'outside_in/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,59 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\outside_in\Tests\Ajax;
|
||||
|
||||
use Drupal\ajax_test\Controller\AjaxTestController;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\system\Tests\Ajax\AjaxTestBase;
|
||||
|
||||
/**
|
||||
* Performs tests on opening and manipulating dialogs via AJAX commands.
|
||||
*
|
||||
* @group outside_in
|
||||
*/
|
||||
class OffCanvasDialogTest extends AjaxTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['outside_in'];
|
||||
|
||||
/**
|
||||
* Test sending AJAX requests to open and manipulate offcanvas 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);
|
||||
|
||||
$offcanvas_expected_response = [
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#drupal-offcanvas',
|
||||
'settings' => NULL,
|
||||
'data' => $dialog_contents,
|
||||
'dialogOptions' =>
|
||||
[
|
||||
'title' => 'AJAX Dialog & contents',
|
||||
'modal' => FALSE,
|
||||
'autoResize' => FALSE,
|
||||
'resizable' => 'w',
|
||||
'draggable' => FALSE,
|
||||
'drupalAutoButtons' => FALSE,
|
||||
'buttons' => [],
|
||||
],
|
||||
'effect' => 'fade',
|
||||
'speed' => 1000,
|
||||
];
|
||||
|
||||
// Emulate going to the JS version of the page and check the JSON response.
|
||||
$ajax_result = $this->drupalGetAjax('ajax-test/dialog-contents', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog_offcanvas']]);
|
||||
$this->assertEqual($offcanvas_expected_response, $ajax_result[3], 'Off-canvas dialog JSON response matches.');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for a page wrapper.
|
||||
*
|
||||
* For consistent wrapping to {{ page }} render in all themes.
|
||||
*
|
||||
* Available variables:
|
||||
* - children: Contains the child elements of the page.
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% if children %}
|
||||
<div class="dialog-offcanvas__main-canvas" data-offcanvas-main-canvas >
|
||||
{{ children }}
|
||||
</div>
|
||||
{% endif %}
|
|
@ -0,0 +1,9 @@
|
|||
name: 'Off-canvas tests'
|
||||
type: module
|
||||
description: 'Provides off-canvas test links.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- block
|
||||
- outside_in
|
|
@ -0,0 +1,29 @@
|
|||
offcanvas_test.links:
|
||||
path: '/offcanvas-test-links'
|
||||
defaults:
|
||||
_controller: '\Drupal\offcanvas_test\Controller\TestController::linksDisplay'
|
||||
_title: 'Links'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
offcanvas_test.thing1:
|
||||
path: '/offcanvas-thing1'
|
||||
defaults:
|
||||
_controller: '\Drupal\offcanvas_test\Controller\TestController::thing1'
|
||||
_title: 'Thing 1'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
offcanvas_test.thing2:
|
||||
path: '/offcanvas-thing2'
|
||||
defaults:
|
||||
_controller: '\Drupal\offcanvas_test\Controller\TestController::thing2'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
||||
|
||||
offcanvas_test.dialog_links:
|
||||
path: '/offcanvas-dialog-links'
|
||||
defaults:
|
||||
_controller: '\Drupal\offcanvas_test\Controller\TestController::otherDialogLinks'
|
||||
requirements:
|
||||
_access: 'TRUE'
|
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\offcanvas_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 offcanvas tray.
|
||||
*
|
||||
* @return array
|
||||
* Render array with links.
|
||||
*/
|
||||
public function linksDisplay() {
|
||||
return [
|
||||
'offcanvas_link_1' => [
|
||||
'#title' => 'Click Me 1!',
|
||||
'#type' => 'link',
|
||||
'#url' => Url::fromRoute('offcanvas_test.thing1'),
|
||||
'#attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'offcanvas',
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'outside_in/drupal.outside_in',
|
||||
],
|
||||
],
|
||||
],
|
||||
'offcanvas_link_2' => [
|
||||
'#title' => 'Click Me 2!',
|
||||
'#type' => 'link',
|
||||
'#url' => Url::fromRoute('offcanvas_test.thing2'),
|
||||
'#attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'offcanvas',
|
||||
'data-dialog-options' => Json::encode([
|
||||
'width' => 555,
|
||||
]),
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'outside_in/drupal.outside_in',
|
||||
],
|
||||
],
|
||||
],
|
||||
'other_dialog_links' => [
|
||||
'#title' => 'Display more links!',
|
||||
'#type' => 'link',
|
||||
'#url' => Url::fromRoute('offcanvas_test.dialog_links'),
|
||||
'#attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'offcanvas',
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'outside_in/drupal.outside_in',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays dialogs links to be displayed inside the offcanvas tray.
|
||||
*
|
||||
* This links are used to test opening a modal and another offcanvas link from
|
||||
* inside the offcanvas tray.
|
||||
*
|
||||
* @todo Update tests to check these links work in the offcanvas tray.
|
||||
* 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('offcanvas_test.thing2'),
|
||||
'attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'modal',
|
||||
],
|
||||
],
|
||||
'offcanvas_link' => [
|
||||
'title' => 'Offcanvas link!',
|
||||
'url' => Url::fromRoute('offcanvas_test.thing2'),
|
||||
'attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'dialog',
|
||||
'data-dialog-renderer' => 'offcanvas',
|
||||
],
|
||||
],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'outside_in/drupal.outside_in',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\offcanvas_test\Plugin\Block;
|
||||
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Provides an 'Off-canvas test block' block.
|
||||
*
|
||||
* @Block(
|
||||
* id = "offcanvas_links_block",
|
||||
* admin_label = @Translation("Off-canvas test block")
|
||||
* )
|
||||
*/
|
||||
class TestBlock extends BlockBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
return [
|
||||
'offcanvas_link_1' => [
|
||||
'#title' => $this->t('Click Me 1!'),
|
||||
'#type' => 'link',
|
||||
'#url' => Url::fromRoute('offcanvas_test.thing1'),
|
||||
'#attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'offcanvas',
|
||||
],
|
||||
],
|
||||
'offcanvas_link_2' => [
|
||||
'#title' => $this->t('Click Me 2!'),
|
||||
'#type' => 'link',
|
||||
'#url' => Url::fromRoute('offcanvas_test.thing2'),
|
||||
'#attributes' => [
|
||||
'class' => ['use-ajax'],
|
||||
'data-dialog-type' => 'offcanvas',
|
||||
],
|
||||
],
|
||||
'#attached' => [
|
||||
'library' => [
|
||||
'outside_in/drupal.off_canvas',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#main-canvas.js-outside-in-edit-mode a,
|
||||
#main-canvas.js-outside-in-edit-mode input {
|
||||
pointer-events: inherit !important;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
name: 'CSS Test fix'
|
||||
type: module
|
||||
description: 'Provides CSS fixes for tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- outside_in
|
|
@ -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 outside_in_test_css_page_attachments(array &$attachments) {
|
||||
// Unconditionally attach an asset to the page.
|
||||
$attachments['#attached']['library'][] = 'outside_in_test_css/drupal.css_fix';
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\outside_in\FunctionalJavascript;
|
||||
|
||||
/**
|
||||
* Tests the off-canvas tray functionality.
|
||||
*
|
||||
* @group outside_in
|
||||
*/
|
||||
class OffCanvasTest extends OutsideInJavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block', 'system', 'toolbar', 'outside_in', 'offcanvas_test'];
|
||||
|
||||
/**
|
||||
* Tests that regular non-contextual links will work with the off-canvas tray.
|
||||
*/
|
||||
public function testOffCanvasLinks() {
|
||||
$themes = ['bartik', 'stark'];
|
||||
// Test the same functionality on multiple themes.
|
||||
foreach ($themes as $theme) {
|
||||
$this->enableTheme($theme);
|
||||
$this->drupalGet('/offcanvas-test-links');
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$web_assert = $this->assertSession();
|
||||
|
||||
// Make sure off-canvas tray is on page when first loaded.
|
||||
$web_assert->elementNotExists('css', '#drupal-offcanvas');
|
||||
|
||||
// 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-offcanvas');
|
||||
// Check that response text is on page.
|
||||
$web_assert->pageTextContains("Thing $link_index says hello");
|
||||
$offcanvas_tray = $this->getTray();
|
||||
|
||||
// Check that tray is visible.
|
||||
$this->assertEquals(TRUE, $offcanvas_tray->isVisible());
|
||||
$header_text = $offcanvas_tray->find('css', '.ui-dialog-title')->getText();
|
||||
|
||||
$tray_text = $offcanvas_tray->findById('drupal-offcanvas')->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('', $header_text);
|
||||
|
||||
$style = $page->find('css', '.ui-dialog-offcanvas')->getAttribute('style');
|
||||
self::assertTrue(strstr($style, 'width: 555px;') !== FALSE, 'Dialog width respected.');
|
||||
}
|
||||
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() {
|
||||
$themes = ['stark', 'bartik'];
|
||||
$narrow_width_breakpoint = 768;
|
||||
$offset = 20;
|
||||
$height = 800;
|
||||
$page = $this->getSession()->getPage();
|
||||
$web_assert = $this->assertSession();
|
||||
|
||||
// Test the same functionality on multiple themes.
|
||||
foreach ($themes as $theme) {
|
||||
$this->enableTheme($theme);
|
||||
// Testing at the wider width.
|
||||
$this->getSession()->resizeWindow($narrow_width_breakpoint + $offset, $height);
|
||||
$this->drupalGet('/offcanvas-test-links');
|
||||
$this->assertFalse($page->find('css', '.dialog-offcanvas__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-offcanvas__main-canvas', 'style', 'padding-right');
|
||||
|
||||
// Testing at the narrower width.
|
||||
$this->getSession()->resizeWindow($narrow_width_breakpoint - $offset, $height);
|
||||
$this->drupalGet('/offcanvas-test-links');
|
||||
$this->assertFalse($page->find('css', '.dialog-offcanvas__main-canvas')->hasAttribute('style'), 'Body not padded on narrow page load.');
|
||||
$page->clickLink("Click Me 1!");
|
||||
$this->waitForOffCanvasToOpen();
|
||||
$this->assertFalse($page->find('css', '.dialog-offcanvas__main-canvas')->hasAttribute('style'), 'Body not padded on narrow page with tray open.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,287 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\outside_in\FunctionalJavascript;
|
||||
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Testing opening and saving block forms in the off-canvas tray.
|
||||
*
|
||||
* @group outside_in
|
||||
*/
|
||||
class OutsideInBlockFormTest extends OutsideInJavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'block',
|
||||
'system',
|
||||
'breakpoint',
|
||||
'toolbar',
|
||||
'contextual',
|
||||
'outside_in',
|
||||
'quickedit',
|
||||
'search',
|
||||
// Add test module to override CSS pointer-events properties because they
|
||||
// cause test failures.
|
||||
'outside_in_test_css',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// @todo Ensure that this test class works against bartik and stark:
|
||||
// https://www.drupal.org/node/2784881.
|
||||
$this->enableTheme('bartik');
|
||||
$user = $this->createUser([
|
||||
'administer blocks',
|
||||
'access contextual links',
|
||||
'access toolbar',
|
||||
'administer nodes',
|
||||
'access in-place editing',
|
||||
'search content',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$this->placeBlock('system_powered_by_block', ['id' => 'powered']);
|
||||
$this->placeBlock('system_branding_block', ['id' => 'branding']);
|
||||
$this->placeBlock('search_form_block', ['id' => 'search']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests opening Offcanvas tray by click blocks and elements in the blocks.
|
||||
*
|
||||
* @dataProvider providerTestBlocks
|
||||
*/
|
||||
public function testBlocks($block_id, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item) {
|
||||
$web_assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$block_selector = '#' . $block_id;
|
||||
$this->drupalGet('user');
|
||||
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->waitForElement("{$toolbar_item}.is-active");
|
||||
}
|
||||
$this->toggleEditingMode();
|
||||
if (isset($toolbar_item)) {
|
||||
$this->waitForNoElement("{$toolbar_item}.is-active");
|
||||
}
|
||||
|
||||
$this->openBlockForm($block_selector);
|
||||
|
||||
switch ($block_id) {
|
||||
case 'block-powered':
|
||||
// Fill out form, save the form.
|
||||
$page->fillField('settings[label]', $new_page_text);
|
||||
$page->checkField('settings[label_display]');
|
||||
break;
|
||||
|
||||
case 'block-branding':
|
||||
// Fill out form, save the form.
|
||||
$page->fillField('settings[site_information][site_name]', $new_page_text);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isset($new_page_text)) {
|
||||
$page->pressButton($button_text);
|
||||
// Make sure the changes are present.
|
||||
// @todo Use a wait method that will take into account the form submitting
|
||||
// and all JavaScript activity. https://www.drupal.org/node/2837676
|
||||
// The use \Behat\Mink\WebAssert::pageTextContains to check text.
|
||||
$this->assertJsCondition('jQuery("' . $block_selector . ' ' . $label_selector . '").html() == "' . $new_page_text . '"');
|
||||
}
|
||||
|
||||
$this->openBlockForm($block_selector);
|
||||
|
||||
$this->toggleEditingMode();
|
||||
// Canvas should close when editing module is closed.
|
||||
$this->waitForOffCanvasToClose();
|
||||
|
||||
// Go into Edit mode again.
|
||||
$this->toggleEditingMode();
|
||||
|
||||
$element_selector = "$block_selector {$element_selector}";
|
||||
// Open block form by clicking a element inside the block.
|
||||
// This confirms that default action for links and form elements is
|
||||
// suppressed.
|
||||
$this->openBlockForm($element_selector);
|
||||
|
||||
// Exit edit mode using ESC.
|
||||
$web_assert->elementTextContains('css', '.contextual-toolbar-tab button', 'Editing');
|
||||
$web_assert->elementAttributeContains('css', '.dialog-offcanvas__main-canvas', 'class', 'js-outside-in-edit-mode');
|
||||
// Simulate press the Escape key.
|
||||
$this->getSession()->executeScript('jQuery("body").trigger(jQuery.Event("keyup", { keyCode: 27 }));');
|
||||
$this->waitForOffCanvasToClose();
|
||||
$this->getSession()->wait(100);
|
||||
$web_assert->elementTextContains('css', '#drupal-live-announce', 'Exited edit mode.');
|
||||
$web_assert->elementTextNotContains('css', '.contextual-toolbar-tab button', 'Editing');
|
||||
$web_assert->elementAttributeNotContains('css', '.dialog-offcanvas__main-canvas', 'class', 'js-outside-in-edit-mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dataprovider for testBlocks().
|
||||
*/
|
||||
public function providerTestBlocks() {
|
||||
$blocks = [
|
||||
'block-powered' => [
|
||||
'id' => 'block-powered',
|
||||
'new_page_text' => 'Can you imagine anyone showing the label on this block?',
|
||||
'element_selector' => '.content a',
|
||||
'label_selector' => 'h2',
|
||||
'button_text' => 'Save Powered by Drupal',
|
||||
'toolbar_item' => '#toolbar-item-user',
|
||||
],
|
||||
'block-branding' => [
|
||||
'id' => 'block-branding',
|
||||
'new_page_text' => 'The site that will live a very short life.',
|
||||
'element_selector' => 'a[rel="home"]:nth-child(2)',
|
||||
'label_selector' => '.site-branding__name a',
|
||||
'button_text' => 'Save Site branding',
|
||||
'toolbar_item' => '#toolbar-item-administration',
|
||||
],
|
||||
'block-search' => [
|
||||
'id' => 'block-search',
|
||||
'new_page_text' => NULL,
|
||||
'element_selector' => '#edit-submit',
|
||||
'label_selector' => 'h2',
|
||||
'button_text' => 'Save Search form',
|
||||
'toolbar_item' => NULL,
|
||||
],
|
||||
];
|
||||
return $blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables Editing mode by pressing "Edit" button in the toolbar.
|
||||
*/
|
||||
protected function toggleEditingMode() {
|
||||
$this->waitForElement('div[data-contextual-id="block:block=powered:langcode=en|outside_in::langcode=en"] .contextual-links a', 10000);
|
||||
// Waiting for QuickEdit icon animation.
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
$edit_button = $this->getSession()->getPage()->find('css', '#toolbar-bar div.contextual-toolbar-tab button');
|
||||
|
||||
$edit_button->press();
|
||||
// Waiting for Toolbar animation.
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that Off-Canvas block form is valid.
|
||||
*/
|
||||
protected function assertOffCanvasBlockFormIsValid() {
|
||||
$web_assert = $this->assertSession();
|
||||
// Check that common block form elements exist.
|
||||
$web_assert->elementExists('css', 'input[data-drupal-selector="edit-settings-label"]');
|
||||
$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.
|
||||
*/
|
||||
protected function openBlockForm($block_selector) {
|
||||
$this->click($block_selector);
|
||||
$this->waitForOffCanvasToOpen();
|
||||
$this->assertOffCanvasBlockFormIsValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests QuickEdit links behavior.
|
||||
*/
|
||||
public function testQuickEditLinks() {
|
||||
$quick_edit_selector = '#quickedit-entity-toolbar';
|
||||
$body_selector = '.field--name-body p';
|
||||
$block_selector = '#block-powered';
|
||||
$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();
|
||||
// Load the same page twice.
|
||||
foreach ([1, 2] as $page_load_times) {
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
// Waiting for Toolbar module.
|
||||
// @todo Remove the hack after https://www.drupal.org/node/2542050.
|
||||
$this->waitForElement('.toolbar-fixed');
|
||||
// Waiting for Toolbar animation.
|
||||
$web_assert->assertWaitOnAjaxRequest();
|
||||
// The 2nd page load we should already be in edit mode.
|
||||
if ($page_load_times == 1) {
|
||||
$this->toggleEditingMode();
|
||||
}
|
||||
// In Edit mode clicking field should open QuickEdit toolbar.
|
||||
$page->find('css', $body_selector)->click();
|
||||
$this->waitForElement($quick_edit_selector);
|
||||
// Exit Edit mode.
|
||||
$this->toggleEditingMode();
|
||||
// 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);
|
||||
|
||||
// Enter Edit mode.
|
||||
$this->toggleEditingMode();
|
||||
$this->openBlockForm($block_selector);
|
||||
$page->find('css', $body_selector)->click();
|
||||
$this->waitForElement($quick_edit_selector);
|
||||
// Offcanvas should be closed when opening QuickEdit toolbar.
|
||||
$this->waitForOffCanvasToClose();
|
||||
|
||||
$this->openBlockForm($block_selector);
|
||||
// QuickEdit toolbar should be closed when opening Offcanvas.
|
||||
$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->toggleEditingMode();
|
||||
// Open QuickEdit toolbar before going into Edit mode.
|
||||
$this->clickContextualLink('.node', "Quick edit");
|
||||
$this->waitForElement($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 Offcanvas.
|
||||
$web_assert->elementNotExists('css', $quick_edit_selector);
|
||||
// Open QuickEdit toolbar via contextual link while in Edit mode.
|
||||
$this->clickContextualLink('.node', "Quick edit", FALSE);
|
||||
$this->waitForOffCanvasToClose();
|
||||
$this->waitForElement($quick_edit_selector);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\outside_in\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
|
||||
|
||||
/**
|
||||
* Base class contains common test functionality for the Settings Tray module.
|
||||
*/
|
||||
abstract class OutsideInJavascriptTestBase extends JavascriptTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function drupalGet($path, array $options = array(), array $headers = array()) {
|
||||
$return = parent::drupalGet($path, $options, $headers);
|
||||
|
||||
// After the page loaded we need to additionally wait until the settings
|
||||
// tray Ajax activity is done.
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables a theme.
|
||||
*
|
||||
* @param string $theme
|
||||
* The theme.
|
||||
*/
|
||||
public 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 tray to open.
|
||||
*/
|
||||
protected function waitForOffCanvasToOpen() {
|
||||
$web_assert = $this->assertSession();
|
||||
$web_assert->assertWaitOnAjaxRequest();
|
||||
$this->waitForElement('#drupal-offcanvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for Off-canvas tray to close.
|
||||
*/
|
||||
protected function waitForOffCanvasToClose() {
|
||||
$this->waitForNoElement('#drupal-offcanvas');
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for an element to appear on the page.
|
||||
*
|
||||
* @param string $selector
|
||||
* CSS selector.
|
||||
* @param int $timeout
|
||||
* (optional) Timeout in milliseconds, defaults to 10000.
|
||||
*/
|
||||
protected function waitForElement($selector, $timeout = 10000) {
|
||||
$condition = "(jQuery('$selector').length > 0)";
|
||||
$this->assertJsCondition($condition, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Off-Canvas tray element.
|
||||
*
|
||||
* @return \Behat\Mink\Element\NodeElement|null
|
||||
*/
|
||||
protected function getTray() {
|
||||
$tray = $this->getSession()->getPage()->find('css', '.ui-dialog[aria-describedby="drupal-offcanvas"]');
|
||||
$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 = "(jQuery('$selector').length == 0)";
|
||||
$this->assertJsCondition($condition, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks a contextual link.
|
||||
*
|
||||
* @todo Remove this function when related trait added in
|
||||
* https://www.drupal.org/node/2821724.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector for the element that contains the contextual link.
|
||||
* @param string $link_locator
|
||||
* The link id, title, or text.
|
||||
* @param bool $force_visible
|
||||
* If true then the button will be forced to visible so it can be clicked.
|
||||
*/
|
||||
protected function clickContextualLink($selector, $link_locator, $force_visible = TRUE) {
|
||||
if ($force_visible) {
|
||||
$this->toggleContextualTriggerVisibility($selector);
|
||||
}
|
||||
|
||||
$element = $this->getSession()->getPage()->find('css', $selector);
|
||||
$element->find('css', '.contextual button')->press();
|
||||
$element->findLink($link_locator)->click();
|
||||
|
||||
if ($force_visible) {
|
||||
$this->toggleContextualTriggerVisibility($selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility of a contextual trigger.
|
||||
*
|
||||
* @todo Remove this function when related trait added in
|
||||
* https://www.drupal.org/node/2821724.
|
||||
*
|
||||
* @param string $selector
|
||||
* The selector for the element that contains the contextual link.
|
||||
*/
|
||||
protected function toggleContextualTriggerVisibility($selector) {
|
||||
// Hovering over the element itself with should be enough, but does not
|
||||
// work. Manually remove the visually-hidden class.
|
||||
$this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').toggleClass('visually-hidden');");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\outside_in\Unit\Ajax;
|
||||
|
||||
use Drupal\outside_in\Ajax\OpenOffCanvasDialogCommand;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\outside_in\Ajax\OpenOffCanvasDialogCommand
|
||||
* @group outside_in
|
||||
*/
|
||||
class OpenOffCanvasDialogCommandTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::render
|
||||
*/
|
||||
public function testRender() {
|
||||
$command = new OpenOffCanvasDialogCommand('Title', '<p>Text!</p>', ['url' => 'example']);
|
||||
|
||||
$expected = [
|
||||
'command' => 'openDialog',
|
||||
'selector' => '#drupal-offcanvas',
|
||||
'settings' => NULL,
|
||||
'data' => '<p>Text!</p>',
|
||||
'dialogOptions' => [
|
||||
'url' => 'example',
|
||||
'title' => 'Title',
|
||||
'modal' => FALSE,
|
||||
'autoResize' => FALSE,
|
||||
'resizable' => 'w',
|
||||
'draggable' => FALSE,
|
||||
'drupalAutoButtons' => FALSE,
|
||||
'buttons' => [],
|
||||
],
|
||||
'effect' => 'fade',
|
||||
'speed' => 1000,
|
||||
];
|
||||
$this->assertEquals($expected, $command->render());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\outside_in\Unit;
|
||||
|
||||
use Drupal\Core\Routing\AdminContext;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\outside_in\OutsideInManager;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\outside_in\OutsideInManager
|
||||
* @group outside_in
|
||||
*/
|
||||
class OutsideInManagerTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::isApplicable
|
||||
* @dataProvider providerTestIsApplicable
|
||||
*/
|
||||
public function testIsApplicable($is_admin_route, $route_name, $has_permission, $expected) {
|
||||
$admin_context = $this->prophesize(AdminContext::class);
|
||||
$admin_context->isAdminRoute()->willReturn($is_admin_route);
|
||||
|
||||
$route_match = $this->prophesize(RouteMatchInterface::class);
|
||||
$route_match->getRouteName()->willReturn($route_name);
|
||||
|
||||
$account = $this->prophesize(AccountInterface::class);
|
||||
$account->hasPermission('administer blocks')->willReturn($has_permission);
|
||||
|
||||
$outside_in_manager = new OutsideInManager($admin_context->reveal(), $route_match->reveal(), $account->reveal());
|
||||
|
||||
$this->assertSame($expected, $outside_in_manager->isApplicable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for ::testIsApplicable().
|
||||
*/
|
||||
public function providerTestIsApplicable() {
|
||||
$data = [];
|
||||
|
||||
// Passing combination.
|
||||
$data[] = [FALSE, 'the_route_name', TRUE, TRUE];
|
||||
|
||||
// Failing combinations.
|
||||
$data[] = [TRUE, 'the_route_name', TRUE, FALSE];
|
||||
$data[] = [TRUE, 'the_route_name', FALSE, FALSE];
|
||||
$data[] = [TRUE, 'block.admin_demo', TRUE, FALSE];
|
||||
$data[] = [TRUE, 'block.admin_demo', FALSE, FALSE];
|
||||
$data[] = [FALSE, 'the_route_name', FALSE, FALSE];
|
||||
$data[] = [FALSE, 'block.admin_demo', TRUE, FALSE];
|
||||
$data[] = [FALSE, 'block.admin_demo', FALSE, FALSE];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue