diff options
Diffstat (limited to 'addons/website/static/src/js')
| -rw-r--r-- | addons/website/static/src/js/website.ace.js | 86 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.backend.js | 32 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.editor.js | 701 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.js | 50 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.menu.js | 21 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.mobile.js | 4 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.snippets.animation.js | 10 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.snippets.editor.js | 754 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tests.js | 40 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.banner.js | 224 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.install-app.js | 64 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.js | 333 | ||||
| -rw-r--r-- | addons/website/static/src/js/website.tour.menu-editor.js | 115 |
13 files changed, 1490 insertions, 944 deletions
diff --git a/addons/website/static/src/js/website.ace.js b/addons/website/static/src/js/website.ace.js index 685ace69467..f3b8a5c5885 100644 --- a/addons/website/static/src/js/website.ace.js +++ b/addons/website/static/src/js/website.ace.js @@ -1,35 +1,34 @@ (function () { 'use strict'; - var globalEditor; - var hash = "#advanced-view-editor"; var website = openerp.website; website.add_template_file('/website/static/src/xml/website.ace.xml'); - website.ready().then(function () { - if (window.location.hash.indexOf(hash) >= 0) { - launch(); - } - }); - - function launch () { - if (globalEditor) { - globalEditor.open(); - } else { - globalEditor = new website.ace.ViewEditor(); - globalEditor.appendTo($(document.body)); - } - } - website.EditorBar.include({ events: _.extend({}, website.EditorBar.prototype.events, { 'click a[data-action=ace]': 'launchAce', }), + start: function () { + var self = this; + this.globalEditor = null; + return this._super.apply(this, arguments).then(function () { + if (window.location.hash.indexOf(hash) >= 0) { + self.launchAce(); + } + }); + }, launchAce: function (e) { - e.preventDefault(); - launch(); + if (e) { + e.preventDefault(); + } + if (this.globalEditor) { + this.globalEditor.open(); + } else { + this.globalEditor = new website.ace.ViewEditor(this); + this.globalEditor.appendTo($(document.body)); + } }, }); @@ -59,8 +58,12 @@ website.ace.ViewOption = openerp.Widget.extend({ template: 'website.ace_view_option', init: function (parent, options) { + var indent = "- "; this.view_id = options.id; this.view_name = options.name; + for (var i = 0; i<options.level; i++) { + this.view_name = indent + this.view_name; + } this._super(parent); }, }); @@ -100,6 +103,10 @@ self.aceEditor.resize(); self.$el.width(width); } + function resizeEditorHeight(height) { + self.$el.css('top', height); + self.$('.ace_editor').css('bottom', height); + } function storeEditorWidth() { window.localStorage.setItem('ace_editor_width', self.$el.width()); } @@ -129,22 +136,55 @@ $('button[data-action=edit]').click(function () { self.close(); }); + this.getParent().on('change:height', this, function (editor) { + resizeEditorHeight(editor.get('height')); + }); resizeEditor(readEditorWidth()); + resizeEditorHeight(this.getParent().get('height')); }, loadViews: function (views) { var self = this; + var $viewList = self.$('#ace-view-list'); + var views = this.buildViewGraph(views); + _.each(views, function (view) { + if (view.id) { + new website.ace.ViewOption(self, view).appendTo($viewList); + self.loadView(view.id); + } + }); + }, + buildViewGraph: function (views) { var activeViews = _.uniq(_.filter(views, function (view) { return view.active; }), false, function (view) { return view.id; }); - var $viewList = self.$('#ace-view-list'); + var index = {}; + var roots = []; _.each(activeViews, function (view) { - if (view.id) { - new website.ace.ViewOption(self, view).appendTo($viewList); - self.loadView(view.id); + index[view.id] = view; + view.children = []; + }); + _.each(index, function (view) { + var parentId = view.inherit_id; + if (parentId && index[parentId]) { + index[parentId].children.push(view); + } else { + roots.push(view); } }); + var result = []; + function visit (node, level) { + node.level = level; + result.push(node); + _.each(node.children, function (child) { + visit(child, level + 1); + }); + } + _.each(roots, function (node) { + visit(node, 0); + }); + return result; }, loadView: function (id) { var viewId = parseInt(id, 10); diff --git a/addons/website/static/src/js/website.backend.js b/addons/website/static/src/js/website.backend.js new file mode 100644 index 00000000000..43347b85cb7 --- /dev/null +++ b/addons/website/static/src/js/website.backend.js @@ -0,0 +1,32 @@ +openerp.website = function(instance) { +var _t = instance.web._t; + +instance.web.form.WidgetWebsiteButton = instance.web.form.AbstractField.extend({ + template: 'WidgetWebsiteButton', + render_value: function() { + this._super(); + this.$("button:first") + .toggleClass("btn-success", this.get_value()) + .toggleClass("btn-danger", !this.get_value()); + this.$("a:first").attr("href", this.view.datarecord.website_url || "/" ); + if (this.node.attrs.class) { + this.$el.addClass(this.node.attrs.class); + } + }, + start: function() { + var self = this; + this._super.apply(this, arguments); + + this.$("button:first").on("click", function () { + console.log("click", !!$(this).hasClass("btn-danger")); + self.set_value(!!$(this).hasClass("btn-danger")); + return self.view.recursive_save(); + }); + }, +}); + +instance.web.form.widgets = instance.web.form.widgets.extend({ + 'website_button': 'instance.web.form.WidgetWebsiteButton', +}); + +}; diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 74cf6ab81c0..51f14a0f6d5 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -2,7 +2,7 @@ 'use strict'; var website = openerp.website; - // $.fn.data automatically parses value, '0'|'1' -> 0|1 + var _t = openerp._t; website.add_template_file('/website/static/src/xml/website.editor.xml'); website.dom_ready.done(function () { @@ -14,12 +14,12 @@ $(document).on('click', 'a.js_link2post', function (ev) { ev.preventDefault(); - var form = document.createElement('form'); - form.action = this.pathname; // restrict to same origin - form.method = 'POST'; - // TODO: support this.search as form input fields - $(this).append(form); - form.submit(); + website.form(this.pathname, 'POST'); + }); + + $(document).on('submit', '.cke_editable form', function (ev) { + // Disable form submition in editable mode + ev.preventDefault(); }); $(document).on('hide.bs.dropdown', '.dropdown', function (ev) { @@ -32,11 +32,32 @@ }); }); + /** + * An editing host is an HTML element with @contenteditable=true, or the + * child of a document in designMode=on (but that one's not supported) + * + * https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#editing-host + */ + function is_editing_host(element) { + return element.getAttribute('contentEditable') === 'true'; + } + /** + * Checks that both the element's content *and the element itself* are + * editable: an editing host is considered non-editable because its content + * is editable but its attributes should not be considered editable + */ + function is_editable_node(element) { + return !(element.data('oe-model') === 'ir.ui.view' + || element.data('cke-realelement') + || is_editing_host(element) + || element.isReadOnly()); + } + function link_dialog(editor) { return new website.editor.RTELinkDialog(editor).appendTo(document.body); } - function image_dialog(editor) { - return new website.editor.RTEImageDialog(editor).appendTo(document.body); + function image_dialog(editor, image) { + return new website.editor.RTEImageDialog(editor, image).appendTo(document.body); } // only enable editors manually @@ -54,18 +75,13 @@ init: function (editor) { editor.on('doubleclick', function (evt) { var element = evt.data.element; - if (element.is('img') - && !element.data('cke-realelement') - && !element.isReadOnly() - && (element.data('oe-model') !== 'ir.ui.view')) { - image_dialog(editor); + if (element.is('img') && is_editable_node(element)) { + image_dialog(editor, element); return; } element = get_selected_link(editor) || evt.data.element; - if (element.isReadOnly() - || !element.is('a') - || element.data('oe-model')) { + if (!(element.is('a') && is_editable_node(element))) { return; } @@ -73,39 +89,6 @@ link_dialog(editor); }, null, null, 500); - var previousSelection; - editor.on('selectionChange', function (evt) { - var selected = evt.data.path.lastElement; - if (previousSelection) { - // cleanup previous selection - $(previousSelection).next().remove(); - previousSelection = null; - } - if (!selected.is('img') - || selected.data('cke-realelement') - || selected.isReadOnly() - || selected.data('oe-model') === 'ir.ui.view') { - return; - } - - // display button - var $el = $(previousSelection = selected.$); - var $btn = $('<button type="button" class="btn btn-primary" contenteditable="false">Edit</button>') - .insertAfter($el) - .click(function (e) { - e.preventDefault(); - e.stopPropagation(); - image_dialog(editor); - }); - - var position = $el.position(); - $btn.css({ - position: 'absolute', - top: $el.height() / 2 + position.top - $btn.outerHeight() / 2, - left: $el.width() / 2 + position.left - $btn.outerWidth() / 2, - }); - }); - //noinspection JSValidateTypes editor.addCommand('link', { exec: function (editor) { @@ -129,13 +112,11 @@ label: 'Link', command: 'link', toolbar: 'links,10', - icon: '/website/static/lib/ckeditor/plugins/link/icons/link.png', }); editor.ui.addButton('Image', { label: 'Image', command: 'image', toolbar: 'insert,10', - icon: '/website/static/lib/ckeditor/plugins/image/icons/image.png', }); editor.setKeystroke(CKEDITOR.CTRL + 76 /*L*/, 'link'); @@ -204,177 +185,186 @@ } }); - CKEDITOR.plugins.add('oeref', { - requires: 'widget', - - init: function (editor) { - editor.widgets.add('oeref', { - editables: { text: '*' }, - - upcast: function (el) { - return el.attributes['data-oe-type'] - && el.attributes['data-oe-type'] !== 'monetary'; - }, - }); - editor.widgets.add('monetary', { - editables: { text: 'span.oe_currency_value' }, - - upcast: function (el) { - return el.attributes['data-oe-type'] === 'monetary'; - } - }); - } - }); - - CKEDITOR.plugins.add('bootstrapcombo', { - requires: 'richcombo', - + CKEDITOR.plugins.add('linkstyle', { + requires: 'panelbutton,floatpanel', init: function (editor) { - var config = editor.config; - - editor.ui.addRichCombo('BootstrapLinkCombo', { - // default title - label: "Links", - // hover - title: "Link styling", - toolbar: 'styles,10', - allowedContent: ['a'], + var label = "Link Style"; + editor.ui.add('LinkStyle', CKEDITOR.UI_PANELBUTTON, { + label: label, + title: label, + icon: '/website/static/src/img/bglink.png', + modes: { wysiwyg: true }, + editorFocus: true, panel: { - css: [ - '/website/static/lib/bootstrap/css/bootstrap.css', - CKEDITOR.skin.getPath( 'editor' ) - ].concat( config.contentsCss ), - multiSelect: true, + css: '/website/static/lib/bootstrap/css/bootstrap.css', + attributes: { 'role': 'listbox', 'aria-label': label }, }, types: { - 'basic': 'btn-default', - 'primary': 'btn-primary', - 'success': 'btn-success', - 'info': 'btn-info', - 'warning': 'btn-warning', - 'danger': 'btn-danger', + 'btn-default': _t("Basic"), + 'btn-primary': _t("Primary"), + 'btn-success': _t("Success"), + 'btn-info': _t("Info"), + 'btn-warning': _t("Warning"), + 'btn-danger': _t("Danger"), }, - sizes: { - 'large': 'btn-lg', - 'default': '', - 'small': 'btn-sm', - 'extra small': 'btn-xs', + 'btn-xs': _t("Extra Small"), + 'btn-sm': _t("Small"), + '': _t("Default"), + 'btn-lg': _t("Large") }, - init: function () { - this.add('', 'Reset'); - this.startGroup("Types"); - for(var type in this.types) { - if (!this.types.hasOwnProperty(type)) { continue; } - var cls = this.types[type]; - var el = _.str.sprintf( - '<span class="btn %s">%s</span>', - cls, type); - this.add(type, el); - } - this.startGroup("Sizes"); - for (var size in this.sizes) { - if (!this.sizes.hasOwnProperty(size)) { continue; } - cls = this.sizes[size]; - - el = _.str.sprintf( - '<span class="btn btn-default %s">%s</span>', - cls, size); - this.add(size, el); - } - this.commit(); - }, onRender: function () { var self = this; editor.on('selectionChange', function (e) { var path = e.data.path, el; - if (!(el = path.contains('a'))) { - self.element = null; + if (!(e = path.contains('a')) || e.isReadOnly()) { self.disable(); return; } self.enable(); - // This is crap, but getting the currently selected - // element from within onOpen absolutely does not - // work, so store the "current" element in the - // widget instead - self.element = el; }); + // no hook where button is available, so wait + // "some time" after render. setTimeout(function () { - // Because I can't find any normal hook where the - // bloody button's bloody element is available self.disable(); - }, 0); + }, 0) + }, + enable: function () { + this.setState(CKEDITOR.TRISTATE_OFF); + }, + disable: function () { + this.setState(CKEDITOR.TRISTATE_DISABLED); }, - onOpen: function () { - this.showAll(); - this.unmarkAll(); - for(var val in this.types) { - if (!this.types.hasOwnProperty(val)) { continue; } - var cls = this.types[val]; - if (!this.element.hasClass(cls)) { continue; } + onOpen: function () { + var link = get_selected_link(editor); + var id = this._.id; + var block = this._.panel._.panel._.blocks[id]; + var $root = $(block.element.$); + $root.find('button').removeClass('active').removeProp('disabled'); + + // enable buttons matching link state + for (var type in this.types) { + if (!this.types.hasOwnProperty(type)) { continue; } + if (!link.hasClass(type)) { continue; } - this.mark(val); - break; + $root.find('button[data-type=types].' + type) + .addClass('active'); } - var found; - for(val in this.sizes) { - if (!this.sizes.hasOwnProperty(val)) { continue; } - cls = this.sizes[val]; - if (!cls || !this.element.hasClass(cls)) { continue; } - + for (var size in this.sizes) { + if (!this.sizes.hasOwnProperty(size)) { continue; } + if (!size || !link.hasClass(size)) { continue; } found = true; - this.mark(val); - break; + $root.find('button[data-type=sizes].' + size) + .addClass('active'); } - if (!found && this.element.hasClass('btn')) { - this.mark('default'); + if (!found && link.hasClass('btn')) { + $root.find('button[data-type="sizes"][data-set-class=""]') + .addClass('active'); } }, - onClick: function (value) { + + onBlock: function (panel, block) { + var self = this; + block.autoSize = true; + + var html = ['<div style="padding: 5px">']; + html.push('<div style="white-space: nowrap">'); + _(this.types).each(function (label, key) { + html.push(_.str.sprintf( + '<button type="button" class="btn %s" ' + + 'data-type="types" data-set-class="%s">%s</button>', + key, key, label)); + }); + html.push('</div>'); + html.push('<div style="white-space: nowrap; margin: 5px 0; text-align: center">'); + _(this.sizes).each(function (label, key) { + html.push(_.str.sprintf( + '<button type="button" class="btn btn-default %s" ' + + 'data-type="sizes" data-set-class="%s">%s</button>', + key, key, label)); + }); + html.push('</div>'); + html.push('<button type="button" class="btn btn-link btn-block" ' + + 'data-type="reset">Reset</button>'); + html.push('</div>'); + + block.element.setHtml(html.join(' ')); + var $panel = $(block.element.$); + $panel.on('click', 'button', function () { + self.clicked(this); + }); + }, + clicked: function (button) { editor.focus(); - editor.fire('saveShapshot'); + editor.fire('saveSnapshot'); - // basic btn setup - var el = this.element; - if (!el.hasClass('btn')) { - el.addClass('btn'); - el.addClass('btn-default'); + var $button = $(button), + $link = $(get_selected_link(editor).$); + if (!$link.hasClass('btn')) { + $link.addClass('btn btn-default'); } - - if (!value) { - this.setClass(this.types); - this.setClass(this.sizes); - el.removeClass('btn'); - } else if (value in this.types) { - this.setClass(this.types, value); - } else if (value in this.sizes) { - this.setClass(this.sizes, value); + switch($button.data('type')) { + case 'reset': + $link.removeClass('btn') + .removeClass(_.keys(this.types).join(' ')) + .removeClass(_.keys(this.sizes).join(' ')); + break; + case 'types': + $link.removeClass(_.keys(this.types).join(' ')) + .addClass($button.data('set-class')); + break; + case 'sizes': + $link.removeClass(_.keys(this.sizes).join(' ')) + .addClass($button.data('set-class')); } + this._.panel.hide(); - editor.fire('saveShapshot'); + editor.fire('saveSnapshot'); }, - setClass: function (classMap, value) { - var element = this.element; - _(classMap).each(function (cls) { - if (!cls) { return; } - element.removeClass(cls); - }.bind(this)); - - var cls = classMap[value]; - if (cls) { - element.addClass(cls); + + }); + } + }); + + CKEDITOR.plugins.add('oeref', { + requires: 'widget', + + init: function (editor) { + editor.widgets.add('oeref', { + editables: { text: '*' }, + draggable: false, + + upcast: function (el) { + var matches = el.attributes['data-oe-type'] && el.attributes['data-oe-type'] !== 'monetary'; + if (!matches) { return false; } + + if (el.attributes['data-oe-original']) { + while (el.children.length) { + el.children[0].remove(); + } + el.add(new CKEDITOR.htmlParser.text( + el.attributes['data-oe-original'] + )); } + return true; + }, + }); + editor.widgets.add('monetary', { + editables: { text: 'span.oe_currency_value' }, + draggable: false, + + upcast: function (el) { + return el.attributes['data-oe-type'] === 'monetary'; } }); - }, + } }); var editor = new website.EditorBar(); @@ -384,15 +374,15 @@ editor.edit(); } }); - $body.css('padding-top', '50px'); // Not working properly: editor.$el.outerHeight()); }; - /* ----- TOP EDITOR BAR FOR ADMIN ---- */ + + /* ----- TOP EDITOR BAR FOR ADMIN ---- */ website.EditorBar = openerp.Widget.extend({ template: 'website.editorbar', events: { 'click button[data-action=edit]': 'edit', 'click button[data-action=save]': 'save', - 'click button[data-action=cancel]': 'cancel', + 'click a[data-action=cancel]': 'cancel', 'click a[data-action=new_page]': 'new_page', }, container: 'body', @@ -408,15 +398,15 @@ if (item.header) { menu.append('<li class="dropdown-header">' + item.name + '</li>'); } else { - menu.append(_.str.sprintf('<li role="presentation"><a href="#" data-view-id="%s" role="menuitem"><strong class="icon-check%s"></strong> %s</a></li>', - item.id, item.active ? '' : '-empty', item.name)); + menu.append(_.str.sprintf('<li role="presentation"><a href="#" data-view-id="%s" role="menuitem"><strong class="fa fa%s-square-o"></strong> %s</a></li>', + item.id, item.active ? '-check' : '', item.name)); } }); // Adding Static Menus menu.append('<li class="divider"></li>'); menu.append('<li><a data-action="ace" href="#">HTML Editor</a></li>'); menu.append('<li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>'); - menu.append('<li><a href="/web#action=website.action_module_website">Install Apps</a></li>'); + menu.append('<li><a href="/web#return_label=Website&action=website.action_module_website">Install Apps</a></li>'); self.trigger('rte:customize_menu_ready'); } ); @@ -432,7 +422,6 @@ }, start: function() { var self = this; - this.saving_mutex = new openerp.Mutex(); this.$('#website-top-edit').hide(); @@ -450,13 +439,27 @@ this.rte = new website.RTE(this); this.rte.on('change', this, this.proxy('rte_changed')); this.rte.on('rte:ready', this, function () { + self.setup_hover_buttons(); self.trigger('rte:ready'); + self.check_height(); }); + $(window).on('resize', _.debounce(this.check_height.bind(this), 50)); + this.check_height(); + return $.when( this._super.apply(this, arguments), this.rte.appendTo(this.$('#website-top-edit .nav.pull-right')) - ); + ).then(function () { + self.check_height(); + }); + }, + check_height: function () { + var editor_height = this.$el.outerHeight(); + if (this.get('height') != editor_height) { + $(document.body).css('padding-top', editor_height); + this.set('height', editor_height); + } }, edit: function () { this.$buttons.edit.prop('disabled', true); @@ -464,7 +467,7 @@ this.$('#website-top-edit').show(); $('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden"); - this.rte.start_edition(); + this.rte.start_edition().then(this.check_height.bind(this)); }, rte_changed: function () { this.$buttons.save.prop('disabled', false); @@ -513,16 +516,23 @@ if (!_.isArray(failed)) { failed = [failed]; } _(failed).each(function (failure) { + var html = failure.error.exception_type === "except_osv"; + if (html) { + var msg = $("<div/>").text(failure.error.message).html(); + var data = msg.substring(3,msg.length-2).split(/', u'/); + failure.error.message = '<b>' + data[0] + '</b><br/>' + data[1]; + } $(root).find('.' + failure.id) .removeClass(failure.id) .popover({ + html: html, trigger: 'hover', content: failure.error.message, placement: 'auto top', }) // Force-show popovers so users will notice them. .popover('show'); - }) + }); }); }); }, @@ -540,7 +550,20 @@ }); }, cancel: function () { - website.reload(); + new $.Deferred(function (d) { + var $dialog = $(openerp.qweb.render('website.editor.discard')).appendTo(document.body); + $dialog.on('click', '.btn-danger', function () { + d.resolve(); + }).on('hidden.bs.modal', function () { + d.reject(); + }); + d.always(function () { + $dialog.remove(); + }); + $dialog.modal('show'); + }).then(function () { + website.reload(); + }) }, new_page: function (ev) { ev.preventDefault(); @@ -548,9 +571,100 @@ window_title: "New Page", input: "Page Title", }).then(function (val) { - document.location = '/pagenew/' + encodeURI(val); + if (val) { + document.location = '/pagenew/' + encodeURI(val); + } + }); + }, + + /** + * Creates a "hover" button for image and link edition + * + * @param {String} label the button's label + * @param {Function} editfn edition function, called when clicking the button + * @param {String} [classes] additional classes to set on the button + * @returns {jQuery} + */ + make_hover_button: function (label, editfn, classes) { + return $(openerp.qweb.render('website.editor.hoverbutton', { + label: label, + classes: classes, + })).hide().appendTo(document.body).click(function (e) { + e.preventDefault(); + e.stopPropagation(); + editfn.call(this, e); }); }, + /** + * For UI clarity, during RTE edition when the user hovers links and + * images a small button should appear to make the capability clear, + * as not all users think of double-clicking the image or link. + */ + setup_hover_buttons: function () { + var editor = this.rte.editor; + var $link_button = this.make_hover_button(_t("Edit Link"), function () { + var sel = new CKEDITOR.dom.element(previous); + editor.getSelection().selectElement(sel); + link_dialog(editor); + $link_button.hide(); + previous = null; + }, 'btn-xs'); + var $image_button = this.make_hover_button(_t("Edit Image"), function () { + image_dialog(editor, new CKEDITOR.dom.element(previous)); + $image_button.hide(); + previous = null; + }); + + // 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 + // still set to the element having triggered showing the button. + var previous; + $(editor.element.$).on('mouseover', 'a, img', function (e) { + // Back from edit button -> ignore + if (previous && previous === this) { return; } + + var selected = new CKEDITOR.dom.element(this); + if (!is_editable_node(selected)) { + return; + } + + previous = this; + var $selected = $(this); + var position = $selected.offset(); + if ($selected.is('img')) { + var image_top = position.top + parseInt($selected.css('marginTop'), 10); + var image_left = position.left + parseInt($selected.css('marginLeft'), 10); + $link_button.hide(); + // center button on image + $image_button.show().offset({ + top: $selected.outerHeight() / 2 + + image_top + - $image_button.outerHeight() / 2, + left: $selected.outerWidth() / 2 + + image_left + - $image_button.outerWidth() / 2, + }); + } else { + $image_button.hide(); + // put button below link, horizontally centered + $link_button.show().offset({ + top: $selected.outerHeight() + + position.top, + left: $selected.outerWidth() / 2 + + position.left + - $link_button.outerWidth() / 2 + }) + } + }).on('mouseleave', 'a, img', function (e) { + var current = document.elementFromPoint(e.clientX, e.clientY); + if (current === $link_button[0] || current === $image_button[0]) { + return; + } + $image_button.add($link_button).hide(); + previous = null; + }); + } }); var blocks_selector = _.keys(CKEDITOR.dtd.$block).join(','); @@ -694,9 +808,11 @@ self.setup_editables(root); - // disable firefox's broken table resizing thing - document.execCommand("enableObjectResizing", false, "false"); - document.execCommand("enableInlineTableEditing", false, "false"); + try { + // disable firefox's broken table resizing thing + document.execCommand("enableObjectResizing", false, "false"); + document.execCommand("enableInlineTableEditing", false, "false"); + } catch (e) {} self.trigger('rte:ready'); def.resolve(); @@ -754,7 +870,7 @@ var plugins = [ 'a11yhelp', 'basicstyles', 'blockquote', 'clipboard', 'colorbutton', 'colordialog', 'dialogadvtab', - 'elementspath', 'enterkey', 'entities', 'filebrowser', + 'elementspath', /*'enterkey',*/ 'entities', 'filebrowser', 'find', 'floatingspace','format', 'htmlwriter', 'iframe', 'indentblock', 'indentlist', 'justify', 'list', 'pastefromword', 'pastetext', 'preview', @@ -781,19 +897,16 @@ fillEmptyBlocks: false, filebrowserImageUploadUrl: "/website/attach", // Support for sharedSpaces in 4.x - extraPlugins: 'sharedspace,customdialogs,tablebutton,oeref,bootstrapcombo', + extraPlugins: 'sharedspace,customdialogs,tablebutton,oeref,linkstyle', // Place toolbar in controlled location sharedSpaces: { top: 'oe_rte_toolbar' }, toolbar: [{ - name: 'clipboard', items: [ - "Undo" - ]},{ name: 'basicstyles', items: [ "Bold", "Italic", "Underline", "Strike", "Subscript", "Superscript", "TextColor", "BGColor", "RemoveFormat" ]},{ name: 'span', items: [ - "Link", "Blockquote", "BulletedList", + "Link", "LinkStyle", "Blockquote", "BulletedList", "NumberedList", "Indent", "Outdent" ]},{ name: 'justify', items: [ @@ -803,7 +916,7 @@ "Image", "TableButton" ]},{ name: 'styles', items: [ - "Styles", "BootstrapLinkCombo" + "Styles" ]} ], // styles dropdown in toolbar @@ -827,6 +940,7 @@ events: { 'hidden.bs.modal': 'destroy', 'click button.save': 'save', + 'click button[data-dismiss="modal"]': 'cancel', }, init: function (editor) { this._super(); @@ -840,6 +954,8 @@ save: function () { this.close(); }, + cancel: function () { + }, close: function () { this.$el.modal('hide'); }, @@ -848,7 +964,7 @@ website.editor.LinkDialog = website.editor.Dialog.extend({ template: 'website.editor.dialog.link', events: _.extend({}, website.editor.Dialog.prototype.events, { - 'change .url-source': function (e) { this.changed($(e.target)); }, + 'change :input.url-source': function (e) { this.changed($(e.target)); }, 'mousedown': function (e) { var $target = $(e.target).closest('.list-group-item'); if (!$target.length || $target.hasClass('active')) { @@ -856,7 +972,7 @@ return; } - this.changed($target.find('.url-source')); + this.changed($target.find('.url-source').filter(':input')); }, 'click button.remove': 'remove_link', 'change input#link-text': function (e) { @@ -865,22 +981,45 @@ }), init: function (editor) { this._super(editor); - // url -> name mapping for existing pages - this.pages = Object.create(null); this.text = null; + // Store last-performed request to be able to cancel/abort it. + this.page_exists_req = null; + this.search_pages_req = null; }, start: function () { var self = this; - return $.when( - this.fetch_pages().done(this.proxy('fill_pages')), - this._super() - ).done(function () { - self.bind_data(); + this.$('#link-page').select2({ + minimumInputLength: 1, + placeholder: _t("New or existing page"), + query: function (q) { + $.when( + self.page_exists(q.term), + self.fetch_pages(q.term) + ).then(function (exists, results) { + var rs = _.map(results, function (r) { + return { id: r.url, text: r.name, }; + }); + if (!exists) { + rs.push({ + create: true, + id: q.term, + text: _.str.sprintf(_t("Create page '%s'"), q.term), + }); + } + q.callback({ + more: false, + results: rs + }); + }, function () { + q.callback({more: false, results: []}); + }); + }, }); + return this._super().then(this.proxy('bind_data')); }, save: function () { var self = this, _super = this._super.bind(this); - var $e = this.$('.list-group-item.active .url-source'); + var $e = this.$('.list-group-item.active .url-source').filter(':input'); var val = $e.val(); if (!val || !$e[0].checkValidity()) { // FIXME: error message @@ -892,15 +1031,18 @@ var done = $.when(); if ($e.hasClass('email-address')) { this.make_link('mailto:' + val, false, val); - } else if ($e.hasClass('existing')) { - self.make_link(val, false, this.pages[val]); - } else if ($e.hasClass('pages')) { - // Create the page, get the URL back - done = $.get(_.str.sprintf( - '/pagenew/%s?noredirect', encodeURI(val))) - .then(function (response) { - self.make_link(response, false, val); - }); + } else if ($e.hasClass('page')) { + var data = $e.select2('data'); + if (!data.create) { + self.make_link(data.id, false, data.text); + } else { + // Create the page, get the URL back + done = $.get(_.str.sprintf( + '/pagenew/%s?noredirect', encodeURI(data.id))) + .then(function (response) { + self.make_link(response, false, data.id); + }); + } } else { this.make_link(val, this.$('input.window-new').prop('checked')); } @@ -923,13 +1065,6 @@ var match, $control; if ((match = /mailto:(.+)/.exec(href))) { $control = this.$('input.email-address').val(match[1]); - } else if (href in this.pages) { - $control = this.$('select.existing').val(href); - } else if ((match = /\/page\/(.+)/.exec(href))) { - var actual_href = '/page/website.' + match[1]; - if (actual_href in this.pages) { - $control = this.$('select.existing').val(actual_href); - } } if (!$control) { $control = this.$('input.url').val(href); @@ -941,30 +1076,38 @@ this.$('input.window-new').prop('checked', new_window); }, changed: function ($e) { - this.$('.url-source').not($e).val(''); + this.$('.url-source').filter(':input').not($e).val('') + .filter(function () { return !!$(this).data('select2'); }) + .select2('data', null); $e.closest('.list-group-item') .addClass('active') .siblings().removeClass('active') .addBack().removeClass('has-error'); }, - fetch_pages: function () { - return openerp.jsonRpc('/web/dataset/call_kw', 'call', { + call: function (method, args, kwargs) { + var self = this; + var req = method + '_req'; + + if (this[req]) { this[req].abort(); } + + return this[req] = openerp.jsonRpc('/web/dataset/call_kw', 'call', { model: 'website', - method: 'list_pages', - args: [null], - kwargs: { - context: website.get_context() - }, + method: method, + args: args, + kwargs: kwargs, + }).always(function () { + self[req] = null; }); }, - fill_pages: function (results) { - var self = this; - var pages = this.$('select.existing')[0]; - _(results).each(function (result) { - self.pages[result.url] = result.name; - - pages.options[pages.options.length] = - new Option(result.name, result.url); + page_exists: function (term) { + return this.call('page_exists', [null, term], { + context: website.get_context(), + }); + }, + fetch_pages: function (term) { + return this.call('search_pages', [null, term], { + limit: 9, + context: website.get_context(), }); }, }); @@ -1110,6 +1253,9 @@ }); return this._super(); }, + cancel: function () { + this.trigger('cancel'); + }, /** * Sets the provided image url as the dialog's value-to-save and @@ -1159,32 +1305,37 @@ }, }); website.editor.RTEImageDialog = website.editor.ImageDialog.extend({ - init: function () { - this._super.apply(this, arguments); + init: function (editor, image) { + this._super(editor); + + this.element = image; this.on('start', this, this.proxy('started')); this.on('save', this, this.proxy('saved')); }, started: function (holder) { - var selection = this.editor.getSelection(); - var el = selection && selection.getSelectedElement(); - this.element = null; - - if (el && el.is('img')) { - this.element = el; - _(this.image_styles).each(function (style) { - if (el.hasClass(style)) { - holder.style = style; - } - }); - holder.url = el.getAttribute('src'); + if (!this.element) { + var selection = this.editor.getSelection(); + this.element = selection && selection.getSelectedElement(); } + + var el = this.element; + if (!el || !el.is('img')) { + return; + } + _(this.image_styles).each(function (style) { + if (el.hasClass(style)) { + holder.style = style; + } + }); + holder.url = el.getAttribute('src'); }, saved: function (data) { var element, editor = this.editor; if (!(element = this.element)) { element = editor.document.createElement('img'); element.addClass('img'); + element.addClass('img-responsive'); // focus event handler interactions between bootstrap (modal) // and ckeditor (RTE) lead to blowing the stack in Safari and // Chrome (but not FF) when this is done synchronously => @@ -1314,6 +1465,11 @@ // a/@href, ...) _(mutations).chain() .filter(function (m) { + // ignore any change related to mundane image-edit-button + if (m.target && m.target.className + && m.target.className.indexOf('image-edit-button') !== -1) { + return false; + } switch(m.type) { case 'attributes': // ignore .cke_focus being added or removed // if attribute is not a class, can't be .cke_focus change @@ -1327,8 +1483,11 @@ // ignore mutation if the *only* change is .cke_focus return change.length !== 1 || change[0] === 'cke_focus'; case 'childList': - // <br type="_moz"> appears when focusing RTE in FF, ignore - return m.addedNodes.length !== 1 || m.addedNodes[0].nodeName !== 'BR'; + // 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. + return !!remove_mundane_nodes(m.addedNodes).length || + !!remove_mundane_nodes(m.removedNodes).length; default: return true; } @@ -1338,11 +1497,27 @@ while (node && !$(node).hasClass('oe_editable')) { node = node.parentNode; } - $(m.target).trigger('node_changed'); return node; }) .compact() .uniq() .each(function (node) { $(node).trigger('content_changed'); }) }); + function remove_mundane_nodes(nodes) { + if (!nodes || !nodes.length) { return []; } + + var output = []; + for(var i=0; i<nodes.length; ++i) { + var node = nodes[i]; + if (node.nodeType === document.ELEMENT_NODE) { + if (node.nodeName === 'BR' && node.getAttribute('type') === '_moz') { + // <br type="_moz"> appears when focusing RTE in FF, ignore + continue; + } + } + + output.push(node); + } + return output; + } })(); diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index b1ba9b801fc..64767d95171 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -13,6 +13,31 @@ }, dict); }; + website.parseQS = function (qs) { + var match, + params = {}, + pl = /\+/g, // Regex for replacing addition symbol with a space + search = /([^&=]+)=?([^&]*)/g; + + while ((match = search.exec(qs))) { + var name = decodeURIComponent(match[1].replace(pl, " ")); + var value = decodeURIComponent(match[2].replace(pl, " ")); + params[name] = value; + } + return params; + }; + + var parsedSearch; + website.parseSearch = function () { + if (!parsedSearch) { + parsedSearch = website.parseQS(window.location.search.substring(1)); + } + 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) { @@ -173,7 +198,7 @@ init: function() {} }, options || {}); - var type = _.intersect(Object.keys(options), ['input', 'textarea', 'select']); + var type = _.intersection(Object.keys(options), ['input', 'textarea', 'select']); type = type.length ? type[0] : 'text'; options.field_type = type; options.field_name = options.field_name || options[type]; @@ -211,6 +236,7 @@ if (field.is('input[type="text"], select')) { field.keypress(function (e) { if (e.which == 13) { + e.preventDefault(); dialog.find('.btn-primary').trigger('click'); } }); @@ -218,6 +244,21 @@ return def; }; + website.form = function (url, method, params) { + var form = document.createElement('form'); + form.action = url; + form.method = method; + _.each(params, function (v, k) { + var param = document.createElement('input'); + param.type = 'hidden'; + param.name = k; + param.value = v; + form.appendChild(param); + }); + document.body.appendChild(form); + form.submit(); + }; + dom_ready.then(function () { /* ----- BOOTSTRAP STUFF ---- */ @@ -225,15 +266,14 @@ /* ----- PUBLISHING STUFF ---- */ $(document).on('click', '.js_publish_management .js_publish_btn', function () { - var $data = $(this).parent(".js_publish_management"); - var self=this; + var $data = $(this).parents(".js_publish_management:first"); + var self=this; openerp.jsonRpc($data.data('controller') || '/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')}) .then(function (result) { $data.toggleClass("css_unpublished css_published"); - $(self).toggleClass("btn-success btn-danger"); $data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off'); }).fail(function (err, data) { - website.error(data, '/web#model='+$data.data('object')+'&id='+$data.data('id')); + website.error(data, '/web#return_label=Website&model='+$data.data('object')+'&id='+$data.data('id')); }); }); diff --git a/addons/website/static/src/js/website.menu.js b/addons/website/static/src/js/website.menu.js index d24c9024023..87dd46dafe9 100644 --- a/addons/website/static/src/js/website.menu.js +++ b/addons/website/static/src/js/website.menu.js @@ -5,26 +5,22 @@ website.menu = {}; website.add_template_file('/website/static/src/xml/website.menu.xml'); - var globalEditor; - website.EditorBar.include({ events: _.extend({}, website.EditorBar.prototype.events, { 'click a[data-action="edit-structure"]': 'editStructure', }), editStructure: function () { var self = this; - globalEditor = self; var context = website.get_context(); openerp.jsonRpc('/web/dataset/call_kw', 'call', { model: 'website.menu', method: 'get_tree', - args: [[context.website_id]], + args: [context.website_id], kwargs: { context: context }, }).then(function (menu) { var result = new website.menu.EditMenuDialog(menu).appendTo(document.body); - self.trigger('tour:menu_editor_dialog_ready'); return result; }); }, @@ -104,7 +100,7 @@ new_window: link[1], }); var $menu = self.$('[data-menu-id="' + id + '"]'); - $menu.find('> div > span').text(menu_obj.name); + $menu.find('.js_menu_label').first().text(menu_obj.name); }); dialog.appendTo(document.body); } else { @@ -155,7 +151,6 @@ template: 'website.menu.dialog.add', init: function (editor, data) { this.data = data; - this.update_mode = !!this.data; return this._super.apply(this, arguments); }, start: function () { @@ -165,14 +160,13 @@ self.bind_data(self.data.name, self.data.url, self.data.new_window); } var $link_text = self.$('#link-text').focus(); - self.$('#link-existing').change(function () { - if (!$link_text.val()) { - $link_text.val($(this).find('option:selected').text()); - $link_text.focus(); - } + self.$('#link-page').change(function (e) { + if ($link_text.val()) { return; } + var data = $(this).select2('data'); + $link_text.val(data.create ? data.id : data.text); + $link_text.focus(); }); }); - globalEditor.trigger('tour:new_menu_entry_dialog_ready'); return result; }, save: function () { @@ -193,7 +187,6 @@ } }, destroy: function () { - globalEditor.trigger('tour:new_menu_entry_dialog_closed'); this._super.apply(this, arguments); }, }); diff --git a/addons/website/static/src/js/website.mobile.js b/addons/website/static/src/js/website.mobile.js index d4630d0a0c2..4acf2d78db8 100644 --- a/addons/website/static/src/js/website.mobile.js +++ b/addons/website/static/src/js/website.mobile.js @@ -20,5 +20,9 @@ document.getElementById("mobile-viewport").src = window.location.origin + window.location.pathname + "#mobile-preview"; this.$el.modal(); }, + destroy: function () { + $('.modal-backdrop').remove(); + this._super(); + }, }); })(); diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index 7277f80ba95..7d3b84f2254 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -51,9 +51,10 @@ }, }); - website.snippet.animationRegistry.carousel = website.snippet.Animation.extend({ + website.snippet.animationRegistry.carousel = + website.snippet.animationRegistry.slider = website.snippet.Animation.extend({ start: function () { - this.$target.carousel({interval: false}); + this.$target.carousel({interval: 10000}); }, }); @@ -82,7 +83,7 @@ var self = this; var speed = parseFloat(self.$target.attr("data-scroll-background-ratio") || 0); - if (speed == 1) { + if (speed === 1 || this.$target.css("background-image") === "none") { this.$target.css("background-attachment", "fixed").css("background-position", "0px 0px"); return; } else { @@ -101,10 +102,11 @@ } else { offset = - self.$target.offset().top * speed; } - self.$target.attr("data-scroll-background-offset", offset); + self.$target.attr("data-scroll-background-offset", offset > 0 ? 0 : offset); $(window).scroll(); }; img.src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); + $(window).scroll(); } }); diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 07979bfe696..d4f8920663a 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -26,7 +26,6 @@ self.snippets.$button.removeClass("hidden"); website.snippet.stop_animation(); website.snippet.start_animation(); - self.trigger('tour:editor_bar_loaded'); }); return this._super.apply(this, arguments); @@ -77,6 +76,7 @@ hack_to_add_snippet_id(); }); + website.snippet.styles = {}; website.snippet.selector = []; website.snippet.BuildingBlock = openerp.Widget.extend({ template: 'website.snippets', @@ -135,27 +135,33 @@ this.$modal = $(openerp.qweb.render('website.snippets_modal')); this.$modal.appendTo("body"); + + $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) { + e.preventDefault(); + }); + + this.getParent().on('change:height', this, function (editor) { + self.$el.css('top', editor.get('height')); + }); }, fetch_snippet_templates: function () { var self = this; - this.style_templates = {}; openerp.jsonRpc("/website/snippets", 'call', {}) .then(function (html) { var $html = $(html); - var $styles = $html.find("#snippet_styles"); - $styles.find("> [data-snippet-id]").each(function () { + var $styles = $html.find("[data-snippet-style-id]"); + $styles.each(function () { var $style = $(this); - var snipped_id = $style.data('snippet-id'); - self.style_templates[snipped_id] = { - 'snipped-id' : snipped_id, + var style_id = $style.data('snippet-style-id'); + website.snippet.styles[style_id] = { + 'snippet-style-id' : style_id, 'selector': $style.data('selector'), - 'class': $style.find(".oe_snippet_class").text(), - 'label': $style.find(".oe_snippet_label").text() + '$el': $style, }; }); - $styles.remove(); + $styles.addClass("hidden"); self.$snippets = $html.find(".tab-content > div > div").addClass("oe_snippet"); self.$el.append($html); @@ -185,35 +191,36 @@ bind_snippet_click_editor: function () { var self = this; var snipped_event_flag = false; - $("body").on('click', "[data-oe-model] [data-snippet-id], [data-oe-model][data-snippet-id]", function (event) { - if (snipped_event_flag) { - return; - } - snipped_event_flag = true; - setTimeout(function () {snipped_event_flag = false;}, 0); - var $target = $(event.currentTarget); - if (self.$active_snipped_id && self.$active_snipped_id.is($target)) { - return; - } - self.make_active($target); - }); - $("[data-oe-model]").on('click', function () { - if (!snipped_event_flag && self.$active_snipped_id && !self.$active_snipped_id.parents("[data-snippet-id]:first")) { - self.make_active(false); - } - }); + $("#wrapwrap").on('click', function (event) { + if (snipped_event_flag) { + 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.attr("data-oe-model") && !$target.parents("[data-oe-model]:first").length) { + $target = false; + } + if (self.$active_snipped_id && self.$active_snipped_id.is($target)) { + return; + } + self.make_active($target); + }); }, - snippet_blur: function ($snipped_id) { - if ($snipped_id) { - if ($snipped_id.data("snippet-editor")) { - $snipped_id.data("snippet-editor").onBlur(); + snippet_blur: function ($snippet) { + if ($snippet) { + if ($snippet.data("snippet-editor")) { + $snippet.data("snippet-editor").onBlur(); } } }, - snippet_focus: function ($snipped_id) { - if ($snipped_id) { - if ($snipped_id.data("snippet-editor")) { - $snipped_id.data("snippet-editor").onFocus(); + snippet_focus: function ($snippet) { + if ($snippet) { + if ($snippet.data("snippet-editor")) { + $snippet.data("snippet-editor").onFocus(); } } }, @@ -226,31 +233,35 @@ } } }, - make_active: function ($snipped_id) { - if ($snipped_id && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snipped_id.get(0)) { + make_active: function ($snippet) { + if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) { return; } if (this.$active_snipped_id) { this.snippet_blur(this.$active_snipped_id); this.$active_snipped_id = false; } - if ($snipped_id) { - if(_.indexOf(this.snippets, $snipped_id.get(0)) === -1) { - this.snippets.push($snipped_id.get(0)); + if ($snippet && $snippet.length) { + if(_.indexOf(this.snippets, $snippet.get(0)) === -1) { + this.snippets.push($snippet.get(0)); } - this.$active_snipped_id = $snipped_id; + this.$active_snipped_id = $snippet; this.create_overlay(this.$active_snipped_id); - this.snippet_focus($snipped_id); + this.snippet_focus($snippet); + } + $("#oe_snippets").trigger('snippet-activated', $snippet); + if ($snippet) { + $snippet.trigger('snippet-activated', $snippet); } }, - create_overlay: function ($snipped_id) { - if (typeof $snipped_id.data("snippet-editor") === 'undefined') { - var $targets = this.activate_overlay_zones($snipped_id); + create_overlay: function ($snippet) { + if (typeof $snippet.data("snippet-editor") === 'undefined') { + var $targets = this.activate_overlay_zones($snippet); if (!$targets.length) return; - var editor = website.snippet.editorRegistry[$snipped_id.data("snippet-id")] || website.snippet.editorRegistry.resize; - $snipped_id.data("snippet-editor", new editor(this, $snipped_id)); + var editor = website.snippet.editorRegistry[$snippet.data("snippet-id")] || website.snippet.editorRegistry.resize; + $snippet.data("snippet-editor", new editor(this, $snippet)); } - this.cover_target($snipped_id.data('overlay'), $snipped_id); + this.cover_target($snippet.data('overlay'), $snippet); }, path_eval: function(path){ @@ -301,7 +312,12 @@ $toInsert = $snippet.find('.oe_snippet_body').clone(); $toInsert.removeClass('oe_snippet_body'); - $toInsert.attr('data-snippet-id', snipped_id); + $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')) { @@ -325,14 +341,12 @@ } }, out: function(){ - if( action === 'insert'){ + var prev = $toInsert.prev(); + if( action === 'insert' && this === prev[0]){ dropped = false; $toInsert.detach(); } - }, - drop: function(){ - dropped = true; - }, + } }); }, stop: function(ev, ui){ @@ -353,7 +367,18 @@ website.snippet.start_animation(); self.create_overlay($target); - $target.data("snippet-editor").drop_and_build_snippet($target); + if ($snippet.data("snippet-editor")) { + $target.data("snippet-editor").drop_and_build_snippet($target); + } + + $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'); @@ -364,7 +389,10 @@ snippet.drop_and_build_snippet($target); } } - setTimeout(function () {self.make_active($target);},0); + setTimeout(function () { + $("#oe_snippets").trigger('snippet-dropped', $target); + self.make_active($target); + },0); } else { $toInsert.remove(); if (self.$modal.find('input:not(:checked)').length) { @@ -528,6 +556,205 @@ }); + website.snippet.styleRegistry = {}; + website.snippet.StyleEditor = openerp.Class.extend({ + // initialisation (don't overwrite) + init: function (parent, $target, snippet_id) { + this.parent = parent; + this.$target = $target; + var styles = this.$target.data("snippet-style-ids") || {}; + styles[snippet_id] = this; + this.$target.data("snippet-style-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.required = this.$el.data("required"); + + this.set_active(); + this.$el.find('li[data-class] a').on('mouseover mouseout click', _.bind(this._mouse, this)); + this.$target.on('snippet-style-reset', _.bind(this.set_active, this)); + + this.start(); + }, + _mouse: function (event) { + var self = this; + + if (event.type === 'mouseout') { + if (!this.over) return; + this.over = false; + } else if (event.type === 'click') { + this.over = false; + }else { + this.over = true; + } + + var $prev, $next; + if (event.type === 'mouseout') { + $prev = $(event.currentTarget).parent(); + $next = this.$el.find("li[data-class].active"); + } else { + $prev = this.$el.find("li[data-class].active"); + $next = $(event.currentTarget).parent(); + } + if (!$prev.length) { + $prev = false; + } + if ($prev && $prev[0] === $next[0]) { + $next = false; + if (this.required) { + return; + } + } + + var np = {'$next': $next, '$prev': $prev}; + + if (event.type === 'click') { + setTimeout(function () { + self.set_active(); + self.$target.trigger("snippet-style-change", [self, np]); + },0); + this.select(event, {'$next': $next, '$prev': $prev}); + } else { + setTimeout(function () { + self.$target.trigger("snippet-style-preview", [self, np]); + },0); + this.preview(event, np); + } + }, + // start is call just after the init + 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) { + var self = this; + // add or remove html class + if (np.$prev) { + this.$target.removeClass(np.$prev.data('class' || "")); + } + if (np.$next) { + this.$target.addClass(np.$next.data('class') || ""); + } + }, + /* 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) { + var self = this; + + // add or remove html class + if (np.$prev) { + this.$target.removeClass(np.$prev.data('class') || ""); + } + if (np.$next) { + this.$target.addClass(np.$next.data('class') || ""); + } + }, + /* 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"); + } + }); + + + website.snippet.styleRegistry.size = website.snippet.StyleEditor.extend({ + select: function(event, np) { + this._super(event, np); + this.parent.parent.cover_target(this.$overlay, this.$target); + }, + preview: function (event, np) { + this._super(event, np); + this.parent.parent.cover_target(this.$overlay, this.$target); + } + }); + + website.snippet.styleRegistry.background = website.snippet.StyleEditor.extend({ + _get_bg: function () { + return this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, ""); + }, + _set_bg: function (src) { + this.$target.css("background-image", src && src !== "" ? 'url(' + src + ')' : ""); + }, + start: function () { + this._super(); + var src = this._get_bg(); + this.$el.find("li[data-class].active.oe_custom_bg").data("src", src); + }, + select: function(event, np) { + var self = this; + this._super(event, np); + if (np.$next) { + if (np.$next.hasClass("oe_custom_bg")) { + var editor = new website.editor.ImageDialog(); + editor.on('start', self, function (o) {o.url = np.$prev && np.$prev.data("src") || np.$next && np.$next.data("src") || "";}); + editor.on('save', self, function (o) { + self._set_bg(o.url); + np.$next.data("src", o.url); + self.$target.trigger("snippet-style-change", [self, np]); + }); + editor.on('cancel', self, function () { + if (!np.$prev || np.$prev.data("src") === "") { + self.$target.removeClass(np.$next.data("class")); + self.$target.trigger("snippet-style-change", [self, np]); + } + }); + editor.appendTo($('body')); + } else { + this._set_bg(np.$next.data("src")); + } + } else { + this._set_bg(false); + this.$target.removeClass(np.$prev.data("class")); + } + }, + preview: function (event, np) { + this._super(event, np); + if (np.$next) { + this._set_bg(np.$next.data("src")); + } + }, + set_active: function () { + var self = this; + var bg = self.$target.css("background-image"); + this.$el.find('li').removeClass("active"); + var $active = this.$el.find('li[data-class]') + .filter(function () { + var $li = $(this); + return ($li.data('src') && bg.indexOf($li.data('src')) >= 0) || + (!$li.data('src') && self.$target.hasClass($li.data('class'))); + }) + .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=""]'); + } + $active.addClass("active"); + this.$el.find('li:has(li[data-class].active)').addClass("active"); + } + }); + + website.snippet.editorRegistry = {}; website.snippet.Editor = openerp.Class.extend({ init: function (parent, dom) { @@ -641,49 +868,22 @@ setTimeout(function () {self.parent.create_overlay(self.$target);},0); }, - _clone: function () { - var self = this; - this.$overlay.on('click', '.oe_snippet_clone', function () { - var $clone = self.$target.clone(false); - self.$target.after($clone); - return false; - }); - }, - load_style_options: function () { var self = this; var $styles = this.$overlay.find('.oe_options'); var $ul = $styles.find('ul:first'); - _.each(this.parent.style_templates, function (val) { + _.each(website.snippet.styles, function (val) { if (!self.parent.dom_filter(val.selector).is(self.$target)) { return; } - var $li = $("<li class='oe_style'/>").data(val); - $li.append($('<a/>').text(val.label)); - $ul.append($li); - if (self.$target.hasClass( "oe_snippet_" + $li.data("snipped-id") )) { - $li.addClass("active"); - } - $styles.removeClass("hidden"); + 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); }); - $styles.on('click', 'li.oe_style a', _.bind(this.change_style, this)); - }, - change_style: function (event) { - var $li = $(event.currentTarget).parent(); - var snipped_id = $li.data("snipped-id"); - var active = $li.hasClass("active"); - if (website.snippet.editorRegistry[snipped_id]) { - var snippet = new website.snippet.editorRegistry[snipped_id](this, this.$target); - snippet.drop_and_build_snippet(this.$target); - } - var _class = "oe_snippet_" + snipped_id + " " + ($li.data("class") || ""); - if (active) { - this.$target.removeClass(_class); - } else { - this.$target.addClass(_class); + if ($ul.find("li").length) { + $styles.removeClass("hidden"); } - $li.toggleClass("active"); }, get_parent_block: function () { @@ -709,16 +909,24 @@ */ start: function () { var self = this; - this.$overlay.on('click', '.oe_snippet_remove', function () { - self.onBlur(); - var index = _.indexOf(self.parent.snippets, self.$target.get(0)); - delete self.parent.snippets[index]; - self.$target.remove(); - self.$overlay.remove(); - return false; - }); + 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(); - this._clone(); + }, + + on_clone: function () { + var $clone = this.$target.clone(false); + this.$target.after($clone); + return false; + }, + + 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; }, /* @@ -747,60 +955,8 @@ * function called just before save vue */ clean_for_save: function () { - - }, - - change_background: function (bg, ul_options, callback) { - var self = this; - this.set_options_background(bg, ul_options); - var $ul = this.$editor.find(ul_options); - var bg_value = (typeof bg === 'string' ? this.$target.find(bg) : $(bg)).css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); - - // bind event on options - var $li = $ul.find("li"); - $li.on('click', function (event) { - if ($(this).data("value")) { - $li.removeClass("active"); - $(this).addClass("active"); - self.$editor.find('input').val(""); - } else { - var editor = new website.editor.ImageDialog(); - editor.on('start', self, function (o) {o.url = bg_value;}); - editor.on('save', self, function (o) { - var $bg = typeof bg === 'string' ? self.$target.find(bg) : $(bg); - $bg.css("background-image", "url(" + o.url + ")"); - }); - editor.appendTo($('body')); - } - if (callback) { - callback(); - } - }) - .on('mouseover', function (event) { - if ($(this).data("value")) { - var src = $(this).data("value"); - var $bg = typeof bg === 'string' ? self.$target.find(bg) : $(bg); - $bg.css("background-image", "url(" + src + ")"); - } - }) - .on('mouseout', function (event) { - var src = $ul.find('li.active').data("value"); - var $bg = typeof bg === 'string' ? self.$target.find(bg) : $(bg); - $bg.css("background-image", "url(" + src + ")"); - }); - }, - - set_options_background: function (bg, ul_options) { - var $ul = this.$editor.find(ul_options); - var bg_value = (typeof bg === 'string' ? this.$target.find(bg) : $(bg)).css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); - - // select in ul options - $ul.find("li").removeClass("active"); - var selected = $ul.find('[data-value="' + bg_value + '"], [data-value="' + bg_value.replace(/.*:\/\/[^\/]+/, '') + '"]'); - selected.addClass('active'); - if (!selected.length) { - $ul.find('.oe_custom_bg b').html(bg_value); - } + this.$target.removeAttr('contentEditable') + .find('*').removeAttr('contentEditable'); }, }); @@ -934,7 +1090,6 @@ return this.grid; }, - _drag_and_drop_after_insert_dropzone: function(){ var self = this; var $zones = $(".row:has(> .oe_drop_zone)").each(function () { @@ -963,7 +1118,36 @@ this.$target.addClass("col-md-offset-" + this.$target.prevAll(".oe_drop_to_remove").length); 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); + }, + onFocus : function () { + this._super(); + this.hide_remove_button(); + }, + on_clone: function () { + var $clone = this.$target.clone(false); + 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) { + return false; + } else { + $parent.data("snippet-editor").on_remove(); + } + } + this._super(); + this.hide_remove_button(); + return false; + }, on_resize: function (compass, beginClass, current) { if (compass !== 'w') return; @@ -989,186 +1173,177 @@ }, }); - website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.resize.extend({ + website.snippet.editorRegistry.slider = website.snippet.editorRegistry.resize.extend({ drop_and_build_snippet: function() { - var id = 0; - $("body .carousel").each(function () { - var _id = +$(this).attr("id").replace(/^myCarousel/, ''); - if (id <= _id) { - id = _id + 1; - } - }); - this.$target.attr("id", "myCarousel" + id); + 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='#myCarousel']").attr("data-target", "#myCarousel" + id); + this.$target.find("[data-target]").attr("data-target", "#myCarousel" + id); this.rebind_event(); }, - onFocus: function () { - this._super(); + // 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'); + this.$target.carousel('pause'); }, - onBlur: function () { - this._super(); - this.$target.carousel('cycle'); - }, 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"); } - this.$target.removeAttr('contentEditable') - .find('.content, .carousel-image img') - .removeAttr('contentEditable'); + }, + onFocus: function () { + this._super(); + this.$target.carousel('pause'); + }, + onBlur: function () { + this._super(); + this.$target.carousel('cycle'); }, 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', _.bind(this.on_add, this)); - this.$editor.find(".js_remove").on('click', _.bind(this.on_remove, this)); - - this.change_background(".item.active", 'ul[name="carousel-background"]'); - this.change_style(); - this.change_size(); - this.set_options_style(); - this.$target.carousel(); - - var self = this; - this.$target.on('slide.bs.carousel', function () { - self.set_options_style(); - self.set_options_background(".item.active", 'ul[name="carousel-background"]'); - self.$target.carousel(); - }); + this.$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.rebind_event(); }, - // rebind event to active carousel on edit mode - rebind_event: function () { + on_add_slide: function () { var self = this; - this.$target.off('click').on('click', '.carousel-control', function () { - self.$target.carousel($(this).data('slide')); }); - this.$target.off('click').on('click', '.carousel-indicators [data-target]', function () { - self.$target.carousel(+$(this).data('slide-to')); }); - - this.$target.attr('contentEditable', 'false') - .find('.content, .carousel-image img') - .attr('contentEditable', 'true'); - }, - on_add: function (e) { - e.preventDefault(); var cycle = this.$inner.find('.item').length; - var $active = this.$inner.find('.item.active'); + 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(); - var bg = this.$editor.find('ul[name="carousel-background"] li:not([data-value="'+ $active.css("background-image").replace(/.*:\/\/[^\/]+|\)$/g, '') +'"]):first').data("value"); - $clone.css("background-image", "url('"+ bg +"')"); + + // insert $clone.removeClass('active').insertAfter($active); - this.$target.carousel().carousel(++index); - this.rebind_event(); + setTimeout(function() { + self.$target.carousel().carousel(++index); + self.rebind_event(); + },0); + return $clone; }, - on_remove: function (e) { - e.preventDefault(); + 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.$inner.find('.item.active').fadeOut(1000, function () { - $(this).remove(); - self.$indicators.find('[data-target]:last').remove(); - self.$indicators.find("[data-slide-to]").removeClass("active"); - self.$indicators.find("[data-slide-to='" + new_index + "']").addClass("active"); + 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 () { - new_index = index % cycle; - self.$target.carousel().carousel( new_index + 1 ); - self.rebind_event(); + self.$target.carousel( index > 0 ? --index : cycle ); }, 500); } else { this.$target.find('.carousel-control, .carousel-indicators').addClass("hidden"); } }, - set_options_style: function () { - var style = false; - var $el = this.$inner.find('.item.active'); - var $ul = this.$editor.find('ul[name="carousel-style"]'); - - if ($el.hasClass('text_only')) - style = 'text_only'; - if ($el.hasClass('image_text')) - style = 'image_text'; - if ($el.hasClass('text_image')) - style = 'text_image'; + }); - $ul.find('[data-value="' + style + '"]').addClass('active'); + website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.slider.extend({ + clean_for_save: function () { + this._super(); + this.$target.css("background-image", ""); + this.$target.removeClass(this._class); }, - change_style: function () { + start : function () { var self = this; - var $ul = this.$editor.find('ul[name="carousel-style"]'); - var $li = $ul.find("li"); + this._super(); - $li.on('click', function (event) { - $li.removeClass("active"); - $(this).addClass("active"); - }) - .on('mouseover', function (event) { - var $el = self.$inner.find('.item.active'); - $el.removeClass('image_text text_image text_only'); - $el.addClass($(event.currentTarget).data("value")); - }) - .on('mouseout', function (event) { - var $el = self.$inner.find('.item.active'); - $el.removeClass('image_text text_image text_only'); - $el.addClass($ul.find('li.active').data("value")); - }); - }, - change_size: function () { - var $el = this.$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-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"); - var size = 'oe_big'; - if (this.$target.hasClass('oe_small')) - size = 'oe_small'; - else if (this.$target.hasClass('oe_medium')) - size = 'oe_medium'; + self.$target.carousel(); + }); + this.$target.trigger('slid'); + }, + on_add_slide: function () { + var $clone = this._super(); - var $ul = this.$editor.find('ul[name="carousel-size"]'); - var $li = $ul.find("li"); + // 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") || ""); - $ul.find('[data-value="' + size + '"]').addClass('active'); + 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')); }); - $li.on('click', function (event) { - $li.removeClass("active"); - $(this).addClass("active"); - }) - .on('mouseover', function (event) { - $el.removeClass('oe_big oe_small oe_medium'); - $el.addClass($(event.currentTarget).data("value")); - }) - .on('mouseout', function (event) { - $el.removeClass('oe_big oe_small oe_medium'); - $el.addClass($ul.find('li.active').data("value")); - }); - } + this.$target.find('.carousel-image, .content').attr('contentEditable', 'true'); + this._super(); + }, }); website.snippet.editorRegistry.parallax = website.snippet.editorRegistry.resize.extend({ start : function () { var self = this; this._super(); - this.change_background(this.$target, 'ul[name="parallax-background"]', function () { + this.scroll(); + this.$target.on('snippet-style-change snippet-style-preview', function () { self.$target.data("snippet-view").set_values(); }); - this.scroll(); - this.change_size(); + this.$target.attr('contentEditable', 'false'); + this.$target.find('> div > .oe_structure').attr('contentEditable', 'true'); }, scroll: function () { var self = this; @@ -1191,34 +1366,6 @@ this.$target.find(".parallax") .css("background-position", '') .removeAttr("data-scroll-background-offset"); - }, - change_size: function () { - var self = this; - - var size = 'oe_big'; - if (this.$target.hasClass('oe_small')) - size = 'oe_small'; - else if (this.$target.hasClass('oe_medium')) - size = 'oe_medium'; - var $ul = this.$editor.find('ul[name="parallax-size"]'); - var $li = $ul.find("li"); - - $ul.find('[data-value="' + size + '"]').addClass('active'); - - $li.on('click', function (event) { - $li.removeClass("active"); - $(this).addClass("active"); - self.$target.data("snippet-view").set_values(); - return false; - }) - .on('mouseover', function (event) { - self.$target.removeClass('oe_big oe_small oe_medium'); - self.$target.addClass($(event.currentTarget).data("value")); - }) - .on('mouseout', function (event) { - self.$target.removeClass('oe_big oe_small oe_medium'); - self.$target.addClass($ul.find('li.active').data("value")); - }); } }); @@ -1227,7 +1374,8 @@ * Don't need to add data-snippet-id="..." into the views */ - website.snippet.selector.push([".row > div[class*='col-md-']", 'colmd']); + website.snippet.selector.push([".row > [class*='col-md-']", 'colmd']); website.snippet.selector.push(['hr', 'hr']); + website.snippet.selector.push(['blockquote', 'quote']); })(); diff --git a/addons/website/static/src/js/website.tests.js b/addons/website/static/src/js/website.tests.js new file mode 100644 index 00000000000..977dc8e1848 --- /dev/null +++ b/addons/website/static/src/js/website.tests.js @@ -0,0 +1,40 @@ +var tests = {}; +var droptest = function () { + var errors = []; + for (var snippet_id in tests) { + if (!tests[snippet_id]['activated'] || !tests[snippet_id]['dropped']){ + console.log("Can't dropped or activated snippet: " + snippet_id); + } + } + if (errors.length) { + console.log(tests); + throw new Error("Can't dropped or activated at least one snippet"); + } + $("#oe_snippets").off('snippet-activated snippet-dropped'); +}; +var droptesttime = setTimeout(droptest,0); + +$("#oe_snippets").off('snippet-activated snippet-dropped') + .on('snippet-activated', function (event, dom) { + tests[$(dom).data('src-snippet-id')]['activated'] = true; + clearTimeout(droptesttime); + droptesttime = setTimeout(droptest,0); + }) + .on('snippet-dropped', function (event, dom, src_snipped_id) { + tests[$(dom).data('src-snippet-id')]['dropped'] = true; + clearTimeout(droptesttime); + droptesttime = setTimeout(droptest,0); + }); +var $thumbnails = $('#oe_snippets div.oe_snippet[data-snippet-id] .oe_snippet_thumbnail'); +$thumbnails.each(function () { + var $thumbnail = $(this); + tests[$thumbnail.parent().data('snippet-id')] = {}; + var position = $thumbnail.position(); + $thumbnail.trigger( $.Event( "mousedown", { which: 1, pageX: position.left, pageY: position.top } ) ); + $thumbnail.trigger( $.Event( "mousemove", { which: 1, pageX: position.left+100, pageY: position.top+100 } ) ); + $first_drop = $(".oe_drop_zone").first(); + position = $first_drop.position(); + $first_drop.trigger( $.Event( "mouseup", { which: 1, pageX: position.left+20, pageY: position.top+20 } ) ); + clearTimeout(droptesttime); + droptesttime = setTimeout(droptest,0); +}); diff --git a/addons/website/static/src/js/website.tour.banner.js b/addons/website/static/src/js/website.tour.banner.js index 89d19087775..029753d91a7 100644 --- a/addons/website/static/src/js/website.tour.banner.js +++ b/addons/website/static/src/js/website.tour.banner.js @@ -3,159 +3,151 @@ var website = openerp.website; - var render = website.tour.render; + website.EditorBar.include({ + start: function () { + this.registerTour(new website.BannerTour(this)); + return this._super(); + }, + }); - website.EditorBannerTour = website.EditorTour.extend({ - id: 'add_banner_tour', + website.BannerTour = website.Tour.extend({ + id: 'banner', name: "Insert a banner", + path: '/page/website.homepage', init: function (editor) { var self = this; - var $body = $(document.body); self.steps = [ { - stepId: 'welcome', - orphan: true, + stepId: 'welcome', + title: "Welcome to your website!", + content: "This tutorial will guide you to build your home page. We will start by adding a banner.", + template: self.popover({ next: "Start Tutorial", end: "Skip It" }), backdrop: true, - title: "Welcome to your website!", - content: "This tutorial will guide you to build your home page. We will start by adding a banner.", - template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }), }, { - stepId: 'edit-page', - element: 'button[data-action=edit]', + stepId: 'edit-page', + element: 'button[data-action=edit]', placement: 'bottom', - title: "Edit this page", - content: "Every page of your website can be modified through the <i>Edit</i> button.", - template: render('website.tour_popover'), - onShow: function () { - editor.on('tour:editor_bar_loaded', editor, function() { - self.movetoStep('add-block'); - }); + title: "Edit this page", + content: "Every page of your website can be modified through the <i>Edit</i> button.", + trigger: { + id: 'rte:ready', + type: 'openerp', + emitter: editor, }, }, { - stepId: 'add-block', - element: 'button[data-action=snippet]', + stepId: 'add-banner', + element: 'button[data-action=snippet]', placement: 'bottom', - title: "Insert building blocks", - content: "To add content in a page, you can insert building blocks.", - template: render('website.tour_popover'), - onShow: function () { - $('button[data-action=snippet]').click(function () { - self.movetoStep('drag-banner'); - }); - } + title: "Insert building blocks", + content: "To add content in a page, you can insert building blocks.", + trigger: 'click', }, { - stepId: 'drag-banner', - element: '#website-top-navbar [data-snippet-id=carousel].ui-draggable', + stepId: 'drag-banner', + snippet: 'carousel', placement: 'bottom', - title: "Drag & Drop a Banner", - content: "Drag the Banner block and drop it in your page.", - template: render('website.tour_popover'), - onShow: function () { - function beginDrag () { - $('.popover.tour').remove(); - function goToNextStep () { - $('#oe_snippets').hide(); - self.movetoStep('edit-title'); - $body.off('mouseup', goToNextStep); - } - $body.off('mousedown', beginDrag); - $body.on('mouseup', goToNextStep); - } - - $body.on('mousedown', beginDrag); - }, + title: "Drag & Drop a Banner", + content: "Drag the Banner block and drop it in your page.", + trigger: 'drag', }, { - stepId: 'edit-title', - element: '#wrap [data-snippet-id=carousel]:first .carousel-caption', + stepId: 'edit-title', + element: '#wrap [data-snippet-id=carousel]:first .carousel-caption', placement: 'top', - title: "Customize banner's text", - content: "Click in the text and start editing it. Click continue once it's done.", - template: render('website.tour_popover', { next: "Continue" }), - onHide: function () { - var $banner = $("#wrap [data-snippet-id=carousel]:first"); - if ($banner.length) { - $banner.click(); - } - }, + title: "Customize banner's text", + content: "Click in the text and start editing it. Click continue once it's done.", + template: self.popover({ next: "Continue" }), }, { - stepId: 'customize-banner', - element: '.oe_overlay_options .oe_options', + stepId: 'customize-banner', + element: '.oe_overlay_options .oe_options', placement: 'left', - title: "Customize the banner", - content: "You can customize characteristic of any blocks through the Customize menu. For instance, change the background of the banner.", - template: render('website.tour_popover', { next: "Continue" }), - onShow: function () { - $('.dropdown-menu [name=carousel-background]').click(function () { - self.movetoStep('save-changes'); - }); + title: "Customize the banner", + content: "Customize any block through this menu. Try to change the background of the banner.", + template: self.popover({ next: "Continue" }), + }, + { + stepId: 'add-three-cols', + element: 'button[data-action=snippet]', + placement: 'bottom', + title: "Add Another Block", + content: "Let's add another building block to your page.", + trigger: 'click', + }, + { + stepId: 'drag-three-columns', + snippet: 'three-columns', + placement: 'bottom', + title: "Drag & Drop a Block", + content: "Drag the <em>'3 Columns'</em> block and drop it below the banner.", + trigger: 'drag', + }, + { + stepId: 'activate-text-block-title', + element: '#wrap [data-snippet-id=three-columns] .text-center[data-snippet-id=colmd]', + placement: 'top', + title: "Edit an Area", + content: "Select any area of the page to modify it. Click on this subtitle.", + trigger: { + id: 'snippet-activated', + emitter: '#wrap [data-snippet-id=three-columns] .text-center[data-snippet-id=colmd]', }, }, { - stepId: 'save-changes', - element: 'button[data-action=save]', + stepId: 'remove-text-block-title', + element: '.oe_active .oe_snippet_remove', + placement: 'top', + title: "Delete the Title", + content: "From this toolbar you can move, duplicate or delete the selected zone. Click on the trash can to delete the title.", + trigger: 'click', + }, + { + stepId: 'save-changes', + element: 'button[data-action=save]', placement: 'right', - reflex: true, - title: "Save your modifications", - content: "Once you click on save, your website page is updated.", - template: render('website.tour_popover'), - onHide: function () { - self.saveStep('part-2'); + title: "Save your modifications", + content: "Publish your page by clicking on the <em>'Save'</em> button.", + trigger: 'click', + }, + { + stepId: 'part-2', + title: "Congratulation!", + content: "Your homepage has been updated.", + template: self.popover({ next: "Continue" }), + }, + { + stepId: 'show-mobile', + element: 'a[data-action=show-mobile-preview]', + placement: 'bottom', + title: "Test Your Mobile Version", + content: "Let's check how your homepage looks like on mobile devices.", + trigger: { + id: 'shown.bs.modal', + emitter: $(document), }, }, { - stepId: 'part-2', - orphan: true, - title: "Congratulation!", - content: "Your homepage have been updated. Now, we suggest you to insert others building blocks like texts and images to structure your page.", - template: render('website.tour_popover', { next: "Continue" }), + stepId: 'show-mobile-close', + element: 'button[data-dismiss=modal]', + placement: 'right', + title: "Close Mobile Preview", + content: "Scroll in the mobile preview to test the rendering. Once it's ok, close this dialog.", + trigger: 'click', }, { - stepId: 'show-tutorials', - element: '#help-menu-button', + stepId: 'show-tutorials', + element: '#help-menu-button', placement: 'left', - reflex: true, - title: "Help is always available", - content: "You can always click here if you want more helps or continue to build and get more tips about your website contents like page menu, ...", - template: render('website.tour_popover', { end: "Close" }), + title: "More Tutorials", + content: "Get more tutorials through this <em>'Help'</em> menu or click on the left <em>'Edit'</em> button to continue modifying this page.", + template: self.popover({ end: "Close Tutorial" }), + trigger: 'click', } ]; return this._super(); }, - startOfPart2: function () { - var currentStepIndex = this.currentStepIndex(); - var secondPartIndex = this.indexOfStep('part-2'); - var showTutorialsIndex = this.indexOfStep('show-tutorials'); - return (currentStepIndex === secondPartIndex || currentStepIndex === showTutorialsIndex) && !this.tour.ended(); - }, - canResume: function () { - return this.startOfPart2() || this._super(); - }, - }); - - website.EditorBar.include({ - start: function () { - var menu = $('#help-menu'); - var bannerTour = new website.EditorBannerTour(this); - var $menuItem = $($.parseHTML('<li><a href="#">'+bannerTour.name+'</a></li>')); - $menuItem.click(function () { - if (url.pathname !== '/' && url.pathname !== '/page/website.homepage') { - window.location.replace('/page/website.homepage?banner-tutorial=true'); - } - bannerTour.reset(); - bannerTour.start(); - }); - menu.append($menuItem); - var url = new website.UrlParser(window.location.href); - if (url.search.indexOf('?banner-tutorial=true') === 0 || bannerTour.startOfPart2()) { - bannerTour.start(); - } - return this._super(); - }, }); }()); diff --git a/addons/website/static/src/js/website.tour.install-app.js b/addons/website/static/src/js/website.tour.install-app.js deleted file mode 100644 index 1ca5fd50b16..00000000000 --- a/addons/website/static/src/js/website.tour.install-app.js +++ /dev/null @@ -1,64 +0,0 @@ -(function () { - 'use strict'; - - var website = openerp.website; - - var render = website.tour.render; - - website.InstallAppTour = website.EditorTour.extend({ - id: 'install_app', - name: "Install a new App", - init: function (editor) { - var self = this; - var $body = $(document.body); - self.steps = [ - { - stepId: 'welcome-install-app', - orphan: true, - backdrop: true, - title: "Install an App", - content: "You can intall some apps to manage your website content.", - template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }), - }, - { - stepId: 'customize', - element: '#customize-menu-button', - placement: 'left', - title: "Install an app", - content: "Add new apps by customizing your website.", - template: render('website.tour_popover'), - onShow: function () { - editor.on('rte:customize_menu_ready', editor, function() { - self.movetoStep('install-app'); - }); - }, - }, - { - stepId: 'install-app', - element: '#customize-menu li:last a', - placement: 'left', - orphan: true, - title: "Install an app", - content: "Click 'Install Apps' to select and install the application you would like to manage from your website.", - template: render('website.tour_popover'), - }, - ]; - return this._super(); - }, - }); - - website.EditorBar.include({ - start: function () { - var menu = $('#help-menu'); - var installAppTour = new website.InstallAppTour(this); - var $menuItem = $($.parseHTML('<li><a href="#">'+installAppTour.name+'</a></li>')); - $menuItem.click(function () { - installAppTour.reset(); - installAppTour.start(); - }); - menu.append($menuItem); - return this._super(); - }, - }); - -}()); diff --git a/addons/website/static/src/js/website.tour.js b/addons/website/static/src/js/website.tour.js index f9a931bfaaa..ea3c7bcf5b5 100644 --- a/addons/website/static/src/js/website.tour.js +++ b/addons/website/static/src/js/website.tour.js @@ -4,13 +4,7 @@ var website = openerp.website; website.add_template_file('/website/static/src/xml/website.tour.xml'); - website.tour = { - render: function render (template, dict) { - return openerp.qweb.render(template, dict); - } - }; - - website.EditorTour = openerp.Class.extend({ + website.Tour = openerp.Class.extend({ tour: undefined, steps: [], tourStorage: window.localStorage, @@ -19,27 +13,81 @@ name: this.id, storage: this.tourStorage, keyboard: false, + template: this.popover(), + onHide: function () { + window.scrollTo(0, 0); + } }); - this.tour.addSteps(_.map(this.steps, function (step) { - step.title = website.tour.render('website.tour_popover_title', { title: step.title }); - return step; - })); - // TODO: Disabled until properly implemented - // this.monkeyPatchTour(); + this.registerSteps(); }, - monkeyPatchTour: function () { + registerSteps: function () { var self = this; - // showStep should wait for 'element' to appear instead of moving to the next step - self.tour.showStep = function (i) { - var step = self.tour.getStep(i); - return (function proceed () { - if (step.orphan || $(step.element).length > 0) { - return Tour.prototype.showStep.call(self.tour, i); - } else { - setTimeout(proceed, 50); - } - }()); - }; + this.tour.addSteps(_.map(this.steps, function (step) { + step.title = openerp.qweb.render('website.tour_popover_title', { title: step.title }); + if (!step.element) { + step.orphan = true; + } + if (step.snippet) { + step.element = '#oe_snippets div.oe_snippet[data-snippet-id="'+step.snippet+'"] .oe_snippet_thumbnail'; + } + if (step.trigger) { + if (step.trigger === 'click') { + step.triggers = function (callback) { + $(step.element).one('click', function () { + (callback || self.moveToNextStep).apply(self); + }); + }; + } else if (step.trigger === 'drag') { + step.triggers = function (callback) { + self.onSnippetDragged(callback || self.moveToNextStep); + }; + } else if (step.trigger && step.trigger.id) { + if (step.trigger.emitter && step.trigger.type === 'openerp') { + step.triggers = function (callback) { + step.trigger.emitter.on(step.trigger.id, self, function customHandler () { + step.trigger.emitter.off(step.trigger.id, customHandler); + (callback || self.moveToNextStep).apply(self, arguments); + }); + }; + } else { + step.triggers = function (callback) { + var emitter = _.isString(step.trigger.emitter) ? $(step.trigger.emitter) : (step.trigger.emitter || $(step.element)); + emitter.on(step.trigger.id, function () { + (callback || self.moveToNextStep).apply(self, arguments); + }); + }; + } + } else if (step.trigger.modal) { + step.triggers = function (callback) { + var $doc = $(document); + function onStop () { + if (step.trigger.modal.stopOnClose) { + self.stop(); + } + } + $doc.on('hide.bs.modal', onStop); + $doc.one('shown.bs.modal', function () { + $('.modal button.btn-primary').one('click', function () { + $doc.off('hide.bs.modal', onStop); + (callback || self.moveToNextStep).apply(self, [step.trigger.modal.afterSubmit]); + }); + (callback || self.moveToNextStep).apply(self); + }); + }; + } + } + step.onShow = (function () { + var executed = false; + return function () { + if (!executed) { + _.isFunction(step.onStart) && step.onStart(); + _.isFunction(step.triggers) && step.triggers(); + executed = true; + } + }; + }()); + return step; + })); }, reset: function () { this.tourStorage.removeItem(this.id+'_current_step'); @@ -48,13 +96,10 @@ $('.popover.tour').remove(); }, start: function () { - if (this.canResume()) { + if (this.resume() || ((this.currentStepIndex() === 0) && !this.tour.ended())) { this.tour.start(); } }, - canResume: function () { - return (this.currentStepIndex() === 0) && !this.tour.ended(); - }, currentStepIndex: function () { var index = this.tourStorage.getItem(this.id+'_current_step') || 0; return parseInt(index, 10); @@ -68,20 +113,72 @@ }); return index; }, - movetoStep: function (stepId) { - $('.popover.tour').remove(); - var index = this.indexOfStep(stepId); - if (index > -1) { - this.tour.goto(index); + isCurrentStep: function (stepId) { + return this.currentStepIndex() === this.indexOfStep(stepId); + }, + moveToStep: function (step) { + var index = _.isNumber(step) ? step : this.indexOfStep(step); + if (index >= this.steps.length) { + this.stop(); + } else if (index >= 0) { + var self = this; + $('.popover.tour').remove(); + setTimeout(function () { + setTimeout(function () { + self.tour.goto(index); + }, 0); + }, 0); } }, - saveStep: function (stepId) { - var index = this.indexOfStep(stepId); - this.tourStorage.setItem(this.id+'_current_step', index); + moveToNextStep: function () { + var nextStepIndex = this.currentStepIndex() + 1; + this.moveToStep(nextStepIndex); }, stop: function () { this.tour.end(); }, + redirect: function (url) { + url = url || new website.UrlParser(window.location.href); + var path = (this.path && url.pathname !== this.path) ? this.path : url.pathname; + var search = url.activateTutorial(this.id); + var newUrl = path + search; + window.location.replace(newUrl); + }, + ended: function () { + return this.tourStorage.getItem(this.id+'_end') === "yes"; + }, + resume: function () { + // Override if necessary + return this.tourStorage.getItem(this.id+'_current_step') && !this.ended(); + }, + trigger: function (url) { + // Override if necessary + url = url || new website.UrlParser(window.location.href); + return url.isActive(this.id); + }, + testUrl: function (pattern) { + var url = new website.UrlParser(window.location.href); + return pattern.test(url.pathname+url.search); + }, + popover: function (options) { + return openerp.qweb.render('website.tour_popover', options); + }, + onSnippetDragged: function (callback) { + var self = this; + function beginDrag () { + $('.popover.tour').remove(); + function advance () { + if (_.isFunction(callback)) { + callback.apply(self); + } + } + $(document.body).one('mouseup', advance); + } + $('#website-top-navbar [data-snippet-id].ui-draggable').one('mousedown', beginDrag); + }, + onSnippetDraggedAdvance: function () { + onSnippetDragged(self.moveToNextStep); + }, }); website.UrlParser = openerp.Class.extend({ @@ -96,17 +193,179 @@ this.pathname = a.pathname; this.origin = a.origin; this.search = a.search; + this.hash = a.hash; + function generateTrigger (id) { + return "tutorial."+id+"=true"; + } + this.activateTutorial = function (id) { + var urlTrigger = generateTrigger(id); + var querystring = _.filter(this.search.split('?'), function (str) { + return str; + }); + if (querystring.length > 0) { + var queries = _.filter(querystring[0].split("&"), function (query) { + return query.indexOf("tutorial.") < 0 + }); + queries.push(urlTrigger); + return "?"+_.uniq(queries).join("&"); + } else { + return "?"+urlTrigger; + } + }; + this.isActive = function (id) { + var urlTrigger = generateTrigger(id); + return this.search.indexOf(urlTrigger) >= 0; + }; + }, + }); + + var TestConsole = openerp.Class.extend({ + tests: [], + editor: null, + init: function (editor) { + if (!editor) { + throw new Error("Editor cannot be null or undefined"); + } + this.editor = editor; + }, + test: function (id) { + return _.find(this.tests, function (tour) { + return tour.id === id; + }); + }, + snippetSelector: function (snippetId) { + return '#oe_snippets div.oe_snippet[data-snippet-id="'+snippetId+'"] .oe_snippet_thumbnail'; + }, + snippetThumbnail: function (snippetId) { + return $(this.snippetSelector(snippetId)).first(); + }, + snippetThumbnailExists: function (snippetId) { + return this.snippetThumbnail(snippetId).length > 0; + }, + dragAndDropSnippet: function (snippetId) { + function actualDragAndDrop ($thumbnail) { + var thumbnailPosition = $thumbnail.position(); + $thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top })); + $thumbnail.trigger($.Event("mousemove", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top+500 })); + var $dropZone = $(".oe_drop_zone").first(); + var dropPosition = $dropZone.position(); + $dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top })); + } + if (this.snippetThumbnailExists(snippetId)) { + actualDragAndDrop(this.snippetThumbnail(snippetId)); + } else { + this.editor.on('rte:ready', this, function () { + actualDragAndDrop(this.snippetThumbnail(snippetId)); + }); + } }, }); website.EditorBar.include({ + tours: [], + init: function () { + var result = this._super(); + website.TestConsole = new TestConsole(this); + return result; + }, start: function () { $('.tour-backdrop').click(function (e) { e.stopImmediatePropagation(); e.preventDefault(); }); + var url = new website.UrlParser(window.location.href); + var menu = $('#help-menu'); + _.each(this.tours, function (tour) { + var $menuItem = $($.parseHTML('<li><a href="#">'+tour.name+'</a></li>')); + $menuItem.click(function () { + tour.redirect(url); + tour.reset(); + tour.start(); + }); + menu.append($menuItem); + if (tour.trigger()) { + tour.start(); + } + }); return this._super(); }, + registerTour: function (tour) { + var self = this; + var testId = 'test_'+tour.id+'_tour'; + this.tours.push(tour); + var test = { + id: tour.id, + run: function (force) { + if (force === true) { + this.reset(); + } + var actionSteps = _.filter(tour.steps, function (step) { + return step.trigger; + }); + function executeStep (step) { + window.localStorage.setItem(testId, step.stepId); + step.triggers(function () { + var nextStep = actionSteps.shift(); + if (nextStep) { + // Ensure the previous step has been fully propagated + setTimeout(function () { + setTimeout(function () { + executeStep(nextStep); + }, 0); + }, 0); + } else { + window.localStorage.removeItem(testId); + } + }); + var $element = $(step.element); + if (step.snippet && step.trigger === 'drag') { + website.TestConsole.dragAndDropSnippet(step.snippet); + } else if (step.trigger.id === 'change') { + var currentValue = $element.val(); + var options = $element[0].options; + // FIXME: It may be necessary to set a particular value + var newValue = _.find(options, function (option) { + return option.value !== currentValue; + }).value; + $element.val(newValue).trigger($.Event("change")); + } else { + $element.trigger($.Event("click", { srcElement: $element })); + } + } + var url = new website.UrlParser(window.location.href); + if (tour.path && url.pathname !== tour.path) { + window.localStorage.setItem(testId, actionSteps[0].stepId); + window.location.href = tour.path; + } else { + var lastStepId = window.localStorage.getItem(testId); + var currentStep = actionSteps.shift(); + if (lastStepId) { + while (currentStep && lastStepId !== currentStep.stepId) { + currentStep = actionSteps.shift(); + } + } + if (currentStep.snippet && $(currentStep.element).length === 0) { + self.on('rte:ready', this, function () { + executeStep(currentStep); + }); + } else { + setTimeout(function () { + setTimeout(function () { + executeStep(currentStep); + }, 0); + }, 0); + } + } + }, + reset: function () { + window.localStorage.removeItem(testId); + }, + }; + website.TestConsole.tests.push(test); + if (window.localStorage.getItem(testId)) { + test.run(); + } + }, }); }()); diff --git a/addons/website/static/src/js/website.tour.menu-editor.js b/addons/website/static/src/js/website.tour.menu-editor.js deleted file mode 100644 index ede81025d9c..00000000000 --- a/addons/website/static/src/js/website.tour.menu-editor.js +++ /dev/null @@ -1,115 +0,0 @@ -(function () { - 'use strict'; - - var website = openerp.website; - - var render = website.tour.render; - - website.EditorBuilderTour = website.EditorTour.extend({ - id: 'menu_editor', - name: "Add a new page", - init: function (editor) { - var self = this; - var $body = $(document.body); - self.steps = [ - { - stepId: 'welcome-menu-editor', - orphan: true, - backdrop: true, - title: "Menu editor", - content: "We will show how to edit your website's menu.", - template: render('website.tour_popover', { next: "Start Tutorial", end: "Skip It" }), - }, - { - stepId: 'content-menu', - element: '#content-menu-button', - placement: 'left', - reflex: true, - title: "Edit the content", - content: "Click here to edit the menu.", - template: render('website.tour_popover'), - }, - { - stepId: 'edit-entry', - element: 'a[data-action=edit-structure]', - placement: 'left', - title: "Edit menu", - content: "Click here to create a new menu entry and manage options.", - template: render('website.tour_popover'), - onShow: function () { - editor.on('tour:menu_editor_dialog_ready', editor, function() { - self.movetoStep('add-menu-entry'); - }); - }, - }, - { - stepId: 'add-menu-entry', - element: 'a.js_add_menu', - placement: 'left', - title: "Add menu entry", - content: "Click here to create a new menu entry.", - template: render('website.tour_popover'), - onShow: function () { - editor.on('tour:new_menu_entry_dialog_ready', editor, function() { - self.movetoStep('enter-entry-name'); - }); - }, - }, - { - stepId: 'enter-entry-name', - element: '#link-text', - placement: 'left', - title: "Choose a label", - content: "This label will appear in the top menu and will be visible by all your audience.\nGive a meaningful name to help your visitors. For instance, 'Photos Gallery'.", - template: render('website.tour_popover', { next: "Continue" }), - }, - { - stepId: 'enter-page-name', - element: '#link-new', - placement: 'left', - title: "Link your menu to a 'gallery' page", - content: "This page does not exist. Create it by filling the name here. For instance, 'gallery'.", - template: render('website.tour_popover', { next: "Continue" }), - }, - { - stepId: 'save-page', - element: '.modal-footer:last button.btn-primary', - placement: 'right', - title: "Save the page", - content: "Save your new page.", - template: render('website.tour_popover'), - onShow: function () { - editor.on('tour:new_menu_entry_dialog_closed', editor, function () { - self.movetoStep('save-menu'); - }); - }, - }, - { - stepId: 'save-menu', - element: '.modal-footer button.btn-primary', - placement: 'right', - reflex: true, - title: "Save the menu", - content: "Save the menu to edit the Gallery content directly from the interface.", - template: render('website.tour_popover'), - }, - ]; - return this._super(); - }, - }); - - website.EditorBar.include({ - start: function () { - var menu = $('#help-menu'); - var builderTour = new website.EditorBuilderTour(this); - var $menuItem = $($.parseHTML('<li><a href="#">'+builderTour.name+'</a></li>')); - $menuItem.click(function () { - builderTour.reset(); - builderTour.start(); - }); - menu.append($menuItem); - return this._super(); - }, - }); - -}()); |
