Commit 9e49307d authored by Dries's avatar Dries

Issue #1809352 by nick_schuch, larowlan, jthorson, Wim Leers, Devin Carlson,...

Issue #1809352 by nick_schuch, larowlan, jthorson, Wim Leers, Devin Carlson, effulgentsia, tim.plunkett, jessebeach | cweagans: Added Write tour.module and add it to core.
parent 395d3a6b
/* Artfully masterminded by ZURB */
#joyRideTipContent { display: none; }
/* Default styles for the container */
.joyride-tip-guide {
position: absolute;
background: #000;
background: rgba(0,0,0,0.8);
display: none;
color: #fff;
width: 300px;
z-index: 101;
top: 0; /* keeps the page from scrolling when calculating position */
left: 0;
font-family: "HelveticaNeue", "Helvetica Neue", "Helvetica", Helvetica, Arial, Lucida, sans-serif;
font-weight: normal;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
}
.joyride-content-wrapper {
padding: 10px 10px 15px 15px;
}
/* Mobile */
@media only screen and (max-width: 767px) {
.joyride-tip-guide {
width: 95% !important;
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
left: 2.5% !important;
}
.joyride-tip-guide-wrapper {
width: 100%;
}
}
/* Add a little css triangle pip, older browser just miss out on the fanciness of it */
.joyride-tip-guide span.joyride-nub {
display: block;
position: absolute;
left: 22px;
width: 0;
height: 0;
border: solid 14px;
border: solid 14px;
}
.joyride-tip-guide span.joyride-nub.top {
/*
IE7/IE8 Don't support rgba so we set the fallback
border color here. However, IE7/IE8 are also buggy
in that the fallback color doesn't work for
border-bottom-color so here we set the border-color
and override the top,left,right colors below.
*/
border-color: #000;
border-color: rgba(0,0,0,0.8);
border-top-color: transparent !important;
border-left-color: transparent !important;
border-right-color: transparent !important;
top: -28px;
bottom: none;
}
.joyride-tip-guide span.joyride-nub.bottom {
/*
IE7/IE8 Don't support rgba so we set the fallback
border color here. However, IE7/IE8 are also buggy
in that the fallback color doesn't work for
border-top-color so here we set the border-color
and override the bottom,left,right colors below.
*/
border-color: #000;
border-color: rgba(0,0,0,0.8) !important;
border-bottom-color: transparent !important;
border-left-color: transparent !important;
border-right-color: transparent !important;
bottom: -28px;
bottom: none;
}
.joyride-tip-guide span.joyride-nub.right {
border-color: #000;
border-color: rgba(0,0,0,0.8) !important;
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
top: 22px;
bottom: none;
left: auto;
right: -28px;
}
.joyride-tip-guide span.joyride-nub.left {
border-color: #000;
border-color: rgba(0,0,0,0.8) !important;
border-top-color: transparent !important;
border-left-color: transparent !important;
border-bottom-color: transparent !important;
top: 22px;
left: -28px;
right: auto;
bottom: none;
}
/* Typography */
.joyride-tip-guide h1,.joyride-tip-guide h2,.joyride-tip-guide h3,.joyride-tip-guide h4,.joyride-tip-guide h5,.joyride-tip-guide h6 {
line-height: 1.25;
margin: 0;
font-weight: bold;
color: #fff;
}
.joyride-tip-guide h1 { font-size: 30px; }
.joyride-tip-guide h2 { font-size: 26px; }
.joyride-tip-guide h3 { font-size: 22px; }
.joyride-tip-guide h4 { font-size: 18px; }
.joyride-tip-guide h5 { font-size: 16px; }
.joyride-tip-guide h6 { font-size: 14px; }
.joyride-tip-guide p {
margin: 0 0 18px 0;
font-size: 14px;
line-height: 18px;
}
.joyride-tip-guide a {
color: rgb(255,255,255);
text-decoration: none;
border-bottom: dotted 1px rgba(255,255,255,0.6);
}
.joyride-tip-guide a:hover {
color: rgba(255,255,255,0.8);
border-bottom: none;
}
/* Button Style */
.joyride-tip-guide .joyride-next-tip {
width: auto;
padding: 6px 18px 4px;
font-size: 13px;
text-decoration: none;
color: rgb(255,255,255);
border: solid 1px rgb(0,60,180);
background: rgb(0,99,255);
background: -moz-linear-gradient(top, rgb(0,99,255) 0%, rgb(0,85,214) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(0,99,255)), color-stop(100%,rgb(0,85,214)));
background: -webkit-linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
background: -o-linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
background: -ms-linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0063ff', endColorstr='#0055d6',GradientType=0 );
background: linear-gradient(top, rgb(0,99,255) 0%,rgb(0,85,214) 100%);
text-shadow: 0 -1px 0 rgba(0,0,0,0.5);
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
-webkit-box-shadow: 0px 1px 0px rgba(255,255,255,0.3) inset;
-moz-box-shadow: 0px 1px 0px rgba(255,255,255,0.3) inset;
box-shadow: 0px 1px 0px rgba(255,255,255,0.3) inset;
}
.joyride-next-tip:hover {
color: rgb(255,255,255) !important;
border: solid 1px rgb(0,60,180) !important;
background: rgb(43,128,255);
background: -moz-linear-gradient(top, rgb(43,128,255) 0%, rgb(29,102,211) 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgb(43,128,255)), color-stop(100%,rgb(29,102,211)));
background: -webkit-linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
background: -o-linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
background: -ms-linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#2b80ff', endColorstr='#1d66d3',GradientType=0 );
background: linear-gradient(top, rgb(43,128,255) 0%,rgb(29,102,211) 100%);
}
.joyride-timer-indicator-wrap {
width: 50px;
height: 3px;
border: solid 1px rgba(255,255,255,0.1);
position: absolute;
right: 17px;
bottom: 16px;
}
.joyride-timer-indicator {
display: block;
width: 0;
height: inherit;
background: rgba(255,255,255,0.25);
}
.joyride-close-tip {
position: absolute;
right: 10px;
top: 10px;
color: rgba(255,255,255,0.4) !important;
text-decoration: none;
font-family: Verdana, sans-serif;
font-size: 10px;
font-weight: bold;
border-bottom: none !important;
}
.joyride-close-tip:hover {
color: rgba(255,255,255,0.9) !important;
}
.joyride-modal-bg {
position: fixed;
height: 100%;
width: 100%;
background: rgb(0,0,0);
background: transparent;
background: rgba(0,0,0, 0.5);
-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
filter: alpha(opacity=50);
opacity: 0.5;
z-index: 100;
display: none;
top: 0;
left: 0;
cursor: pointer;
}
/**
* @file
* RTL styling for tour module.
*/
.js .toolbar .bar .tour-toolbar-tab.tab {
float: left;
}
.tour-progress {
right: 0;
left: 15px;
}
/**
* @file
* Styling for tour module.
*/
/* Tab appearance. */
.js .toolbar .bar .tour-toolbar-tab.tab {
float: right; /* LTR */
}
.js .toolbar .bar .tour-toolbar-tab button {
padding-bottom: 1em;
padding-top: 1em;
color: #fff;
font-weight: bold;
}
.js .toolbar .bar .tour-toolbar-tab button.active {
background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
}
/* Joyride tips should always be on top of everything else. */
.joyride-tip-guide {
z-index: 999;
}
/* Override placement of the tour progress indicator. */
.tour-progress {
position: absolute;
bottom: 10px;
right: 15px; /* LTR */
}
/* @todo Remove once http://drupal.org/node/1916690 is resolved. */
.js .toolbar .bar .tour-toolbar-tab.tab.element-hidden {
display: none;
}
This diff is collapsed.
/**
* @file
* Attaches behaviors for the Tour module's toolbar tab.
*/
(function ($, Backbone, Drupal, document) {
"use strict";
/**
* Attaches the tour's toolbar tab behavior.
*/
Drupal.behaviors.tour = {
attach: function (context) {
var model = new Drupal.tour.models.StateModel();
var view = new Drupal.tour.views.ToggleTourView({
el: $(context).find('#toolbar-tab-tour'),
model: model
});
// Update the model based on Overlay events.
$(document)
// Overlay is opening: cancel tour if active and mark overlay as open.
.on('drupalOverlayOpen.tour', function () {
model.set({ isActive: false, overlayIsOpen: true });
})
// Overlay is loading a new URL: clear tour & cancel if active.
.on('drupalOverlayBeforeLoad.tour', function () {
model.set({ isActive: false, overlayTour: [] });
})
// Overlay is closing: clear tour & cancel if active, mark overlay closed.
.on('drupalOverlayClose.tour', function () {
model.set({ isActive: false, overlayIsOpen: false, overlayTour: [] });
})
// Overlay has loaded DOM: check whether a tour is available.
.on('drupalOverlayReady.tour', function () {
// We must select the tour in the Overlay's window using the Overlay's
// jQuery, because the joyride plugin only works for the window in which
// it was loaded.
// @todo Make upstream contribution so this can be simplified, which
// should also allow us to *not* load jquery.joyride.js in the Overlay,
// resulting in better front-end performance.
var overlay = Drupal.overlay.iframeWindow;
var $overlayContext = overlay.jQuery(overlay.document);
model.set('overlayTour', $overlayContext.find('#tour'));
});
model
// Allow other scripts to respond to tour events.
.on('change:isActive', function (model, isActive) {
$(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
})
// Initialization: check whether a tour is available on the current page.
.set('tour', $(context).find('#tour'));
}
};
Drupal.tour = Drupal.tour || { models: {}, views: {}};
/**
* Backbone Model for tours.
*/
Drupal.tour.models.StateModel = Backbone.Model.extend({
defaults: {
// Indicates whether the Drupal root window has a tour.
tour: [],
// Indicates whether the Overlay is open.
overlayIsOpen: false,
// Indicates whether the Overlay window has a tour.
overlayTour: [],
// Indicates whether the tour is currently running.
isActive: false,
// Indicates which tour is the active one (necessary to cleanly stop).
activeTour: []
}
});
/**
* Handles edit mode toggle interactions.
*/
Drupal.tour.views.ToggleTourView = Backbone.View.extend({
events: { 'click': 'onClick' },
/**
* Implements Backbone Views' initialize().
*/
initialize: function () {
this.model.on('change:tour change:overlayTour change:overlayIsOpen change:isActive', this.render, this);
this.model.on('change:isActive', this.toggleTour, this);
},
/**
* Implements Backbone Views' render().
*/
render: function () {
// Render the visibility.
this.$el.toggleClass('element-hidden', this._getTour().length === 0);
// Render the state.
var isActive = this.model.get('isActive');
this.$el.find('button')
.toggleClass('active', isActive)
.attr('aria-pressed', isActive);
return this;
},
/**
* Model change handler; starts or stops the tour.
*/
toggleTour: function() {
if (this.model.get('isActive')) {
var $tour = this._getTour();
this._removeIrrelevantTourItems($tour, this._getDocument());
var that = this;
$tour.joyride({
postRideCallback: function () { that.model.set('isActive', false); }
});
this.model.set({ isActive: true, activeTour: $tour });
}
else {
this.model.get('activeTour').joyride('destroy');
this.model.set({ isActive: false, activeTour: [] });
}
},
/**
* Toolbar tab click event handler; toggles isActive.
*/
onClick: function (event) {
this.model.set('isActive', !this.model.get('isActive'));
event.preventDefault();
event.stopPropagation();
},
/**
* Gets the tour.
*
* @return jQuery
* A jQuery element pointing to a <ol> containing tour items.
*/
_getTour: function () {
var whichTour = (this.model.get('overlayIsOpen')) ? 'overlayTour' : 'tour';
return this.model.get(whichTour);
},
/**
* Gets the relevant document as a jQuery element.
*
* @return jQuery
* A jQuery element pointing to the document within which a tour would be
* started given the current state. I.e. when the Overlay is open, this will
* point to the HTML document inside the Overlay's iframe, otherwise it will
* point to the Drupal root window.
*/
_getDocument: function () {
return (this.model.get('overlayIsOpen')) ? $(Drupal.overlay.iframeWindow.document) : $(document);
},
/**
* Removes tour items for elements that don't exist.
*
* @param jQuery $tour
* A jQuery element pointing to a <ol> containing tour items.
* @param jQuery $document
* A jQuery element pointing to the document within which the elements
* should be sought.
*
* @see _getDocument()
*/
_removeIrrelevantTourItems: function ($tour, $document) {
var removals = false;
$tour
.find('li')
.each(function () {
var $this = $(this);
var itemId = $this.attr('data-id');
var itemClass = $this.attr('data-class');
if ($document.find('#' + itemId + ', .' + itemClass).length === 0) {
removals = true;
$this.remove();
}
});
// If there were removals, we'll have to do some clean-up.
if (removals) {
var total = $tour.find('li').length;
$tour
.find('li')
// Rebuild the progress data.
.each(function (index) {
var progress = Drupal.t('!tour_item of !total', { '!tour_item': index + 1, '!total': total });
$(this).find('.tour-progress').text(progress);
})
// Update the last item to have "End tour" as the button.
.last()
.attr('data-text', Drupal.t('End tour'));
}
}
});
})(jQuery, Backbone, Drupal, document);
<?php
/**
* @file
* Contains \Drupal\tour\Plugin\Core\Entity\Tour.
*/
namespace Drupal\tour\Plugin\Core\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
use Drupal\tour\TipsBag;
/**
* Defines the configured text tour entity.
*
* @Plugin(
* id = "tour",
* label = @Translation("Tour"),
* module = "tour",
* controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
* config_prefix = "tour.tour",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "uuid" = "uuid",
* }
* )
*/
class Tour extends ConfigEntityBase {
/**
* The name (plugin ID) of the tour.
*
* @var string
* Unique identifier for this tour.
*/
public $id;
/**
* The label of the tour.
*
* @var string
* A human readable name for this tour.
*/
public $label;
/**
* The paths in which this tip can be displayed.
*
* @var array
* An array of paths.
*/
protected $paths;
/**
* Holds the collection of tips that are attached to this tour.
*
* @var \Drupal\tour\TipsBag
*/
protected $tipsBag;
/**
* The array of plugin config, only used for export and to populate the $tipsBag.
*
* @var array
*/
protected $tips;
/**
* Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::__construct();
*/
public function __construct(array $values, $entity_type) {
parent::__construct($values, $entity_type);
$this->tipsBag = new TipsBag(drupal_container()->get('plugin.manager.tour'), $this->tips);
}
/**
* Returns label of tour.
*
* @return string
* The label of the tour.
*/
public function getLabel() {
return $this->label;
}
/**
* The paths that this tour will appear on.
*
* @return array
* Returns array of paths for the tour.
*/
public function getPaths() {
return $this->paths;
}
/**
* Returns tip plugin.
*
* @return string
* The identifier of the tip.
*/
public function getTip($id) {
return $this->tipsBag->get($id);
}
/**
* Returns a list of tips.
*
* @return array
* A list of tips.
*/
public function getTipList() {
return array_keys($this->tips);
}
/**
* Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::getExportProperties();
*/
public function getExportProperties() {
$properties = parent::getExportProperties();
$names = array(
'id',
'label',
'paths',
'tips',
);
foreach ($names as $name) {
$properties[$name] = $this->get($name);
}
return $properties;
}
}
<?php
/**
* @file
* Contains \Drupal\tour\TipPluginText.
*/
namespace Drupal\tour\Plugin\tour\tip;
use Drupal\Core\Annotation\Plugin;
use Drupal\tour\TipPluginBase;
/**
* Displays some text as a tip.
*
* @Plugin(
* id = "text",
* module = "tour"
* )
*/
class TipPluginText extends TipPluginBase {
/**
* The body text which is used for render of this Text Tip.
*
* @var string
* A string of text used as the body.
*/
protected $body;
/**
* The forced position of where the tip will be located.
*
* @var string
* A string of left|right|top|bottom.
*/
protected $location;
/**
* Returns a ID that is guaranteed uniqueness.
*
* @return string
* A unique string.
*/
public function getAriaId() {
static $id;
if (!isset($id)) {
$id = drupal_html_id($this->get('id'));
}
return $id;
}
/**
* Returns body of the text tip.
*
* @return string
* The body of the text tip.
*/
public function getBody() {
return $this->get('body');
}
/**
* Returns location of the text tip.
*
* @return string
* The location (left|right|top|bottom) of the text tip.
*/
public function getLocation() {
return $this->get('location');
}
/**
* Overrides \Drupal\tour\Plugin\tour\tour\TipPluginInterface::getAttributes();
*/
public function getAttributes() {
$attributes = parent::getAttributes();
$attributes['data-aria-describedby'] = 'tour-tip-' . $this->getAriaId() . '-contents';
$attributes['data-aria-labelledby'] = 'tour-tip-' . $this->getAriaId() . '-label';