diff --git a/addons/website/static/lib/bootstrap-tour/bootstrap-tour.css b/addons/website/static/lib/bootstrap-tour/bootstrap-tour.css
new file mode 100755
index 00000000000..72c6e8cbab7
--- /dev/null
+++ b/addons/website/static/lib/bootstrap-tour/bootstrap-tour.css
@@ -0,0 +1,43 @@
+.tour-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1009;
+ background-color: #000;
+ opacity: 0.8;
+}
+.tour-step-backdrop {
+ position: relative;
+ z-index: 1011;
+}
+.tour-step-background {
+ position: absolute;
+ z-index: 1010;
+ background: #fff;
+ border-radius: 6px;
+}
+.popover[class*="tour-"] .popover-navigation {
+ padding: 9px 14px;
+}
+.popover[class*="tour-"] .popover-navigation *[data-role=end] {
+ float: right;
+}
+.popover[class*="tour-"] .popover-navigation *[data-role=prev],
+.popover[class*="tour-"] .popover-navigation *[data-role=next],
+.popover[class*="tour-"] .popover-navigation *[data-role=end] {
+ cursor: pointer;
+}
+.popover[class*="tour-"] .popover-navigation *[data-role=prev].disabled,
+.popover[class*="tour-"] .popover-navigation *[data-role=next].disabled,
+.popover[class*="tour-"] .popover-navigation *[data-role=end].disabled {
+ cursor: default;
+}
+.popover[class*="tour-"].orphan {
+ position: fixed;
+ margin-top: 0;
+}
+.popover[class*="tour-"].orphan .arrow {
+ display: none;
+}
diff --git a/addons/website/static/lib/bootstrap-tour/bootstrap-tour.js b/addons/website/static/lib/bootstrap-tour/bootstrap-tour.js
new file mode 100755
index 00000000000..8b45d6da9ee
--- /dev/null
+++ b/addons/website/static/lib/bootstrap-tour/bootstrap-tour.js
@@ -0,0 +1,559 @@
+/* ===========================================================
+# bootstrap-tour - v0.6.1
+# http://bootstraptour.com
+# ==============================================================
+# Copyright 2012-2013 Ulrich Sossou
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+*/
+(function() {
+ (function($, window) {
+ var Tour, document;
+ document = window.document;
+ Tour = (function() {
+ function Tour(options) {
+ this._options = $.extend({
+ name: "tour",
+ container: "body",
+ keyboard: true,
+ storage: window.localStorage,
+ debug: false,
+ backdrop: false,
+ redirect: true,
+ orphan: false,
+ basePath: "",
+ template: "
").parent().html();
+ $element.popover({
+ placement: step.placement,
+ trigger: "manual",
+ title: step.title,
+ content: step.content,
+ html: true,
+ animation: step.animation,
+ container: step.container,
+ template: step.template,
+ selector: step.element
+ }).popover("show");
+ $tip = $element.data("bs.popover") ? $element.data("bs.popover").tip() : $element.data("popover").tip();
+ $tip.attr("id", step.id);
+ this._scrollIntoView($tip);
+ this._reposition($tip, step);
+ if (isOrphan) {
+ return this._center($tip);
+ }
+ };
+
+ Tour.prototype._reposition = function($tip, step) {
+ var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
+ offsetWidth = $tip[0].offsetWidth;
+ offsetHeight = $tip[0].offsetHeight;
+ tipOffset = $tip.offset();
+ originalLeft = tipOffset.left;
+ originalTop = tipOffset.top;
+ offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
+ if (offsetBottom < 0) {
+ tipOffset.top = tipOffset.top + offsetBottom;
+ }
+ offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
+ if (offsetRight < 0) {
+ tipOffset.left = tipOffset.left + offsetRight;
+ }
+ if (tipOffset.top < 0) {
+ tipOffset.top = 0;
+ }
+ if (tipOffset.left < 0) {
+ tipOffset.left = 0;
+ }
+ $tip.offset(tipOffset);
+ if (step.placement === "bottom" || step.placement === "top") {
+ if (originalLeft !== tipOffset.left) {
+ return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, "left");
+ }
+ } else {
+ if (originalTop !== tipOffset.top) {
+ return this._replaceArrow($tip, (tipOffset.top - originalTop) * 2, offsetHeight, "top");
+ }
+ }
+ };
+
+ Tour.prototype._center = function($tip) {
+ return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
+ };
+
+ Tour.prototype._replaceArrow = function($tip, delta, dimension, position) {
+ return $tip.find(".arrow").css(position, delta ? 50 * (1 - delta / dimension) + "%" : "");
+ };
+
+ Tour.prototype._scrollIntoView = function(tip) {
+ return $("html, body").stop().animate({
+ scrollTop: Math.ceil(tip.offset().top - ($(window).height() / 2))
+ });
+ };
+
+ Tour.prototype._onResize = function(callback, timeout) {
+ return $(window).on("resize.tour-" + this._options.name, function() {
+ clearTimeout(timeout);
+ return timeout = setTimeout(callback, 100);
+ });
+ };
+
+ Tour.prototype._setupKeyboardNavigation = function() {
+ var _this = this;
+ if (this._options.keyboard) {
+ return $(document).on("keyup.tour-" + this._options.name, function(e) {
+ if (!e.which) {
+ return;
+ }
+ switch (e.which) {
+ case 39:
+ e.preventDefault();
+ if (_this._current < _this._steps.length - 1) {
+ return _this.next();
+ } else {
+ return _this.end();
+ }
+ break;
+ case 37:
+ e.preventDefault();
+ if (_this._current > 0) {
+ return _this.prev();
+ }
+ break;
+ case 27:
+ e.preventDefault();
+ return _this.end();
+ }
+ });
+ }
+ };
+
+ Tour.prototype._makePromise = function(result) {
+ if (result && $.isFunction(result.then)) {
+ return result;
+ } else {
+ return null;
+ }
+ };
+
+ Tour.prototype._callOnPromiseDone = function(promise, cb, arg) {
+ var _this = this;
+ if (promise) {
+ return promise.then(function(e) {
+ return cb.call(_this, arg);
+ });
+ } else {
+ return cb.call(this, arg);
+ }
+ };
+
+ Tour.prototype._showBackdrop = function(element) {
+ if (this.backdrop.overlay !== null) {
+ return;
+ }
+ this._showOverlay();
+ if (element != null) {
+ return this._showOverlayElement(element);
+ }
+ };
+
+ Tour.prototype._hideBackdrop = function() {
+ if (this.backdrop.overlay === null) {
+ return;
+ }
+ if (this.backdrop.$element) {
+ this._hideOverlayElement();
+ }
+ return this._hideOverlay();
+ };
+
+ Tour.prototype._showOverlay = function() {
+ this.backdrop = $("
", {
+ "class": "tour-backdrop"
+ });
+ return $("body").append(this.backdrop);
+ };
+
+ Tour.prototype._hideOverlay = function() {
+ this.backdrop.remove();
+ return this.backdrop.overlay = null;
+ };
+
+ Tour.prototype._showOverlayElement = function(element) {
+ var $background, $element, offset;
+ $element = $(element);
+ $background = $("
");
+ offset = $element.offset();
+ offset.top = offset.top;
+ offset.left = offset.left;
+ $background.width($element.innerWidth()).height($element.innerHeight()).addClass("tour-step-background").offset(offset);
+ $element.addClass("tour-step-backdrop");
+ $("body").append($background);
+ this.backdrop.$element = $element;
+ return this.backdrop.$background = $background;
+ };
+
+ Tour.prototype._hideOverlayElement = function() {
+ this.backdrop.$element.removeClass("tour-step-backdrop");
+ this.backdrop.$background.remove();
+ this.backdrop.$element = null;
+ return this.backdrop.$background = null;
+ };
+
+ return Tour;
+
+ })();
+ return window.Tour = Tour;
+ })(jQuery, window);
+
+}).call(this);
diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js
new file mode 100644
index 00000000000..f218572120b
--- /dev/null
+++ b/addons/website/static/src/js/website.tour.js
@@ -0,0 +1,127 @@
+(function () {
+ 'use strict';
+
+ var website = openerp.website;
+ website.templates.push('/website/static/src/xml/website.tour.xml');
+
+ var tourStorage = window.sessionStorage;
+
+ website.EditorTour = openerp.Class.extend({
+ observers: [],
+ start: function () {
+ var self = this;
+ tourStorage.removeItem("tour_current_step");
+ tourStorage.removeItem("tour_end");
+ var tour = new Tour({
+ storage: tourStorage,
+ });
+ tour.addSteps([
+ {
+ element: "button[data-action=edit]",
+ template: openerp.qweb.render("website.tour_simple"),
+ backdrop: true,
+ title: "Switch to edit mode",
+ content: "Press the 'Edit' button to start modifying the page",
+ },
+ {
+ element: "#oe_rte_toolbar",
+ template: openerp.qweb.render("website.tour_confirm"),
+ placement: "bottom",
+ title: "Edit the page inline",
+ content: "Use rich text editing to make inline modifications to your page",
+ },
+ {
+ element: "button[data-action=snippet]",
+ template: openerp.qweb.render("website.tour_simple"),
+ backdrop: true,
+ reflex: true,
+ title: "Add a snippet to your page",
+ content: "Click on the 'Insert Blocks' button to open the snippet collection",
+ },
+ {
+ element: "[data-snippet-id=title]",
+ template: openerp.qweb.render("website.tour_simple"),
+ placement: "right",
+ title: "Choose a title for the page",
+ content: "Drag the 'Title' block to the body of the page and drop it on a purple zone",
+ onShown: function () {
+ $('[data-snippet-id=title]').on('mousedown', function () {
+ function goToNextStep () {
+ self.toEditTitle();
+ $('body').off('mouseup', goToNextStep);
+ }
+ $('body').on('mouseup', goToNextStep);
+ });
+ }
+ },
+ {
+ element: "#wrap [data-snippet-id=title]",
+ template: openerp.qweb.render("website.tour_confirm"),
+ placement: "bottom",
+ backdrop: true,
+ title: "Modify the title",
+ content: "Click on the title and modify it to fit your needs",
+ },
+ {
+ element: "button[data-action=save]",
+ template: openerp.qweb.render("website.tour_simple"),
+ backdrop: true,
+ title: "Save your modifications",
+ content: "Click on the 'Save' button to persist your changes to the CMS database",
+ reflex: true,
+ onNext: function () {
+ self.stop();
+ }
+ },
+ ]);
+ tour.start(true);
+ self.tour = tour;
+ self.tourStorage = tourStorage;
+ },
+ toBuildingBlocks: function () {
+ $('#step-0').remove();
+ this.tour.goto(1);
+ },
+ toEditTitle: function () {
+ $('#step-3').remove();
+ this.tour.goto(4);
+ },
+ onDomMutation: function (selector, callback) {
+ var target = document.querySelector(selector);
+ var observer = new MutationObserver(function(mutations) {
+ _.each(mutations, function(mutation) {
+ callback(mutation)
+ });
+ });
+ var config = {
+ attributes: true,
+ childList: true,
+ characterData: true,
+ };
+ observer.observe(target, config);
+ this.observers.push(observer);
+ },
+ stop: function () {
+ this.tour.end();
+ _.each(this.observers, function (observer) {
+ observer.disconnect();
+ });
+ tourStorage.setItem("tour_current_step", 0);
+ tourStorage.setItem("tour_end", "yes");
+ },
+ });
+
+ website.EditorBar.include({
+ start: function () {
+ if (window.location.href.indexOf("tour=true") >= 0 && tourStorage.getItem("tour_end") !== "yes") {
+ var editorTour = new website.EditorTour();
+ editorTour.start();
+ this.on('rte:ready', this, function () {
+ editorTour.toBuildingBlocks()
+ });
+ }
+ return this._super();
+ },
+ });
+
+}());
\ No newline at end of file
diff --git a/addons/website/static/src/xml/website.tour.xml b/addons/website/static/src/xml/website.tour.xml
new file mode 100644
index 00000000000..13219b471ed
--- /dev/null
+++ b/addons/website/static/src/xml/website.tour.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file