diff --git a/addons/account_accountant/i18n/es_AR.po b/addons/account_accountant/i18n/es_AR.po new file mode 100644 index 00000000000..0e2684b0431 --- /dev/null +++ b/addons/account_accountant/i18n/es_AR.po @@ -0,0 +1,23 @@ +# Spanish (Argentina) translation for openobject-addons +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:04+0000\n" +"PO-Revision-Date: 2013-10-07 21:16+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Spanish (Argentina) \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-10-08 05:42+0000\n" +"X-Generator: Launchpad (build 16799)\n" + +#. module: account_accountant +#: model:ir.actions.client,name:account_accountant.action_client_account_menu +msgid "Open Accounting Menu" +msgstr "" diff --git a/addons/account_cancel/i18n/et.po b/addons/account_cancel/i18n/et.po new file mode 100644 index 00000000000..440850791a7 --- /dev/null +++ b/addons/account_cancel/i18n/et.po @@ -0,0 +1,23 @@ +# Estonian translation for openobject-addons +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:05+0000\n" +"PO-Revision-Date: 2013-10-09 14:39+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Estonian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n" +"X-Generator: Launchpad (build 16799)\n" + +#. module: account_cancel +#: view:account.invoice:0 +msgid "Cancel" +msgstr "" diff --git a/addons/auth_crypt/i18n/et.po b/addons/auth_crypt/i18n/et.po index a634e17faa5..cbe529a053d 100644 --- a/addons/auth_crypt/i18n/et.po +++ b/addons/auth_crypt/i18n/et.po @@ -1,30 +1,28 @@ # Estonian translation for openobject-addons -# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011 +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 # This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2011. +# FIRST AUTHOR , 2013. # msgid "" msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2012-12-03 16:03+0000\n" -"PO-Revision-Date: 2011-10-10 19:33+0000\n" -"Last-Translator: Aare Vesi \n" +"POT-Creation-Date: 2012-12-21 17:05+0000\n" +"PO-Revision-Date: 2013-10-09 14:40+0000\n" +"Last-Translator: FULL NAME \n" "Language-Team: Estonian \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-12-04 05:53+0000\n" -"X-Generator: Launchpad (build 16335)\n" +"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n" +"X-Generator: Launchpad (build 16799)\n" -#. module: base_crypt -#: model:ir.model,name:base_crypt.model_res_users +#. module: auth_crypt +#: field:res.users,password_crypt:0 +msgid "Encrypted Password" +msgstr "Krüpteeritud Parool" + +#. module: auth_crypt +#: model:ir.model,name:auth_crypt.model_res_users msgid "Users" -msgstr "" - -#, python-format -#~ msgid "Error" -#~ msgstr "Viga" - -#~ msgid "res.users" -#~ msgstr "res.users" +msgstr "Kasutajad" diff --git a/addons/auth_oauth_signup/i18n/et.po b/addons/auth_oauth_signup/i18n/et.po new file mode 100644 index 00000000000..2d159250cff --- /dev/null +++ b/addons/auth_oauth_signup/i18n/et.po @@ -0,0 +1,23 @@ +# Estonian translation for openobject-addons +# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:05+0000\n" +"PO-Revision-Date: 2013-10-09 14:34+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Estonian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n" +"X-Generator: Launchpad (build 16799)\n" + +#. module: auth_oauth_signup +#: model:ir.model,name:auth_oauth_signup.model_res_users +msgid "Users" +msgstr "Kasutajad" diff --git a/addons/base_geolocalize/__init__.py b/addons/base_geolocalize/__init__.py new file mode 100644 index 00000000000..9afb820ae15 --- /dev/null +++ b/addons/base_geolocalize/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2013_Today OpenERP SA (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import models diff --git a/addons/base_geolocalize/__openerp__.py b/addons/base_geolocalize/__openerp__.py new file mode 100644 index 00000000000..c5e08c08e98 --- /dev/null +++ b/addons/base_geolocalize/__openerp__.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2013_Today OpenERP SA (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'Partners Geo-Localization', + 'version': '1.0', + 'category': 'Customer Relationship Management', + 'description': """ +Partners geolocalization +======================== + """, + 'author': 'OpenERP SA', + 'depends': ['crm'], + 'demo': [ + # 'res_partner_demo.xml', + ], + 'data': [ + 'views/res_partner_view.xml', + ], + 'test': [], + 'installable': True, + 'auto_install': False, + 'images': [], +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/base_geolocalize/models/__init__.py b/addons/base_geolocalize/models/__init__.py new file mode 100644 index 00000000000..4e5bc1614d1 --- /dev/null +++ b/addons/base_geolocalize/models/__init__.py @@ -0,0 +1 @@ +import res_partner diff --git a/addons/base_geolocalize/models/res_partner.py b/addons/base_geolocalize/models/res_partner.py new file mode 100644 index 00000000000..af43b990526 --- /dev/null +++ b/addons/base_geolocalize/models/res_partner.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2013_Today OpenERP SA (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +try: + import simplejson as json +except ImportError: + import json # noqa +import urllib + +from openerp.osv import osv, fields +from openerp import tools +from openerp.tools.translate import _ + + +def geo_find(addr): + url = 'https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=' + url += urllib.quote(addr.encode('utf8')) + + try: + result = json.load(urllib.urlopen(url)) + except Exception, e: + raise osv.except_osv(_('Network error'), + _('Cannot contact geolocation servers. Please make sure that your internet connection is up and running (%s).') % e) + if result['status'] != 'OK': + return None + + try: + geo = result['results'][0]['geometry']['location'] + return float(geo['lat']), float(geo['lng']) + except (KeyError, ValueError): + return None + + +def geo_query_address(street=None, zip=None, city=None, state=None, country=None): + if country and ',' in country and (country.endswith(' of') or country.endswith(' of the')): + # put country qualifier in front, otherwise GMap gives wrong results, + # e.g. 'Congo, Democratic Republic of the' => 'Democratic Republic of the Congo' + country = '{1} {0}'.format(*country.split(',', 1)) + return tools.ustr(', '.join(filter(None, [street, + ("%s %s" % (zip or '', city or '')).strip(), + state, + country]))) + + +class res_partner(osv.osv): + _inherit = "res.partner" + + _columns = { + 'partner_latitude': fields.float('Geo Latitude'), + 'partner_longitude': fields.float('Geo Longitude'), + 'date_localization': fields.date('Geo Localization Date'), + } + + def geo_localize(self, cr, uid, ids, context=None): + # Don't pass context to browse()! We need country names in english below + for partner in self.browse(cr, uid, ids): + if not partner: + continue + result = geo_find(geo_query_address(street=partner.street, + zip=partner.zip, + city=partner.city, + state=partner.state_id.name, + country=partner.country_id.name)) + if result: + self.write(cr, uid, [partner.id], { + 'partner_latitude': result[0], + 'partner_longitude': result[1], + 'date_localization': fields.date.context_today(self, cr, uid, context=context) + }, context=context) + return True diff --git a/addons/base_geolocalize/views/res_partner_view.xml b/addons/base_geolocalize/views/res_partner_view.xml new file mode 100644 index 00000000000..24cc8d2d21d --- /dev/null +++ b/addons/base_geolocalize/views/res_partner_view.xml @@ -0,0 +1,30 @@ + + + + + + res.partner.geo.inherit + res.partner + + + + + + + ", + afterSetState: function(key, value) {}, + afterGetState: function(key, value) {}, + afterRemoveState: function(key) {}, + onStart: function(tour) {}, + onEnd: function(tour) {}, + onShow: function(tour) {}, + onShown: function(tour) {}, + onHide: function(tour) {}, + onHidden: function(tour) {}, + onNext: function(tour) {}, + onPrev: function(tour) {} + }, options); + this._steps = []; + this.setCurrentStep(); + this.backdrop = { + overlay: null, + $element: null, + $background: null + }; + } + + Tour.prototype.setState = function(key, value) { + var keyName; + if (this._options.storage) { + keyName = "" + this._options.name + "_" + key; + this._options.storage.setItem(keyName, value); + return this._options.afterSetState(keyName, value); + } else { + if (this._state == null) { + this._state = {}; + } + return this._state[key] = value; + } + }; + + Tour.prototype.removeState = function(key) { + var keyName; + if (this._options.storage) { + keyName = "" + this._options.name + "_" + key; + this._options.storage.removeItem(keyName); + return this._options.afterRemoveState(keyName); + } else { + if (this._state != null) { + return delete this._state[key]; + } + } + }; + + Tour.prototype.getState = function(key) { + var keyName, value; + if (this._options.storage) { + keyName = "" + this._options.name + "_" + key; + value = this._options.storage.getItem(keyName); + } else { + if (this._state != null) { + value = this._state[key]; + } + } + if (value === void 0 || value === "null") { + value = null; + } + this._options.afterGetState(key, value); + return value; + }; + + Tour.prototype.addSteps = function(steps) { + var step, _i, _len, _results; + _results = []; + for (_i = 0, _len = steps.length; _i < _len; _i++) { + step = steps[_i]; + _results.push(this.addStep(step)); + } + return _results; + }; + + Tour.prototype.addStep = function(step) { + return this._steps.push(step); + }; + + Tour.prototype.getStep = function(i) { + if (this._steps[i] != null) { + return $.extend({ + id: "step-" + i, + path: "", + placement: "right", + title: "", + content: "

