summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/website/static/src/js')
-rw-r--r--addons/website/static/src/js/website.ace.js86
-rw-r--r--addons/website/static/src/js/website.backend.js32
-rw-r--r--addons/website/static/src/js/website.editor.js701
-rw-r--r--addons/website/static/src/js/website.js50
-rw-r--r--addons/website/static/src/js/website.menu.js21
-rw-r--r--addons/website/static/src/js/website.mobile.js4
-rw-r--r--addons/website/static/src/js/website.snippets.animation.js10
-rw-r--r--addons/website/static/src/js/website.snippets.editor.js754
-rw-r--r--addons/website/static/src/js/website.tests.js40
-rw-r--r--addons/website/static/src/js/website.tour.banner.js224
-rw-r--r--addons/website/static/src/js/website.tour.install-app.js64
-rw-r--r--addons/website/static/src/js/website.tour.js333
-rw-r--r--addons/website/static/src/js/website.tour.menu-editor.js115
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();
- },
- });
-
-}());