Commit 42b81c64 authored by alexpott's avatar alexpott

Issue #2753941 by tedbow, tim.plunkett, tkoleary, SKAUGHT, drpal,...

Issue #2753941 by tedbow, tim.plunkett, tkoleary, SKAUGHT, drpal, effulgentsia, andrewmacpherson, webchick, xjm, Bojhan, Gábor Hojtsy, Wim Leers, catch, nod_: [Experimental] Create Outside In module MVP to provide block configuration in Off-Canvas tray and expand site edit mode
parent 9e2f220d
......@@ -113,6 +113,7 @@
"drupal/migrate_drupal_ui": "self.version",
"drupal/node": "self.version",
"drupal/options": "self.version",
"drupal/outside_in": "self.version",
"drupal/page_cache": "self.version",
"drupal/path": "self.version",
"drupal/quickedit": "self.version",
......
/**
* @file
* Styling for Outside-In module.
*/
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
box-sizing: border-box;
height: 100%;
overflow-y: auto;
z-index: 501;
}
/* Shift the main canvas to the right for right-to-left languages. */
[dir="rtl"] #main-canvas-wrapper.js-tray-open #main-canvas {
right: 0;
}
/* Position the button that closes the offcanvas tray. */
#offcanvas > button.offcanvasClose {
position: static;
float: right; /* LTR */
height: 52px;
width: 40px;
border: 0;
border-radius: 0;
background: url(../../../misc/icons/bebebe/ex.svg) center center no-repeat;
color: transparent;
cursor: pointer;
z-index: 501;
}
#offcanvas > button.offcanvasClose:focus {
outline: none;
}
[dir="rtl"] #offcanvas > button.offcanvasClose {
float: left;
}
/* Create a place to name the tray. */
#offcanvas h1 {
padding: 15px 25% 15px 15px; /* LTR */
margin-top: 0;
margin-bottom: 0;
font-size: 120%;
}
[dir="rtl"] #offcanvas h1 {
text-align: right;
padding-right: 0;
padding-left: 25%;
}
/* Wrap the form that's inside the offcanvas tray. */
#offcanvas > .offcanvas-content {
height: 10000px;
padding: 0 15px;
}
[dir="rtl"] #offcanvas .offcanvas-content {
text-align: right;
}
#offcanvas > .form-item,
#offcanvas > .form-item .form-item {
width: 100%;
}
/*
* Position the edit toolbar tab.
* @todo Move changes into contextual module when outside-in is not
* experimental: https://www.drupal.org/node/2784591.
*/
.toolbar .toolbar-bar .contextual-toolbar-tab.toolbar-tab {
float: left;
}
/* Media queries. */
/* @todo Rework breakpoints: https://www.drupal.org/node/2784599. */
@media (max-width: 700px) {
#offcanvas {
position: absolute;
display: block;
right: 0;
top: 0;
width: 300px;
margin-right: -300px;
padding-top: 39px;
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
#main-canvas-wrapper.js-tray-open #offcanvas {
margin-right: 0;
right: 0;
top: 0;
}
#main-canvas-wrapper.js-tray-open #main-canvas {
position: static;
width: 100%;
}
}
@media (min-width: 700px) {
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
position: fixed;
display: inline-block;
width: 35%;
-webkit-transform: translateX(100%);
-moz-transform: translateX(100%);
-o-transform: translateX(100%);
-ms-transform: translateX(100%);
transform: translateX(100%);
}
[dir="rtl"] #offcanvas {
text-align: right;
-webkit-transform: translateX(-100%);
-moz-transform: translateX(-100%);
-o-transform: translateX(-100%);
-ms-transform: translateX(-100%);
transform: translateX(-100%);
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
/* Move the offcanvas tray on canvas. */
#main-canvas-wrapper.js-tray-open #offcanvas {
-webkit-transform: translateX(0);
-moz-transform: translateX(0);
-o-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
/* Reduce the width of the main canvas to provide space for the offcanvas tray. */
#main-canvas-wrapper.js-tray-open #main-canvas {
width: 65%;
}
}
@media (min-width: 900px) {
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
position: fixed;
display: inline-block;
width: 30%;
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
/* Reduce the width of the main canvas to provide space for the offcanvas tray. */
#main-canvas-wrapper.js-tray-open #main-canvas {
width: 70%;
}
}
@media (min-width: 1000px) {
/* Position the offcanvas tray container outside the right of the viewport. */
#offcanvas {
position: fixed;
display: inline-block;
width: 25%;
}
/* Wrap the rest of the site so we can control its width. */
#main-canvas-wrapper #main-canvas {
display: inline-block;
width: 100%;
}
/* Reduce the width of the main canvas to provide space for the offcanvas tray. */
#main-canvas-wrapper.js-tray-open #main-canvas {
width: 75%;
}
}
/*
* Form layout changes, mostly specific to Bartik theme and menu.
* @todo Remove when more general form styling is done:
* https://www.drupal.org/node/2784437.
*/
#offcanvas td {
width: auto;
}
#offcanvas .menu-enabled {
width: auto;
}
#offcanvas table#menu-overview th {
display: none;
}
#offcanvas table#menu-overview tr td:first-child {
min-width: 110px;
}
#offcanvas details > .details-wrapper {
padding: 5px;
overflow: scroll;
}
#offcanvas .tabledrag-toggle-weight {
font-size: 80%;
}
#offcanvas input:focus,
#offcanvas summary:focus {
outline: none;
box-shadow: 2px 2px #ddd;
}
/**
* @file
* Motion effects for outside-in 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 offcanvas tray container, with 2s delay to match main canvas speed. */
#offcanvas {
-webkit-transition: all .7s ease 2s;
-moz-transition: all .7s ease 2s;
transition: all .7s ease 2s;
}
#main-canvas-wrapper #main-canvas,
#main-canvas-wrapper.js-tray-open #main-canvas {
-webkit-transition: all .7s ease;
-moz-transition: all .7s ease;
transition: all .7s ease;
}
/* 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. */
#main-canvas-wrapper .contextual,
#main-canvas-wrapper .outside-in-editable,
#main-canvas-wrapper.js-tray-open .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;
}
@media (max-width: 700px) {
#offcanvas {
-webkit-transition: all .7s ease;
-moz-transition: all .7s ease;
transition: all .7s ease;
}
#main-canvas-wrapper.js-tray-open #offcanvas {
-webkit-transition: all .7s ease;
-moz-transition: all .7s ease;
transition: all .7s ease;
}
}
/* Transition the administration tray.
#toolbar-administration,
#toolbar-administration * {
-webkit-transition: all .7s ease;
-moz-transition: all .7s ease;
transition: all .7s ease;
}*/
/**
* @file
* Visual styling for Outside-In module.
*/
/**
* Style the contextual links toggle tab in the toolbar.
*
* @todo Move this into toolbar when module is not experimental:
* https://www.drupal.org/node/2784593.
*/
/* Style both the edit and editing states. */
button.toolbar-icon.toolbar-icon-edit.toolbar-item {
background: #0e69be;
background-image: -webkit-linear-gradient(top, #0094f0, #0e69be);
background-image: linear-gradient(to bottom, #0094f0, #0e69be);
}
button.toolbar-icon.toolbar-icon-edit.toolbar-item:hover,
button.toolbar-icon.toolbar-icon-edit.toolbar-item:focus {
background-image: -webkit-linear-gradient(top, #0094f0, #0e69be);
background-image: linear-gradient(to bottom, #0094f0, #0e69be);
color: #fff;
}
button.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);
}
button.toolbar-icon.toolbar-icon-edit.toolbar-item:hover,
button.toolbar-icon.toolbar-icon-edit.toolbar-item:focus {
background-image: -webkit-linear-gradient(top, #0094f0, #0e69be);
background-image: linear-gradient(to bottom, #0094f0, #0e69be);
outline: none;
}
button.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);
}
/* Style the toolbar when in edit mode. */
#toolbar-bar.js-outside-in-edit-mode {
background-color: #fff;
}
/* Change text color for white background. */
#toolbar-bar.js-outside-in-edit-mode .toolbar-item {
color: #999;
}
#toolbar-bar.js-outside-in-edit-mode .toolbar-item .is-active {
color: #333;
}
/* Set color back to white for 'editing' button only. */
#toolbar-bar.js-outside-in-edit-mode button.toolbar-icon.toolbar-icon-edit.toolbar-item.is-active {
color: #fff;
}
#toolbar-bar.js-outside-in-edit-mode button.toolbar-icon.toolbar-icon-edit.toolbar-item.is-active:hover {
background-image: -webkit-linear-gradient(top, #0094f0, #0e69be);
background-image: linear-gradient(to bottom, #0094f0, #0e69be);
}
/*
* Style the editables while in edit mode.
*/
/* Highlight editable regions in edit mode. */
#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);
}
#main-canvas.js-outside-in-edit-mode .outside-in-editable:hover {
outline: 1px dashed rgba(0,0,0,0.5);
box-shadow: 0 0 0 1px rgba(255,255,255,0.7);
background-color: rgba(0,0,0,0.2);
}
/* Turn off the outlines on editables when the tray is open. */
#main-canvas-wrapper.js-tray-open .outside-in-editable {
outline: transparent;
outline-color: transparent;
box-shadow: none;
}
#main-canvas-wrapper.js-tray-open .contextual {
opacity: 0;
}
#main-canvas-wrapper.js-tray-open .contextual:hover {
opacity: 1;
}
/**
* Style the offcanvas container.
*
* @todo Move Off-canvas css into core Off-canvas library:
* https://www.drupal.org/node/2784443.
*/
#offcanvas {
background: #fff;
border-left: 1px solid #ddd; /* LTR */
box-shadow: -2px 2px 1px 1px rgba(0, 0, 0, 0.1); /* LTR */
}
[dir="rtl"] #offcanvas {
border-right: 1px solid #ddd;
box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.1);
}
/* Style the tray header. */
#offcanvas h1 {
font-size: 120%;
border-bottom: 1px solid #ddd;
}
/**
* @file
* Drupal's off-canvas library.
*/
(function ($, Drupal) {
'use strict';
// Set the initial state of the off-canvas element.
// If the state has been set previously, use it.
Drupal.offCanvas = {
visible: (Drupal.offCanvas ? Drupal.offCanvas.visible : false)
};
/**
* Create a wrapper container for the off-canvas element.
*
* @return {jQuery}
* jQuery object that is the off-canvas wrapper element.
*/
Drupal.theme.createOffCanvasWrapper = function createOffCanvasWrapper() {
return $('<div id="offcanvas" ' + (document.dir === 'ltr' ? 'data-offset-right' : 'data-offset-left') + ' role="region" aria-labelledby="offcanvas-header"></div>');
};
/**
* Create the title element for the off-canvas element.
*
* @param {string} title
* The title string.
*
* @return {object}
* jQuery object that is the off-canvas title element.
*/
Drupal.theme.createTitle = function createTitle(title) {
return $('<h1 id="offcanvas-header">' + title + '</h1>');
};
/**
* Create the actual off-canvas content.
*
* @param {string} data
* This is fully rendered HTML from Drupal.
*
* @return {object}
* jQuery object that is the off-canvas content element.
*/
Drupal.theme.createOffCanvasContent = function createOffCanvasContent(data) {
return $('<div class="offcanvas-content">' + data + '</div>');
};
/**
* Create the off-canvas close element.
*
* @param {object} offCanvasWrapper
* The jQuery off-canvas wrapper element
* @param {object} pageWrapper
* The jQuery off page wrapper element
*
* @return {jQuery}
* jQuery object that is the off-canvas close element.
*/
Drupal.theme.createOffCanvasClose = function createOffCanvasClose(offCanvasWrapper, pageWrapper) {
return $([
'<button class="offcanvasClose" aria-label="',
Drupal.t('Close configuration tray.'),
'"><span class="visually-hidden">',
Drupal.t('Close'),
'</span></button>'
].join(''))
.on('click', function () {
pageWrapper
.removeClass('js-tray-open')
.one('webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', function () {
Drupal.offCanvas.visible = false;
offCanvasWrapper.remove();
Drupal.announce(Drupal.t('Configuration tray closed.'));
}
);
});
};
/**
* Command to open an off-canvas element.
*
* @param {Drupal.Ajax} ajax
* The Drupal Ajax object.
* @param {object} response
* Object holding the server response.
* @param {number} [status]
* The HTTP status code.
*/
Drupal.AjaxCommands.prototype.openOffCanvas = function (ajax, response, status) {
// Discover display/viewport size.
// @todo Work on breakpoints for tray size:
// https://www.drupal.org/node/2784599.
var $pageWrapper = $('#main-canvas-wrapper');
// var pageWidth = $pageWrapper.width();
// Construct off-canvas wrapper
var $offcanvasWrapper = Drupal.theme('createOffCanvasWrapper');
// Construct off-canvas internal elements.
var $offcanvasClose = Drupal.theme('createOffCanvasClose', $offcanvasWrapper, $pageWrapper);
var $title = Drupal.theme('createTitle', response.dialogOptions.title);
var $offcanvasContent = Drupal.theme('createOffCanvasContent', response.data);
// Put everything together.
$offcanvasWrapper.append([$offcanvasClose, $title, $offcanvasContent]);
// Handle opening or updating tray with content.
var existingTray = false;
if (Drupal.offCanvas.visible) {
// Remove previous content then append new content.
$pageWrapper.find('#offcanvas').remove();
existingTray = true;
}
$pageWrapper.addClass('js-tray-open');
Drupal.offCanvas.visible = true;
$pageWrapper.append($offcanvasWrapper);
if (existingTray) {
Drupal.announce(Drupal.t('Configuration tray content has been updated.'));
}
else {
Drupal.announce(Drupal.t('Configuration tray opened.'));
}
Drupal.attachBehaviors(document.querySelector('#offcanvas'), drupalSettings);
};
})(jQuery, Drupal);
/**
* @file
* Drupal's Outside-In library.
*/
(function ($, Drupal) {
'use strict';
$('.outside-in-editable')
// Bind an event listener to the .outside-in-editable div
// This listen for click events and stops default actions of those elements.
.on('click', '.js-outside-in-edit-mode', function (e) {
if (localStorage.getItem('Drupal.contextualToolbar.isViewing') === 'false') {
e.preventDefault();
}
})
// Bind an event listener to the .outside-in-editable div
// When a click occurs try and find the outside-in edit link
// and click it.
.not('div.contextual a, div.contextual button')
.on('click', function (e) {
if ($(e.target.offsetParent).hasClass('contextual')) {
return;
}
if (!localStorage.getItem('Drupal.contextualToolbar.isViewing')) {
return;
}
var editLink = $(e.target).find('li.outside-inblock-configure a')[0];
if (!editLink) {
var closest = $(e.target).closest('.outside-in-editable');
editLink = closest.find('li.outside-inblock-configure a')[0];
}
editLink.click();
});
/**
* 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.
data.$el.find('.use-ajax').once('ajax').each(function () {
// Below is copied directly from ajax.js to keep behavior the same.
var element_settings = {};
// Clicked links look better with the throbber than the progress bar.
element_settings.progress = {type: 'throbber'};
// For anchor tags, these will go to the target of the anchor rather
// than the usual location.
var href = $(this).attr('href');
if (href) {
element_settings.url = href;
element_settings.event = 'click';
}
element_settings.dialogType = $(this).data('dialog-type');
element_settings.dialog = $(this).data('dialog-options');
element_settings.base = $(this).attr('id');
element_settings.element = this;
Drupal.ajax(element_settings);
});
// Bind a listener to all 'Quick edit' links for blocks
// Click "Edit" button in toolbar to force Contextual Edit which starts
// Outside-In edit mode also.
data.$el.find('.outside-inblock-configure a').on('click', function () {
if (!isActiveMode()) {
$('div.contextual-toolbar-tab.toolbar-tab button').click();
}
});
});
/**
* Gets all items that should be toggled with class during edit mode.
*
* @return {*}
* Items that should be toggled.
*/
var getItemsToToggle = function () {
return $('#main-canvas, #toolbar-bar, .outside-in-editable a, .outside-in-editable button')
.not('div.contextual a, div.contextual button');
};
var isActiveMode = function () {
return $('#toolbar-bar').hasClass('js-outside-in-edit-mode');
};
var setToggleActiveMode = function setToggleActiveMode(forceActive) {
forceActive = forceActive || false;
if (forceActive || !isActiveMode()) {
$('#toolbar-bar .contextual-toolbar-tab button').text(Drupal.t('Editing'));
// Close the Manage tray if open when entering edit mode.
if ($('#toolbar-item-administration-tray').hasClass('is-active')) {
$('#toolbar-item-administration').click();
}
getItemsToToggle().addClass('js-outside-in-edit-mode');
$('.edit-mode-inactive').addClass('visually-hidden');
}
else {
$('#toolbar-bar .contextual-toolbar-tab button').text(Drupal.t('Edit'));
getItemsToToggle().removeClass('js-outside-in-edit-mode');
$('.edit-mode-inactive').removeClass('visually-hidden');
}
};
/**
* 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) {
setToggleActiveMode(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.toggleActiveMode = {
attach: function () {
$('.contextual-toolbar-tab.toolbar-tab button').on('click', function () {
setToggleActiveMode();
});
}
};
})(jQuery, Drupal);
name: 'Outside-In'
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
<?php
/**
* @file
* Install, update and uninstall functions for the Outside-In 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();
}