diff --git a/addons/event/event_demo.xml b/addons/event/event_demo.xml index a5655cf0cf7..ffc4be3a77c 100644 --- a/addons/event/event_demo.xml +++ b/addons/event/event_demo.xml @@ -155,7 +155,6 @@ -
5-days Technical Training
diff --git a/addons/event_sale/event_demo.xml b/addons/event_sale/event_demo.xml index 7ac1d5f2738..fabaad18ee4 100644 --- a/addons/event_sale/event_demo.xml +++ b/addons/event_sale/event_demo.xml @@ -41,6 +41,10 @@ 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 + + + + Technical Training True diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 0b7c421dbf8..7d132fff7eb 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import base64 import cStringIO import contextlib import hashlib @@ -49,7 +48,19 @@ MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768) class Website(openerp.addons.web.controllers.main.Home): @website.route('/', type='http', auth="public", multilang=True) def index(self, **kw): - return self.page("website.homepage") + # TODO: check if plain SQL is needed + menu = request.registry['website.menu'] + root_domain = [('parent_id', '=', False)] # TODO: multiwebsite ('website_id', '=', request.website.id), + root_id = menu.search(request.cr, request.uid, root_domain, limit=1, context=request.context)[0] + first_menu = menu.search_read( + request.cr, request.uid, [('parent_id', '=', root_id)], ['url'], + limit=1, order='sequence', context=request.context) + if first_menu: + first_menu = first_menu[0]['url'] + if first_menu and first_menu != '/': + return request.redirect(first_menu) + else: + return self.page("website.homepage") @website.route('/pagenew/', type='http', auth="user") def pagenew(self, path, noredirect=NOPE): @@ -124,11 +135,8 @@ class Website(openerp.addons.web.controllers.main.Home): values = { 'path': path, } - try: - html = request.website.render(path, values) - except ValueError: - html = request.website.render('website.404', values) - return html + + return request.website.render(path, values) @website.route('/website/customize_template_toggle', type='json', auth='user') def customize_template_set(self, view_id): diff --git a/addons/website/data/website_data.xml b/addons/website/data/website_data.xml index 854462ca606..d246b3e6fc1 100644 --- a/addons/website/data/website_data.xml +++ b/addons/website/data/website_data.xml @@ -8,6 +8,22 @@ + + Main Menu + + + + Homepage + / + + + + + Contact us + /page/website.contactus + + + public public diff --git a/addons/website/models/website.py b/addons/website/models/website.py index 51efec12979..85420f28a21 100644 --- a/addons/website/models/website.py +++ b/addons/website/models/website.py @@ -1,22 +1,25 @@ # -*- coding: utf-8 -*- import fnmatch import functools +import operator +import logging +import math import simplejson - -import openerp -from openerp.osv import osv, fields -from openerp.addons.web import http -from openerp.addons.web.http import request +import traceback import urllib from urlparse import urljoin -import math -import traceback -from openerp.tools.safe_eval import safe_eval -from openerp.exceptions import AccessError, AccessDenied -import werkzeug -from openerp.addons.base.ir.ir_qweb import QWebException -import logging +import werkzeug +import werkzeug.exceptions +import werkzeug.wrappers + +import openerp +from openerp.exceptions import AccessError, AccessDenied +from openerp.osv import osv, fields +from openerp.tools.safe_eval import safe_eval +from openerp.addons.web import http +from openerp.addons.web.http import request + logger = logging.getLogger(__name__) def route(routes, *route_args, **route_kwargs): @@ -179,8 +182,7 @@ class website(osv.osv): try: view_ref = IMD.get_object_reference(cr, uid, module, xmlid) except ValueError: - logger.error("Website Rendering Error.\n\n%s" % traceback.format_exc()) - return self.render(cr, uid, ids, 'website.404', qweb_context) + return self.error(cr, uid, 404, qweb_context, context=context) if 'main_object' not in qweb_context: try: @@ -197,15 +199,23 @@ class website(osv.osv): logger.error(err) qweb_context['error'] = err[1] logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc()) - return self.render(cr, uid, ids, 'website.401', qweb_context) + return self.error(cr, uid, 401, qweb_context, context=context) except Exception, e: qweb_context['template'] = getattr(e, 'qweb_template', '') node = getattr(e, 'qweb_node', None) qweb_context['node'] = node and node.toxml() qweb_context['expr'] = getattr(e, 'qweb_eval', '') qweb_context['traceback'] = traceback.format_exc() - logger.error("Website Rendering Error.\n%(template)s\n%(expr)s\n%(node)s\n\n%(traceback)s" % qweb_context) - return view.render(cr, uid, 'website.500' if qweb_context['editable'] else 'website.404', qweb_context, context=context) + logger.exception("Website Rendering Error.\n%(template)s\n%(expr)s\n%(node)s" % qweb_context) + return self.error(cr, uid, 500 if qweb_context['editable'] else 404, + qweb_context, context=context) + + def error(self, cr, uid, code, qweb_context, context=None): + View = request.registry['ir.ui.view'] + return werkzeug.wrappers.Response( + View.render(cr, uid, 'website.%d' % code, qweb_context), + status=code, + content_type='text/html;charset=utf-8') def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None): # Compute Pager @@ -352,6 +362,75 @@ class website(osv.osv): html += request.website.render(template, {'object_id': object_id}) return html + def get_menu_tree(self, cr, uid, ids, context=None): + return self.pool['website.menu'].get_tree(cr, uid, ids[0], context=context) + +class website_menu(osv.osv): + _name = "website.menu" + _description = "Website Menu" + _columns = { + 'name': fields.char('Menu', size=64, required=True, translate=True), + 'url': fields.char('Url', required=True, translate=True), + 'new_window': fields.boolean('New Window'), + 'sequence': fields.integer('Sequence'), + # TODO: support multiwebsite once done for ir.ui.views + 'website_id': fields.many2one('website', 'Website'), + 'parent_id': fields.many2one('website.menu', 'Parent Menu', select=True, ondelete="cascade"), + 'parent_left': fields.integer('Parent Left', select=True), + 'parent_right': fields.integer('Parent Right', select=True), + } + _defaults = { + 'url': '', + 'sequence': 0, + } + _order = "parent_left" + + # TODO: ormcache + def get_tree(self, cr, uid, website_id, fields=None, context=None): + if not fields: + fields = ['id', 'name', 'url', 'new_window', 'sequence', 'parent_id'] + root_domain = [('parent_id', '=', False)] # ('website_id', '=', website_id), + root_menu_id = self.search(cr, uid, root_domain, limit=1, context=context)[0] + menu_ids = self.search(cr, uid, [('id', 'child_of', root_menu_id)], 0, False, False, context) + menu_items = self.read(cr, uid, menu_ids, fields, context=context) + menu_items_map = dict((menu_item["id"], menu_item) for menu_item in menu_items) + for menu_item in menu_items: + if menu_item['parent_id']: + parent = menu_item['parent_id'][0] + else: + parent = False + if parent in menu_items_map: + parent_obj = menu_items_map[parent] + parent_obj.setdefault('level', 0) + parent_obj.setdefault('children', []).append(menu_item) + menu_item['level'] = parent_obj['level'] + 1 + + # sort by sequence a tree using parent_id + for menu_item in menu_items: + menu_item.setdefault('children', []).sort( + key=operator.itemgetter('sequence')) + + return menu_items[0] + + def save(self, cr, uid, website_id, data, context=None): + def replace_id(old_id, new_id): + for menu in data['data']: + if menu['id'] == old_id: + menu['id'] = new_id + if menu['parent_id'] == old_id: + menu['parent_id'] = new_id + to_delete = data['to_delete'] + if to_delete: + self.unlink(cr, uid, to_delete, context=context) + for menu in data['data']: + mid = menu['id'] + if isinstance(mid, str): + new_id = self.create(cr, uid, {'name': menu['name']}, context=context) + replace_id(mid, new_id) + for menu in data['data']: + self.write(cr, uid, [menu['id']], menu, context=context) + return True + class ir_attachment(osv.osv): _inherit = "ir.attachment" def _website_url_get(self, cr, uid, ids, name, arg, context=None): @@ -392,6 +471,15 @@ class res_partner(osv.osv): } return urlplus('https://maps.google.be/maps' , params) +class res_company(osv.osv): + _inherit = "res.company" + def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None): + partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).parent_id + return partner and partner.google_map_img(zoom, width, height, context=context) or None + def google_map_link(self, cr, uid, ids, zoom=8, context=None): + partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).parent_id + return partner and partner.google_map_link(zoom, context=context) or None + class base_language_install(osv.osv): _inherit = "base.language.install" _columns = { diff --git a/addons/website/security/ir.model.access.csv b/addons/website/security/ir.model.access.csv index 431170e462b..8304e9861ea 100644 --- a/addons/website/security/ir.model.access.csv +++ b/addons/website/security/ir.model.access.csv @@ -1,6 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_website_public,website,website.model_website,base.group_public,1,0,0,0 access_website,website,website.model_website,base.group_user,1,0,0,0 +access_website_menu,access_website_menu,model_website_menu,,1,0,0,0 access_website_converter_test,access_website_converter_test,model_website_converter_test,,1,1,1,1 access_website_converter_test_sub,access_website_converter_test_sub,model_website_converter_test_sub,,1,1,1,1 access_website_qweb,access_website_qweb,model_website_qweb,,0,0,0,0 diff --git a/addons/website/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js b/addons/website/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js new file mode 100644 index 00000000000..e6c0d62453a --- /dev/null +++ b/addons/website/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js @@ -0,0 +1,429 @@ +/* + * jQuery UI Nested Sortable + * v 1.3.5 / 21 jun 2012 + * http://mjsarfatti.com/code/nestedSortable + * + * Depends on: + * jquery.ui.sortable.js 1.8+ + * + * Copyright (c) 2010-2012 Manuele J Sarfatti + * Licensed under the MIT License + * http://www.opensource.org/licenses/mit-license.php + */ + +(function($) { + + $.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, { + + options: { + tabSize: 20, + disableNesting: 'mjs-nestedSortable-no-nesting', + errorClass: 'mjs-nestedSortable-error', + doNotClear: false, + listType: 'ol', + maxLevels: 0, + protectRoot: false, + rootID: null, + rtl: false, + isAllowed: function(item, parent) { return true; } + }, + + _create: function() { + this.element.data('sortable', this.element.data('nestedSortable')); + + if (!this.element.is(this.options.listType)) + throw new Error('nestedSortable: Please check the listType option is set to your actual list type'); + + return $.ui.sortable.prototype._create.apply(this, arguments); + }, + + destroy: function() { + this.element + .removeData("nestedSortable") + .unbind(".nestedSortable"); + return $.ui.sortable.prototype.destroy.apply(this, arguments); + }, + + _mouseDrag: function(event) { + + //Compute the helpers position + this.position = this._generatePosition(event); + this.positionAbs = this._convertPositionTo("absolute"); + + if (!this.lastPositionAbs) { + this.lastPositionAbs = this.positionAbs; + } + + var o = this.options; + + //Do scrolling + if(this.options.scroll) { + var scrolled = false; + if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') { + + if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; + else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) + this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; + + if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; + else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) + this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; + + } else { + + if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed); + else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) + scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed); + + if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed); + else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) + scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed); + + } + + if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) + $.ui.ddmanager.prepareOffsets(this, event); + } + + //Regenerate the absolute position used for position checks + this.positionAbs = this._convertPositionTo("absolute"); + + // Find the top offset before rearrangement, + var previousTopOffset = this.placeholder.offset().top; + + //Set the helper position + if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px'; + if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px'; + + //Rearrange + for (var i = this.items.length - 1; i >= 0; i--) { + + //Cache variables and intersection, continue if no intersection + var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item); + if (!intersection) continue; + + if(itemElement != this.currentItem[0] //cannot intersect with itself + && this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before + && !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked + && (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true) + //&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container + ) { + + $(itemElement).mouseenter(); + + this.direction = intersection == 1 ? "down" : "up"; + + if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) { + $(itemElement).mouseleave(); + this._rearrange(event, item); + } else { + break; + } + + // Clear emtpy ul's/ol's + this._clearEmpty(itemElement); + + this._trigger("change", event, this._uiHash()); + break; + } + } + + var parentItem = (this.placeholder[0].parentNode.parentNode && + $(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length) + ? $(this.placeholder[0].parentNode.parentNode) + : null, + level = this._getLevel(this.placeholder), + childLevels = this._getChildLevels(this.helper); + + // To find the previous sibling in the list, keep backtracking until we hit a valid list item. + var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null; + if (previousItem != null) { + while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) { + if (previousItem[0].previousSibling) { + previousItem = $(previousItem[0].previousSibling); + } else { + previousItem = null; + break; + } + } + } + + // To find the next sibling in the list, keep stepping forward until we hit a valid list item. + var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null; + if (nextItem != null) { + while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) { + if (nextItem[0].nextSibling) { + nextItem = $(nextItem[0].nextSibling); + } else { + nextItem = null; + break; + } + } + } + + var newList = document.createElement(o.listType); + + this.beyondMaxLevels = 0; + + // If the item is moved to the left, send it to its parent's level unless there are siblings below it. + if (parentItem != null && nextItem == null && + (o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth()) || + !o.rtl && (this.positionAbs.left < parentItem.offset().left))) { + parentItem.after(this.placeholder[0]); + this._clearEmpty(parentItem[0]); + this._trigger("change", event, this._uiHash()); + } + // If the item is below a sibling and is moved to the right, make it a child of that sibling. + else if (previousItem != null && + (o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize) || + !o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))) { + this._isAllowed(previousItem, level, level+childLevels+1); + if (!previousItem.children(o.listType).length) { + previousItem[0].appendChild(newList); + } + // If this item is being moved from the top, add it to the top of the list. + if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) { + previousItem.children(o.listType).prepend(this.placeholder); + } + // Otherwise, add it to the bottom of the list. + else { + previousItem.children(o.listType)[0].appendChild(this.placeholder[0]); + } + this._trigger("change", event, this._uiHash()); + } + else { + this._isAllowed(parentItem, level, level+childLevels); + } + + //Post events to containers + this._contactContainers(event); + + //Interconnect with droppables + if($.ui.ddmanager) $.ui.ddmanager.drag(this, event); + + //Call callbacks + this._trigger('sort', event, this._uiHash()); + + this.lastPositionAbs = this.positionAbs; + return false; + + }, + + _mouseStop: function(event, noPropagation) { + + // If the item is in a position not allowed, send it back + if (this.beyondMaxLevels) { + + this.placeholder.removeClass(this.options.errorClass); + + if (this.domPosition.prev) { + $(this.domPosition.prev).after(this.placeholder); + } else { + $(this.domPosition.parent).prepend(this.placeholder); + } + + this._trigger("revert", event, this._uiHash()); + + } + + // Clean last empty ul/ol + for (var i = this.items.length - 1; i >= 0; i--) { + var item = this.items[i].item[0]; + this._clearEmpty(item); + } + + $.ui.sortable.prototype._mouseStop.apply(this, arguments); + + }, + + serialize: function(options) { + + var o = $.extend({}, this.options, options), + items = this._getItemsAsjQuery(o && o.connected), + str = []; + + $(items).each(function() { + var res = ($(o.item || this).attr(o.attribute || 'id') || '') + .match(o.expression || (/(.+)[-=_](.+)/)), + pid = ($(o.item || this).parent(o.listType) + .parent(o.items) + .attr(o.attribute || 'id') || '') + .match(o.expression || (/(.+)[-=_](.+)/)); + + if (res) { + str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']') + + '=' + + (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID)); + } + }); + + if(!str.length && o.key) { + str.push(o.key + '='); + } + + return str.join('&'); + + }, + + toHierarchy: function(options) { + + var o = $.extend({}, this.options, options), + sDepth = o.startDepthCount || 0, + ret = []; + + $(this.element).children(o.items).each(function () { + var level = _recursiveItems(this); + ret.push(level); + }); + + return ret; + + function _recursiveItems(item) { + var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/)); + if (id) { + var currentItem = {"id" : id[2]}; + if ($(item).children(o.listType).children(o.items).length > 0) { + currentItem.children = []; + $(item).children(o.listType).children(o.items).each(function() { + var level = _recursiveItems(this); + currentItem.children.push(level); + }); + } + return currentItem; + } + } + }, + + toArray: function(options) { + + var o = $.extend({}, this.options, options), + sDepth = o.startDepthCount || 0, + ret = [], + left = 2; + + ret.push({ + "item_id": o.rootID, + "parent_id": 'none', + "depth": sDepth, + "left": '1', + "right": ($(o.items, this.element).length + 1) * 2 + }); + + $(this.element).children(o.items).each(function () { + left = _recursiveArray(this, sDepth + 1, left); + }); + + ret = ret.sort(function(a,b){ return (a.left - b.left); }); + + return ret; + + function _recursiveArray(item, depth, left) { + + var right = left + 1, + id, + pid; + + if ($(item).children(o.listType).children(o.items).length > 0) { + depth ++; + $(item).children(o.listType).children(o.items).each(function () { + right = _recursiveArray($(this), depth, right); + }); + depth --; + } + + id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/)); + + if (depth === sDepth + 1) { + pid = o.rootID; + } else { + var parentItem = ($(item).parent(o.listType) + .parent(o.items) + .attr(o.attribute || 'id')) + .match(o.expression || (/(.+)[-=_](.+)/)); + pid = parentItem[2]; + } + + if (id) { + ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right}); + } + + left = right + 1; + return left; + } + + }, + + _clearEmpty: function(item) { + + var emptyList = $(item).children(this.options.listType); + if (emptyList.length && !emptyList.children().length && !this.options.doNotClear) { + emptyList.remove(); + } + + }, + + _getLevel: function(item) { + + var level = 1; + + if (this.options.listType) { + var list = item.closest(this.options.listType); + while (list && list.length > 0 && + !list.is('.ui-sortable')) { + level++; + list = list.parent().closest(this.options.listType); + } + } + + return level; + }, + + _getChildLevels: function(parent, depth) { + var self = this, + o = this.options, + result = 0; + depth = depth || 0; + + $(parent).children(o.listType).children(o.items).each(function (index, child) { + result = Math.max(self._getChildLevels(child, depth + 1), result); + }); + + return depth ? result + 1 : result; + }, + + _isAllowed: function(parentItem, level, levels) { + var o = this.options, + isRoot = $(this.domPosition.parent).hasClass('ui-sortable') ? true : false, + maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list + + // Is the root protected? + // Are we trying to nest under a no-nest? + // Are we nesting too deep? + if (!o.isAllowed(this.currentItem, parentItem) || + parentItem && parentItem.hasClass(o.disableNesting) || + o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) { + this.placeholder.addClass(o.errorClass); + if (maxLevels < levels && maxLevels != 0) { + this.beyondMaxLevels = levels - maxLevels; + } else { + this.beyondMaxLevels = 1; + } + } else { + if (maxLevels < levels && maxLevels != 0) { + this.placeholder.addClass(o.errorClass); + this.beyondMaxLevels = levels - maxLevels; + } else { + this.placeholder.removeClass(o.errorClass); + this.beyondMaxLevels = 0; + } + } + } + + })); + + $.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options); +})(jQuery); diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css index 474012afc98..54276e715c4 100644 --- a/addons/website/static/src/css/editor.css +++ b/addons/website/static/src/css/editor.css @@ -114,7 +114,7 @@ table.editorbar-panel td.selected { .oe_translate_or { color: white; - padding: 0 0.2em; + padding: 0 0 0 1em; } .oe_translate_examples li { @@ -142,6 +142,84 @@ table.editorbar-panel td.selected { background: #ffffb6; } +div.oe_menu_buttons { + top: -8px; + right: -8px; +} + +ul.oe_menu_editor > li:first-child > div > i:before { + content: "\f015"; +} +ul.oe_menu_editor, ul.oe_menu_editor ul { + list-style-type: none; + margin: 0; + padding: 0; +} +ul.oe_menu_editor ul { + padding-left: 30px; +} +ul.oe_menu_editor button { + margin-left: 4px; +} +ul.oe_menu_editor li { + margin: 5px 0 0 0; + padding: 0; +} +ul.oe_menu_editor .oe_menu_placeholder { + outline: 1px dashed #4183c4; +} +ul.oe_menu_editor .mjs-nestedSortable-error { + background: #fbe3e4; + border-color: transparent; +} +ul.oe_menu_editor li div { + border: 1px solid #d4d4d4; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + border-color: #d4d4d4 #d4d4d4 #bcbcbc; + padding: 6px; + margin: 0; + cursor: move; + background: #f6f6f6; + background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(47%, #f6f6f6), color-stop(100%, #ededed)); + background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%); + background: -o-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%); + background: -ms-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%); + background: linear-gradient(to bottom, #ffffff 0%, #f6f6f6 47%, #ededed 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 ); +} +ul.oe_menu_editor li.mjs-nestedSortable-branch div { + background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%); + background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%); +} +ul.oe_menu_editor li.mjs-nestedSortable-leaf div { + background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%); + background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%); +} +ul.oe_menu_editor li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div { + border-color: #999999; + background: #fafafa; +} +ul.oe_menu_editor .disclose { + cursor: pointer; + width: 10px; + display: none; +} +ul.oe_menu_editor li.mjs-nestedSortable-collapsed > ul { + display: none; +} +ul.oe_menu_editor li.mjs-nestedSortable-branch > div > .disclose { + display: inline-block; +} +ul.oe_menu_editor li.mjs-nestedSortable-collapsed > div > .disclose > span:before { + content: "+ "; +} +ul.oe_menu_editor li.mjs-nestedSortable-expanded > div > .disclose > span:before { + content: "- "; +} + /* ---- RTE ---- */ .oe_editable .btn { -webkit-user-select: auto; diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass index 5dbe7a8eee9..7ec41b2e760 100644 --- a/addons/website/static/src/css/editor.sass +++ b/addons/website/static/src/css/editor.sass @@ -108,7 +108,7 @@ table.editorbar-panel // ---- TRANSLATIONS ---- {{{ .oe_translate_or color: white - padding: 0 0.2em + padding: 0 0 0 1em .oe_translate_examples li margin: 10px padding: 4px @@ -124,6 +124,84 @@ table.editorbar-panel background: rgb(255, 255, 182) // }}} +// -------- MENU -------- {{{ +div.oe_menu_buttons + top: -8px + right: -8px +ul.oe_menu_editor + > li:first-child > div > i:before + content: "\f015" + &, & ul + list-style-type: none + margin: 0 + padding: 0 + & ul + padding-left: 30px + + button + margin-left: 4px + + li + margin: 5px 0 0 0 + padding: 0 + + .oe_menu_placeholder + outline: 1px dashed #4183C4 + + .mjs-nestedSortable-error + background: #fbe3e4 + border-color: transparent + + + // TODO: use compass mixins + li div + border: 1px solid #d4d4d4 + -webkit-border-radius: 3px + -moz-border-radius: 3px + border-radius: 3px + border-color: #D4D4D4 #D4D4D4 #BCBCBC + padding: 6px + margin: 0 + cursor: move + background: #f6f6f6 + background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%) + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed)) + background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%) + background: -o-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%) + background: -ms-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%) + background: linear-gradient(to bottom, #ffffff 0%,#f6f6f6 47%,#ededed 100%) + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 ) + + li.mjs-nestedSortable-branch div + background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #f0ece9 100%) + background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#f0ece9 100%) + + li.mjs-nestedSortable-leaf div + background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #bcccbc 100%) + background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#bcccbc 100%) + + li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div + border-color: #999 + background: #fafafa + + .disclose + cursor: pointer + width: 10px + display: none + + li.mjs-nestedSortable-collapsed > ul + display: none + + li.mjs-nestedSortable-branch > div > .disclose + display: inline-block + + li.mjs-nestedSortable-collapsed > div > .disclose > span:before + content: '+ ' + + li.mjs-nestedSortable-expanded > div > .disclose > span:before + content: '- ' +// }}} + /* ---- RTE ---- */ // bootstrap makes .btn elements unselectable -> RTE double-click can't know diff --git a/addons/website/static/src/img/blocks/block_quotes_slider.png b/addons/website/static/src/img/blocks/block_quotes_slider.png new file mode 100644 index 00000000000..c52ec7ab17d Binary files /dev/null and b/addons/website/static/src/img/blocks/block_quotes_slider.png differ diff --git a/addons/website/static/src/img/blocks/block_well.png b/addons/website/static/src/img/blocks/block_well.png new file mode 100644 index 00000000000..716fff5e075 Binary files /dev/null and b/addons/website/static/src/img/blocks/block_well.png differ diff --git a/addons/website/static/src/js/website.ace.js b/addons/website/static/src/js/website.ace.js index 02f5eb68d5e..685ace69467 100644 --- a/addons/website/static/src/js/website.ace.js +++ b/addons/website/static/src/js/website.ace.js @@ -6,7 +6,7 @@ var hash = "#advanced-view-editor"; var website = openerp.website; - website.templates.push('/website/static/src/xml/website.ace.xml'); + website.add_template_file('/website/static/src/xml/website.ace.xml'); website.ready().then(function () { if (window.location.hash.indexOf(hash) >= 0) { diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index b6102de0fd3..45120030da3 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -4,7 +4,7 @@ var website = openerp.website; // $.fn.data automatically parses value, '0'|'1' -> 0|1 - website.templates.push('/website/static/src/xml/website.editor.xml'); + website.add_template_file('/website/static/src/xml/website.editor.xml'); website.dom_ready.done(function () { var is_smartphone = $(document.body)[0].clientWidth < 767; @@ -14,7 +14,7 @@ }); function link_dialog(editor) { - return new website.editor.LinkDialog(editor).appendTo(document.body); + return new website.editor.RTELinkDialog(editor).appendTo(document.body); } function image_dialog(editor) { return new website.editor.RTEImageDialog(editor).appendTo(document.body); @@ -805,6 +805,106 @@ this.pages = Object.create(null); this.text = null; }, + start: function () { + var self = this; + return $.when( + this.fetch_pages().done(this.proxy('fill_pages')), + this._super() + ).done(function () { + self.bind_data(); + }); + }, + save: function () { + var self = this, _super = this._super.bind(this); + var $e = this.$('.list-group-item.active .url-source'); + var val = $e.val(); + if (!val || !$e[0].checkValidity()) { + // FIXME: error message + $e.closest('.form-group').addClass('has-error'); + $e.focus(); + return; + } + + var done = $.when(); + if ($e.hasClass('email-address')) { + this.make_link('mailto:' + val, false, val); + } else if ($e.hasClass('existing')) { + self.make_link(val, false, this.pages[val]); + } else if ($e.hasClass('pages')) { + // Create the page, get the URL back + done = $.get(_.str.sprintf( + '/pagenew/%s?noredirect', encodeURI(val))) + .then(function (response) { + self.make_link(response, false, val); + }); + } else { + this.make_link(val, this.$('input.window-new').prop('checked')); + } + done.then(_super); + }, + make_link: function (url, new_window, label) { + }, + bind_data: function (text, href, new_window) { + href = href || this.element && (this.element.data( 'cke-saved-href') + || this.element.getAttribute('href')); + if (!href) { return; } + + if (new_window === undefined) { + new_window = this.element.getAttribute('target') === '_blank'; + } + if (text === undefined) { + text = this.element.getText(); + } + + var match, $control; + if ((match = /mailto:(.+)/.exec(href))) { + $control = this.$('input.email-address').val(match[1]); + } else if (href in this.pages) { + $control = this.$('select.existing').val(href); + } else if ((match = /\/page\/(.+)/.exec(href))) { + var actual_href = '/page/website.' + match[1]; + if (actual_href in this.pages) { + $control = this.$('select.existing').val(actual_href); + } + } + if (!$control) { + $control = this.$('input.url').val(href); + } + + this.changed($control); + + this.$('input#link-text').val(text); + this.$('input.window-new').prop('checked', new_window); + }, + changed: function ($e) { + this.$('.url-source').not($e).val(''); + $e.closest('.list-group-item') + .addClass('active') + .siblings().removeClass('active') + .addBack().removeClass('has-error'); + }, + fetch_pages: function () { + return openerp.jsonRpc('/web/dataset/call_kw', 'call', { + model: 'website', + method: 'list_pages', + args: [null], + kwargs: { + context: website.get_context() + }, + }); + }, + fill_pages: function (results) { + var self = this; + var pages = this.$('select.existing')[0]; + _(results).each(function (result) { + self.pages[result.url] = result.name; + + pages.options[pages.options.length] = + new Option(result.name, result.url); + }); + }, + }); + website.editor.RTELinkDialog = website.editor.LinkDialog.extend({ start: function () { var element; if ((element = this.get_selected_link()) && element.hasAttribute('href')) { @@ -815,10 +915,7 @@ this.add_removal_button(); } - return $.when( - this.fetch_pages().done(this.proxy('fill_pages')), - this._super() - ).done(this.proxy('bind_data')); + return this._super(); }, add_removal_button: function () { this.$('.modal-footer').prepend( @@ -884,66 +981,6 @@ }, 0); } }, - save: function () { - var self = this, _super = this._super.bind(this); - var $e = this.$('.list-group-item.active .url-source'); - var val = $e.val(); - if (!val || !$e[0].checkValidity()) { - // FIXME: error message - $e.closest('.form-group').addClass('has-error'); - return; - } - - var done = $.when(); - if ($e.hasClass('email-address')) { - this.make_link('mailto:' + val, false, val); - } else if ($e.hasClass('existing')) { - self.make_link(val, false, this.pages[val]); - } else if ($e.hasClass('pages')) { - // Create the page, get the URL back - done = $.get(_.str.sprintf( - '/pagenew/%s?noredirect', encodeURI(val))) - .then(function (response) { - self.make_link(response, false, val); - }); - } else { - this.make_link(val, this.$('input.window-new').prop('checked')); - } - done.then(_super); - }, - bind_data: function () { - var href = this.element && (this.element.data( 'cke-saved-href') - || this.element.getAttribute('href')); - if (!href) { return; } - - var match, $control; - if (match = /mailto:(.+)/.exec(href)) { - $control = this.$('input.email-address').val(match[1]); - } else if (href in this.pages) { - $control = this.$('select.existing').val(href); - } else if (match = /\/page\/(.+)/.exec(href)) { - var actual_href = '/page/website.' + match[1]; - if (actual_href in this.pages) { - $control = this.$('select.existing').val(actual_href); - } - } - if (!$control) { - $control = this.$('input.url').val(href); - } - - this.changed($control); - - this.$('input#link-text').val(this.element.getText()); - this.$('input.window-new').prop( - 'checked', this.element.getAttribute('target') === '_blank'); - }, - changed: function ($e) { - this.$('.url-source').not($e).val(''); - $e.closest('.list-group-item') - .addClass('active') - .siblings().removeClass('active') - .addBack().removeClass('has-error'); - }, /** * CKEDITOR.plugins.link.getSelectedLink ignores the editor's root, * if the editor is set directly on a link it will thus not work. @@ -951,27 +988,8 @@ get_selected_link: function () { return get_selected_link(this.editor); }, - fetch_pages: function () { - return openerp.jsonRpc('/web/dataset/call_kw', 'call', { - model: 'website', - method: 'list_pages', - args: [null], - kwargs: { - context: website.get_context() - }, - }); - }, - fill_pages: function (results) { - var self = this; - var pages = this.$('select.existing')[0]; - _(results).each(function (result) { - self.pages[result.url] = result.name; - - pages.options[pages.options.length] = - new Option(result.name, result.url); - }); - }, }); + /** * ImageDialog widget. Lets users change an image, including uploading a * new image in OpenERP or selecting the image style (if supported by diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index 682a6175086..cfca69d5f88 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -5,35 +5,30 @@ // The following line can be removed in 2017 openerp.website = website; - var templates = website.templates = [ - '/website/static/src/xml/website.xml' - ]; - website.get_context = function (dict) { var html = document.documentElement; return _.extend({ - lang: html.getAttribute('lang').replace('-', '_') + lang: html.getAttribute('lang').replace('-', '_'), + website_id: html.getAttribute('data-website-id')|0 }, dict); }; /* ----- TEMPLATE LOADING ---- */ - website.add_template = function(template) { - templates.push(template); - }; - website.load_templates = function(templates) { - var dones = _(templates).map(function (t) { - return new $.Deferred(function (d) { - openerp.qweb.add_template(t, function(err) { - if (err) { - d.reject(err); - } else { - d.resolve(); - } - }); + var templates_def = $.Deferred().resolve(); + website.add_template_file = function(template) { + templates_def = templates_def.then(function() { + var def = $.Deferred(); + openerp.qweb.add_template(template, function(err) { + if (err) { + def.reject(err); + } else { + def.resolve(); + } }); + return def; }); - return $.when.apply(null, dones); }; + website.add_template_file('/website/static/src/xml/website.xml'); website.reload = function () { location.hash = "scrollTop=" + window.document.body.scrollTop; if (location.search.indexOf("enable_editor") > -1) { @@ -110,20 +105,33 @@ */ website.ready = function() { if (!all_ready) { - var tpl = website.load_templates(templates); - all_ready = dom_ready.then(function () { + all_ready = $.when(dom_ready, templates_def).then(function () { if ($('html').data('editable')) { + website.id = $('html').data('website-id'); website.session = new openerp.Session(); var modules = ['website']; return openerp._t.database.load_translations(website.session, modules, website.get_context().lang); } - }).then(tpl).promise(); + }).promise(); } return all_ready; }; + website.error = function(data, url) { + var $error = $(openerp.qweb.render('website.error_dialog', { + 'title': data.data ? data.data.arguments[0] : data.statusText, + 'message': data.data ? data.data.arguments[1] : "", + 'backend_url': url + })); + $error.appendTo("body"); + $error.modal('show'); + }; + dom_ready.then(function () { + /* ----- BOOTSTRAP STUFF ---- */ + $('.js_tooltip').bstooltip(); + /* ----- PUBLISHING STUFF ---- */ $('[data-publish]:has(.js_publish)').each(function () { var $pub = $("[data-publish]", this); @@ -140,11 +148,13 @@ $(document).on('click', '.js_publish', function (e) { e.preventDefault(); - var $data = $(":first", this).parents("[data-publish]"); - $data.attr("data-publish", $data.first().attr("data-publish") == 'off' ? 'on' : 'off'); - openerp.jsonRpc('/website/publish', 'call', {'id': $(this).data('id'), 'object': $(this).data('object')}) + var $a = $(this); + var $data = $a.find(":first").parents("[data-publish]"); + openerp.jsonRpc($a.data('controller') || '/website/publish', 'call', {'id': +$a.data('id'), 'object': $a.data('object')}) .then(function (result) { $data.attr("data-publish", +result ? 'on' : 'off'); + }).fail(function (err, data) { + website.error(data, '/web#model='+$a.data('object')+'&id='+$a.data('id')); }); }); @@ -156,11 +166,13 @@ $data.toggleClass("css_unpublish css_publish"); $btn.removeClass("btn-default btn-success"); - openerp.jsonRpc('/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')}) + openerp.jsonRpc($data.data('controller') || '/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')}) .then(function (result) { $btn.toggleClass("btn-default", !result).toggleClass("btn-success", result); $data.toggleClass("css_unpublish", !result).toggleClass("css_publish", result); $data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off'); + }).fail(function (err, data) { + website.error(data, '/web#model='+$data.data('object')+'&id='+$data.data('id')); }); }); diff --git a/addons/website/static/src/js/website.menu.js b/addons/website/static/src/js/website.menu.js new file mode 100644 index 00000000000..afbe8997387 --- /dev/null +++ b/addons/website/static/src/js/website.menu.js @@ -0,0 +1,193 @@ +(function () { + 'use strict'; + + var website = openerp.website; + website.menu = {}; + website.add_template_file('/website/static/src/xml/website.menu.xml'); + + website.EditorBar.include({ + events: _.extend({}, website.EditorBar.prototype.events, { + 'click a[data-action="edit-structure"]': 'editStructure', + }), + editStructure: function () { + var context = website.get_context(); + openerp.jsonRpc('/web/dataset/call_kw', 'call', { + model: 'website.menu', + method: 'get_tree', + args: [[context.website_id]], + kwargs: { + context: context + }, + }).then(function (menu) { + return new website.menu.EditMenuDialog(menu).appendTo(document.body); + }); + }, + }); + + website.menu.EditMenuDialog = website.editor.Dialog.extend({ + template: 'website.menu.dialog.edit', + events: _.extend({}, website.editor.Dialog.prototype.events, { + 'click button.js_add_menu': 'add_menu', + 'click button.js_edit_menu': 'edit_menu', + 'click button.js_delete_menu': 'delete_menu', + }), + init: function (menu) { + this.menu = menu; + this.root_menu_id = menu.id; + this.flat = this.flatenize(menu); + this.to_delete = []; + this._super(); + }, + start: function () { + var r = this._super.apply(this, arguments); + var button = openerp.qweb.render('website.menu.dialog.footer-button'); + this.$('.modal-footer').prepend(button); + this.$('.oe_menu_editor').nestedSortable({ + listType: 'ul', + handle: 'div', + items: 'li', + maxLevels: 2, + toleranceElement: '> div', + forcePlaceholderSize: true, + opacity: 0.6, + placeholder: 'oe_menu_placeholder', + tolerance: 'pointer', + attribute: 'data-menu-id', + expression: '()(.+)', // nestedSortable takes the second match of an expression (*sigh*) + }); + return r; + }, + flatenize: function (node, dict) { + dict = dict || {}; + var self = this; + dict[node.id] = node; + node.children.forEach(function (child) { + self.flatenize(child, dict); + }); + return dict; + }, + add_menu: function () { + var self = this; + var dialog = new website.menu.MenuEntryDialog(); + dialog.on('add-menu', this, function (link) { + var new_menu = { + id: _.uniqueId('new-'), + name: link[2] || link[0], + url: link[0], + new_window: link[1], + parent_id: false, + sequence: 0, + children: [], + }; + self.flat[new_menu.id] = new_menu; + self.$('.oe_menu_editor').append( + openerp.qweb.render( + 'website.menu.dialog.submenu', { submenu: new_menu })); + }); + dialog.appendTo(document.body); + }, + edit_menu: function (ev) { + var self = this; + var menu_id = $(ev.currentTarget).closest('[data-menu-id]').data('menu-id'); + var menu = self.flat[menu_id]; + if (menu) { + var dialog = new website.menu.MenuEntryDialog(undefined, menu); + dialog.on('update-menu', this, function (link) { + var id = link.shift(); + var menu_obj = self.flat[id]; + _.extend(menu_obj, { + name: link[2], + url: link[0], + new_window: link[1], + }); + var $menu = self.$('[data-menu-id="' + id + '"]'); + $menu.find('> div > span').text(menu_obj.name); + }); + dialog.appendTo(document.body); + } else { + alert("Could not find menu entry"); + } + }, + delete_menu: function (ev) { + var self = this; + var $menu = $(ev.currentTarget).closest('[data-menu-id]'); + var mid = $menu.data('menu-id')|0; + if (mid) { + this.to_delete.push(mid); + } + $menu.remove(); + }, + save: function () { + var self = this; + var new_menu = this.$('.oe_menu_editor').nestedSortable('toArray', {startDepthCount: 0}); + var levels = []; + var data = []; + var context = website.get_context(); + // Resquence, re-tree and remove useless data + new_menu.forEach(function (menu) { + if (menu.item_id) { + levels[menu.depth] = (levels[menu.depth] || 0) + 1; + var mobj = self.flat[menu.item_id]; + mobj.sequence = levels[menu.depth]; + mobj.parent_id = (menu.parent_id|0) || menu.parent_id || self.root_menu_id; + delete(mobj.children); + delete(mobj.level); + data.push(mobj); + } + }); + openerp.jsonRpc('/web/dataset/call_kw', 'call', { + model: 'website.menu', + method: 'save', + args: [[context.website_id], { data: data, to_delete: self.to_delete }], + kwargs: { + context: context + }, + }).then(function (menu) { + self.close(); + website.reload(); + }); + }, + }); + + website.menu.MenuEntryDialog = website.editor.LinkDialog.extend({ + template: 'website.menu.dialog.add', + init: function (editor, data) { + this.data = data; + this.update_mode = !!this.data; + return this._super.apply(this, arguments); + }, + start: function () { + var self = this; + return $.when(this._super.apply(this, arguments)).then(function () { + if (self.data) { + self.bind_data(self.data.name, self.data.url, self.data.new_window); + } + var $link_text = self.$('#link-text').focus(); + self.$('#link-existing').change(function () { + if (!$link_text.val()) { + $link_text.val($(this).find('option:selected').text()); + $link_text.focus(); + } + }); + }); + }, + save: function () { + var $e = this.$('#link-text'); + if (!$e.val() || !$e[0].checkValidity()) { + $e.closest('.form-group').addClass('has-error'); + $e.focus(); + return; + } + return this._super.apply(this, arguments); + }, + make_link: function (url, new_window, label) { + var menu_label = this.$('input#link-text').val() || label; + if (this.data) { + this.trigger('update-menu', [this.data.id, url, new_window, menu_label]); + } else { + this.trigger('add-menu', [url, new_window, menu_label]); + } + }, + }); + +})(); diff --git a/addons/website/static/src/js/website.seo.js b/addons/website/static/src/js/website.seo.js index 342837f43c1..ecfc3d77b1c 100644 --- a/addons/website/static/src/js/website.seo.js +++ b/addons/website/static/src/js/website.seo.js @@ -2,7 +2,7 @@ 'use strict'; var website = openerp.website; - website.templates.push('/website/static/src/xml/website.seo.xml'); + website.add_template_file('/website/static/src/xml/website.seo.xml'); website.EditorBar.include({ events: _.extend({}, website.EditorBar.prototype.events, { diff --git a/addons/website/static/src/js/website.snippets.js b/addons/website/static/src/js/website.snippets.js index 303e670b133..41ae03ed78b 100644 --- a/addons/website/static/src/js/website.snippets.js +++ b/addons/website/static/src/js/website.snippets.js @@ -2,7 +2,7 @@ 'use strict'; var website = openerp.website; - website.templates.push('/website/static/src/xml/website.snippets.xml'); + website.add_template_file('/website/static/src/xml/website.snippets.xml'); website.EditorBar.include({ start: function () { @@ -589,7 +589,8 @@ * Displayed into the overlay options on focus */ _readXMLData: function() { - this.$el = this.parent.$snippets.siblings("[data-snippet-id='"+this.snippet_id+"']").clone(); + var self = this; + this.$el = this.parent.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone(); this.$editor = this.$el.find(".oe_snippet_options"); var $options = this.$overlay.find(".oe_overlay_options"); this.$editor.prependTo($options.find(".oe_options ul")); diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index f5d3237c28d..489855b419f 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -2,7 +2,7 @@ 'use strict'; var website = openerp.website; - website.templates.push('/website/static/src/xml/website.tour.xml'); + website.add_template_file('/website/static/src/xml/website.tour.xml'); website.tour = { render: function render (template, dict) { diff --git a/addons/website/static/src/js/website.translator.js b/addons/website/static/src/js/website.translator.js index 7e237598530..1c37c91d972 100644 --- a/addons/website/static/src/js/website.translator.js +++ b/addons/website/static/src/js/website.translator.js @@ -2,7 +2,7 @@ 'use strict'; var website = openerp.website; - website.templates.push('/website/static/src/xml/website.translator.xml'); + website.add_template_file('/website/static/src/xml/website.translator.xml'); var nodialog = 'website_translator_nodialog'; website.EditorBar.include({ @@ -17,6 +17,7 @@ .text("Translate") .after(openerp.qweb.render('website.TranslatorAdditionalButtons')); self.$('[data-action=snippet]').hide(); + self.$('[data-action=edit-structure]').hide(); self.$('#customize-menu-button').hide(); }); }, diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 685712d3411..fd89e70d2e1 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -39,22 +39,22 @@
  • -

    +

    -

    +
  • -

    +

    -

    +
  • @@ -65,20 +65,20 @@ Open in new window -

    +

    -

    +
  • -

    +

    -

    +
  • diff --git a/addons/website/static/src/xml/website.menu.xml b/addons/website/static/src/xml/website.menu.xml new file mode 100644 index 00000000000..c9c73c32ae7 --- /dev/null +++ b/addons/website/static/src/xml/website.menu.xml @@ -0,0 +1,59 @@ + + +
  • +
    + + +
    + + +
    +
    + +
      + + + +
    +
  • +
    + + + + + + Edit Structure +
      + + + +
    +
    +
    + + + Add Menu Entry + Edit Menu Entry + + + +
    +
    +

    + +

    + +
    +
    +
    +
    +
    diff --git a/addons/website/static/src/xml/website.seo.xml b/addons/website/static/src/xml/website.seo.xml index 17d68fb70f0..2c3f8cab52a 100644 --- a/addons/website/static/src/xml/website.seo.xml +++ b/addons/website/static/src/xml/website.seo.xml @@ -32,7 +32,7 @@
    - +
    diff --git a/addons/website/static/src/xml/website.translator.xml b/addons/website/static/src/xml/website.translator.xml index dac074688d8..4a3432cd9f8 100644 --- a/addons/website/static/src/xml/website.translator.xml +++ b/addons/website/static/src/xml/website.translator.xml @@ -1,8 +1,8 @@ - or - Edit Master + or + Edit Master + + + diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml index 82c8c94a2c4..39afaefe13a 100644 --- a/addons/website/views/snippets.xml +++ b/addons/website/views/snippets.xml @@ -323,6 +323,7 @@
    + Well
    @@ -716,7 +717,7 @@
    - + Quotes Slider
    diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 57e6fded011..de7ace47558 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -10,9 +10,28 @@ -