", + next: i === this._steps.length - 1 ? -1 : i + 1, + prev: i - 1, + animation: true, + container: this._options.container, + backdrop: this._options.backdrop, + redirect: this._options.redirect, + orphan: this._options.orphan, + template: this._options.template, + onShow: this._options.onShow, + onShown: this._options.onShown, + onHide: this._options.onHide, + onHidden: this._options.onHidden, + onNext: this._options.onNext, + onPrev: this._options.onPrev + }, this._steps[i]); + } + }; + + Tour.prototype.start = function(force) { + var promise, + _this = this; + if (force == null) { + force = false; + } + if (this.ended() && !force) { + return this._debug("Tour ended, start prevented."); + } + $(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=next]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=next]:not(.disabled)", function(e) { + e.preventDefault(); + return _this.next(); + }); + $(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=prev]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=prev]:not(.disabled)", function(e) { + e.preventDefault(); + return _this.prev(); + }); + $(document).off("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=end]").on("click.tour-" + this._options.name, ".popover.tour-" + this._options.name + " *[data-role=end]", function(e) { + e.preventDefault(); + return _this.end(); + }); + this._onResize(function() { + return _this.showStep(_this._current); + }); + this._setupKeyboardNavigation(); + promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0); + return this._callOnPromiseDone(promise, this.showStep, this._current); + }; + + Tour.prototype.next = function() { + var promise; + if (this.ended()) { + return this._debug("Tour ended, next prevented."); + } + promise = this.hideStep(this._current); + return this._callOnPromiseDone(promise, this._showNextStep); + }; + + Tour.prototype.prev = function() { + var promise; + if (this.ended()) { + return this._debug("Tour ended, prev prevented."); + } + promise = this.hideStep(this._current); + return this._callOnPromiseDone(promise, this._showPrevStep); + }; + + Tour.prototype.goto = function(i) { + var promise; + if (this.ended()) { + return this._debug("Tour ended, goto prevented."); + } + promise = this.hideStep(this._current); + return this._callOnPromiseDone(promise, this.showStep, i); + }; + + Tour.prototype.end = function() { + var endHelper, hidePromise, + _this = this; + endHelper = function(e) { + $(document).off("click.tour-" + _this._options.name); + $(document).off("keyup.tour-" + _this._options.name); + $(window).off("resize.tour-" + _this._options.name); + _this.setState("end", "yes"); + if (_this._options.onEnd != null) { + return _this._options.onEnd(_this); + } + }; + hidePromise = this.hideStep(this._current); + return this._callOnPromiseDone(hidePromise, endHelper); + }; + + Tour.prototype.ended = function() { + return !!this.getState("end"); + }; + + Tour.prototype.restart = function() { + this.removeState("current_step"); + this.removeState("end"); + this.setCurrentStep(0); + return this.start(); + }; + + Tour.prototype.hideStep = function(i) { + var hideStepHelper, promise, step, + _this = this; + step = this.getStep(i); + promise = this._makePromise(step.onHide != null ? step.onHide(this, i) : void 0); + hideStepHelper = function(e) { + var $element; + $element = _this._isOrphan(step) ? $("body") : $(step.element); + $element.popover("destroy"); + if (step.reflex) { + $element.css("cursor", "").off("click.tour-" + _this._options.name); + } + if (step.backdrop) { + _this._hideBackdrop(); + } + if (step.onHidden != null) { + return step.onHidden(_this); + } + }; + this._callOnPromiseDone(promise, hideStepHelper); + return promise; + }; + + Tour.prototype.showStep = function(i) { + var promise, showStepHelper, skipToPrevious, step, + _this = this; + step = this.getStep(i); + if (!step) { + return; + } + skipToPrevious = i < this._current; + promise = this._makePromise(step.onShow != null ? step.onShow(this, i) : void 0); + showStepHelper = function(e) { + var current_path, path; + _this.setCurrentStep(i); + path = $.isFunction(step.path) ? step.path.call() : _this._options.basePath + step.path; + current_path = [document.location.pathname, document.location.hash].join(""); + if (_this._isRedirect(path, current_path)) { + _this._redirect(step, path); + return; + } + if (_this._isOrphan(step)) { + if (!step.orphan) { + _this._debug("Skip the orphan step " + (_this._current + 1) + ". Orphan option is false and the element doesn't exist or is hidden."); + if (skipToPrevious) { + _this._showPrevStep(); + } else { + _this._showNextStep(); + } + return; + } + _this._debug("Show the orphan step " + (_this._current + 1) + ". Orphans option is true."); + } + if (step.backdrop) { + _this._showBackdrop(!_this._isOrphan(step) ? step.element : void 0); + } + _this._showPopover(step, i); + if (step.onShown != null) { + step.onShown(_this); + } + return _this._debug("Step " + (_this._current + 1) + " of " + _this._steps.length); + }; + return this._callOnPromiseDone(promise, showStepHelper); + }; + + Tour.prototype.setCurrentStep = function(value) { + if (value != null) { + this._current = value; + return this.setState("current_step", value); + } else { + this._current = this.getState("current_step"); + return this._current = this._current === null ? 0 : parseInt(this._current, 10); + } + }; + + Tour.prototype._showNextStep = function() { + var promise, showNextStepHelper, step, + _this = this; + step = this.getStep(this._current); + showNextStepHelper = function(e) { + return _this.showStep(step.next); + }; + promise = this._makePromise((step.onNext != null ? step.onNext(this) : void 0)); + return this._callOnPromiseDone(promise, showNextStepHelper); + }; + + Tour.prototype._showPrevStep = function() { + var promise, showPrevStepHelper, step, + _this = this; + step = this.getStep(this._current); + showPrevStepHelper = function(e) { + return _this.showStep(step.prev); + }; + promise = this._makePromise((step.onPrev != null ? step.onPrev(this) : void 0)); + return this._callOnPromiseDone(promise, showPrevStepHelper); + }; + + Tour.prototype._debug = function(text) { + if (this._options.debug) { + return window.console.log("Bootstrap Tour '" + this._options.name + "' | " + text); + } + }; + + Tour.prototype._isRedirect = function(path, currentPath) { + return (path != null) && path !== "" && path.replace(/\?.*$/, "").replace(/\/?$/, "") !== currentPath.replace(/\/?$/, ""); + }; + + Tour.prototype._redirect = function(step, path) { + if ($.isFunction(step.redirect)) { + return step.redirect.call(this, path); + } else if (step.redirect === true) { + this._debug("Redirect to " + path); + return document.location.href = path; + } + }; + + Tour.prototype._isOrphan = function(step) { + return (step.element == null) || !$(step.element).length || $(step.element).is(":hidden"); + }; + + Tour.prototype._showPopover = function(step, i) { + var $element, $navigation, $template, $tip, isOrphan, options, + _this = this; + options = $.extend({}, this._options); + $template = $.isFunction(step.template) ? $(step.template(i, step)) : $(step.template); + $navigation = $template.find(".popover-navigation"); + isOrphan = this._isOrphan(step); + if (isOrphan) { + step.element = "body"; + step.placement = "top"; + $template = $template.addClass("orphan"); + } + $element = $(step.element); + $template.addClass("tour-" + this._options.name); + if (step.options) { + $.extend(options, step.options); + } + if (step.reflex) { + $element.css("cursor", "pointer").on("click.tour-" + this._options.name, function(e) { + if (_this._current < _this._steps.length - 1) { + return _this.next(); + } else { + return _this.end(); + } + }); + } + if (step.prev < 0) { + $navigation.find("*[data-role=prev]").addClass("disabled"); + } + if (step.next < 0) { + $navigation.find("*[data-role=next]").addClass("disabled"); + } + step.template = $template.clone().wrap("
").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/lib/nearest/README.md b/addons/website/static/lib/nearest/README.md new file mode 100644 index 00000000000..cc40c5fed38 --- /dev/null +++ b/addons/website/static/lib/nearest/README.md @@ -0,0 +1,22 @@ +jQuery Nearest Element plugin +====== + +**Full documentation is at ** + +**Demo:** + +Method signatures: + + * `$.nearest({x, y}, selector)` - find `$(selector)` closest to x/y point on screen + * `$(elem).nearest(selector)` - find `$(selector)` closest to elem + * `$(elemSet).nearest({x, y})` - filter `$(elemSet)` and return closest to x/y point on screen + +Reverse logic: + + * `$.furthest()` + * `$(elem).furthest()` + +Intersecting/touching: + + * `$.touching()` + * `$(elem).touching()` diff --git a/addons/website/static/lib/nearest/bower.json b/addons/website/static/lib/nearest/bower.json new file mode 100644 index 00000000000..27bad9c8785 --- /dev/null +++ b/addons/website/static/lib/nearest/bower.json @@ -0,0 +1,12 @@ +{ + "name": "jquery-nearest", + "version": "1.2.1", + "main": "jquery.nearest.js", + "ignore": [ + "**/.*", + "test" + ], + "dependencies": { + "jquery": ">=1.4" + } +} \ No newline at end of file diff --git a/addons/website/static/lib/nearest/jquery.nearest.js b/addons/website/static/lib/nearest/jquery.nearest.js new file mode 100644 index 00000000000..7daab9ae209 --- /dev/null +++ b/addons/website/static/lib/nearest/jquery.nearest.js @@ -0,0 +1,223 @@ +/*! + * jQuery Nearest plugin v1.2.1 + * + * Finds elements closest to a single point based on screen location and pixel dimensions + * http://gilmoreorless.github.com/jquery-nearest/ + * Open source under the MIT licence: http://gilmoreorless.mit-license.org/2011/ + * + * Requires jQuery 1.4 or above + * Also supports Ben Alman's "each2" plugin for faster looping (if available) + */ + +/** + * Method signatures: + * + * $.nearest({x, y}, selector) - find $(selector) closest to point + * $(elem).nearest(selector) - find $(selector) closest to elem + * $(elemSet).nearest({x, y}) - filter $(elemSet) and return closest to point + * + * Also: + * $.furthest() + * $(elem).furthest() + * + * $.touching() + * $(elem).touching() + */ +;(function ($, undefined) { + + /** + * Internal method that does the grunt work + * + * @param mixed selector Any valid jQuery selector providing elements to filter + * @param hash options Key/value list of options for matching elements + * @param mixed thisObj (optional) Any valid jQuery selector that represents self + * for the "includeSelf" option + * @return array List of matching elements, can be zero length + */ + var rPerc = /^([\d.]+)%$/; + function nearest(selector, options, thisObj) { + // Normalise selector and dimensions + selector || (selector = 'div'); // I STRONGLY recommend passing in a selector + var $container = $(options.container), + containerOffset = $container.offset() || {left: 0, top: 0}, + containerDims = [ + containerOffset.left + $container.width(), + containerOffset.top + $container.height() + ], + percProps = {x: 0, y: 1, w: 0, h: 1}, + prop, match; + for (prop in percProps) if (percProps.hasOwnProperty(prop)) { + match = rPerc.exec(options[prop]); + if (match) { + options[prop] = containerDims[percProps[prop]] * match[1] / 100; + } + } + + // Get elements and work out x/y points + var $all = $(selector), + cache = [], + furthest = !!options.furthest, + checkX = !!options.checkHoriz, + checkY = !!options.checkVert, + compDist = furthest ? 0 : Infinity, + point1x = parseFloat(options.x) || 0, + point1y = parseFloat(options.y) || 0, + point2x = parseFloat(point1x + options.w) || point1x, + point2y = parseFloat(point1y + options.h) || point1y, + tolerance = options.tolerance || 0, + hasEach2 = !!$.fn.each2, + // Shortcuts to help with compression + min = Math.min, + max = Math.max; + + // Normalise the remaining options + if (!options.includeSelf && thisObj) { + $all = $all.not(thisObj); + } + if (tolerance < 0) { + tolerance = 0; + } + // Loop through all elements and check their positions + $all[hasEach2 ? 'each2' : 'each'](function (i, elem) { + var $this = hasEach2 ? elem : $(this), + off = $this.offset(), + x = off.left, + y = off.top, + w = $this.outerWidth(), + h = $this.outerHeight(), + x2 = x + w, + y2 = y + h, + maxX1 = max(x, point1x), + minX2 = min(x2, point2x), + maxY1 = max(y, point1y), + minY2 = min(y2, point2y), + intersectX = minX2 >= maxX1, + intersectY = minY2 >= maxY1, + distX, distY, distT, isValid; + if ( + // .nearest() / .furthest() + (checkX && checkY) || + // .touching() + (!checkX && !checkY && intersectX && intersectY) || + // .nearest({checkVert: false}) + (checkX && intersectY) || + // .nearest({checkHoriz: false}) + (checkY && intersectX) + ) { + distX = intersectX ? 0 : maxX1 - minX2; + distY = intersectY ? 0 : maxY1 - minY2; + distT = intersectX || intersectY ? + max(distX, distY) : + Math.sqrt(distX * distX + distY * distY); + isValid = furthest ? + distT >= compDist - tolerance : + distT <= compDist + tolerance; + if (isValid) { + compDist = furthest ? + max(compDist, distT) : + min(compDist, distT); + cache.push({ + node: this, + dist: distT + }); + } + } + }); + // Make sure all cached items are within tolerance range + var len = cache.length, + filtered = [], + compMin, compMax, + i, item; + if (len) { + if (furthest) { + compMin = compDist - tolerance; + compMax = compDist; + } else { + compMin = compDist; + compMax = compDist + tolerance; + } + for (i = 0; i < len; i++) { + item = cache[i]; + if (item.dist >= compMin && item.dist <= compMax) { + filtered.push(item.node); + } + } + } + return filtered; + } + + $.each(['nearest', 'furthest', 'touching'], function (i, name) { + + // Internal default options + // Not exposed publicly because they're method-dependent and easily overwritten anyway + var defaults = { + x: 0, // X position of top left corner of point/region + y: 0, // Y position of top left corner of point/region + w: 0, // Width of region + h: 0, // Height of region + tolerance: 1, // Distance tolerance in pixels, mainly to handle fractional pixel rounding bugs + container: document, // Container of objects for calculating %-based dimensions + furthest: name == 'furthest', // Find max distance (true) or min distance (false) + includeSelf: false, // Include 'this' in search results (t/f) - only applies to $(elem).func(selector) syntax + checkHoriz: name != 'touching', // Check variations in X axis (t/f) + checkVert: name != 'touching' // Check variations in Y axis (t/f) + }; + + /** + * $.nearest() / $.furthest() / $.touching() + * + * Utility functions for finding elements near a specific point or region on screen + * + * @param hash point Co-ordinates for the point or region to measure from + * "x" and "y" keys are required, "w" and "h" keys are optional + * @param mixed selector Any valid jQuery selector that provides elements to filter + * @param hash options (optional) Extra filtering options + * Not technically needed as the options could go on the point object, + * but it's good to have a consistent API + * @return jQuery object containing matching elements in selector + */ + $[name] = function (point, selector, options) { + if (!point || point.x === undefined || point.y === undefined) { + return $([]); + } + var opts = $.extend({}, defaults, point, options || {}); + return $(nearest(selector, opts)); + }; + + /** + * SIGNATURE 1: + * $(elem).nearest(selector) / $(elem).furthest(selector) / $(elem).touching(selector) + * + * Finds all elements in selector that are nearest to/furthest from elem + * + * @param mixed selector Any valid jQuery selector that provides elements to filter + * @param hash options (optional) Extra filtering options + * @return jQuery object containing matching elements in selector + * + * SIGNATURE 2: + * $(elemSet).nearest(point) / $(elemSet).furthest(point) / $(elemSet).touching(point) + * + * Filters elemSet to return only the elements nearest to/furthest from point + * Effectively a wrapper for $.nearest(point, elemSet) but with the benefits of method chaining + * + * @param hash point Co-ordinates for the point or region to measure from + * @return jQuery object containing matching elements in elemSet + */ + $.fn[name] = function (selector, options) { + var opts; + if (selector && $.isPlainObject(selector)) { + opts = $.extend({}, defaults, selector, options || {}); + return this.pushStack(nearest(this, opts)); + } + var offset = this.offset(), + dimensions = { + x: offset.left, + y: offset.top, + w: this.outerWidth(), + h: this.outerHeight() + }; + opts = $.extend({}, defaults, dimensions, options || {}); + return this.pushStack(nearest(selector, opts, this)); + }; + }); +})(jQuery); diff --git a/addons/website/static/lib/nearest/jquery.nearest.min.js b/addons/website/static/lib/nearest/jquery.nearest.min.js new file mode 100644 index 00000000000..9bd37ca8733 --- /dev/null +++ b/addons/website/static/lib/nearest/jquery.nearest.min.js @@ -0,0 +1,11 @@ +/*! + * jQuery Nearest plugin v1.2.1 + * + * Finds elements closest to a single point based on screen location and pixel dimensions + * http://gilmoreorless.github.com/jquery-nearest/ + * Open source under the MIT licence: http://gilmoreorless.mit-license.org/2011/ + * + * Requires jQuery 1.4 or above + * Also supports Ben Alman's "each2" plugin for faster looping (if available) + */ +;(function(e,t){function r(t,r,i){t||(t="div");var s=e(r.container),o=s.offset()||{left:0,top:0},u=[o.left+s.width(),o.top+s.height()],a={x:0,y:1,w:0,h:1},f,l;for(f in a)a.hasOwnProperty(f)&&(l=n.exec(r[f]),l&&(r[f]=u[a[f]]*l[1]/100));var c=e(t),h=[],p=!!r.furthest,d=!!r.checkHoriz,v=!!r.checkVert,m=p?0:Infinity,g=parseFloat(r.x)||0,y=parseFloat(r.y)||0,b=parseFloat(g+r.w)||g,w=parseFloat(y+r.h)||y,E=r.tolerance||0,S=!!e.fn.each2,x=Math.min,T=Math.max;!r.includeSelf&&i&&(c=c.not(i)),E<0&&(E=0),c[S?"each2":"each"](function(t,n){var r=S?n:e(this),i=r.offset(),s=i.left,o=i.top,u=r.outerWidth(),a=r.outerHeight(),f=s+u,l=o+a,c=T(s,g),N=x(f,b),C=T(o,y),k=x(l,w),L=N>=c,A=k>=C,O,M,_,D;if(d&&v||!d&&!v&&L&&A||d&&A||v&&L)O=L?0:c-N,M=A?0:C-k,_=L||A?T(O,M):Math.sqrt(O*O+M*M),D=p?_>=m-E:_<=m+E,D&&(m=p?T(m,_):x(m,_),h.push({node:this,dist:_}))});var N=h.length,C=[],k,L,A,O;if(N){p?(k=m-E,L=m):(k=m,L=m+E);for(A=0;A=k&&O.dist<=L&&C.push(O.node)}return C}var n=/^([\d.]+)%$/;e.each(["nearest","furthest","touching"],function(n,i){var s={x:0,y:0,w:0,h:0,tolerance:1,container:document,furthest:i=="furthest",includeSelf:!1,checkHoriz:i!="touching",checkVert:i!="touching"};e[i]=function(n,i,o){if(!n||n.x===t||n.y===t)return e([]);var u=e.extend({},s,n,o||{});return e(r(i,u))},e.fn[i]=function(t,n){var i;if(t&&e.isPlainObject(t))return i=e.extend({},s,t,n||{}),this.pushStack(r(this,i));var o=this.offset(),u={x:o.left,y:o.top,w:this.outerWidth(),h:this.outerHeight()};return i=e.extend({},s,u,n||{}),this.pushStack(r(t,i,this))}})})(jQuery); \ No newline at end of file diff --git a/addons/website/static/lib/nearest/nearest.jquery.json b/addons/website/static/lib/nearest/nearest.jquery.json new file mode 100644 index 00000000000..9c9ee5c05c8 --- /dev/null +++ b/addons/website/static/lib/nearest/nearest.jquery.json @@ -0,0 +1,24 @@ +{ + "name": "nearest", + "title": "jQuery Nearest Element", + "description": "Find the elements in a page that are closest to (or furthest away from) a particular point or element, based on pixel dimensions", + "version": "1.2.1", + "keywords": ["nearest", "furthest", "touching", "pixel", "distance"], + "homepage": "http://gilmoreorless.github.io/jquery-nearest/", + "docs": "http://gilmoreorless.github.io/jquery-nearest/", + "demo": "http://gilmoreorless.github.io/jquery-nearest/demo.html", + "bugs": "https://github.com/gilmoreorless/jquery-nearest/issues", + "author": { + "name": "Gilmore Davidson", + "url": "http://shoehornwithteeth.com/" + }, + "licenses": [ + { + "type": "MIT", + "url": "http://gilmoreorless.mit-license.org/2011/" + } + ], + "dependencies": { + "jquery": ">=1.4" + } +} \ No newline at end of file diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css index 5604c62af75..d0b0b34d180 100644 --- a/addons/website/static/src/css/editor.css +++ b/addons/website/static/src/css/editor.css @@ -1,4 +1,3 @@ -@charset "utf-8"; /* ---- CKEditor Minimal Reset ---- */ .navbar.navbar-inverse .cke_chrome { border: none; @@ -169,7 +168,7 @@ table.editorbar-panel td.selected { position: fixed; left: 0px; right: 0px; - top: 50px; + top: 51px; background: #282828; -webkit-box-shadow: 0px 10px 10px -10px black inset; -moz-box-shadow: 0px 10px 10px -10px black inset; @@ -182,17 +181,21 @@ table.editorbar-panel td.selected { } #oe_snippets .scroll { white-space: nowrap; - overflow-y: auto; + overflow-y: none; } #oe_snippets .nav { - position: fixed; + display: inline-block; + border-bottom: none !important; + vertical-align: middle; + min-width: 120px; z-index: 1; - border: 0; } #oe_snippets .nav > li { - display: inline-block; + display: block; float: none; - font-size: 13px; +} +#oe_snippets .nav > li.active { + background: black !important; } #oe_snippets .nav > li > a { padding: 2px 10px !important; @@ -200,18 +203,19 @@ table.editorbar-panel td.selected { display: block; border: 0; } +#oe_snippets .pill-content { + border: 0; +} #oe_snippets .tab-content { - margin-top: 24px; display: inline-block; white-space: nowrap; + background: black; } #oe_snippets .tab-content > div { background: black; - padding: 5px; } #oe_snippets .tab-content > div label { width: 44px; - height: 100%; color: white; padding-left: 10px; } @@ -231,11 +235,10 @@ table.editorbar-panel td.selected { } .oe_snippet { - display: inline-block; + float: left; vertical-align: top; - width: 100px; - height: 100px; - margin: 1px; + width: 93px; + margin-left: 1px; margin-top: 0px; position: relative; overflow: hidden; @@ -253,14 +256,32 @@ table.editorbar-panel td.selected { color: white; position: relative; } +.oe_snippet .oe_snippet_thumbnail:hover .oe_snippet_thumbnail_img { + -webkit-transform: scale(0.95, 0.95); + -moz-transform: scale(0.95, 0.95); + -ms-transform: scale(0.95, 0.95); + -o-transform: scale(0.95, 0.95); + transform: scale(0.95, 0.95); +} .oe_snippet .oe_snippet_thumbnail .oe_snippet_thumbnail_title { font-size: 12px; display: block; + text-shadow: 0 0 2px black; } .oe_snippet .oe_snippet_thumbnail .oe_snippet_thumbnail_img { + height: 74px; + -webkit-transition: all 150ms linear; + -moz-transition: all 150ms linear; + -o-transition: all 150ms linear; + transition: all 150ms linear; -webkit-box-shadow: inset 0px 0px 0px 3px #333333; -moz-box-shadow: inset 0px 0px 0px 3px #333333; box-shadow: inset 0px 0px 0px 3px #333333; + -webkit-transform: scale(1, 1); + -moz-transform: scale(1, 1); + -ms-transform: scale(1, 1); + -o-transform: scale(1, 1); + transform: scale(1, 1); } .oe_snippet .oe_snippet_thumbnail span, .oe_snippet .oe_snippet_thumbnail div { line-height: 18px; @@ -309,20 +330,21 @@ table.editorbar-panel td.selected { margin: 0px -24px !important; } .oe_drop_zone.oe_insert.oe_overlay { - background: rgba(153, 0, 255, 0.17); border-radius: 3px; + background: rgba(153, 0, 255, 0.5); } .oe_drop_zone, .oe_drop_zone_style { - background: rgba(153, 0, 255, 0.17); - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - -ms-border-radius: 3px; - -o-border-radius: 3px; - border-radius: 3px; + border: none; + background: rgba(153, 0, 255, 0.3); + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + -ms-border-radius: 4px; + -o-border-radius: 4px; + border-radius: 4px; } .oe_drop_zone.oe_hover, .oe_drop_zone_style.oe_hover { - background: rgba(0, 255, 133, 0.22); + background: rgba(0, 255, 133, 0.3); z-index: 1001; } @@ -359,7 +381,7 @@ table.editorbar-panel td.selected { -webkit-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset; -moz-box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset; box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset; - border-color: rgba(153, 0, 255, 0.5); + border-color: rgba(0, 0, 0, 0.5); } .oe_overlay.oe_active .oe_handle { display: block !important; @@ -371,39 +393,43 @@ table.editorbar-panel td.selected { left: 50%; display: block; background: white; - border: solid 1px #9900ff; - -webkit-border-radius: 8px; - -moz-border-radius: 8px; - -ms-border-radius: 8px; - -o-border-radius: 8px; - border-radius: 8px; - width: 16px; - height: 16px; - margin: -8px; + border: solid 1px rgba(0, 0, 0, 0.2); + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + -ms-border-radius: 5px; + -o-border-radius: 5px; + border-radius: 5px; + width: 18px; + height: 18px; + margin: -9px; padding-left: 1px; font-size: 14px; line-height: 14px; - color: #9900ff; + font-family: FontAwesome; + color: rgba(0, 0, 0, 0.5); -webkit-transition: background 100ms linear; -moz-transition: background 100ms linear; -o-transition: background 100ms linear; transition: background 100ms linear; } .oe_overlay.oe_active .oe_handle:hover:before { - background: #9900ff; + background: rgba(0, 0, 0, 0.5); color: white; + -webkit-box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7); + -moz-box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7); + box-shadow: 0 0 5px 3px rgba(255, 255, 255, 0.7); } .oe_overlay.oe_active .oe_handle.e:before { - content: "⇨"; + content: "\F061"; } .oe_overlay.oe_active .oe_handle.s:before { - content: "⇩"; + content: "\F063"; } .oe_overlay.oe_active .oe_handle.w:before { - content: "⇦"; + content: "\F060"; } .oe_overlay.oe_active .oe_handle.n:before { - content: "⇧"; + content: "\F062"; } .oe_overlay .icon.btn { display: inline-block; @@ -568,18 +594,6 @@ table.editorbar-panel td.selected { -o-border-radius: 0.4em; border-radius: 0.4em; } -.oe_seo_configuration .keyword-in-title { - background-color: #5cb85cpointer; - color: white; -} -.oe_seo_configuration .keyword-in-description { - background-color: #428bca; - color: white; -} -.oe_seo_configuration .keyword-in-body { - background-color: #5bc0de; - color: white; -} /* ---- ACE EDITOR ---- */ .oe_ace_view_editor { @@ -636,3 +650,12 @@ table.editorbar-panel td.selected { filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0); opacity: 0; } + +/* ---- EDITOR TOUR ---- */ +div.tour-backdrop { + z-index: 2009; +} + +.popover.tour { + z-index: 2010; +} diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass index 7b2800bd849..51bae3dcf3a 100644 --- a/addons/website/static/src/css/editor.sass +++ b/addons/website/static/src/css/editor.sass @@ -4,6 +4,17 @@ @import "compass/css3/user-interface" @import "compass/css3/transition" +//smartphones, iPhone, portrait 480x320 phones +$smart_phone: 320px +//portrait e-readers (Nook/Kindle), smaller tablets @ 600 or @ 640 wide.*/ +$small-tablet: 481px +//portrait tablets, portrait iPad, landscape e-readers, landscape 800x480 or 854x480 phones +$portrait-tablet: 641px +//tablet, landscape iPad, lo-res laptops ands desktops +$tablet: 961px +//big landscape tablets, laptops, and desktops +$desktop: 1025px + /* ---- CKEditor Minimal Reset ---- */ .navbar.navbar-inverse .cke_chrome @@ -141,7 +152,7 @@ table.editorbar-panel position: fixed left: 0px right: 0px - top: 50px + top: 51px background: rgb(40,40,40) +box-shadow(0px 10px 10px -10px black inset) z-index: 1010 @@ -150,44 +161,47 @@ table.editorbar-panel height: auto .scroll white-space: nowrap - overflow-y: auto + overflow-y: none .nav + display: inline-block + border-bottom: none !important + vertical-align: middle + min-width: 120px > li - display: inline-block + display: block float: none - font-size: 13px + &.active + background: black !important > a padding: 2px 10px !important - width: 100% + width: 100% display: block border: 0 - position: fixed z-index: 1 + .pill-content border: 0 .tab-content - margin-top: 24px display: inline-block white-space: nowrap - > div + background: black + > div background: rgb(0,0,0) - padding: 5px label width: 44px - height: 100% color: #fff padding-left: 10px + div width: 100px text-align: center - @include transform( translate(-39px, 44px) , rotate(-90deg) ) + @include transform( translate(-39px, 44px) , rotate(-90deg) ) @include transform-origin(50% 50%) .oe_snippet - display: inline-block + float: left vertical-align: top - width: 100px - height: 100px - margin: 1px + width: 93px + margin-left: 1px margin-top: 0px position: relative overflow: hidden @@ -201,11 +215,18 @@ table.editorbar-panel background: transparent color: white position: relative + &:hover + .oe_snippet_thumbnail_img + @include transform( scale(.95,.95)) .oe_snippet_thumbnail_title font-size: 12px display: block + +text-shadow(0 0 2px rgb(0,0,0)) .oe_snippet_thumbnail_img + height: 74px + @include transition(all 150ms linear) +box-shadow(inset 0px 0px 0px 3px #333333) + @include transform( scale(1,1)) span, div line-height: 18px & > :not(.oe_snippet_thumbnail) @@ -240,14 +261,19 @@ table.editorbar-panel position: relative margin: 0px -24px !important &.oe_overlay - background: rgba(153, 0, 255, 0.17) border-radius: 3px - + //@include background-image( repeating-linear-gradient(45deg, rgba(255,255,255,.1) ,rgba(255,255,255,.1) 35px, rgba(0,0,0,.1) 35px, rgba(0,0,0,.1) 75px)) + //background-size: 100px 100px + background: rgba(153, 0, 255,.5) + .oe_drop_zone, .oe_drop_zone_style - background: rgba(153, 0, 255, 0.17) - +border-radius(3px) + border: none + //@include background-image( repeating-linear-gradient(45deg, rgba(255,255,255,.1) ,rgba(255,255,255,.1) 35px, rgba(0,0,0,.1) 35px, rgba(0,0,0,.1) 75px)) + //background-size: 100px 100px + background: rgba(153, 0, 255, .3) + +border-radius(4px) &.oe_hover - background: rgba(0, 255, 133, 0.22) + background: rgba(0, 255, 133, .3) z-index: 1001 .oe_drop_zone_style @@ -262,6 +288,7 @@ table.editorbar-panel display: none position: absolute background: transparent + //@include background-image( repeating-linear-gradient(45deg, rgba(255,255,255,.02) ,rgba(255,255,255,.02) 35px, rgba(0,0,0,.02) 35px, rgba(0,0,0,.02) 75px)) +border-radius(3px) @include transition(opacity 100ms linear) +box-sizing(border-box) @@ -271,7 +298,7 @@ table.editorbar-panel border-style: dashed border-width: 1px +box-shadow(0px 0px 0px 1px rgba(255,255,255,0.3), 0px 0px 0px 1px rgba(255,255,255,0.3) inset) - border-color: rgba(153, 0, 255, 0.5) + border-color: rgba(0, 0, 0, 0.5) .oe_handle display: block !important pointer-events: auto @@ -281,27 +308,29 @@ table.editorbar-panel left: 50% display: block background: rgba(255, 255, 255, 1) - border: solid 1px rgba(153, 0, 255, 1) - +border-radius(8px) - width: 16px - height: 16px - margin: -8px + border: solid 1px rgba(0, 0, 0, .2) + +border-radius(5px) + width: 18px + height: 18px + margin: -9px padding-left: 1px font-size: 14px line-height: 14px - color: rgba(153, 0, 255, 1) + font-family: FontAwesome + color: rgba(0,0,0,.5) @include transition(background 100ms linear) &:hover:before - background: rgba(153, 0, 255, 1) + background: rgba(0, 0, 0, .5) color: #fff + +box-shadow(0 0 5px 3px rgba(255,255,255,.7)) .oe_handle.e:before - content: "⇨" + content: "\F061" .oe_handle.s:before - content: "⇩" + content: "\F063" .oe_handle.w:before - content: "⇦" + content: "\F060" .oe_handle.n:before - content: "⇧" + content: "\F062" .icon.btn display: inline-block @@ -436,10 +465,6 @@ $ace_width: 720px $editorbar_height: 40px // TODO Fix => might break with themes $navbar_height: 51px -@mixin pointer-events($type: none) - $type: unquote($type) - @include experimental(pointer-events, $type,-moz, -webkit, not -o, not -ms, -khtml, official) - @mixin editor-font font-size: 14px @@ -481,4 +506,10 @@ $navbar_height: 51px &.oe_ace_closed width: 0 +opacity(0) + +/* ---- EDITOR TOUR ---- */ +div.tour-backdrop + z-index: 2009 +.popover.tour + z-index: 2010 // vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker: diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css index 6af2e7485fe..ce87e592136 100644 --- a/addons/website/static/src/css/website.css +++ b/addons/website/static/src/css/website.css @@ -223,11 +223,15 @@ footer { } /* ---- HACK FOR COVERING UP CK EDITOR BOGUS P INSERTION --- */ -.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child { +.oe_structure.oe_editable.oe_empty:empty, .oe_editable[data-oe-type=html]:empty, .oe_structure.oe_editable.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child { background-image: url("/website/static/src/img/drag_here.png"); +} + +.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child { + background-image: url("/website/static/src/img/edit_here.png"); background-repeat: no-repeat; - background-position: center top; - height: 200px !important; + background-position: center; + height: 220px !important; } .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child { @@ -238,8 +242,8 @@ footer { content: "Click Edit To Create Content"; text-align: center; display: block; - padding-top: 100px; - padding-bottom: 50px; + padding-top: 160px; + padding-bottom: 30px; color: grey; font-size: 24px; } @@ -345,6 +349,17 @@ footer { } /* Parallax Theme */ +.parallax_quote { + background: url("/website/static/src/img/parallax/quote.png") center center no-repeat fixed; + background-size: contain; +} +.parallax_quote .carousel-indicators li { + border: 1px solid grey; +} +.parallax_quote .carousel-indicators .active { + background-color: grey; +} + .parallax_full { background: #eeeeee bottom center repeat fixed; min-height: 650px; diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass index 997b45a0253..5197653429c 100644 --- a/addons/website/static/src/css/website.sass +++ b/addons/website/static/src/css/website.sass @@ -151,11 +151,14 @@ footer /* ---- HACK FOR COVERING UP CK EDITOR BOGUS P INSERTION --- */ -.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child +.oe_structure.oe_editable.oe_empty:empty, .oe_editable[data-oe-type=html]:empty, .oe_structure.oe_editable.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child background-image: url('/website/static/src/img/drag_here.png') + +.oe_structure.oe_empty:empty, [data-oe-type=html]:empty, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child + background-image: url('/website/static/src/img/edit_here.png') background-repeat: no-repeat - background-position: center top - height: 200px !important + background-position: center + height: 220px !important .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child position: static @@ -164,8 +167,8 @@ footer content: 'Click Edit To Create Content' text-align: center display: block - padding-top: 100px - padding-bottom: 50px + padding-top: 160px + padding-bottom: 30px color: grey font-size: 24px @@ -263,6 +266,15 @@ footer /* Parallax Theme */ +.parallax_quote + background: url('/website/static/src/img/parallax/quote.png') center center no-repeat fixed + background-size: contain + .carousel-indicators + li + border: 1px solid grey + .active + background-color: grey + .parallax_full background: #eee bottom center repeat fixed min-height: 650px diff --git a/addons/website/static/src/img/big_picture.png b/addons/website/static/src/img/big_picture.png index cecb39679d5..519e713ff7f 100644 Binary files a/addons/website/static/src/img/big_picture.png and b/addons/website/static/src/img/big_picture.png differ diff --git a/addons/website/static/src/img/drag_here.png b/addons/website/static/src/img/drag_here.png index f2e3d38b2df..0dfeb1bc5da 100644 Binary files a/addons/website/static/src/img/drag_here.png and b/addons/website/static/src/img/drag_here.png differ diff --git a/addons/website/static/src/img/edit_here.png b/addons/website/static/src/img/edit_here.png new file mode 100644 index 00000000000..b53f56e2ac8 Binary files /dev/null and b/addons/website/static/src/img/edit_here.png differ diff --git a/addons/website/static/src/img/parallax/quote.png b/addons/website/static/src/img/parallax/quote.png new file mode 100644 index 00000000000..769cb6b5e0f Binary files /dev/null and b/addons/website/static/src/img/parallax/quote.png differ diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 30514b25780..ffb0f9b86a3 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -3,13 +3,12 @@ var website = openerp.website; // $.fn.data automatically parses value, '0'|'1' -> 0|1 - website.is_editable = $(document.documentElement).data('editable'); website.templates.push('/website/static/src/xml/website.editor.xml'); website.dom_ready.done(function () { var is_smartphone = $(document.body)[0].clientWidth < 767; - if (website.is_editable && !is_smartphone) { + if (!is_smartphone) { website.ready().then(website.init_editor); } }); @@ -21,16 +20,15 @@ return new website.editor.RTEImageDialog(editor).appendTo(document.body); } - if (website.is_editable) { - // only enable editors manually - CKEDITOR.disableAutoInline = true; - // EDIT ALL THE THINGS - CKEDITOR.dtd.$editable = $.extend( - {}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline); - // Disable removal of empty elements on CKEDITOR activation. Empty - // elements are used for e.g. support of FontAwesome icons - CKEDITOR.dtd.$removeEmpty = {}; - } + // only enable editors manually + CKEDITOR.disableAutoInline = true; + // EDIT ALL THE THINGS + CKEDITOR.dtd.$editable = $.extend( + {}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline); + // Disable removal of empty elements on CKEDITOR activation. Empty + // elements are used for e.g. support of FontAwesome icons + CKEDITOR.dtd.$removeEmpty = {}; + website.init_editor = function () { CKEDITOR.plugins.add('customdialogs', { // requires: 'link,image', @@ -211,8 +209,9 @@ } }); // Adding Static Menus - menu.append('
  • Change Theme
  • '); + menu.append('
  • Change Theme
  • '); menu.append('
  • Advanced view editor
  • '); + self.trigger('rte:customize_menu_ready'); } ); }); @@ -616,7 +615,7 @@ } else if ($e.hasClass('pages')) { // Create the page, get the URL back done = $.get(_.str.sprintf( - '/pagenew/%s?noredirect', encodeURIComponent(val))) + '/pagenew/%s?noredirect', encodeURI(val))) .then(function (response) { self.make_link(response, false, val); }); @@ -813,6 +812,7 @@ var element, editor = this.editor; if (!(element = this.element)) { element = editor.document.createElement('img'); + element.addClass('img'); // focus event handler interactions between bootstrap (modal) // and ckeditor (RTE) lead to blowing the stack in Safari and // Chrome (but not FF) when this is done synchronously => diff --git a/addons/website/static/src/js/website.seo.js b/addons/website/static/src/js/website.seo.js index 635b80bb192..945b0ed353d 100644 --- a/addons/website/static/src/js/website.seo.js +++ b/addons/website/static/src/js/website.seo.js @@ -1,6 +1,6 @@ (function () { 'use strict'; - + var website = openerp.website; website.templates.push('/website/static/src/xml/website.seo.xml'); diff --git a/addons/website/static/src/js/website.snippets.js b/addons/website/static/src/js/website.snippets.js index d85b2a814eb..96f153bf86e 100644 --- a/addons/website/static/src/js/website.snippets.js +++ b/addons/website/static/src/js/website.snippets.js @@ -243,12 +243,10 @@ // activate drag and drop for the snippets in the snippet toolbar make_snippet_draggable: function($snippets){ var self = this; - var $toInsert = false; var $tumb = $snippets.find(".oe_snippet_thumbnail:first"); var left = $tumb.outerWidth()/2; var top = $tumb.outerHeight()/2; - var dropped = false; - var $snippet = false; + var $toInsert, dropped, $snippet, action, snipped_id; $snippets.draggable({ greedy: true, @@ -265,8 +263,8 @@ self.hide(); dropped = false; $snippet = $(this); - var snipped_id = $snippet.data('snippet-id'); - var action = $snippet.find('.oe_snippet_body').size() ? 'insert' : 'mutate'; + snipped_id = $snippet.data('snippet-id'); + action = $snippet.find('.oe_snippet_body').size() ? 'insert' : 'mutate'; if( action === 'insert'){ if (!$snippet.data('selector-siblings') && !$snippet.data('selector-children') && !$snippet.data('selector-vertical-children')) { console.debug($snippet.data("snippet-id") + " have oe_snippet_body class and have not for insert action"+ @@ -312,38 +310,46 @@ }, drop: function(){ dropped = true; - - var $target = false; - if(action === 'insert'){ - $target = $toInsert; - - if (website.snippet.animationRegistry[snipped_id]) { - new website.snippet.animationRegistry[snipped_id]($target); - } - - self.create_overlay($target); - $target.data("snippet-editor").build_snippet($target); - - } else { - $target = $(this).data('target'); - - self.create_overlay($target); - if (website.snippet.editorRegistry[snipped_id]) { - var snippet = new website.snippet.editorRegistry[snipped_id](self, $target); - snippet.build_snippet($target); - } - } - - $('.oe_drop_zone').remove(); - - setTimeout(function () {self.make_active($target);},0); }, }); }, - stop: function(){ + stop: function(ev, ui){ + if (action === 'insert' && ! dropped) { + var el = $('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first() + if (el) { + el.after($toInsert) + dropped = true; + } + } + $('.oe_drop_zone').droppable('destroy').remove(); - if (!dropped && self.$modal.find('input:not(:checked)').length) { - self.$modal.modal('toggle'); + if (dropped) { + var $target = false; + if(action === 'insert'){ + $target = $toInsert; + + if (website.snippet.animationRegistry[snipped_id]) { + new website.snippet.animationRegistry[snipped_id]($target); + } + + self.create_overlay($target); + $target.data("snippet-editor").build_snippet($target); + + } else { + $target = $(this).data('target'); + + self.create_overlay($target); + if (website.snippet.editorRegistry[snipped_id]) { + var snippet = new website.snippet.editorRegistry[snipped_id](self, $target); + snippet.build_snippet($target); + } + } + setTimeout(function () {self.make_active($target);},0); + } else { + $toInsert.remove(); + if (self.$modal.find('input:not(:checked)').length) { + self.$modal.modal('toggle'); + } } }, }); @@ -488,7 +494,6 @@ $zone.appendTo('#oe_manipulators'); $zone.data('target',$target); $target.data('overlay',$zone); - console.log($target[0], $zone); $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () { self.cover_target($zone, $target); @@ -746,16 +751,8 @@ change_background: function (bg, ul_options) { var self = this; + this.set_options_background(bg, ul_options); var $ul = this.$editor.find(ul_options); - var bg_value = (typeof bg === 'string' ? self.$target.find(bg) : $(bg)).css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); - - // select in ul options - $ul.find("li").removeClass("active"); - var selected = $ul.find('[data-value="' + bg_value + '"], [data-value="' + bg_value.replace(/.*:\/\/[^\/]+/, '') + '"]'); - selected.addClass('active'); - if (!selected.length) { - $ul.find('.oe_custom_bg b').html(bg_value); - } // bind envent on options var $li = $ul.find("li"); @@ -787,6 +784,19 @@ $bg.css("background-image", "url(" + src + ")"); }); }, + + set_options_background: function (bg, ul_options) { + var $ul = this.$editor.find(ul_options); + var bg_value = (typeof bg === 'string' ? this.$target.find(bg) : $(bg)).css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); + + // select in ul options + $ul.find("li").removeClass("active"); + var selected = $ul.find('[data-value="' + bg_value + '"], [data-value="' + bg_value.replace(/.*:\/\/[^\/]+/, '') + '"]'); + selected.addClass('active'); + if (!selected.length) { + $ul.find('.oe_custom_bg b').html(bg_value); + } + }, }); @@ -974,50 +984,95 @@ }); website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.resize.extend({ - build_snippet: function($target) { - var id = "myCarousel" + $("body .carousel").length; - $target.attr("id", id); - $target.find(".carousel-control").attr("href", "#"+id); + build_snippet: function() { + var id = 0; + $("body .carousel").each(function () { + var _id = +$(this).attr("id").replace(/^myCarousel/, ''); + if (id <= _id) { + id = _id + 1; + } + }); + this.$target.attr("id", "myCarousel" + id); + this.$target.find(".carousel-control").attr("href", "#myCarousel" + id); + this.$target.find("[data-target='#myCarousel']").attr("data-target", "#myCarousel" + id); + this.rebind_event(); + }, + onFocus: function () { + this._super(); + this.$target.carousel('pause'); + }, + onBlur: function () { + this._super(); + this.$target.carousel('cycle'); }, start : function () { this._super(); + this.id = this.$target.attr("id"); + + this.$inner = this.$target.find('.carousel-inner'); + this.$indicators = this.$target.find('.carousel-indicators'); + this.$editor.find(".js_add").on('click', _.bind(this.on_add, this)); this.$editor.find(".js_remove").on('click', _.bind(this.on_remove, this)); this.change_background(".item.active", 'ul[name="carousel-background"]'); this.change_style(); - this.set_options_style(); this.change_size(); + this.set_options_style(); + this.$target.carousel(); + + var self = this; + this.$target.on('slide.bs.carousel', function () { + self.set_options_style(); + self.set_options_background(".item.active", 'ul[name="carousel-background"]'); + self.$target.carousel(); + }); + + this.rebind_event(); + }, + // rebind event to active carousel on edit mode + rebind_event: function () { + var self = this; + this.$target.on('click', '.carousel-control', function () { + self.$target.carousel($(this).data('slide')); }); + this.$target.on('click', '.carousel-indicators [data-target]', function () { + self.$target.carousel(+$(this).data('slide-to')); }); }, on_add: function (e) { e.preventDefault(); - this.$target.find('.carousel-control').removeClass("hidden"); - var $inner = this.$target.find('.carousel-inner'); - var cycle = $inner.find('.item').length; - $inner.find('.item.active').clone().removeClass('active').appendTo($inner); - this.$target.carousel(cycle); - this.set_options_background(); - this.set_options_style(); + var cycle = this.$inner.find('.item').length; + var $active = this.$inner.find('.item.active'); + var index = $active.index(); + this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden"); + this.$indicators.append('
  • '); + $active.clone().removeClass('active').insertAfter($active); + this.$target.carousel().carousel(++index); }, on_remove: function (e) { e.preventDefault(); - var $inner = this.$target.find('.carousel-inner'); - var nb = $inner.find('.item').length; - if (nb > 1) { - $inner - .find('.item.active').remove().end() - .find('.item:first').addClass('active'); - this.$target.carousel(0); - this.set_options_style(); - } - if (nb <= 1) { - this.$target.find('.carousel-control').addClass("hidden"); + var self = this; + var new_index = 0; + var cycle = this.$inner.find('.item').length - 1; + var index = this.$inner.find('.item.active').index(); + if (cycle > 0) { + this.$inner.find('.item.active').fadeOut(1000, function () { + $(this).remove(); + self.$indicators.find('[data-target]:last').remove(); + self.$indicators.find("[data-slide-to]").removeClass("active"); + self.$indicators.find("[data-slide-to='" + new_index + "']").addClass("active"); + }); + setTimeout(function () { + new_index = index % cycle; + self.$target.carousel( new_index + 1 ); + }, 500); + } else { + this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden"); } }, set_options_style: function () { var style = false; - var $el = this.$target.find('.carousel-inner .item.active'); + var $el = this.$inner.find('.item.active'); var $ul = this.$editor.find('ul[name="carousel-style"]'); var $li = $ul.find("li"); @@ -1040,12 +1095,12 @@ $(this).addClass("active"); }) .on('mouseover', function (event) { - var $el = self.$target.find('.carousel-inner .item.active'); + var $el = self.$inner.find('.item.active'); $el.removeClass('image_text text_image text_only'); $el.addClass($(event.currentTarget).data("value")); }) .on('mouseout', function (event) { - var $el = self.$target.find('.carousel-inner .item.active'); + var $el = self.$inner.find('.item.active'); $el.removeClass('image_text text_image text_only'); $el.addClass($ul.find('li.active').data("value")); }); @@ -1087,33 +1142,6 @@ }, }); - website.snippet.animationRegistry.surprise = website.snippet.Animation.extend({ - start: function() { - this._super(); - var hue=0; - var beat = false; - var self = this; - self.$target.append(''); - - var a = setInterval(function(){ - self.$target.next().css({'-webkit-filter':'hue-rotate('+hue+'deg)'}); - self.$target.prev().css({'-webkit-filter':'hue-rotate('+(-hue)+'deg)'}); - hue -= 5; - }, 10); - - setTimeout(function(){ - clearInterval(a); - setInterval(function(){ - var filter = 'hue-rotate('+hue+'deg)'+ (beat ? ' invert()' : ''); - $(document.documentElement).css({'-webkit-filter': filter}); hue += 5; - if(hue % 35 === 0){ - beat = !beat; - } - }, 10); - },5000); - } - }); - /* * data-snippet-id automatically setted * Don't need to add data-snippet-id="..." into the views 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..09e222acf64 --- /dev/null +++ b/addons/website/static/src/js/website.tour.js @@ -0,0 +1,256 @@ +(function () { + 'use strict'; + + var website = openerp.website; + website.templates.push('/website/static/src/xml/website.tour.xml'); + + function render (template, dict) { + return openerp.qweb.render(template, dict); + } + + website.EditorTour = openerp.Class.extend({ + tour: undefined, + steps: [], + tourStorage: window.localStorage, + init: function () { + this.tour = new Tour({ + name: this.id, + storage: this.tourStorage, + keyboard: false, + }); + this.tour.addSteps(_.map(this.steps, function (step) { + step.title = render('website.tour_popover_title', { title: step.title }); + return step; + })); + this.monkeyPatchTour(); + }, + monkeyPatchTour: function () { + var self = this; + // showStep should wait for 'element' to appear instead of moving to the next step + self.tour.showStep = function (i) { + var step = self.tour.getStep(i); + return (function proceed () { + if (step.orphan || $(step.element).length > 0) { + return Tour.prototype.showStep.call(self.tour, i); + } else { + setTimeout(proceed, 50); + } + }()); + }; + }, + reset: function () { + this.tourStorage.removeItem(this.id+'_current_step'); + this.tourStorage.removeItem(this.id+'_end'); + this.tour._current = 0; + $('.popover.tour').remove(); + }, + start: function () { + if (this.canResume()) { + this.tour.start(); + } + }, + canResume: function () { + return (this.currentStepIndex() === 0) && !this.tour.ended(); + }, + currentStepIndex: function () { + var index = this.tourStorage.getItem(this.id+'_current_step') || 0; + return parseInt(index, 10); + }, + indexOfStep: function (stepId) { + var index = -1; + _.each(this.steps, function (step, i) { + if (step.stepId === stepId) { + index = i; + } + }); + return index; + }, + movetoStep: function (stepId) { + $('.popover.tour').remove(); + var index = this.indexOfStep(stepId); + if (index > -1) { + this.tour.goto(index); + } + }, + saveStep: function (stepId) { + var index = this.indexOfStep(stepId); + this.tourStorage.setItem(this.id+'_current_step', index); + }, + stop: function () { + this.tour.end(); + }, + }); + + website.EditorBasicTour = website.EditorTour.extend({ + id: 'add_banner_tour', + name: "Insert a banner", + init: function (editor) { + var self = this; + self.steps = [ + { + stepId: 'welcome', + orphan: true, + backdrop: true, + title: "Welcome to your website!", + content: "This tutorial will guide you to build your first page. We will start by adding a banner.", + template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }), + }, + { + stepId: 'edit-page', + element: 'button[data-action=edit]', + placement: 'bottom', + reflex: true, + title: "Edit this page", + content: "Every page of your website can be modified through the Edit button.", + template: render('website.tour_popover'), + }, + { + stepId: 'add-block', + element: 'button[data-action=snippet]', + placement: 'bottom', + title: "Insert building blocks", + content: "To add content in a page, you can insert building blocks.", + template: render('website.tour_popover'), + onShow: function () { + function refreshAddBlockStep () { + self.tour.showStep(self.indexOfStep('add-block')); + editor.off('rte:ready', editor, refreshAddBlockStep); + } + editor.on('rte:ready', editor, refreshAddBlockStep); + $('button[data-action=snippet]').click(function () { + self.movetoStep('drag-banner'); + }); + } + }, + { + stepId: 'drag-banner', + element: '#website-top-navbar [data-snippet-id=carousel]', + placement: 'bottom', + title: "Drag & Drop a Banner", + content: "Drag the Banner block and drop it in your page.

    Tip: release the mouse button when you are in a valid zone, with a preview of the banner.

    ", + template: render('website.tour_popover'), + onShow: function () { + function beginDrag () { + $('.popover.tour').remove(); + $('body').off('mousedown', beginDrag); + function goToNextStep () { + $('#oe_snippets').hide(); + self.movetoStep('edit-title'); + $('body').off('mouseup', goToNextStep); + } + $('body').on('mouseup', goToNextStep); + } + $('body').on('mousedown', beginDrag); + }, + }, + { + stepId: 'edit-title', + element: '#wrap [data-snippet-id=carousel]:first .carousel-caption', + placement: 'left', + title: "Customize banner's text", + content: "Click in the text and start editing it. Click continue once it's done.", + template: render('website.tour_popover', { next: "Continue" }), + onHide: function () { + var $banner = $("#wrap [data-snippet-id=carousel]:first"); + if ($banner.length) { + $banner.click(); + } + }, + }, + { + stepId: 'customize-banner', + element: '.oe_overlay_options .oe_options', + placement: 'left', + title: "Customize the banner", + content: "You can customize components of your page through the Customize menu. Try to change the background of your banner.", + template: render('website.tour_popover', { next: "Continue" }), + onShow: function () { + $('.dropdown-menu [name=carousel-background]').click(function () { + self.movetoStep('save-changes'); + }); + }, + }, + { + stepId: 'save-changes', + element: 'button[data-action=save]', + placement: 'right', + reflex: true, + title: "Save your modifications", + content: "Once you click on save, your website page is updated.", + template: render('website.tour_popover'), + onHide: function () { + self.saveStep('part-2'); + }, + + }, + { + stepId: 'part-2', + orphan: true, + title: "Congratulation!", + content: "Your homepage have been updated. Now, we suggest you to insert others building blocks like texts and images to structure your page.", + template: render('website.tour_popover', { next: "Continue" }), + }, + { + stepId: 'show-tutorials', + element: '#help-menu-button', + placement: 'left', + title: "Help is always available", + content: "But you can always click here if you want more tutorials.", + template: render('website.tour_popover', { end: "Close" }), + }, + ]; + return this._super(); + }, + startOfPart2: function () { + var currentStepIndex = this.currentStepIndex(); + var secondPartIndex = this.indexOfStep('part-2'); + var showTutorialsIndex = this.indexOfStep('show-tutorials'); + return (currentStepIndex === secondPartIndex || currentStepIndex === showTutorialsIndex) && !this.tour.ended(); + }, + canResume: function () { + return this.startOfPart2() || this._super(); + }, + }); + + website.UrlParser = openerp.Class.extend({ + init: function (url) { + var a = document.createElement('a'); + a.href = url; + this.href = a.href; + this.host = a.host; + this.protocol = a.protocol; + this.port = a.port; + this.hostname = a.hostname; + this.pathname = a.pathname; + this.origin = a.origin; + this.search = a.search; + }, + }); + + website.EditorBar.include({ + start: function () { + website.tutorials = { + basic: new website.EditorBasicTour(this), + }; + var menu = $('#help-menu'); + _.each(website.tutorials, function (tutorial) { + var $menuItem = $($.parseHTML('
  • '+tutorial.name+'
  • ')); + $menuItem.click(function () { + tutorial.reset(); + tutorial.start(); + }) + menu.append($menuItem); + }); + var url = new website.UrlParser(window.location.href); + if (url.search.indexOf('?tutorial=true') === 0 || website.tutorials.basic.startOfPart2()) { + website.tutorials.basic.start(); + } + $('.tour-backdrop').click(function (e) { + e.stopImmediatePropagation(); + e.preventDefault(); + }); + return this._super(); + }, + }); + +}()); diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 157d19b56c0..685712d3411 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -56,7 +56,7 @@ + id="link-new" placeholder="Page Name"/>
  • 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..d2f562dec61 --- /dev/null +++ b/addons/website/static/src/xml/website.tour.xml @@ -0,0 +1,27 @@ + + + +
    +
    +

    +
    + + + +
    +
    + + + +
    diff --git a/addons/website/static/src/xml/website.xml b/addons/website/static/src/xml/website.xml index c2f7abe6ed2..b3e4cacd32a 100644 --- a/addons/website/static/src/xml/website.xml +++ b/addons/website/static/src/xml/website.xml @@ -28,6 +28,12 @@
  • +
  • Apps
  • diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml index 1a79d52d5cc..8a768a9fbb9 100644 --- a/addons/website/views/snippets.xml +++ b/addons/website/views/snippets.xml @@ -40,8 +40,7 @@
  • Velour
  • Wood
  • Yellow Green
  • -
  • -
  • Chose your picture
  • +
  • Choose a photo...
  • - @@ -145,7 +148,7 @@
    - Jumbotron + Big Message
    @@ -211,7 +214,7 @@

    Your Website Title

    -

    Ans a great subtitle too

    +

    And a great subtitle too

    @@ -361,15 +364,6 @@
    - -
    -
    -
    - - Surprise! -
    -
    -
    @@ -720,6 +714,76 @@
    +
    +
    + + Quotes Slider +
    +
    + + + +
    +
    + +