diff options
| author | Quentin (OpenERP) <qdp-launchpad@openerp.com> | 2014-03-26 16:27:51 +0100 |
|---|---|---|
| committer | Quentin (OpenERP) <qdp-launchpad@openerp.com> | 2014-03-26 16:27:51 +0100 |
| commit | e0b27b4ee8e2d26eeae98a4dbc69a3485f39a8a8 (patch) | |
| tree | a3887e48d1bdeaf27b04c5c4e636e677b9e1c586 /addons/website/static/src/js | |
| parent | 78457a17ea7175c02baf823c296b5cefe1a52a03 (diff) | |
| parent | 0d4d9af5f4b7ca695e33af370848350b437c132e (diff) | |
[MERGE] merged trunk up to revision 9251
bzr revid: qdp-launchpad@openerp.com-20140326152751-b6bo0fawrzmd71f0
Diffstat (limited to 'addons/website/static/src/js')
| -rw-r--r-- | addons/website/static/src/js/website.ace.js | 17 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.editor.js | 191 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.editor.newpage.js | 2 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.js | 242 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.mobile.js | 5 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.snippets.animation.js | 47 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.snippets.editor.js | 1268 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.banner.js | 22 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.js | 94 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.test.admin.js | 106 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.test.js | 34 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.translator.js | 4 |
12 files changed, 1157 insertions, 875 deletions
diff --git a/addons/website/static/src/js/website.ace.js b/addons/website/static/src/js/website.ace.js index ed7037e903c..a8af74bb52d 100644 --- a/addons/website/static/src/js/website.ace.js +++ b/addons/website/static/src/js/website.ace.js @@ -39,12 +39,17 @@ this.xml = text; }, isWellFormed: function () { + var error; if (document.implementation.createDocument) { - var dom = new DOMParser().parseFromString(this.xml, "text/xml"); - var error = dom.getElementsByTagName("parsererror"); - return error.length === 0 || error; - } else if (window.ActiveXObject) { - // TODO test in IE + // use try catch for ie + try { + var dom = new DOMParser().parseFromString(this.xml, "text/xml"); + error = dom.getElementsByTagName("parsererror"); + return error.length === 0 || $(error).text(); + } catch (e) {} + } + if (window.ActiveXObject) { + // IE var msDom = new ActiveXObject("Microsoft.XMLDOM"); msDom.async = false; msDom.loadXML(this.xml); @@ -284,7 +289,7 @@ def.reject("server", session, error); }); } else { - def.reject(null, session, $(isWellFormed).text()); + def.reject(null, session, isWellFormed); } return def; }, diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index dc82c0c17b9..ae126ad16ab 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -20,6 +20,10 @@ website.form(this.pathname, 'POST'); }); + $(document).on('click', '.cke_editable label', function (ev) { + ev.preventDefault(); + }); + $(document).on('submit', '.cke_editable form', function (ev) { // Disable form submition in editable mode ev.preventDefault(); @@ -66,8 +70,10 @@ // only enable editors manually CKEDITOR.disableAutoInline = true; // EDIT ALL THE THINGS - CKEDITOR.dtd.$editable = $.extend( - {}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline); + CKEDITOR.dtd.$editable = _.omit( + $.extend({}, CKEDITOR.dtd.$block, CKEDITOR.dtd.$inline), + // well maybe not *all* the things + 'ul', 'ol', 'li', 'table', 'tr', 'th', 'td'); // Disable removal of empty elements on CKEDITOR activation. Empty // elements are used for e.g. support of FontAwesome icons CKEDITOR.dtd.$removeEmpty = {}; @@ -100,6 +106,7 @@ }, canUndo: false, editorFocus: true, + context: 'a', }); //noinspection JSValidateTypes editor.addCommand('image', { @@ -109,6 +116,7 @@ }, canUndo: false, editorFocus: true, + context: 'img', }); editor.ui.addButton('Link', { @@ -199,8 +207,9 @@ icon: '/website/static/src/img/bglink.png', modes: { wysiwyg: true }, editorFocus: true, + context: 'a', panel: { - css: '/website/static/lib/bootstrap/css/bootstrap.css', + css: '/web/static/lib/bootstrap/css/bootstrap.css', attributes: { 'role': 'listbox', 'aria-label': label }, }, @@ -340,14 +349,40 @@ requires: 'widget', init: function (editor) { + var specials = { + // Can't find the correct ACL rule to only allow img tags + image: { content: '*' }, + html: { text: '*' }, + monetary: { + text: { + selector: 'span.oe_currency_value', + allowedContent: { } + } + } + }; + _(specials).each(function (editable, type) { + editor.widgets.add(type, { + draggable: false, + editables: editable, + upcast: function (el) { + return el.attributes['data-oe-type'] === type; + + } + }); + }); editor.widgets.add('oeref', { - editables: { text: '*' }, draggable: false, - + editables: { + text: { + selector: '*', + allowedContent: { } + }, + }, upcast: function (el) { - var matches = el.attributes['data-oe-type'] && el.attributes['data-oe-type'] !== 'monetary'; - if (!matches) { return false; } - + var type = el.attributes['data-oe-type']; + if (!type || (type in specials)) { + return false; + } if (el.attributes['data-oe-original']) { while (el.children.length) { el.children[0].remove(); @@ -357,16 +392,9 @@ )); } return true; - }, - }); - editor.widgets.add('monetary', { - editables: { text: 'span.oe_currency_value' }, - draggable: false, - - upcast: function (el) { - return el.attributes['data-oe-type'] === 'monetary'; } }); + editor.widgets.add('icons', { draggable: false, @@ -377,8 +405,11 @@ }); }, upcast: function (el) { - return el.attributes['class'] - && (/\bfa\b/.test(el.attributes['class'])); + return el.hasClass('fa') + // ignore ir.ui.view (other data-oe-model should + // already have been matched by oeref and + // monetary? + && !el.attributes['data-oe-model']; } }); } @@ -625,11 +656,11 @@ var $link_button = this.make_hover_button(_t("Change"), function () { var sel = new CKEDITOR.dom.element(previous); editor.getSelection().selectElement(sel); - if (previous.tagName.toUpperCase() === 'A') { - link_dialog(editor); - } else if(sel.hasClass('fa')) { + if(sel.hasClass('fa')) { new website.editor.FontIconsDialog(editor, previous) .appendTo(document.body); + } else if (previous.tagName.toUpperCase() === 'A') { + link_dialog(editor); } $link_button.hide(); previous = null; @@ -640,6 +671,11 @@ previous = null; }, 'btn-sm'); + function is_icons_widget(element) { + var w = editor.widgets.getByElement(element); + return w && w.name === 'icons'; + } + // previous is the state of the button-trigger: it's the // currently-ish hovered element which can trigger a button showing. // -ish, because when moving to the button itself ``previous`` is @@ -649,8 +685,15 @@ // Back from edit button -> ignore if (previous && previous === this) { return; } + // hover button should appear for "editable" links and images + // (img and a nodes whose *attributes* are editable, they + // can not be "editing hosts") *or* for non-editing-host + // elements bearing an ``fa`` class. These should have been + // made into CKE widgets which are editing hosts by + // definition, so instead check if the element has been + // converted/upcasted to an fa widget var selected = new CKEDITOR.dom.element(this); - if (!is_editable_node(selected) && !selected.hasClass('fa')) { + if (!(is_editable_node(selected) || is_icons_widget(selected))) { return; } @@ -837,7 +880,6 @@ document.execCommand("enableInlineTableEditing", false, "false"); } catch (e) {} - // detect & setup any CKEDITOR widget within a newly dropped // snippet. There does not seem to be a simple way to do it for // HTML not inserted via ckeditor APIs: @@ -1436,6 +1478,7 @@ this.page += $target.hasClass('previous') ? -1 : 1; this.display_attachments(); }, + 'click .existing-attachment-remove': 'try_remove', }), init: function (parent) { this.image = null; @@ -1458,7 +1501,7 @@ kwargs: { fields: ['name', 'website_url'], domain: [['res_model', '=', 'ir.ui.view']], - order: 'name', + order: 'id desc', context: website.get_context(), } }); @@ -1468,6 +1511,7 @@ this.display_attachments(); }, display_attachments: function () { + this.$('.help-block').empty(); var per_screen = IMAGES_PER_ROW * IMAGES_ROWS; var from = this.page * per_screen; @@ -1495,6 +1539,34 @@ } this.close() }, + + try_remove: function (e) { + var $help_block = this.$('.help-block').empty(); + var self = this; + var id = parseInt($(e.target).data('id'), 10); + var attachment = _.findWhere(this.records, {id: id}); + + return openerp.jsonRpc('/web/dataset/call_kw', 'call', { + model: 'ir.attachment', + method: 'try_remove', + args: [], + kwargs: { + ids: [id], + context: website.get_context() + } + }).then(function (prevented) { + if (_.isEmpty(prevented)) { + self.records = _.without(self.records, attachment); + self.display_attachments(); + return; + } + $help_block.replaceWith(openerp.qweb.render( + 'website.editor.dialog.image.existing.error', { + views: prevented[id] + } + )); + }); + }, }); function get_selected_link(editor) { @@ -1684,6 +1756,9 @@ // ignore mutation if the *only* change is .cke_focus return change.length !== 1 || change[0] === 'cke_focus'; case 'childList': + setTimeout(function () { + fixup_browser_crap(m.addedNodes); + }, 0); // Remove ignorable nodes from addedNodes or removedNodes, // if either set remains non-empty it's considered to be an // impactful change. Otherwise it's ignored. @@ -1714,6 +1789,9 @@ if (node.nodeName === 'BR' && node.getAttribute('type') === '_moz') { // <br type="_moz"> appears when focusing RTE in FF, ignore continue; + } else if (node.nodeName === 'DIV' && $(node).hasClass('oe_drop_zone')) { + // ignore dropzone inserted by snippets + continue } } @@ -1721,4 +1799,69 @@ } return output; } + + var programmatic_styles = { + float: 1, + display: 1, + position: 1, + top: 1, + left: 1, + right: 1, + bottom: 1, + }; + function fixup_browser_crap(nodes) { + if (!nodes || !nodes.length) { return; } + /** + * Checks that the node only has a @style, not e.g. @class or whatever + */ + function has_only_style(node) { + for (var i = 0; i < node.attributes.length; i++) { + var attr = node.attributes[i]; + if (attr.attributeName !== 'style') { + return false; + } + } + return true; + } + function has_programmatic_style(node) { + for (var i = 0; i < node.style.length; i++) { + var style = node.style[i]; + if (programmatic_styles[style]) { + return true; + } + } + return false; + } + + for (var i=0; i<nodes.length; ++i) { + var node = nodes[i]; + if (node.nodeType !== document.ELEMENT_NODE) { continue; } + + if (node.nodeName === 'SPAN' + && has_only_style(node) + && !has_programmatic_style(node)) { + // On backspace, webkit browsers create a <span> with a bunch of + // inline styles "remembering" where they come from. Refs: + // http://www.neotericdesign.com/blog/2013/3/working-around-chrome-s-contenteditable-span-bug + // https://code.google.com/p/chromium/issues/detail?id=226941 + // https://bugs.webkit.org/show_bug.cgi?id=114791 + // http://dev.ckeditor.com/ticket/9998 + var child, parent = node.parentNode; + while (child = node.firstChild) { + parent.insertBefore(child, node); + } + parent.removeChild(node); + // chances are we had e.g. + // <p>foo</p> + // <p>bar</p> + // merged the lines getting this in webkit + // <p>foo<span>bar</span></p> + // after unwrapping the span, we have 2 text nodes + // <p>[foo][bar]</p> + // where we probably want only one. Normalize will merge + // adjacent text nodes. However, does not merge text and cdata + parent.normalize(); + } + } + } })(); diff --git a/addons/website/static/src/js/website.editor.newpage.js b/addons/website/static/src/js/website.editor.newpage.js index b475223ecd9..77a086790fa 100644 --- a/addons/website/static/src/js/website.editor.newpage.js +++ b/addons/website/static/src/js/website.editor.newpage.js @@ -48,7 +48,7 @@ } }).then(function (val, field, $dialog) { if (val) { - var url = '/website/add/' + encodeURI(val); + var url = '/website/add' + encodeURI(val); if ($dialog.find('input[type="checkbox"]').is(':checked')) url +="?add_menu=1"; document.location = url; } diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index 671dfc14ee9..31e39b3bbc4 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -2,9 +2,11 @@ "use strict"; var website = {}; - // The following line can be removed in 2017 openerp.website = website; + /* ---------------------------------------------------- + Helpers + ---------------------------------------------------- */ website.get_context = function (dict) { var html = document.documentElement; return _.extend({ @@ -34,26 +36,11 @@ } return parsedSearch; }; + website.parseHash = function () { return website.parseQS(window.location.hash.substring(1)); }; - /* ----- TEMPLATE LOADING ---- */ - 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; - }); - }; - 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) { @@ -63,100 +50,9 @@ } }; - var all_ready = null; - var dom_ready = website.dom_ready = $.Deferred(); - $(document).ready(function () { - website.is_editable = website.is_editable || $('html').data('editable'); - website.is_editable_button= website.is_editable_button || $('html').data('editable'); - dom_ready.resolve(); - }); - - website.init_kanban = function ($kanban) { - $('.js_kanban_col', $kanban).each(function () { - var $col = $(this); - var $pagination = $('.pagination', $col); - if(!$pagination.size()) { - return; - } - - var page_count = $col.data('page_count'); - var scope = $pagination.last().find("li").size()-2; - var kanban_url_col = $pagination.find("li a:first").attr("href").replace(/[0-9]+$/, ''); - - var data = { - 'domain': $col.data('domain'), - 'model': $col.data('model'), - 'template': $col.data('template'), - 'step': $col.data('step'), - 'orderby': $col.data('orderby') - }; - - $pagination.on('click', 'a', function (ev) { - ev.preventDefault(); - var $a = $(ev.target); - if($a.parent().hasClass('active')) { - return; - } - - var page = +$a.attr("href").split(",").pop().split('-')[1]; - data['page'] = page; - - $.post('/website/kanban/', data, function (col) { - $col.find("> .thumbnail").remove(); - $pagination.last().before(col); - }); - - var page_start = page - parseInt(Math.floor((scope-1)/2), 10); - if (page_start < 1 ) page_start = 1; - var page_end = page_start + (scope-1); - if (page_end > page_count ) page_end = page_count; - - if (page_end - page_start < scope) { - page_start = page_end - scope > 0 ? page_end - scope : 1; - } - - $pagination.find('li.prev a').attr("href", kanban_url_col+(page-1 > 0 ? page-1 : 1)); - $pagination.find('li.next a').attr("href", kanban_url_col+(page < page_end ? page+1 : page_end)); - for(var i=0; i < scope; i++) { - $pagination.find('li:not(.prev):not(.next):eq('+i+') a').attr("href", kanban_url_col+(page_start+i)).html(page_start+i); - } - $pagination.find('li.active').removeClass('active'); - $pagination.find('li:has(a[href="'+kanban_url_col+page+'"])').addClass('active'); - - }); - - }); - }; - - /** - * Returns a deferred resolved when the templates are loaded - * and the Widgets can be instanciated. - */ - website.ready = function() { - if (!all_ready) { - all_ready = dom_ready.then(function () { - return templates_def; - }).then(function () { - if (website.is_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); - } - }).promise(); - } - return all_ready; - }; - - website.error = function(data, url) { - var $error = $(openerp.qweb.render('website.error_dialog', { - 'title': data.data ? data.data.arguments[0] : "", - 'message': data.data ? data.data.arguments[1] : data.statusText, - 'backend_url': url - })); - $error.appendTo("body"); - $error.modal('show'); - }; + /* ---------------------------------------------------- + Widgets + ---------------------------------------------------- */ website.prompt = function (options) { /** @@ -229,11 +125,13 @@ dialog.on('click', '.btn-primary', function () { def.resolve(field.val(), field, dialog); dialog.remove(); + $('.modal-backdrop').remove(); }); }); dialog.on('hidden.bs.modal', function () { def.reject(); dialog.remove(); + $('.modal-backdrop').remove(); }); if (field.is('input[type="text"], select')) { field.keypress(function (e) { @@ -246,6 +144,16 @@ return def; }; + website.error = function(data, url) { + var $error = $(openerp.qweb.render('website.error_dialog', { + 'title': data.data ? data.data.arguments[0] : "", + 'message': data.data ? data.data.arguments[1] : data.statusText, + 'backend_url': url + })); + $error.appendTo("body"); + $error.modal('show'); + }; + website.form = function (url, method, params) { var form = document.createElement('form'); form.setAttribute('action', url); @@ -261,11 +169,117 @@ form.submit(); }; - dom_ready.then(function () { + website.init_kanban = function ($kanban) { + $('.js_kanban_col', $kanban).each(function () { + var $col = $(this); + var $pagination = $('.pagination', $col); + if(!$pagination.size()) { + return; + } + + var page_count = $col.data('page_count'); + var scope = $pagination.last().find("li").size()-2; + var kanban_url_col = $pagination.find("li a:first").attr("href").replace(/[0-9]+$/, ''); + + var data = { + 'domain': $col.data('domain'), + 'model': $col.data('model'), + 'template': $col.data('template'), + 'step': $col.data('step'), + 'orderby': $col.data('orderby') + }; + + $pagination.on('click', 'a', function (ev) { + ev.preventDefault(); + var $a = $(ev.target); + if($a.parent().hasClass('active')) { + return; + } + + var page = +$a.attr("href").split(",").pop().split('-')[1]; + data['page'] = page; + + $.post('/website/kanban', data, function (col) { + $col.find("> .thumbnail").remove(); + $pagination.last().before(col); + }); + + var page_start = page - parseInt(Math.floor((scope-1)/2), 10); + if (page_start < 1 ) page_start = 1; + var page_end = page_start + (scope-1); + if (page_end > page_count ) page_end = page_count; + + if (page_end - page_start < scope) { + page_start = page_end - scope > 0 ? page_end - scope : 1; + } + + $pagination.find('li.prev a').attr("href", kanban_url_col+(page-1 > 0 ? page-1 : 1)); + $pagination.find('li.next a').attr("href", kanban_url_col+(page < page_end ? page+1 : page_end)); + for(var i=0; i < scope; i++) { + $pagination.find('li:not(.prev):not(.next):eq('+i+') a').attr("href", kanban_url_col+(page_start+i)).html(page_start+i); + } + $pagination.find('li.active').removeClass('active'); + $pagination.find('li:has(a[href="'+kanban_url_col+page+'"])').addClass('active'); + + }); + + }); + }; + + /* ---------------------------------------------------- + Async Ready and Template loading + ---------------------------------------------------- */ + 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; + }); + }; + website.add_template_file('/website/static/src/xml/website.xml'); + + website.dom_ready = $.Deferred(); + $(document).ready(function () { + website.is_editable = website.is_editable || $('html').data('editable'); + website.is_editable_button= website.is_editable_button || $('html').data('editable'); + website.dom_ready.resolve(); + // fix for ie + if($.fn.placeholder) $('input, textarea').placeholder(); + }); + + var all_ready = null; + /** + * Returns a deferred resolved when the templates are loaded + * and the Widgets can be instanciated. + */ + website.ready = function() { + if (!all_ready) { + all_ready = website.dom_ready.then(function () { + return templates_def; + }).then(function () { + if (website.is_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); + } + }).promise(); + } + return all_ready; + }; - /* ----- BOOTSTRAP STUFF ---- */ - // $('.js_tooltip').bstooltip(); + website.inject_tour = function() { + // if a tour is active inject tour js + } + website.dom_ready.then(function () { /* ----- PUBLISHING STUFF ---- */ $(document).on('click', '.js_publish_management .js_publish_btn', function () { var $data = $(this).parents(".js_publish_management:first"); diff --git a/addons/website/static/src/js/website.mobile.js b/addons/website/static/src/js/website.mobile.js index 4acf2d78db8..88a558f5094 100644 --- a/addons/website/static/src/js/website.mobile.js +++ b/addons/website/static/src/js/website.mobile.js @@ -17,7 +17,10 @@ 'hidden.bs.modal': 'destroy' }, start: function () { - document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + "#mobile-preview"; + if (!window.location.origin) { // fix for ie9 + window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: ''); + } + document.getElementById("mobile-viewport").src = window.location.origin + (window.location.pathname.length ? '/' : '') + window.location.pathname + "#mobile-preview"; this.$el.modal(); }, destroy: function () { diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index bc2ea064731..56ad596d71a 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -5,20 +5,26 @@ if (!website.snippet) website.snippet = {}; website.snippet.readyAnimation = []; - website.snippet.start_animation = function () { + website.snippet.start_animation = function (editable_mode, $target) { for (var k in website.snippet.animationRegistry) { var Animation = website.snippet.animationRegistry[k]; - var selector = "[data-snippet-id='"+k+"']"; + var selector = ""; if (Animation.prototype.selector) { - selector += ", " + Animation.prototype.selector; + if (selector != "") selector += ", " + selector += Animation.prototype.selector; } + if ($target) { + if ($target.is(selector)) selector = $target; + else continue; + } + $(selector).each(function() { var $snipped_id = $(this); if ( !$snipped_id.parents("#oe_snippets").length && !$snipped_id.parent("body").length && !$snipped_id.data("snippet-view")) { website.snippet.readyAnimation.push($snipped_id); - $snipped_id.data("snippet-view", new Animation($snipped_id)); + $snipped_id.data("snippet-view", new Animation($snipped_id, editable_mode)); } }); } @@ -32,7 +38,9 @@ } }); }; - $(document).ready(website.snippet.start_animation); + $(document).ready(function () { + website.snippet.start_animation(); + }); website.snippet.animationRegistry = {}; @@ -41,9 +49,9 @@ $: function () { return this.$el.find.apply(this.$el, arguments); }, - init: function (dom) { + init: function (dom, editable_mode) { this.$el = this.$target = $(dom); - this.start(); + this.start(editable_mode); }, /* * start @@ -64,10 +72,14 @@ start: function () { this.$target.carousel({interval: 10000}); }, + stop: function () { + this.$target.carousel('pause'); + this.$target.removeData("bs.carousel"); + }, }); website.snippet.animationRegistry.parallax = website.snippet.Animation.extend({ - parallax: ".parallax", + selector: ".parallax", start: function () { var self = this; setTimeout(function () {self.set_values();}); @@ -104,7 +116,7 @@ img.onload = function () { var offset = 0; var padding = parseInt($(document.body).css("padding-top")); - if (speed < 1) { + if (speed > 1) { var inner_offset = - self.$target.outerHeight() + this.height / this.width * document.body.clientWidth; var outer_offset = self.$target.offset().top - (document.body.clientHeight - self.$target.outerHeight()) - padding; offset = - outer_offset * speed + inner_offset; @@ -119,4 +131,21 @@ } }); + website.snippet.animationRegistry.share = website.snippet.Animation.extend({ + selector: ".oe_share", + start: function () { + var url = encodeURIComponent(window.location.href); + var title = encodeURIComponent($("title").text()); + this.$target.find("a").each(function () { + var $a = $(this); + $a.attr("href", $(this).attr("href").replace("{url}", url).replace("{title}", title)); + if ($a.attr("target") && $a.attr("target").match(/_blank/i)) { + $a.click(function () { + window.open(this.href,'','menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=550,width=600'); + return false; + }); + } + }); + }, + }); })(); diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 23c5956ebf4..52deeabf5e6 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -1,6 +1,136 @@ (function () { 'use strict'; +/* Building block / Snippet Editor + + The building blocks appear in the edit bar website. These prebuilt html block + allowing the designer to easily generate content on a page (drag and drop). + Options allow snippets to add customizations part html code according to their + selector (jQuery) and javascript object. + + How to create content? + + Designers can add their own html block in the "snippets" (/website/views/snippets.xml). + The block must be added in one of four menus (structure, content, feature or effect). + Structure: + <div> + <div class="oe_snippet_thumbnail"> + <img class="oe_snippet_thumbnail_img" src="...image src..."/> + <span class="oe_snippet_thumbnail_title">...Block Name...</span> + </div> + <div class="oe_snippet_body"> + ... + <!-- + The block with class 'oe_snippet_body' is inserted in the page. + This class is removed when the block is dropped. + The block can be made of any html tag and content. --> + </div> + </div> + + How to create options? + + Designers can add their own html block in the "snippet_options" (/website/views/snippets.xml). + Structure: + + <div data-snippet-option-id='...' <!-- Required: javascript object id (but javascript + for this option object is not required) --> + data-selector="..." <!-- Required: jQuery selector. + Apply options on all The part of html who + match with this jQuery selector. + E.g.: If the selector is div, all div will be selected + and can be highlighted and assigned an editor. --> + data-selector-siblings="..." <!-- Optional: jQuery selector. + The html part can be insert or move beside + the selected html block --> + data-selector-children="..." <!-- Optional: jQuery selector. + The html part can be insert or move inside + the selected html block --> + data-selector-vertical-children='...'> <!-- Optional: jQuery selector. + The html part can be insert or move inside + the selected html block. The drop zone is + displayed vertically --> + ... + <li><a href="#">...</a></li> <!-- Optional: html li list. + List of menu items displayed in customize + menu. If the li tag have 'data-class', the + class is automaticcally added or removed to + the html content when the user select this item. --> + ... + <li class="dropdown-submenu" <!-- Optional: html li list exemple. !--> + data-required="true"> <!-- Optional: if only one item can be selected + and can't be unselect. !--> + <a tabindex="-1" href="#">...</a> <!-- bootstrap dropdown button !--> + <ul class="dropdown-menu"> + <li data-value="text_only"><a>...</a></li> <!-- by default data-value is apply + like a class to html block !--> + </ul> + </li> + </div> + + How to create a javascript object for an options? + + openerp.website.snippet.options["...option-id..."] = website.snippet.Option.extend({ + // start is called when the user click into a block or when the user drop a block + // into the page (just after the init method). + // start is usually used to bind event. + // + // this.$target: block html inserted inside the page + // this.$el: html li list of this options + // this.$overlay: html editor overlay who content resize bar, customize menu... + start: function () {}, + + + // onFocus is called when the user click inside the block inserted in page + // and when the user drop on block into the page + onFocus : function () {}, + + + // onBlur is called when the user click outside the block inserted in page, if + // the block is focused + onBlur : function () {}, + + + // on_clone is called when the snippet is duplicate + // @variables: $clone is allready inserted is the page + on_clone: function ($clone) {}, + + + // on_remove is called when the snippet is removed (dom is removing after this tigger) + on_remove: function () {}, + + + // drop_and_build_snippet is called just after that a thumbnail is drag and dropped + // into a drop zone. The content is already inserted in the page. + drop_and_build_snippet: function () {}, + + // select is called when a user select an item in the li list of options + // By default, if the li item have a data-value attribute, the data-vlue it's apply + // like a class to the html block (this.$target) + // @variables: next_previous = {$next, $prev} + // $next = next item selected or false + // $prev = previous item selected or false + select: function (event, next_previous) {} + + // preview is called when a user is on mouse over or mouse out of an item + // variables: next_previous = {$next, $prev} + // $next = next item selected or false + // $prev = previous item selected or false + preview: function (event, next_previous) {} + + // clean_for_save + // clean_for_save is called just before to save the vue + // Sometime it's important to remove or add some datas (contentEditable, added + // classes to a running animation...) + clean_for_save: function () {} + }); + + + // 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped + // 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated + +*/ + + var website = openerp.website; website.add_template_file('/website/static/src/xml/website.snippets.xml'); @@ -18,25 +148,18 @@ }, edit: function () { var self = this; - $("body").off('click'); + $("[data-oe-model] *, [data-oe-type=html] *").off('click'); window.snippets = this.snippets = new website.snippet.BuildingBlock(this); this.snippets.appendTo(this.$el); - this.on('rte:ready', this, function () { self.snippets.$button.removeClass("hidden"); - website.snippet.stop_animation(); - website.snippet.start_animation(); + website.snippet.stop_animation(); }); return this._super.apply(this, arguments); }, save: function () { - this.snippets.make_active(false); - - // FIXME: call clean_for_save on all snippets of the page, not only modified ones - // important for banner of parallax that changes data automatically. this.snippets.clean_for_save(); - remove_added_snippet_id(); this._super(); }, }); @@ -49,38 +172,11 @@ })) { return; } - hack_to_add_snippet_id() }); - // puts $el at the same absolute position as $target - function hack_to_add_snippet_id () { - _.each(website.snippet.selector, function (val) { - $(val[0]).each(function() { - if (!$(this).is("[data-snippet-id]") && $(this).parents("[data-oe-model]").length) { - $(this).attr("data-snippet-id", val[1]); - } - }); - }); - } - function remove_added_snippet_id () { - _.each(website.snippet.selector, function (val) { - $(val[0]).each(function() { - if ($(this).data("snippet-id") === val[1]) { - $(this).removeAttr("data-snippet-id"); - } - }); - }); - } - - $(document).ready(function() { - hack_to_add_snippet_id(); - }); - - // 'snippet-dropped' is triggered on '#oe_snippets' whith $target as attribute when a snippet is dropped - // 'snippet-activated' is triggered on '#oe_snippets' (and on snippet) when a snippet is activated - if (!website.snippet) website.snippet = {}; - website.snippet.styles = {}; + website.snippet.templateOptions = {}; + website.snippet.globalSelector = ""; website.snippet.selector = []; website.snippet.BuildingBlock = openerp.Widget.extend({ template: 'website.snippets', @@ -92,7 +188,6 @@ $("<div id='oe_manipulators'></div>").appendTo('body'); } this.$active_snipped_id = false; - hack_to_add_snippet_id(); this.snippets = []; observer.observe(document.body, { @@ -159,17 +254,23 @@ .then(function (html) { var $html = $(html); - var $styles = $html.find("[data-snippet-style-id]"); + var selector = []; + var $styles = $html.find("[data-snippet-option-id]"); $styles.each(function () { var $style = $(this); - var style_id = $style.data('snippet-style-id'); - website.snippet.styles[style_id] = { - 'snippet-style-id' : style_id, + var style_id = $style.data('snippet-option-id'); + website.snippet.templateOptions[style_id] = { + 'snippet-option-id' : style_id, 'selector': $style.data('selector'), '$el': $style, + 'selector-siblings': $style.data('selector-siblings'), + 'selector-children': $style.data('selector-children'), + 'selector-vertical-children': $style.data('selector-vertical-children') }; + selector.push($style.data('selector')); }); $styles.addClass("hidden"); + website.snippet.globalSelector = selector.join(","); self.$snippets = $html.find(".tab-content > div > div").addClass("oe_snippet"); self.$el.append($html); @@ -190,13 +291,14 @@ var mt = parseInt($target.css("margin-top") || 0); var mb = parseInt($target.css("margin-bottom") || 0); $el.css({ - 'position': 'absolute', 'width': $target.outerWidth(), - 'height': $target.outerHeight() + mt + mb+1, - 'top': pos.top - mt, + 'top': pos.top - mt - 5, 'left': pos.left }); - $el.find(".oe_handle.size").css("bottom", (mb-7)+'px'); + $el.find(".oe_handle.e,.oe_handle.w").css({'height': $target.outerHeight() + mt + mb+1}); + $el.find(".oe_handle.s").css({'top': $target.outerHeight() + mt + mb}); + $el.find(".oe_handle.size").css({'top': $target.outerHeight() + mt}); + $el.find(".oe_handle.s,.oe_handle.n").css({'width': $target.outerWidth()-2}); }, show: function () { this.$el.removeClass("hidden"); @@ -213,18 +315,24 @@ bind_snippet_click_editor: function () { var self = this; - var snipped_event_flag = false; + var snipped_event_flag; $("#wrapwrap").on('click', function (event) { - if (snipped_event_flag) { + if (snipped_event_flag || !event.srcElement) { return; } snipped_event_flag = true; + setTimeout(function () {snipped_event_flag = false;}, 0); var $target = $(event.srcElement); - if (!$target.attr("data-snippet-id")) { - $target = $target.parents("[data-snippet-id]:first"); + + if ($target.parents(".oe_overlay").length) { + return; + } + + if (!$target.is(website.snippet.globalSelector)) { + $target = $target.parents(website.snippet.globalSelector).first(); } - if (!$target.attr("data-oe-model") && !$target.parents("[data-oe-model]:first").length) { + if (!self.dom_filter($target).length) { $target = false; } if (self.$active_snipped_id && self.$active_snipped_id.is($target)) { @@ -248,13 +356,16 @@ } }, clean_for_save: function () { - for (var k in this.snippets) { - if (!this.snippets.hasOwnProperty(k)) { continue; } - var editor = $(this.snippets[k]).data("snippet-editor"); + var self = this; + $(website.snippet.globalSelector).each(function () { + var $snippet = $(this); + self.make_active($snippet); + self.make_active(false); + var editor = $snippet.data("snippet-editor"); if (editor) { editor.clean_for_save(); } - } + }); }, make_active: function ($snippet) { if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) { @@ -281,8 +392,7 @@ if (typeof $snippet.data("snippet-editor") === 'undefined') { var $targets = this.activate_overlay_zones($snippet); if (!$targets.length) return; - var editor = website.snippet.editorRegistry[$snippet.data("snippet-id")] || website.snippet.editorRegistry.resize; - $snippet.data("snippet-editor", new editor(this, $snippet)); + $snippet.data("snippet-editor", new website.snippet.Editor(this, $snippet)); } this.cover_target($snippet.data('overlay'), $snippet); }, @@ -318,36 +428,47 @@ start: function(){ self.hide(); dropped = false; + // snippet_selectors => to get selector-siblings, selector-children, selector-vertical-children $snippet = $(this); - snipped_id = $snippet.data('snippet-id'); + $toInsert = $snippet.find('.oe_snippet_body').clone(); + $toInsert.removeClass('oe_snippet_body'); + + var selector = []; + var selector_siblings = []; + var selector_children = []; + var selector_vertical_children = []; + for (var k in website.snippet.templateOptions) { + if ($toInsert.is(website.snippet.templateOptions[k].selector)) { + selector.push(website.snippet.templateOptions[k].selector); + if (website.snippet.templateOptions[k]['selector-siblings']) + selector_siblings.push(website.snippet.templateOptions[k]['selector-siblings']); + if (website.snippet.templateOptions[k]['selector-children']) + selector_children.push(website.snippet.templateOptions[k]['selector-children']); + if (website.snippet.templateOptions[k]['selector-vertical-children']) + selector_vertical_children.push(website.snippet.templateOptions[k]['selector-vertical-children']); + } + } + 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')) { + if (!selector_siblings.length && !selector_children.length && !selector_vertical_children.length) { console.debug($snippet.data("snippet-id") + " have oe_snippet_body class and have not for insert action"+ "data-selector-siblings, data-selector-children or data-selector-vertical-children tag for mutate action"); return; } self.activate_insertion_zones({ - siblings: $snippet.data('selector-siblings'), - children: $snippet.data('selector-children'), - vertical_children: $snippet.data('selector-vertical-children') + siblings: selector_siblings.join(","), + children: selector_children.join(","), + vertical_children: selector_vertical_children.join(","), }); - $toInsert = $snippet.find('.oe_snippet_body').clone(); - $toInsert.removeClass('oe_snippet_body'); - $toInsert.data('src-snippet-id', snipped_id); - if (!$toInsert.data('snippet-id')) { - $toInsert.attr('data-snippet-id', snipped_id); - } else { - snipped_id = $toInsert.data('snippet-id'); - } - } else if( action === 'mutate' ){ if (!$snippet.data('selector')) { console.debug($snippet.data("snippet-id") + " have not oe_snippet_body class and have not data-selector tag"); return; } - var $targets = self.activate_overlay_zones($snippet.data('selector')); + var $targets = self.activate_overlay_zones(selector_children.join(",")); $targets.each(function(){ var $clone = $(this).data('overlay').clone(); $clone.addClass("oe_drop_zone").data('target', $(this)); @@ -382,38 +503,29 @@ } $('.oe_drop_zone').droppable('destroy').remove(); + if (dropped) { var $target = false; - if(action === 'insert'){ - $target = $toInsert; + $target = $toInsert; - website.snippet.start_animation(); + setTimeout(function () { + $("#oe_snippets").trigger('snippet-dropped', $target); + website.snippet.start_animation(true, $target); + // drop_and_build_snippet self.create_overlay($target); if ($target.data("snippet-editor")) { - $target.data("snippet-editor").drop_and_build_snippet($target); + $target.data("snippet-editor").drop_and_build_snippet(); } - - $target.find("[data-snippet-id]").each(function () { - var $snippet = $(this); - var snippet_id = $snippet.data("data-snippet-id"); - self.create_overlay($snippet); - if ($snippet.data("snippet-editor")) { - $snippet.data("snippet-editor").drop_and_build_snippet($snippet); - } - }); - - } 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.drop_and_build_snippet($target); + for (var k in website.snippet.templateOptions) { + $target.find(website.snippet.templateOptions[k].selector).each(function () { + var $snippet = $(this); + self.create_overlay($snippet); + if ($snippet.data("snippet-editor")) { + $snippet.data("snippet-editor").drop_and_build_snippet(); + } + }); } - } - setTimeout(function () { - $("#oe_snippets").trigger('snippet-dropped', $target); // reset snippet for rte $target.removeData("snippet-editor"); @@ -421,16 +533,15 @@ $target.data("overlay").remove(); $target.removeData("overlay"); } - self.create_overlay($target); - $target.find("[data-snippet-id]").each(function () { + $target.find(website.snippet.globalSelector).each(function () { var $snippet = $(this); $snippet.removeData("snippet-editor"); if ($snippet.data("overlay")) { $snippet.data("overlay").remove(); $snippet.removeData("overlay"); } - self.create_overlay($snippet); }); + self.create_overlay($target); // end self.make_active($target); @@ -550,7 +661,7 @@ // generate drop zones covering the elements selected by the selector // we generate overlay drop zones only to get an idea of where the snippet are, the drop activate_overlay_zones: function(selector){ - var $targets = this.dom_filter(selector || '[data-snippet-id]'); + var $targets = this.dom_filter(selector); var self = this; if (typeof selector !== 'string' && !$targets.length) { @@ -577,6 +688,20 @@ var $target = $(this); if (!$target.data('overlay')) { var $zone = $(openerp.qweb.render('website.snippet_overlay')); + + // fix for pointer-events: none with ie9 + if (document.body && document.body.addEventListener) { + $zone.on("click mousedown mousedown", function passThrough(event) { + event.preventDefault(); + $target.each(function() { + // check if clicked point (taken from event) is inside element + event.srcElement = this; + $(this).trigger(event.type); + }); + return false; + }); + } + $zone.appendTo('#oe_manipulators'); $zone.data('target',$target); $target.data('overlay',$zone); @@ -595,23 +720,24 @@ }); - website.snippet.styleRegistry = {}; - website.snippet.StyleEditor = openerp.Class.extend({ + website.snippet.options = {}; + website.snippet.Option = openerp.Class.extend({ // initialisation (don't overwrite) - init: function (parent, $target, snippet_id) { - this.parent = parent; + init: function (BuildingBlock, editor, $target, snippet_id) { + this.BuildingBlock = BuildingBlock; + this.editor = editor; this.$target = $target; - var styles = this.$target.data("snippet-style-ids") || {}; + var styles = this.$target.data("snippet-option-ids") || {}; styles[snippet_id] = this; - this.$target.data("snippet-style-ids", styles); + this.$target.data("snippet-option-ids", styles); this.$overlay = this.$target.data('overlay'); - this['snippet-style-id'] = snippet_id; - this.$el = website.snippet.styles[snippet_id].$el.find(">li").clone(); + this['snippet-option-id'] = snippet_id; + this.$el = website.snippet.templateOptions[snippet_id].$el.find(">li").clone(); this.required = this.$el.data("required"); this.set_active(); - this.$el.find('li[data-class] a').on('mouseover mouseout click', _.bind(this._mouse, this)); + this.$el.find('li[data-value] a').on('mouseover mouseout click', _.bind(this._mouse, this)); this.$target.on('snippet-style-reset', _.bind(this.set_active, this)); this.start(); @@ -631,9 +757,9 @@ var $prev, $next; if (event.type === 'mouseout') { $prev = $(event.currentTarget).parent(); - $next = this.$el.find("li[data-class].active"); + $next = this.$el.find("li[data-value].active"); } else { - $prev = this.$el.find("li[data-class].active"); + $prev = this.$el.find("li[data-value].active"); $next = $(event.currentTarget).parent(); } if (!$prev.length) { @@ -653,70 +779,77 @@ self.set_active(); self.$target.trigger("snippet-style-change", [self, np]); },0); - this.select(event, {'$next': $next, '$prev': $prev}); + this.select({'$next': $next, '$prev': $prev}); } else { setTimeout(function () { self.$target.trigger("snippet-style-preview", [self, np]); },0); - this.preview(event, np); + this.preview(np); } }, - // start is call just after the init + /* set_active + * select and set item active or not (add highlight item and his parents) + * called before start + */ + set_active: function () { + var self = this; + this.$el.find('li').removeClass("active"); + var $active = this.$el.find('li[data-value]') + .filter(function () { + var $li = $(this); + return ($li.data('value') && self.$target.hasClass($li.data('value'))); + }) + .first() + .addClass("active"); + this.$el.find('li:has(li[data-value].active)').addClass("active"); + }, + start: function () { }, - /* select - * called when a user select an item - * variables: np = {$next, $prev} - * $next is false if they are no next item selected - * $prev is false if they are no previous item selected - */ - select: function (event, np) { + + onFocus : function () { + }, + + onBlur : function () { + }, + + on_clone: function ($clone) { + }, + + on_remove: function () { + }, + + drop_and_build_snippet: function () { + }, + + select: function (np) { var self = this; // add or remove html class - if (np.$prev) { - this.$target.removeClass(np.$prev.data('class' || "")); + if (np.$prev && this.required) { + this.$target.removeClass(np.$prev.data('value' || "")); } if (np.$next) { - this.$target.addClass(np.$next.data('class') || ""); + this.$target.addClass(np.$next.data('value') || ""); } }, - /* preview - * called when a user is on mouse over or mouse out of an item - * variables: np = {$next, $prev} - * $next is false if they are no next item selected - * $prev is false if they are no previous item selected - */ - preview: function (event, np) { + + preview: function (np) { var self = this; // add or remove html class - if (np.$prev) { - this.$target.removeClass(np.$prev.data('class') || ""); + if (np.$prev && this.required) { + this.$target.removeClass(np.$prev.data('value') || ""); } if (np.$next) { - this.$target.addClass(np.$next.data('class') || ""); + this.$target.addClass(np.$next.data('value') || ""); } }, - /* set_active - * select and set item active or not (add highlight item and his parents) - * called before start - */ - set_active: function () { - var self = this; - this.$el.find('li').removeClass("active"); - var $active = this.$el.find('li[data-class]') - .filter(function () { - var $li = $(this); - return ($li.data('class') && self.$target.hasClass($li.data('class'))); - }) - .first() - .addClass("active"); - this.$el.find('li:has(li[data-class].active)').addClass("active"); + + clean_for_save: function () { } }); - - website.snippet.styleRegistry.background = website.snippet.StyleEditor.extend({ + website.snippet.options.background = website.snippet.Option.extend({ _get_bg: function () { return this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, ""); }, @@ -726,11 +859,11 @@ start: function () { this._super(); var src = this._get_bg(); - this.$el.find("li[data-class].active.oe_custom_bg").data("src", src); + this.$el.find("li[data-value].active.oe_custom_bg").data("src", src); }, - select: function(event, np) { + select: function(np) { var self = this; - this._super(event, np); + this._super(np); if (np.$next) { if (np.$next.hasClass("oe_custom_bg")) { var editor = new website.editor.ImageDialog(); @@ -742,7 +875,7 @@ }); editor.on('cancel', self, function () { if (!np.$prev || np.$prev.data("src") === "") { - self.$target.removeClass(np.$next.data("class")); + self.$target.removeClass(np.$next.data("value")); self.$target.trigger("snippet-style-change", [self, np]); } }); @@ -752,11 +885,11 @@ } } else { this._set_bg(false); - this.$target.removeClass(np.$prev.data("class")); + this.$target.removeClass(np.$prev.data("value")); } }, - preview: function (event, np) { - this._super(event, np); + preview: function (np) { + this._super(np); if (np.$next) { this._set_bg(np.$next.data("src")); } @@ -765,248 +898,216 @@ var self = this; var bg = self.$target.css("background-image"); this.$el.find('li').removeClass("active"); - var $active = this.$el.find('li[data-class]') + var $active = this.$el.find('li[data-value]') .filter(function () { var $li = $(this); return ($li.data('src') && bg.indexOf($li.data('src')) >= 0) || - (!$li.data('src') && self.$target.hasClass($li.data('class'))); + (!$li.data('src') && self.$target.hasClass($li.data('value'))); }) .first(); if (!$active.length) { $active = this.$target.css("background-image") !== 'none' ? - this.$el.find('li[data-class].oe_custom_bg') : - this.$el.find('li[data-class=""]'); + this.$el.find('li[data-value].oe_custom_bg') : + this.$el.find('li[data-value=""]'); } $active.addClass("active"); - this.$el.find('li:has(li[data-class].active)').addClass("active"); + this.$el.find('li:has(li[data-value].active)').addClass("active"); } }); - - website.snippet.editorRegistry = {}; - website.snippet.Editor = openerp.Class.extend({ - init: function (parent, dom) { - this.parent = parent; - this.$target = $(dom); - this.$overlay = this.$target.data('overlay'); - this.snippet_id = this.$target.data("snippet-id"); - this._readXMLData(); - this.load_style_options(); - this.get_parent_block(); - this.start(); + website.snippet.options.slider = website.snippet.Option.extend({ + unique_id: function () { + var id = 0; + $(".carousel").each(function () { + var cid = 1 + parseInt($(this).attr("id").replace(/[^0123456789]/g, ''),10); + if (id < cid) id = cid; + }); + return "myCarousel" + id; }, + drop_and_build_snippet: function() { + this.id = this.unique_id(); + this.$target.attr("id", this.id); + this.$target.find("[data-slide]").attr("href", "#" + this.id); + this.$target.find("[data-slide-to]").attr("data-target", "#" + this.id); - /* - * _readXMLData - * Read data XML and set value into: - * this.$el : - * all xml data - * this.$overlay : - * Dom hover the $target who content options - * this.$editor : - * content of .oe_snippet_options - * Displayed into the overlay options on focus - */ - _readXMLData: function() { - 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")); - if ($options.find(".oe_options ul li").length) { - $options.find(".oe_options").removeClass("hidden"); - } + this.rebind_event(); }, - - - // activate drag and drop for the snippets in the snippet toolbar - _drag_and_drop: function(){ - var self = this; - this.dropped = false; - this.$overlay.draggable({ - greedy: true, - appendTo: 'body', - cursor: "move", - handle: ".oe_snippet_move", - cursorAt: { - left: 18, - top: 14 - }, - helper: function() { - var $clone = $(this).clone().css({width: "24px", height: "24px", border: 0}); - $clone.find(".oe_overlay_options >:not(:contains(.oe_snippet_move)), .oe_handle").remove(); - $clone.find(":not(.glyphicon)").css({position: 'absolute', top: 0, left: 0}); - $clone.appendTo("body").removeClass("hidden"); - return $clone; - }, - start: _.bind(self._drag_and_drop_start, self), - stop: _.bind(self._drag_and_drop_stop, self) - }); + on_clone: function ($clone) { + var id = this.unique_id(); + $clone.attr("id", id); + $clone.find("[data-slide]").attr("href", "#" + id); + $clone.find("[data-slide-to]").attr("data-target", "#" + id); }, - _drag_and_drop_after_insert_dropzone: function (){}, - _drag_and_drop_active_drop_zone: function ($zones){ + // rebind event to active carousel on edit mode + rebind_event: function () { var self = this; - $zones.droppable({ - over: function(){ - $(".oe_drop_zone.hide").removeClass("hide"); - $(this).addClass("hide").first().after(self.$target); - self.dropped = true; - }, - out: function(){ - $(this).removeClass("hide"); - self.$target.detach(); - self.dropped = false; - }, - }); + this.$target.find('.carousel-indicators [data-slide-to]').off('click').on('click', function () { + self.$target.carousel(+$(this).data('slide-to')); }); + + this.$target.attr('contentEditable', 'false'); + this.$target.find('.oe_structure, .content>.row, [data-slide]').attr('contentEditable', 'true'); }, - _drag_and_drop_start: function (){ + clean_for_save: function () { + this._super(); + this.$target.find(".item").removeClass("next prev left right"); + if(!this.$target.find(".item.active").length) { + this.$target.find(".item:first").addClass("active"); + } + }, + start : function () { var self = this; - self.parent.hide(); - self.parent.editor_busy = true; - self.size = { - width: self.$target.width(), - height: self.$target.height() - }; - self.$target.after("<div class='oe_drop_clone' style='display: none;'/>"); - self.$target.detach(); - self.$overlay.addClass("hidden"); - - self.parent.activate_insertion_zones({ - siblings: self.$el ? self.$el.data('selector-siblings') : false, - children: self.$el ? self.$el.data('selector-children') : false, - vertical_children: self.$el ? self.$el.data('selector-vertical-children') : false, - }); + this._super(); + this.$target.carousel({interval: false}); + this.id = this.$target.attr("id"); + this.$inner = this.$target.find('.carousel-inner'); + this.$indicators = this.$target.find('.carousel-indicators'); - $("body").addClass('move-important'); + this.$el.find(".js_add").on('click', function () {self.on_add_slide(); return false;}); + this.$el.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;}); - self._drag_and_drop_after_insert_dropzone(); - self._drag_and_drop_active_drop_zone($('.oe_drop_zone')); + this.$target.carousel('pause'); + this.rebind_event(); }, - _drag_and_drop_stop: function (){ + on_add_slide: function () { var self = this; - if (!self.dropped) { - $(".oe_drop_clone").after(self.$target); - } - self.$overlay.removeClass("hidden"); - $("body").removeClass('move-important'); - $('.oe_drop_zone').droppable('destroy').remove(); - $(".oe_drop_clone, .oe_drop_to_remove").remove(); - self.parent.editor_busy = false; - self.get_parent_block(); - setTimeout(function () {self.parent.create_overlay(self.$target);},0); - }, + var cycle = this.$inner.find('.item').length; + var $active = this.$inner.find('.item.active, .item.prev, .item.next').first(); + var index = $active.index(); + this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden"); + this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>'); - load_style_options: function () { - var self = this; - var $styles = this.$overlay.find('.oe_options'); - var $ul = $styles.find('ul:first'); - _.each(website.snippet.styles, function (val) { - if (!self.parent.dom_filter(val.selector).is(self.$target)) { - return; - } - var Editor = website.snippet.styleRegistry[val['snippet-style-id']] || website.snippet.StyleEditor; - var editor = new Editor(self, self.$target, val['snippet-style-id']); - $ul.prepend(editor.$el.addClass("snippet-style-" + val['snippet-style-id'])); - }); + var $clone = this.$target.find(".item.active").clone(); - if ($ul.find("li").length) { - $styles.removeClass("hidden"); - } + // insert + $clone.removeClass('active').insertAfter($active); + setTimeout(function() { + self.$target.carousel().carousel(++index); + self.rebind_event(); + },0); + return $clone; }, - - get_parent_block: function () { + on_remove_slide: function () { + if (this.remove_process) { + return; + } var self = this; - var $button = this.$overlay.find('.oe_snippet_parent'); - var $parent = this.$target.parents("[data-snippet-id]:first"); - if ($parent.length) { - $button.removeClass("hidden"); - $button.off("click").on('click', function (event) { - event.preventDefault(); - setTimeout(function () { - self.parent.make_active($parent); - }, 0); + var new_index = 0; + var cycle = this.$inner.find('.item').length - 1; + var index = this.$inner.find('.item.active').index(); + + if (cycle > 0) { + this.remove_process = true; + var $el = this.$inner.find('.item.active'); + self.$target.on('slid.bs.carousel', function (event) { + $el.remove(); + self.$indicators.find("li:last").remove(); + self.$target.off('slid.bs.carousel'); + self.rebind_event(); + self.remove_process = false; + if (cycle == 1) { + self.on_remove_slide(event); + } }); + setTimeout(function () { + self.$target.carousel( index > 0 ? --index : cycle ); + }, 500); } else { - $button.addClass("hidden"); + this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden"); } }, - - /* - * start - * This method is called after init and _readXMLData - */ - start: function () { - var self = this; - this.$overlay.on('click', '.oe_snippet_clone', _.bind(this.on_clone, this)); - this.$overlay.on('click', '.oe_snippet_remove', _.bind(this.on_remove, this)); - this._drag_and_drop(); + }); + website.snippet.options.carousel = website.snippet.options.slider.extend({ + getSize: function () { + this.grid = this._super(); + this.grid.size = 8; + return this.grid; }, - - on_clone: function () { - var $clone = this.$target.clone(false); - this.$target.after($clone); - return false; + clean_for_save: function () { + this._super(); + this.$target.css("background-image", ""); + this.$target.removeClass(this._class); }, - - on_remove: function () { - this.onBlur(); - var index = _.indexOf(this.parent.snippets, this.$target.get(0)); - delete this.parent.snippets[index]; - this.$target.remove(); - this.$overlay.remove(); - return false; + load_style_options : function () { + this._super(); + $(".snippet-style-size li[data-value='']").remove(); }, + start : function () { + var self = this; + this._super(); - /* - * drop_and_build_snippet - * This method is called just after that a thumbnail is drag and dropped into a drop zone - * (after the insertion of this.$body, if this.$body exists) - */ - drop_and_build_snippet: function ($target) { - }, + // set background and prepare to clean for save + var add_class = function (c){ + if (c) self._class = (self._class || "").replace(new RegExp("[ ]+" + c.replace(" ", "|[ ]+")), '') + ' ' + c; + return self._class || ""; + }; + this.$target.on('snippet-style-change snippet-style-preview', function (event, style, np) { + var $active = self.$target.find(".item.active"); + if (style['snippet-option-id'] === "size") return; + if (style['snippet-option-id'] === "background") { + $active.css("background-image", self.$target.css("background-image")); + } + if (np.$prev) { + $active.removeClass(np.$prev.data("value")); + } + if (np.$next) { + $active.addClass(np.$next.data("value")); + add_class(np.$next.data("value")); + } + }); + this.$target.on('slid', function () { // slide.bs.carousel + var $active = self.$target.find(".item.active"); + self.$target + .css("background-image", $active.css("background-image")) + .removeClass(add_class($active.attr("class"))) + .addClass($active.attr("class")) + .trigger("snippet-style-reset"); - /* onFocus - * This method is called when the user click inside the snippet in the dom - */ - onFocus : function () { - this.$overlay.addClass('oe_active'); + self.$target.carousel("pause"); + }); + this.$target.trigger('slid'); }, + on_add_slide: function () { + var $clone = this._super(); - /* onFocus - * This method is called when the user click outside the snippet in the dom, after a focus - */ - onBlur : function () { - this.$overlay.removeClass('oe_active'); + // choose an other background + var bg = this.$target.data("snippet-option-ids").background; + if (!bg) return $clone; + + var $styles = bg.$el.find("li[data-value]:not(.oe_custom_bg)"); + var styles_index = $styles.index($styles.filter(".active")[0]); + $styles.removeClass("active"); + var $select = $($styles[styles_index >= $styles.length-1 ? 0 : styles_index+1]); + $select.addClass("active"); + $clone.css("background-image", $select.data("src") ? "url('"+ $select.data("src") +"')" : ""); + $clone.addClass($select.data("value") || ""); + + return $clone; }, + // rebind event to active carousel on edit mode + rebind_event: function () { + var self = this; + this.$target.find('.carousel-control').off('click').on('click', function () { + self.$target.carousel( $(this).data('slide')); }); - /* clean_for_save - * function called just before save vue - */ - clean_for_save: function () { - this.$target.removeAttr('contentEditable') - .find('*').removeAttr('contentEditable'); - this.$target.removeAttr('attributeEditable') - .find('*').removeAttr('attributeEditable'); + this.$target.find('.carousel-image, .carousel-inner .content > div').attr('contentEditable', 'true'); + this.$target.find('.carousel-image').attr('attributeEditable', 'true'); + this._super(); }, }); - - website.snippet.editorRegistry.resize = website.snippet.Editor.extend({ + website.snippet.options.marginAndResize = website.snippet.Option.extend({ start: function () { var self = this; this._super(); - var $box = $(openerp.qweb.render("website.snippets.resize")); var resize_values = this.getSize(); - if (!resize_values.n) $box.find(".oe_handle.n").remove(); - if (!resize_values.s) $box.find(".oe_handle.s").remove(); - if (!resize_values.e) $box.find(".oe_handle.e").remove(); - if (!resize_values.w) $box.find(".oe_handle.w").remove(); - if (!resize_values.size) $box.find(".oe_handle.size").remove(); + if (resize_values.n) this.$overlay.find(".oe_handle.n").removeClass("readonly"); + if (resize_values.s) this.$overlay.find(".oe_handle.s").removeClass("readonly"); + if (resize_values.e) this.$overlay.find(".oe_handle.e").removeClass("readonly"); + if (resize_values.w) this.$overlay.find(".oe_handle.w").removeClass("readonly"); + if (resize_values.size) this.$overlay.find(".oe_handle.size").removeClass("readonly"); - this.$overlay.append($box.find(".oe_handles").html()); - - this.$overlay.find(".oe_handle:not(:has(.oe_handle_button)), .oe_handle .oe_handle_button").on('mousedown', function (event){ + this.$overlay.find(".oe_handle:not(.size), .oe_handle.size .size").on('mousedown', function (event){ event.preventDefault(); var $handle = $(this); @@ -1057,7 +1158,7 @@ var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[-]*[0-9]+'), 'g'); } - self.parent.editor_busy = true; + self.BuildingBlock.editor_busy = true; var cursor = $handle.css("cursor")+'-important'; var $body = $(document.body); @@ -1071,7 +1172,7 @@ if (dy <= 0) dy = resize; self.$target.css("height", dy+"px"); self.on_resize(compass, null, dy); - self.parent.cover_target(self.$overlay, self.$target); + self.BuildingBlock.cover_target(self.$overlay, self.$target); return; } var dd = event['page'+XY] - xy + resize[1][begin]; @@ -1094,7 +1195,7 @@ if (change) { self.on_resize(compass, beginClass, current); - self.parent.cover_target(self.$overlay, self.$target); + self.BuildingBlock.cover_target(self.$overlay, self.$target); } }; @@ -1102,22 +1203,20 @@ $body.unbind('mousemove', body_mousemove); $body.unbind('mouseup', body_mouseup); $body.removeClass(cursor); - self.parent.editor_busy = false; + self.BuildingBlock.editor_busy = false; self.$target.removeClass("resize_editor_busy"); }; $body.mousemove(body_mousemove); $body.mouseup(body_mouseup); }); + this.$overlay.find(".oe_handle.size .auto_size").on('click', function (event){ + self.$target.css("height", ""); + self.BuildingBlock.cover_target(self.$overlay, self.$target); + return false; + }); }, getSize: function () { - var grid = [0,4,8,16,32,48,64,92,128]; - this.grid = { - // list of class (Array), grid (Array), default value (INT) - n: [_.map(grid, function (v) {return 'mt'+v;}), grid], - s: [_.map(grid, function (v) {return 'mb'+v;}), grid], - // INT if the user can resize the snippet (resizing per INT px) - size: null - }; + this.grid = {}; return this.grid; }, @@ -1163,8 +1262,21 @@ this.change_cursor(); } }); - - website.snippet.editorRegistry.colmd = website.snippet.editorRegistry.resize.extend({ + website.snippet.options["margin-y"] = website.snippet.options.marginAndResize.extend({ + getSize: function () { + this.grid = this._super(); + var grid = [0,4,8,16,32,48,64,92,128]; + this.grid = { + // list of class (Array), grid (Array), default value (INT) + n: [_.map(grid, function (v) {return 'mt'+v;}), grid], + s: [_.map(grid, function (v) {return 'mb'+v;}), grid], + // INT if the user can resize the snippet (resizing per INT px) + size: null + }; + return this.grid; + }, + }); + website.snippet.options["margin-x"] = website.snippet.options.marginAndResize.extend({ getSize: function () { this.grid = this._super(); var width = this.$target.parents(".row:first").first().outerWidth(); @@ -1206,30 +1318,26 @@ this._super(); }, hide_remove_button: function() { - this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", - !this.$target.siblings().length && this.$target.parents("[data-snippet-id]:first").find("[data-snippet-id='colmd']").length > 1); + this.$overlay.find('.oe_snippet_remove').toggleClass("hidden", !this.$target.siblings().length); }, onFocus : function () { this._super(); this.hide_remove_button(); }, - on_clone: function () { - var $clone = this.$target.clone(false); + on_clone: function ($clone) { var _class = $clone.attr("class").replace(/\s*(col-lg-offset-|col-md-offset-)([0-9-]+)/g, ''); - _class += ' col-md-1'; $clone.attr("class", _class); - this.$target.after($clone); this.hide_remove_button(); return false; }, on_remove: function () { if (!this.$target.siblings().length) { - var $parent = this.$target.parents("[data-snippet-id]:first"); - if($parent.find("[data-snippet-id='colmd']").length > 1) { + var $parent = this.$target.parents(".row:first"); + if($parent.find("[class*='col-md']").length > 1) { return false; } else { if (!$parent.data("snippet-editor")) { - this.parent.create_overlay($parent); + this.BuildingBlock.create_overlay($parent); } $parent.data("snippet-editor").on_remove(); } @@ -1263,169 +1371,15 @@ }, }); - website.snippet.editorRegistry.slider = website.snippet.editorRegistry.resize.extend({ - drop_and_build_snippet: function() { - var id = $(".carousel").length; - this.id = "myCarousel" + id; - this.$target.attr("id", this.id); - this.$target.find(".carousel-control").attr("href", "#myCarousel" + id); - this.$target.find("[data-target]").attr("data-target", "#myCarousel" + id); - - this.rebind_event(); - }, - // rebind event to active carousel on edit mode - rebind_event: function () { - var self = this; - this.$target.find('.carousel-indicators [data-target]').off('click').on('click', function () { - self.$target.carousel(+$(this).data('slide-to')); }); - - this.$target.attr('contentEditable', 'false'); - this.$target.find('.oe_structure, .content>.row').attr('contentEditable', 'true'); - }, - clean_for_save: function () { - this._super(); - this.$target.find(".item").removeClass("next prev left right"); - if(!this.$target.find(".item.active").length) { - this.$target.find(".item:first").addClass("active"); - } - }, - start : function () { - var self = this; - 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', function () {self.on_add_slide(); return false;}); - this.$editor.find(".js_remove").on('click', function () {self.on_remove_slide(); return false;}); - - this.$target.carousel('pause'); - this.rebind_event(); - }, - on_add_slide: function () { - var self = this; - var cycle = this.$inner.find('.item').length; - var $active = this.$inner.find('.item.active, .item.prev, .item.next').first(); - var index = $active.index(); - this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden"); - this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>'); - - var $clone = this.$el.find(".item.active").clone(); - - // insert - $clone.removeClass('active').insertAfter($active); - setTimeout(function() { - self.$target.carousel().carousel(++index); - self.rebind_event(); - },0); - return $clone; - }, - on_remove_slide: function () { - if (this.remove_process) { - return; - } - 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.remove_process = true; - var $el = this.$inner.find('.item.active'); - self.$target.on('slid.bs.carousel', function (event) { - $el.remove(); - self.$indicators.find("li:last").remove(); - self.$target.off('slid.bs.carousel'); - self.rebind_event(); - self.remove_process = false; - if (cycle == 1) { - self.on_remove_slide(event); - } - }); - setTimeout(function () { - self.$target.carousel( index > 0 ? --index : cycle ); - }, 500); - } else { - this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden"); - } - }, - }); - - website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.slider.extend({ + website.snippet.options["resize"] = website.snippet.options.marginAndResize.extend({ getSize: function () { this.grid = this._super(); this.grid.size = 8; return this.grid; }, - clean_for_save: function () { - this._super(); - this.$target.css("background-image", ""); - this.$target.removeClass(this._class); - }, - load_style_options : function () { - this._super(); - $(".snippet-style-size li[data-class='']").remove(); - }, - start : function () { - var self = this; - this._super(); - - // set background and prepare to clean for save - var add_class = function (c){ - if (c) self._class = (self._class || "").replace(new RegExp("[ ]+" + c.replace(" ", "|[ ]+")), '') + ' ' + c; - return self._class || ""; - }; - this.$target.on('snippet-style-change snippet-style-preview', function (event, style, np) { - var $active = self.$target.find(".item.active"); - if (style['snippet-style-id'] === "size") return; - if (style['snippet-style-id'] === "background") { - $active.css("background-image", self.$target.css("background-image")); - } - if (np.$prev) { - $active.removeClass(np.$prev.data("class")); - } - if (np.$next) { - $active.addClass(np.$next.data("class")); - add_class(np.$next.data("class")); - } - }); - this.$target.on('slid', function () { // slide.bs.carousel - var $active = self.$target.find(".item.active"); - self.$target - .css("background-image", $active.css("background-image")) - .removeClass(add_class($active.attr("class"))) - .addClass($active.attr("class")) - .trigger("snippet-style-reset"); - - self.$target.carousel(); - }); - this.$target.trigger('slid'); - }, - on_add_slide: function () { - var $clone = this._super(); - - // choose an other background - var $styles = this.$target.data("snippet-style-ids").background.$el.find("li[data-class]:not(.oe_custom_bg)"); - var styles_index = $styles.index($styles.filter(".active")[0]); - var $select = $($styles[styles_index >= $styles.length-1 ? 0 : styles_index+1]); - $clone.css("background-image", $select.data("src") ? "url('"+ $select.data("src") +"')" : ""); - $clone.addClass($select.data("class") || ""); - - return $clone; - }, - // rebind event to active carousel on edit mode - rebind_event: function () { - var self = this; - this.$target.find('.carousel-control').off('click').on('click', function () { - self.$target.carousel( $(this).data('slide')); }); - - this.$target.find('.carousel-inner .content > div').attr('contentEditable', 'true'); - this.$target.find('.carousel-image').attr('attributeEditable', 'true'); - this._super(); - }, }); - website.snippet.editorRegistry.parallax = website.snippet.editorRegistry.resize.extend({ + website.snippet.options.parallax = website.snippet.Option.extend({ getSize: function () { this.grid = this._super(); this.grid.size = 8; @@ -1442,11 +1396,14 @@ self.$target.data("snippet-view").set_values(); }); this.$target.attr('contentEditable', 'false'); - this.$target.find('> div > .oe_structure').attr('contentEditable', 'true'); + + this.$target.find('> div > .oe_structure').attr('contentEditable', 'true'); // saas-3 retro-compatibility + + this.$target.find('> div > div:not(.oe_structure) > .oe_structure').attr('contentEditable', 'true'); }, scroll: function () { var self = this; - var $ul = this.$editor.find('ul[name="parallax-scroll"]'); + var $ul = this.$el.find('ul[name="parallax-scroll"]'); var $li = $ul.find("li"); var speed = this.$target.data('scroll-background-ratio') || 0.6 ; $ul.find('[data-value="' + speed + '"]').addClass('active'); @@ -1468,13 +1425,244 @@ } }); - /* - * data-snippet-id automatically setted - * Don't need to add data-snippet-id="..." into the views - */ - website.snippet.selector.push([".row > [class*='col-md-']", 'colmd']); - website.snippet.selector.push(['hr', 'hr']); - website.snippet.selector.push(['blockquote', 'quote']); + website.snippet.Editor = openerp.Class.extend({ + init: function (BuildingBlock, dom) { + this.BuildingBlock = BuildingBlock; + this.$target = $(dom); + this.$overlay = this.$target.data('overlay'); + this.snippet_id = this.$target.data("snippet-id"); + this._readXMLData(); + this.load_style_options(); + this.get_parent_block(); + this.start(); + }, + + /* + * _readXMLData + * Read data XML and set value into: + * this.$el : + * all xml data + * this.$overlay : + * Dom hover the $target who content options + */ + _readXMLData: function() { + var self = this; + this.$el = this.BuildingBlock.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone(); + var $options = this.$overlay.find(".oe_overlay_options"); + if ($options.find(".oe_options ul li").length) { + $options.find(".oe_options").removeClass("hidden"); + } + }, + + + // activate drag and drop for the snippets in the snippet toolbar + _drag_and_drop: function(){ + var self = this; + this.dropped = false; + this.$overlay.draggable({ + greedy: true, + appendTo: 'body', + cursor: "move", + handle: ".oe_snippet_move", + cursorAt: { + left: 18, + top: 14 + }, + helper: function() { + var $clone = $(this).clone().css({width: "24px", height: "24px", border: 0}); + $clone.find(".oe_overlay_options >:not(:contains(.oe_snippet_move)), .oe_handle").remove(); + $clone.find(":not(.glyphicon)").css({position: 'absolute', top: 0, left: 0}); + $clone.appendTo("body").removeClass("hidden"); + return $clone; + }, + start: _.bind(self._drag_and_drop_start, self), + stop: _.bind(self._drag_and_drop_stop, self) + }); + }, + _drag_and_drop_after_insert_dropzone: function (){}, + _drag_and_drop_active_drop_zone: function ($zones){ + var self = this; + $zones.droppable({ + over: function(){ + $(".oe_drop_zone.hide").removeClass("hide"); + $(this).addClass("hide").first().after(self.$target); + self.dropped = true; + }, + out: function(){ + $(this).removeClass("hide"); + self.$target.detach(); + self.dropped = false; + }, + }); + }, + _drag_and_drop_start: function (){ + var self = this; + self.BuildingBlock.hide(); + self.BuildingBlock.editor_busy = true; + self.size = { + width: self.$target.width(), + height: self.$target.height() + }; + self.$target.after("<div class='oe_drop_clone' style='display: none;'/>"); + self.$target.detach(); + self.$overlay.addClass("hidden"); + + self.BuildingBlock.activate_insertion_zones({ + siblings: self.selector_siblings, + children: self.selector_children, + vertical_children: self.selector_vertical_children, + }); + + $("body").addClass('move-important'); + + self._drag_and_drop_after_insert_dropzone(); + self._drag_and_drop_active_drop_zone($('.oe_drop_zone')); + }, + _drag_and_drop_stop: function (){ + var self = this; + if (!self.dropped) { + $(".oe_drop_clone").after(self.$target); + } + self.$overlay.removeClass("hidden"); + $("body").removeClass('move-important'); + $('.oe_drop_zone').droppable('destroy').remove(); + $(".oe_drop_clone, .oe_drop_to_remove").remove(); + self.BuildingBlock.editor_busy = false; + self.get_parent_block(); + setTimeout(function () {self.BuildingBlock.create_overlay(self.$target);},0); + }, + + load_style_options: function () { + var self = this; + var $styles = this.$overlay.find('.oe_options'); + var $ul = $styles.find('ul:first'); + this.styles = {}; + this.selector_siblings = []; + this.selector_children = []; + this.selector_vertical_children = []; + _.each(website.snippet.templateOptions, function (val) { + if (!self.$target.is(val.selector)) { + return; + } + if (val['selector-siblings']) self.selector_siblings.push(val['selector-siblings']); + if (val['selector-children']) self.selector_children.push(val['selector-children']); + if (val['selector-vertical-children']) self.selector_vertical_children.push(val['selector-vertical-children']); + + var style = val['snippet-option-id']; + var Editor = website.snippet.options[style] || website.snippet.Option; + var editor = self.styles[style] = new Editor(self.BuildingBlock, self, self.$target, style); + $ul.append(editor.$el.addClass("snippet-style-" + style)); + }); + this.selector_siblings = this.selector_siblings.join(","); + if (this.selector_siblings === "") + this.selector_siblings = false; + this.selector_children = this.selector_children.join(","); + if (this.selector_children === "") + this.selector_children = false; + this.selector_vertical_children = this.selector_vertical_children.join(","); + if (this.selector_vertical_children === "") + this.selector_vertical_children = false; + + + if ($ul.find("li").length) { + $styles.removeClass("hidden"); + } + this.$overlay.find('[data-toggle="dropdown"]').dropdown(); + }, + + get_parent_block: function () { + var self = this; + var $button = this.$overlay.find('.oe_snippet_parent'); + var $parent = this.$target.parents(website.snippet.globalSelector).first(); + if ($parent.length) { + $button.removeClass("hidden"); + $button.off("click").on('click', function (event) { + event.preventDefault(); + setTimeout(function () { + self.BuildingBlock.make_active($parent); + }, 0); + }); + } else { + $button.addClass("hidden"); + } + }, + + /* + * start + * This method is called after init and _readXMLData + */ + start: function () { + var self = this; + this.$overlay.on('click', '.oe_snippet_clone', _.bind(this.on_clone, this)); + this.$overlay.on('click', '.oe_snippet_remove', _.bind(this.on_remove, this)); + this._drag_and_drop(); + }, + + on_clone: function () { + var $clone = this.$target.clone(false); + this.$target.after($clone); + for (var i in this.styles){ + this.styles[i].on_clone($clone); + } + return false; + }, + + on_remove: function () { + this.onBlur(); + var index = _.indexOf(this.BuildingBlock.snippets, this.$target.get(0)); + for (var i in this.styles){ + this.styles[i].on_remove(); + } + delete this.BuildingBlock.snippets[index]; + this.$target.remove(); + this.$overlay.remove(); + return false; + }, + + /* + * drop_and_build_snippet + * This method is called just after that a thumbnail is drag and dropped into a drop zone + * (after the insertion of this.$body, if this.$body exists) + */ + drop_and_build_snippet: function () { + for (var i in this.styles){ + this.styles[i].drop_and_build_snippet(); + } + }, + + /* onFocus + * This method is called when the user click inside the snippet in the dom + */ + onFocus : function () { + this.$overlay.addClass('oe_active'); + for (var i in this.styles){ + this.styles[i].onFocus(); + } + }, + + /* onFocus + * This method is called when the user click outside the snippet in the dom, after a focus + */ + onBlur : function () { + for (var i in this.styles){ + this.styles[i].onBlur(); + } + this.$overlay.removeClass('oe_active'); + }, + + /* clean_for_save + * function called just before save vue + */ + clean_for_save: function () { + for (var i in this.styles){ + this.styles[i].clean_for_save(); + } + this.$target.removeAttr('contentEditable') + .find('*').removeAttr('contentEditable'); + this.$target.removeAttr('attributeEditable') + .find('*').removeAttr('attributeEditable'); + }, + }); })(); diff --git a/addons/website/static/src/js/website.tour.banner.js b/addons/website/static/src/js/website.tour.banner.js index 1d4a0c0f752..9c8afcd61e5 100644 --- a/addons/website/static/src/js/website.tour.banner.js +++ b/addons/website/static/src/js/website.tour.banner.js @@ -35,11 +35,11 @@ element: 'button[data-action=snippet]', placement: 'bottom', title: _t("Insert building blocks"), - content: _t("To add content in a page, you can insert building blocks."), + content: _t("Click here to insert blocks of centent in the page."), popover: { fixed: true }, }, { - snippet: 'carousel', + snippet: '#snippet_structure .oe_snippet:first', placement: 'bottom', title: _t("Drag & Drop a Banner"), content: _t("Drag the Banner block and drop it in your page."), @@ -47,14 +47,14 @@ }, { waitFor: '.oe_overlay_options .oe_options:visible', - element: '#wrap [data-snippet-id=carousel]:first .carousel-caption', + element: '#wrap .carousel:first .carousel-caption > div', placement: 'top', - title: _("Customize banner's text"), - content: _("Click in the text and start editing it."), + title: _t("Customize banner's text"), + content: _t("Click in the text and start editing it."), popover: { next: _t("Continue") }, }, { - element: '.oe_overlay_options .oe_options', + element: '.oe_overlay_options .oe_options:visible', placement: 'left', title: _t("Customize the banner"), content: _t("Customize any block through this menu. Try to change the background of the banner."), @@ -69,7 +69,7 @@ popover: { fixed: true }, }, { - snippet: 'features', + snippet: '#snippet_structure .oe_snippet:eq(6)', placement: 'bottom', title: _t("Drag & Drop This Block"), content: _t("Drag the <em>'Features'</em> block and drop it below the banner."), @@ -85,8 +85,8 @@ }, { waitFor: 'button[data-action=edit]:visible', - title: _("Good Job!"), - content: _("Well done, you created your homepage."), + title: _t("Good Job!"), + content: _t("Well done, you created your homepage."), popover: { next: _t("Continue") }, }, { @@ -101,13 +101,13 @@ element: '.modal:has(#mobile-viewport) button[data-dismiss=modal]', placement: 'right', title: _t("Check Mobile Preview"), - content: _t("Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog."), + content: _t("Scroll to check rendering and then close the mobile preview."), popover: { next: _t("Continue") }, }, { waitNot: '.modal', element: '#content-menu-button', - placement: 'bottom', + placement: 'left', title: _t("Add new pages and menus"), content: _t("The 'Content' menu allows you to add pages or add the top menu."), popover: { next: _t("Close Tutorial") }, diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index 2416cb511f1..b7e55fe166b 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -1,32 +1,65 @@ (function () { 'use strict'; -var website = openerp.website; +// raise an error in test mode if openerp don't exist +if (typeof openerp === "undefined") { + var error = "openerp is undefined" + + "\nhref: " + window.location.href + + "\nreferrer: " + document.referrer + + "\nlocalStorage: " + JSON.stringify(window.localStorage); + if (typeof $ !== "undefined") { + error += '\n\n' + $("body").html(); + } + throw new Error(error); +} -if (typeof QWeb2 !== "undefined") -website.add_template_file('/website/static/src/xml/website.tour.xml'); +var website = window.openerp.website; -if (website.EditorBar) -website.EditorBar.include({ - tours: [], - start: function () { - var self = this; - var menu = $('#help-menu'); - _.each(this.tours, function (tour) { - var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>')); - $menuItem.click(function () { - tour.reset(); - tour.run(); - }); - menu.append($menuItem); - }); - return this._super(); - }, - registerTour: function (tour) { - website.Tour.add(tour); - this.tours.push(tour); +// don't rewrite website.Tour in test mode +if (typeof website.Tour !== "undefined") { + return; +} + +// don't need template to use bootstrap Tour in automatic mode +if (typeof QWeb2 !== "undefined") { + website.add_template_file('/website/static/src/xml/website.tour.xml'); + +} + +// don't need to use bootstrap Tour to launch an automatic tour +function bootstrap_tour_stub () { + if (typeof Tour === "undefined") { + window.Tour = function Tour() {}; + Tour.prototype.addSteps = function () {}; + Tour.prototype.end = function () {}; + Tour.prototype.goto = function () {}; } -}); +} + + + +if (website.EditorBar) { + website.EditorBar.include({ + tours: [], + start: function () { + var self = this; + var menu = $('#help-menu'); + _.each(this.tours, function (tour) { + var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>')); + $menuItem.click(function () { + tour.reset(); + tour.run(); + }); + menu.append($menuItem); + }); + return this._super(); + }, + registerTour: function (tour) { + website.Tour.add(tour); + this.tours.push(tour); + } + }); +} ///////////////////////////////////////////////// @@ -175,6 +208,9 @@ website.Tour = openerp.Class.extend({ }, registerTour: function () { + if (this.automatic) { + bootstrap_tour_stub(); + } this.tour = new Tour({ name: this.id, storage: this.tourStorage, @@ -205,7 +241,7 @@ website.Tour = openerp.Class.extend({ if (!step.element) step.orphan = true; if (step.snippet) { - step.element = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail'; + step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail'; } } @@ -292,7 +328,8 @@ website.Tour = openerp.Class.extend({ self.timer = setTimeout(checkNext, self.defaultDelay); } else { self.reset(); - throw new Error("Time overlaps to arrive to step " + step.stepId + ": '" + step._title + "'" + throw new Error("Can't arrive to step " + step.stepId + ": '" + step._title + "'" + + '\nhref: ' + window.location.href + '\nelement: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) + '\nwaitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) + '\nwaitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) @@ -347,12 +384,13 @@ website.Tour = openerp.Class.extend({ } }, endTour: function () { - if (parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1) { + var test = parseInt(this.localStorage.getItem("tour-"+this.id+"-test"),10) >= this.steps.length-1; + this.reset(); + if (test) { console.log('ok'); } else { console.log('error'); } - this.reset(); }, autoNextStep: function () { var self = this; @@ -377,7 +415,7 @@ website.Tour = openerp.Class.extend({ if (step.snippet) { - var selector = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail'; + var selector = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail'; self.autoDragAndDropSnippet(selector); } else if (step.element.match(/#oe_snippets .* \.oe_snippet_thumbnail/)) { diff --git a/addons/website/static/src/js/website.tour.test.admin.js b/addons/website/static/src/js/website.tour.test.admin.js deleted file mode 100644 index 787f05beb23..00000000000 --- a/addons/website/static/src/js/website.tour.test.admin.js +++ /dev/null @@ -1,106 +0,0 @@ -(function () { - 'use strict'; - - var website = openerp.website; - - website.Tour.LoginEdit = website.Tour.extend({ - id: 'login_edit', - name: "Try to log as admin and check editor", - path: '/', - init: function () { - var self = this; - self.steps = [ - { - title: "click login", - element: '#top_menu a[href*="/web/login"]', - }, - { - title: "insert login", - element: '.oe_login_form input[name="login"]', - sampleText: "admin", - }, - { - title: "insert password", - waitFor: '.oe_login_form input[name="login"][value!=""]', - element: '.oe_login_form input[name="password"]', - sampleText: "admin", - }, - { - title: "select 2 Standard tickets", - waitFor: '.oe_login_form input[name="password"][value!=""]', - element: '.oe_login_form button', - }, - { - title: "go back to website from backend", - element: 'a[data-action-model="ir.actions.act_url"]:contains("Website")', - }, - { - title: 'try to edit', - waitNot: '#wrap .carousel', - element: 'button[data-action=edit]:visible', - }, - { - title: 'check edit mode', - waitFor: 'button[data-action=save]:visible', - }, - { - title: 'check branding', - waitFor: '#wrap[data-oe-model="ir.ui.view"]', - }, - { - title: 'check rte', - waitFor: '#oe_rte_toolbar', - }, - { - title: 'check insert block button', - element: '[data-action="snippet"]:visible', - }, - { - title: 'add snippets', - snippet: 'carousel', - }, - { - title: 'try to save', - waitFor: '.oe_overlay_options .oe_options:visible', - element: 'button[data-action=save]:visible', - }, - { - title: 'check saved', - waitFor: '#wrap div.carousel', - element: 'button[data-action=edit]:visible', - }, - { - title: 'try to re-edit', - waitFor: 'button[data-action=save]:visible', - element: '#wrap .carousel', - }, - { - title: 'remove snippet', - element: '.oe_snippet_remove', - }, - { - title: 'try to re-save', - waitNot: '#wrap .carousel', - element: 'button[data-action=save]:visible', - }, - { - title: "click admin", - waitFor: 'button[data-action=edit]:visible', - element: 'a:contains("Administrator")', - }, - { - title: "click logout", - element: '#top_menu a[href*="/logout"]', - }, - { - title: "check logout", - waitFor: '#top_menu a[href*="/web/login"]', - }, - ]; - return this._super(); - }, - }); - // for test without editor bar - website.Tour.add(website.Tour.LoginEdit); - -}()); diff --git a/addons/website/static/src/js/website.tour.test.js b/addons/website/static/src/js/website.tour.test.js deleted file mode 100644 index 3e85ce20dae..00000000000 --- a/addons/website/static/src/js/website.tour.test.js +++ /dev/null @@ -1,34 +0,0 @@ -(function () { - -function LoadScript(src) { - xmlHttp = new XMLHttpRequest(); - xmlHttp.onreadystatechange = function() { - if(xmlHttp.readyState == 4) { - if (xmlHttp.status == 200 || xmlHttp.status == 304) { - new Function(xmlHttp.responseText)(); - } else { - throw new Error("Can't load JavaScript.\nhref: " + window.location.href + "\nsrc: " + src); - } - } - }; - xmlHttp.open("GET", src, false); - xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xmlHttp.send(null); -} - -if (typeof jQuery === "undefined") - LoadScript("/web/static/lib/jquery/jquery.js"); -if (typeof _ === "undefined") - LoadScript("/web/static/lib/underscore/underscore.js"); -if (typeof openerp === "undefined") { - LoadScript("/web/static/lib/qweb/qweb2.js"); - LoadScript("/web/static/src/js/openerpframework.js"); -} -if (typeof openerp === "undefined" || !openerp.website || !openerp.website.add_template_file) - LoadScript("/website/static/src/js/website.js"); -if (typeof Tour === "undefined") - LoadScript("/website/static/lib/bootstrap-tour/bootstrap-tour.js"); -if (typeof openerp === "undefined" || !openerp.website.Tour) - LoadScript("/website/static/src/js/website.tour.js"); - -})();
\ No newline at end of file diff --git a/addons/website/static/src/js/website.translator.js b/addons/website/static/src/js/website.translator.js index 399e5e731b2..44e2117a7f0 100644 --- a/addons/website/static/src/js/website.translator.js +++ b/addons/website/static/src/js/website.translator.js @@ -65,6 +65,7 @@ }, processTranslatableNodes: function () { var self = this; + var source_attr = 'data-oe-source-id'; var $editables = $('[data-oe-model="ir.ui.view"]') .not('link, script') .not('.oe_snippets,.oe_snippet, .oe_snippet *') @@ -72,7 +73,8 @@ $editables.each(function () { var $node = $(this); - var view_id = $node.attr('data-oe-source-id') || $node.attr('data-oe-id'); + var source_id = $node.parents('[' + source_attr + ']:first').attr(source_attr)|0; + var view_id = $node.attr('data-oe-source-id') || source_id || $node.attr('data-oe-id'); self.transNode(this, view_id|0); }); $('.oe_translatable_text').on('paste', function () { |
