summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2016-05-18 18:02:51 +0200
committerqsm-odoo <qsm@odoo.com>2016-07-04 17:23:59 +0200
commit62fb1bb92493b00bd45ed3f4eced5ca8b23f81d6 (patch)
tree911fcb10158fa89ddd75ff2aac195f79236f3e59 /addons/web_editor/static/src
parentc1ab17337bdc816222be372aff314fcf5de8ba92 (diff)
[REF] web_editor: code refactoring and simplification
* Remove dead code + optimization * Use Dialog API from web module * Reorganize files * Correct buggy $() function for snippet option class * Add comments * Fix the snippet parent navigation : If a page contained a three level snippet structure (i.e A contains B contains C), when editing the following bug occured : If B or C was selected first, parenting buttons were correctly set. But if selecting A then C, clicking on the parent lead to going back on A instead of going on B. * Fix snippet thumbnail layout * Better isolate ui css from themes * MediaDialog image list : prevent displaying empty attachments * ...
Diffstat (limited to 'addons/web_editor/static/src')
-rw-r--r--addons/web_editor/static/src/js/editor.js52
-rw-r--r--addons/web_editor/static/src/js/iframe.js12
-rw-r--r--addons/web_editor/static/src/js/inline.js18
-rw-r--r--addons/web_editor/static/src/js/rte.summernote.js10
-rw-r--r--addons/web_editor/static/src/js/snippets.animation.js102
-rw-r--r--addons/web_editor/static/src/js/snippets.editor.js274
-rw-r--r--addons/web_editor/static/src/js/snippets.options.js295
-rw-r--r--addons/web_editor/static/src/js/translator.js26
-rw-r--r--addons/web_editor/static/src/js/widgets.js377
-rw-r--r--addons/web_editor/static/src/less/web_editor.ui.components.less8
-rw-r--r--addons/web_editor/static/src/less/web_editor.ui.less14
-rw-r--r--addons/web_editor/static/src/xml/editor.xml583
-rw-r--r--addons/web_editor/static/src/xml/translator.xml23
13 files changed, 823 insertions, 971 deletions
diff --git a/addons/web_editor/static/src/js/editor.js b/addons/web_editor/static/src/js/editor.js
index 9e842bed4bc..9dd08c3abd5 100644
--- a/addons/web_editor/static/src/js/editor.js
+++ b/addons/web_editor/static/src/js/editor.js
@@ -2,6 +2,7 @@ odoo.define('web_editor.editor', function (require) {
"use strict";
var ajax = require('web.ajax');
+var Dialog = require('web.Dialog');
var Widget = require('web.Widget');
var core = require('web.core');
var base = require('web_editor.base');
@@ -19,23 +20,22 @@ editor.editable = !!$('html').data('editable');
ajax.loadXML('/web_editor/static/src/xml/editor.xml', qweb);
- $(document).on('click', '.note-editable', function (ev) {
- ev.preventDefault();
- });
+$(document).on('click', '.note-editable', function (ev) {
+ ev.preventDefault();
+});
- $(document).on('submit', '.note-editable form .btn', function (ev) {
- // Disable form submition in editable mode
- ev.preventDefault();
- });
+$(document).on('submit', '.note-editable form .btn', function (ev) {
+ ev.preventDefault(); // Disable form submition in editable mode
+});
- $(document).on('hide.bs.dropdown', '.dropdown', function (ev) {
- // Prevent dropdown closing when a contenteditable children is focused
- if (ev.originalEvent
- && $(ev.target).has(ev.originalEvent.target).length
- && $(ev.originalEvent.target).is('[contenteditable]')) {
- ev.preventDefault();
- }
- });
+$(document).on('hide.bs.dropdown', '.dropdown', function (ev) {
+ // Prevent dropdown closing when a contenteditable children is focused
+ if (ev.originalEvent
+ && $(ev.target).has(ev.originalEvent.target).length
+ && $(ev.originalEvent.target).is('[contenteditable]')) {
+ ev.preventDefault();
+ }
+});
//////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -76,8 +76,6 @@ editor.Class = Widget.extend({
return res;
},
start: function () {
- var self = this;
-
$('.dropdown-toggle').dropdown();
this.display_placeholder();
@@ -89,11 +87,12 @@ editor.Class = Widget.extend({
window.onbeforeunload = function (event) {
if (rte.history.getEditableHasUndo().length && !flag) {
flag = true;
- setTimeout(function () {flag=false;},0);
+ _.defer(function () { flag=false; });
return _t('This document is not saved!');
}
};
- return this._super();
+
+ return this._super.apply(this, arguments);
},
display_placeholder: function () {
var $area = $("#wrapwrap").find("[data-oe-model] .oe_structure.oe_empty, [data-oe-model].oe_structure.oe_empty, [data-oe-type=html]")
@@ -135,18 +134,10 @@ editor.Class = Widget.extend({
if (!rte.history.getEditableHasUndo().length) {
return d.resolve();
}
- var $dialog = $(qweb.render('web_editor.discard')).appendTo(document.body);
- $dialog.on('click', '.btn-danger', function () {
- d.resolve();
- }).on('hidden.bs.modal', function () {
- d.reject();
- }).on('keydown.dismiss.bs.modal', function (event) {
- event.stopImmediatePropagation();
- });
- d.always(function () {
- $dialog.remove();
+ var confirm = Dialog.confirm(null, _t("If you discard the current edition, all unsaved changes will be lost. You can cancel to return to the edition mode."), {
+ confirm_callback: d.resolve.bind(d)
});
- $dialog.modal('show');
+ confirm.on("closed", d, d.reject);
}).then(function () {
window.onbeforeunload = null;
editor.reload();
@@ -155,5 +146,4 @@ editor.Class = Widget.extend({
});
return editor;
-
});
diff --git a/addons/web_editor/static/src/js/iframe.js b/addons/web_editor/static/src/js/iframe.js
index 2f494cd041c..398dc2e5bf9 100644
--- a/addons/web_editor/static/src/js/iframe.js
+++ b/addons/web_editor/static/src/js/iframe.js
@@ -1,11 +1,15 @@
odoo.define('web_editor.iframe', function (require) {
'use strict';
+var ajax = require("web.ajax");
+var core = require("web.core");
var editor = require('web_editor.editor');
var translator = require('web_editor.translate');
var rte = require('web_editor.rte');
var snippet_editor = require('web_editor.snippet.editor');
+ajax.loadXML('/web/static/src/xml/base_common.xml', core.qweb);
+
window.top.odoo[callback+"_updown"] = function (value, fields_values, field_name) {
var $editable = $("#editable_area");
if(value !== $editable.prop("innerHTML")) {
@@ -15,7 +19,7 @@ window.top.odoo[callback+"_updown"] = function (value, fields_values, field_name
}
snippet_editor.instance.make_active(false);
}
-
+
$editable.html(value);
if ($('body').hasClass('editor_enable') && value !== fields_values[field_name]) {
@@ -49,7 +53,7 @@ editor.Class.include({
this.on("snippets:ready", this, function () {
$(window.top).trigger("resize");
});
- return this._super();
+ return this._super.apply(this, arguments);
}
});
@@ -61,7 +65,7 @@ snippet_editor.Class.include({
rte.Class.include({
config: function ($editable) {
- var config = this._super($editable);
+ var config = this._super.apply(this, arguments);
if ($.deparam($.param.querystring()).debug !== undefined) {
config.airPopover.splice(7, 0, ['view', ['codeview']]);
}
@@ -71,7 +75,7 @@ rte.Class.include({
translator.Class.include({
start: function () {
- var res = this._super();
+ var res = this._super.apply(this, arguments);
$('button[data-action=save]').hide();
if (window.top.odoo[callback+"_editor"]) {
window.top.odoo[callback+"_editor"](this);
diff --git a/addons/web_editor/static/src/js/inline.js b/addons/web_editor/static/src/js/inline.js
index 8f63d603b20..765ce7682df 100644
--- a/addons/web_editor/static/src/js/inline.js
+++ b/addons/web_editor/static/src/js/inline.js
@@ -9,26 +9,25 @@ var snippet_editor = require('web_editor.snippet.editor');
widget.MediaDialog.include({
start: function () {
- this._super();
+ this._super.apply(this, arguments);
this.$('[href="#editor-media-video"]').addClass('hidden');
}
});
editor.Class.include({
start: function () {
- var self = this;
if (location.search.indexOf("enable_editor") !== -1) {
this.on('rte:start', this, function () {
// move the caret at the end of the text when click after all content
$("#wrapwrap").on('click', function (event) {
if ($(event.target).is("#wrapwrap") || $(event.target).is("#editable_area:empty")) {
- setTimeout(function () {
+ _.defer(function () {
var node = $("#editable_area *")
.filter(function () { return this.textContent.match(/\S|\u00A0/); })
.add($("#editable_area"))
.last()[0];
$.summernote.core.range.create(node, $.summernote.core.dom.nodeLength(node)).select();
- },0);
+ });
}
});
});
@@ -39,8 +38,7 @@ editor.Class.include({
snippet_editor.Class.include({
start: function () {
- var self = this;
- this._super();
+ this._super.apply(this, arguments);
setTimeout(function () {
var $editable = $("#editable_area");
transcoder.img_to_font($editable);
@@ -51,7 +49,7 @@ snippet_editor.Class.include({
});
},
clean_for_save: function () {
- this._super();
+ this._super.apply(this, arguments);
var $editable = $("#editable_area");
transcoder.font_to_img($editable);
transcoder.class_to_style($editable);
@@ -65,7 +63,6 @@ snippet_editor.Class.include({
});
});
},
-
});
window.top.odoo[callback+"_updown"] = function (value, fields_values) {
@@ -76,7 +73,7 @@ window.top.odoo[callback+"_updown"] = function (value, fields_values) {
if (snippet_editor.instance) {
snippet_editor.instance.make_active(false);
}
-
+
$editable.html(value);
transcoder.img_to_font($editable);
@@ -88,5 +85,4 @@ window.top.odoo[callback+"_updown"] = function (value, fields_values) {
$editable.trigger("content_changed");
}
};
-
-}); \ No newline at end of file
+});
diff --git a/addons/web_editor/static/src/js/rte.summernote.js b/addons/web_editor/static/src/js/rte.summernote.js
index 73565e19743..a2c645808d3 100644
--- a/addons/web_editor/static/src/js/rte.summernote.js
+++ b/addons/web_editor/static/src/js/rte.summernote.js
@@ -399,8 +399,7 @@ eventHandler.modules.linkDialog.showLinkDialog = function ($editable, $dialog, l
$editable.data('range').select();
$editable.data('NoteHistory').recordUndo();
- var editor = new widgets.LinkDialog($editable, linkInfo);
- editor.appendTo(document.body);
+ var editor = new widgets.LinkDialog(null, {}, $editable, linkInfo).open();
var def = new $.Deferred();
editor.on("save", this, function (linkInfo) {
@@ -420,15 +419,14 @@ eventHandler.modules.imageDialog.showImageDialog = function ($editable) {
if (r.sc.tagName && r.sc.childNodes.length) {
r.sc = r.sc.childNodes[r.so];
}
- var editor = new widgets.MediaDialog($editable, dom.isImg(r.sc) ? r.sc : null);
- editor.appendTo(document.body);
+ new widgets.MediaDialog(null, {}, $editable, dom.isImg(r.sc) ? r.sc : null).open();
return new $.Deferred().reject();
};
$.summernote.pluginEvents.alt = function (event, editor, layoutInfo, sorted) {
var $editable = layoutInfo.editable();
var $selection = layoutInfo.handle().find('.note-control-selection');
var media = $selection.data('target');
- new widgets.alt($editable, media).appendTo(document.body);
+ new widgets.alt(null, {}, $editable, media).open();
};
//////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -758,7 +756,7 @@ eventHandler.attach = function (oLayoutInfo, options) {
oLayoutInfo.editor().on('dblclick', imageSelector, function (event) {
show_tooltip = false;
if (!$(event.target).closest(".note-toolbar").length) { // prevent icon edition of top bar for default summernote
- new widgets.MediaDialog(oLayoutInfo.editor(), event.target).appendTo(document.body);
+ new widgets.MediaDialog(null, {}, oLayoutInfo.editor(), event.target).open();
}
});
oLayoutInfo.editor().on('click', imageSelector, function (event) {
diff --git a/addons/web_editor/static/src/js/snippets.animation.js b/addons/web_editor/static/src/js/snippets.animation.js
index f396bafcc30..ba311336436 100644
--- a/addons/web_editor/static/src/js/snippets.animation.js
+++ b/addons/web_editor/static/src/js/snippets.animation.js
@@ -7,6 +7,10 @@ var base = require('web_editor.base');
var registry = {};
var ready = [];
+/**
+ * The Animation class provides a way for executing code once a website snippet is loaded in the dom
+ * and handle the case where the website edit mode is triggered.
+ */
var Animation = Class.extend({
selector: false,
$: function () {
@@ -16,67 +20,61 @@ var Animation = Class.extend({
this.$el = this.$target = $(dom);
this.start(editable_mode);
},
- /*
- * start
- * This method is called after init
- */
- start: function (editable_mode) {
- },
- /*
- * stop
- * This method is called to stop the animation (e.g.: when rte is launch)
- */
- stop: function () {
- },
+ /**
+ * start
+ * This method is called after init
+ * @param editable_mode
+ */
+ start: function () {},
+ /**
+ * stop
+ * This method is called to stop the animation (e.g.: when rte is launch)
+ */
+ stop: function () {},
+});
+
+/**
+ * Start animations when loading the website is finished and stop/restart them
+ * once we enter in edit mode.
+ */
+base.ready().always(function () {
+ _.defer(start);
});
-var start = function (editable_mode, $target) {
+return {
+ Class: Animation,
+ registry: registry,
+ start: start,
+ stop: stop
+};
+
+function start(editable_mode, $init_target) {
for (var k in registry) {
var Animation = registry[k];
- var selector = "";
- if (Animation.prototype.selector) {
- if (selector != "") selector += ", "
- selector += Animation.prototype.selector;
- }
- if ($target) {
- if ($target.is(selector)) selector = $target;
- else continue;
- }
+ var selector = Animation.prototype.selector || "";
+ var $target = $init_target ? $init_target.filter(selector) : $(selector);
- $(selector).each(function () {
- var $snipped_id = $(this);
- if ( !$snipped_id.parents("#oe_snippets").length &&
- !$snipped_id.parent("body").length &&
- !$snipped_id.data("snippet-view")) {
- ready.push($snipped_id);
- $snipped_id.data("snippet-view", new Animation($snipped_id, editable_mode));
- } else if ($snipped_id.data("snippet-view")) {
- $snipped_id.data("snippet-view").start(editable_mode);
+ $target.each(function () {
+ var $snippet = $(this);
+ var animation = $snippet.data("snippet-view");
+ if (!$snippet.parents("#oe_snippets").length &&
+ !$snippet.parent("body").length &&
+ !animation) {
+ ready.push($snippet);
+ $snippet.data("snippet-view", new Animation($snippet, editable_mode));
+ } else if (animation) {
+ animation.start(editable_mode);
}
});
}
-};
+}
-var stop = function () {
- $(ready).each(function () {
- var $snipped_id = $(this);
- if ($snipped_id.data("snippet-view")) {
- $snipped_id.data("snippet-view").stop();
+function stop() {
+ _.each(ready, function ($snippet) {
+ var animation = $snippet.data("snippet-view");
+ if (animation) {
+ animation.stop();
}
});
-};
-
-base.ready().always(function () {
- setTimeout(function () {
- start();
- },0);
+}
});
-
-return {
- 'Class': Animation,
- 'registry': registry,
- 'start': start,
- 'stop': stop
-};
-
-}); \ No newline at end of file
diff --git a/addons/web_editor/static/src/js/snippets.editor.js b/addons/web_editor/static/src/js/snippets.editor.js
index ca8819bff50..1bfab9d3dc2 100644
--- a/addons/web_editor/static/src/js/snippets.editor.js
+++ b/addons/web_editor/static/src/js/snippets.editor.js
@@ -5,80 +5,42 @@ var Class = require('web.Class');
var ajax = require('web.ajax');
var core = require('web.core');
var Widget = require('web.Widget');
-var base = require('web_editor.base');
var editor = require('web_editor.editor');
var animation = require('web_editor.snippets.animation');
var options = require('web_editor.snippets.options');
var qweb = core.qweb;
-var dummy = editor.dummy;
-
-/* ----- SNIPPET SELECTOR ---- */
ajax.loadXML('/web_editor/static/src/xml/snippets.xml', qweb);
-editor.Class.include({
- init: function () {
- var self = this;
- var res = this._super.apply(this, arguments);
- var $editable = this.rte.editable();
- this.buildingBlock = new BuildingBlock(this, $editable);
- this.buildingBlock.on("snippets:ready", this, function () {
- self.trigger("snippets:ready");
- });
- return res;
- },
- start: function () {
- var self = this;
- animation.stop();
- this.buildingBlock.insertAfter(this.$el);
- animation.start(true);
-
- this.rte.editable().find("*").off('mousedown mouseup click');
- return this._super();
- },
- save: function () {
- this.buildingBlock.clean_for_save();
- return this._super.apply(this, arguments);
- },
-});
+var data = {};
/* ----- SNIPPET SELECTOR ---- */
-$.extend($.expr[':'],{
- hasData: function (node,i,m) {
+$.extend($.expr[':'], {
+ hasData: function (node) {
return !!_.toArray(node.dataset).length;
},
- data: function (node,i,m) {
+ data: function (node, i, m) {
return $(node).data(m[3]);
}
});
-var globalSelector = {
+data.globalSelector = {
closest: function () { return $(); },
all: function () { return $(); },
is: function () { return false; },
};
-/* ----- Jquery activate block ---- */
-
-$.fn.extend({
- activateBlock: function () {
- var target = globalSelector.closest($(this))[0] || (dom.isBR(this) ? this.parentNode : dom.node(this));
- var evt = document.createEvent("MouseEvents");
- evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, target);
- target.dispatchEvent(evt);
- return this;
- }
-});
-
-/* ----- BuildingBlock (managment of drag&drop and focus/blur snippets) ---- */
-
-var BuildingBlock = Widget.extend({
+/**
+ * Snippet editor main class.
+ * Management of drag&drop and focus/blur of snippets.
+ * Previously named "BuildingBlock".
+ */
+data.Class = Widget.extend({
id: "oe_snippets",
activeSnippets: [],
init: function (parent, $editable) {
- this.parent = parent;
this.$editable = $editable;
this._super.apply(this, arguments);
@@ -87,14 +49,14 @@ var BuildingBlock = Widget.extend({
}
this.$active_snipped_id = false;
this.snippets = [];
-
+
data.instance = this;
},
start: function () {
var self = this;
$(window).resize(function () {
- setTimeout('$(document).click()',0);
+ _.defer(function () { $(document).click(); });
});
this.fetch_snippet_templates();
@@ -106,7 +68,7 @@ var BuildingBlock = Widget.extend({
var _isNotBreakable = $.summernote.core.dom.isNotBreakable;
$.summernote.core.dom.isNotBreakable = function (node) {
- return _isNotBreakable(node) || $(node).is('div') || globalSelector.is($(node));
+ return _isNotBreakable(node) || $(node).is('div') || data.globalSelector.is($(node));
};
$(window).on('resize', function () {
@@ -136,7 +98,7 @@ var BuildingBlock = Widget.extend({
},
_add_check_selector : function (selector, no_check, is_children) {
var self = this;
- var selector = selector.split(/\s*,/).join(":not(.o_snippet_not_selectable), ") + ":not(.o_snippet_not_selectable)";
+ selector = selector.split(/\s*,/).join(":not(.o_snippet_not_selectable), ") + ":not(.o_snippet_not_selectable)";
if (no_check) {
return {
@@ -151,7 +113,7 @@ var BuildingBlock = Widget.extend({
}
};
} else {
- var selector = selector.split(/\s*,/).join(":o_editable, ") + ":o_editable";
+ selector = selector.split(/\s*,/).join(":o_editable, ") + ":o_editable";
return {
closest: function ($from, parentNode) {
var parents = self.$editable.get();
@@ -203,14 +165,14 @@ var BuildingBlock = Widget.extend({
}
$ul.children().tooltip({
- delay: { "show": 500, "hide": 100 },
- container: 'body',
- title: function () {
- return (navigator.appVersion.indexOf('Mac') > -1 ? 'CMD' : 'CTRL')+'+SHIFT+'+($(this).index()+1);
- },
- trigger: 'hover',
- placement: 'top'
- }).on('click', function () {$(this).tooltip('hide');});
+ delay: { "show": 500, "hide": 100 },
+ container: 'body',
+ title: function () {
+ return (navigator.appVersion.indexOf('Mac') > -1 ? 'CMD' : 'CTRL')+'+SHIFT+'+($(this).index()+1);
+ },
+ trigger: 'hover',
+ placement: 'top'
+ }).on('click', function () {$(this).tooltip('hide');});
// t-snippet
$html.find('[data-oe-type="snippet"]').each(function () {
@@ -239,35 +201,35 @@ var BuildingBlock = Widget.extend({
});
$styles.addClass("hidden");
- globalSelector.closest = function ($from) {
- var $temp;
- var $target;
- var len = selector.length;
- for (var i = 0; i<len; i++) {
- $temp = selector[i].closest($from, $target && $target[0]);
- if (!$target || $temp.length) {
- $target = $temp;
- }
+ data.globalSelector.closest = function ($from) {
+ var $temp;
+ var $target;
+ var len = selector.length;
+ for (var i = 0; i<len; i++) {
+ $temp = selector[i].closest($from, $target && $target[0]);
+ if (!$target || $temp.length) {
+ $target = $temp;
}
- return $target;
+ }
+ return $target;
};
- globalSelector.all = function ($from) {
- var $target;
- var len = selector.length;
- for (var i = 0; i<len; i++) {
- if (!$target) $target = selector[i].all($from);
- else $target = $target.add(selector[i].all($from));
- }
- return $target;
+ data.globalSelector.all = function ($from) {
+ var $target;
+ var len = selector.length;
+ for (var i = 0; i<len; i++) {
+ if (!$target) $target = selector[i].all($from);
+ else $target = $target.add(selector[i].all($from));
+ }
+ return $target;
};
- globalSelector.is = function ($from) {
- var len = selector.length;
- for (var i = 0; i<len; i++) {
- if (selector[i].is($from)) {
- return true;
- }
+ data.globalSelector.is = function ($from) {
+ var len = selector.length;
+ for (var i = 0; i<len; i++) {
+ if (selector[i].is($from)) {
+ return true;
}
- return false;
+ }
+ return false;
};
var number = 0;
@@ -364,7 +326,6 @@ var BuildingBlock = Widget.extend({
}
var pos = $target.offset();
var mt = parseInt($target.css("margin-top") || 0);
- var mb = parseInt($target.css("margin-bottom") || 0);
$el.css({
width: $target.outerWidth(),
left: pos.left,
@@ -432,8 +393,8 @@ var BuildingBlock = Widget.extend({
return;
}
- if (!globalSelector.is($target)) {
- $target = globalSelector.closest($target);
+ if (!data.globalSelector.is($target)) {
+ $target = data.globalSelector.closest($target);
}
if (self.$active_snipped_id && self.$active_snipped_id.is($target)) {
@@ -462,14 +423,14 @@ var BuildingBlock = Widget.extend({
var template = self.templateOptions;
for (var k in template) {
var Option = opt[template[k]['option']];
- if (Option && Option.prototype.clean_for_save !== dummy) {
+ if (Option && Option.prototype.clean_for_save !== editor.dummy) {
template[k].selector.all().filter(function () {
- var node = this;
- while (!/o_dirty|o_editable/.test(node.className) && node !== document) {
- node = node.parentNode;
- }
- return node.className.indexOf("o_dirty") !== -1;
- }).each(function () {
+ var node = this;
+ while (!/o_dirty|o_editable/.test(node.className) && node !== document) {
+ node = node.parentNode;
+ }
+ return node.className.indexOf("o_dirty") !== -1;
+ }).each(function () {
new Option(self, null, $(this), k).clean_for_save();
});
}
@@ -505,7 +466,7 @@ var BuildingBlock = Widget.extend({
create_snippet_editor: function ($snippet) {
if (typeof $snippet.data("snippet-editor") === 'undefined') {
if (!this.activate_overlay_zones($snippet).length) return;
- $snippet.data("snippet-editor", new Editor(this, $snippet));
+ $snippet.data("snippet-editor", new data.Editor(this, $snippet));
}
},
@@ -515,7 +476,7 @@ var BuildingBlock = Widget.extend({
var $tumb = $snippets.find(".oe_snippet_thumbnail_img:first");
var left = $tumb.outerWidth()/2;
var top = $tumb.outerHeight()/2;
- var $toInsert, dropped, $snippet, action, snipped_id;
+ var $toInsert, dropped, $snippet;
$snippets.draggable({
greedy: true,
@@ -535,7 +496,6 @@ var BuildingBlock = Widget.extend({
var $base_body = $snippet.find('.oe_snippet_body');
var $selector_siblings = $();
var $selector_children = $();
- var vertical = false;
var temp = self.templateOptions;
for (var k in temp) {
if ($base_body.is(temp[k].base_selector)) {
@@ -553,7 +513,7 @@ var BuildingBlock = Widget.extend({
$toInsert = $base_body.clone();
if (!$selector_siblings.length && !$selector_children.length) {
- console.debug($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: data-drop-near or data-drop-in");
+ console.warn($snippet.find(".oe_snippet_thumbnail_title").text() + " have not insert action: data-drop-near or data-drop-in");
return;
}
@@ -577,7 +537,7 @@ var BuildingBlock = Widget.extend({
},
stop: function (ev, ui) {
$toInsert.removeClass('oe_snippet_body');
-
+
if (! dropped && self.$editable.find('.oe_drop_zone') && ui.position.top > 3) {
var el = self.$editable.find('.oe_drop_zone').nearest({x: ui.position.left, y: ui.position.top}).first();
if (el.length) {
@@ -587,12 +547,12 @@ var BuildingBlock = Widget.extend({
}
self.$editable.find('.oe_drop_zone').droppable('destroy').remove();
-
+
if (dropped) {
var prev = $toInsert.first()[0].previousSibling;
var next = $toInsert.last()[0].nextSibling;
- var rte = self.parent.rte;
+ var rte = self.getParent().rte;
if (prev) {
$toInsert.detach();
@@ -638,7 +598,7 @@ var BuildingBlock = Widget.extend({
// call a method on a snippet and all his children
call_for_all_snippets: function ($snippet, callback) {
var self = this;
- $snippet.add(globalSelector.all($snippet)).each(function () {
+ $snippet.add(data.globalSelector.all($snippet)).each(function () {
var $snippet = $(this);
setTimeout(function () {
self.create_snippet_editor($snippet);
@@ -652,8 +612,8 @@ var BuildingBlock = Widget.extend({
// return the original snippet in the editor bar from a snippet id (string)
get_snippet_from_id: function (id) {
return $('.oe_snippet').filter(function () {
- return $(this).data('option') === id;
- }).first();
+ return $(this).data('option') === id;
+ }).first();
},
// Create element insertion drop zones. two css selectors can be provided
@@ -677,10 +637,10 @@ var BuildingBlock = Widget.extend({
var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
if (test) {
$drop.addClass("oe_vertical").css({
- 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
- 'float': 'none',
- 'display': 'inline-block'
- });
+ 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
+ 'float': 'none',
+ 'display': 'inline-block'
+ });
} else if (float === "left" || float === "right") {
$drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().last().outerHeight()), 30));
}
@@ -688,14 +648,14 @@ var BuildingBlock = Widget.extend({
$drop = $drop.clone();
$zone.prepend($drop);
- var node = $drop[0].nextSibling;
- var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
+ node = $drop[0].nextSibling;
+ test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
if (test) {
$drop.addClass("oe_vertical").css({
- 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
- 'float': 'none',
- 'display': 'inline-block'
- });
+ 'height': parseInt(window.getComputedStyle($zone[0]).lineHeight),
+ 'float': 'none',
+ 'display': 'inline-block'
+ });
} else if (float === "left" || float === "right") {
$drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().first().outerHeight()), 30));
}
@@ -816,8 +776,8 @@ var BuildingBlock = Widget.extend({
if ($target.data('overlay') && $target.data('overlay').hasClass("oe_active")) {
self.cover_target($target.data('overlay'), $target);
}
- },50);
- });
+ }, 50);
+ });
var resize = function () {
if ($zone.parent().length) {
@@ -834,9 +794,11 @@ var BuildingBlock = Widget.extend({
}
});
-/* ----- Editor (object who contain overlay and the option list) ---- */
-
-var Editor = Class.extend({
+/**
+ * Snippet editor Editor class.
+ * Management of the overlay and option list for a snippet.
+ */
+data.Editor = Class.extend({
init: function (BuildingBlock, dom) {
this.buildingBlock = BuildingBlock;
this.$target = $(dom);
@@ -874,12 +836,12 @@ var Editor = Class.extend({
_drag_and_drop_active_drop_zone: function ($zones) {
var self = this;
$zones.droppable({
- over: function () {
+ over: function () {
$(".oe_drop_zone.hide").removeClass("hide");
$(this).addClass("hide").first().after(self.$target);
self.dropped = true;
},
- out: function () {
+ out: function () {
$(this).removeClass("hide");
self.$target.detach();
self.dropped = false;
@@ -898,12 +860,12 @@ var Editor = Class.extend({
self.$overlay.addClass("hidden");
var $selector_siblings;
- for (var i=0; i<self.selector_siblings.length; i++) {
+ for (var i = 0 ; i < self.selector_siblings.length ; i++) {
if (!$selector_siblings) $selector_siblings = self.selector_siblings[i].all();
else $selector_siblings = $selector_siblings.add(self.selector_siblings[i].all());
}
var $selector_children;
- for (var i=0; i<self.selector_children.length; i++) {
+ for (i = 0 ; i < self.selector_children.length ; i++) {
if (!$selector_children) $selector_children = self.selector_children[i].all();
else $selector_children = $selector_children.add(self.selector_children[i].all());
}
@@ -931,7 +893,7 @@ var Editor = Class.extend({
$(".oe_drop_clone, .oe_drop_to_remove").remove();
if (this.dropped) {
- this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
if (prev) {
this.$target.insertAfter(prev);
@@ -961,6 +923,7 @@ var Editor = Class.extend({
this.styles = {};
this.selector_siblings = [];
this.selector_children = [];
+
_.each(this.buildingBlock.templateOptions, function (val, option_id) {
if (!val.selector.is(self.$target)) {
return;
@@ -979,7 +942,7 @@ var Editor = Class.extend({
this.$overlay.find(".oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove").addClass('hidden');
}
- if ($ul.find("li").length) {
+ if ($ul.children().length) {
$styles.removeClass("hidden");
}
this.$overlay.find('[data-toggle="dropdown"]').dropdown();
@@ -988,10 +951,7 @@ var Editor = Class.extend({
get_parent_block: function () {
var self = this;
var $button = this.$overlay.find('.oe_snippet_parent');
- var $parent = this.$target.parents().filter(function () { return $(this).data("snippet-editor"); });
- if (!$parent.length) {
- $parent = globalSelector.closest(this.$target.parent());
- }
+ var $parent = data.globalSelector.closest(this.$target.parent());
if ($parent.length) {
$button.removeClass("hidden");
$button.off("click").on('click', function (event) {
@@ -1010,7 +970,6 @@ var Editor = Class.extend({
* This method is called after init and _readXMLData
*/
start: function () {
- var self = this;
if (!this.$target.parent().is(':o_editable')) {
this.$overlay.find('.oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove').remove();
} else {
@@ -1024,7 +983,7 @@ var Editor = Class.extend({
event.preventDefault();
var $clone = this.$target.clone(false);
- this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
this.$target.after($clone);
this.buildingBlock.call_for_all_snippets($clone, function (editor, $snippet) {
@@ -1039,7 +998,7 @@ var Editor = Class.extend({
event.preventDefault();
this.on_blur();
- this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
var index = _.indexOf(this.buildingBlock.snippets, this.$target.get(0));
this.buildingBlock.call_for_all_snippets(this.$target, function (editor, $snippet) {
@@ -1049,8 +1008,6 @@ var Editor = Class.extend({
});
delete this.buildingBlock.snippets[index];
- var $editable = this.$target.closest(".o_editable");
-
// remove node and his empty
var node = this.$target.parent()[0];
@@ -1103,11 +1060,42 @@ var Editor = Class.extend({
},
});
-var data = {
- Class: BuildingBlock,
- Editor: Editor,
- globalSelector: globalSelector,
-};
-return data;
+/**
+ * Add the ability on the main editor class to instantiate the snippet editor component
+ */
+editor.Class.include({
+ init: function () {
+ var self = this;
+ var res = this._super.apply(this, arguments);
+ var $editable = this.rte.editable();
+ this.buildingBlock = new data.Class(this, $editable);
+ this.buildingBlock.on("snippets:ready", this, function () {
+ self.trigger("snippets:ready");
+ });
+ return res;
+ },
+ start: function () {
+ this.buildingBlock.insertAfter(this.$el);
+ this.rte.editable().find("*").off('mousedown mouseup click');
+ return this._super.apply(this, arguments);
+ },
+ save: function () {
+ this.buildingBlock.clean_for_save();
+ return this._super.apply(this, arguments);
+ },
+});
+
+/**
+ * Add the ability the restart the animations
+ */
+editor.Class.include({
+ start: function () {
+ animation.stop();
+ return this._super.apply(this, arguments).then(function () {
+ animation.start(true);
+ });
+ },
+});
+return data;
});
diff --git a/addons/web_editor/static/src/js/snippets.options.js b/addons/web_editor/static/src/js/snippets.options.js
index 3f933f4c191..77879ce8803 100644
--- a/addons/web_editor/static/src/js/snippets.options.js
+++ b/addons/web_editor/static/src/js/snippets.options.js
@@ -1,89 +1,87 @@
odoo.define('web_editor.snippets.options', function (require) {
'use strict';
- var Class = require('web.Class');
var ajax = require('web.ajax');
+ var Class = require('web.Class');
var core = require('web.core');
var Dialog = require('web.Dialog');
var base = require('web_editor.base');
var editor = require('web_editor.editor');
var widget = require('web_editor.widget');
- var animation = require('web_editor.snippets.animation');
var qweb = core.qweb;
var _t = core._t;
- /* ----- Editor option (object link the the xml with data-js) ---- */
-
+ /**
+ * The SnippetOption class handles one option for one snippet. The registry returned by
+ * this module contains the names of the specialized SnippetOption which can be reference
+ * thanks to the data-js key of the full options web_editor template.
+ */
var SnippetOption = Class.extend({
// initialisation (don't overwrite)
init: function (BuildingBlock, editor, $target, option_id) {
this.buildingBlock = BuildingBlock;
this.editor = editor;
this.$target = $target;
+
+ this.option = option_id;
var option = this.buildingBlock.templateOptions[option_id];
- var styles = this.$target.data("snippet-option-ids") || {};
- styles[option_id] = this;
- this.$target.data("snippet-option-ids", styles);
- this.$overlay = this.$target.data('overlay') || $('<div>');
- this.option= option_id;
- this.$el = option.$el.find(">li").clone();
+ this.$el = option.$el.children("li").clone();
this.data = option.$el.data();
+
+ this.$overlay = this.$target.data('overlay') || $();
},
// helper for this.$target.find
- $: function (selector) {
- return this.$target(selector);
+ $: function () {
+ return this.$target.find.apply(this.$target, arguments);
},
_bind_li_menu: function () {
this.$el.filter("li:hasData").find('a:first')
.off('mouseenter click')
- .on('mouseenter click', _.bind(this._mouse, this));
+ .on('mouseenter click', _.bind(_mouse, this));
this.$el
.off('mouseenter click', "li:hasData a")
- .on('mouseenter click', "li:hasData a", _.bind(this._mouse, this));
+ .on('mouseenter click', "li:hasData a", _.bind(_mouse, this));
- this.$el.closest("ul").add(this.$el)
- .off('mouseleave')
- .on('mouseleave', _.bind(this.reset, this));
+ this.$el.off('mouseleave')
+ .on('mouseleave', _.bind(this.reset, this));
- this.$el
- .off('mouseleave', "ul")
- .on('mouseleave', "ul", _.bind(this.reset, this));
+ this.$el.off('mouseleave', "ul")
+ .on('mouseleave', "ul", _.bind(this.reset, this));
+
+ this.methodsToReset = [];
- this.reset_methods = [];
+ function _mouse(event) {
+ var $next = $(event.currentTarget).parent();
+
+ // triggers preview or apply methods if a menu item has been clicked
+ this.select(event.type === "click" ? "click" : "over", $next);
+ if (event.type === 'click') {
+ this.set_active();
+ this.$target.trigger("snippet-option-change", [this]);
+ } else {
+ this.$target.trigger("snippet-option-preview", [this]);
+ }
+ }
},
/**
- * this method handles mouse:over and mouse:leave on the snippet editor menu
+ * The set_active method tweaks the option dropdown to show the selected value
+ * according to the state of the $target the option customizes.
*/
- _time_mouseleave: null,
- _mouse: function (event) {
- var $next = $(event.currentTarget).parent();
-
- // triggers preview or apply methods if a menu item has been clicked
- this.select(event.type === "click" ? "click" : "over", $next);
- if (event.type === 'click') {
- this.set_active();
- this.$target.trigger("snippet-option-change", [this]);
- } else {
- this.$target.trigger("snippet-option-preview", [this]);
- }
- },
- /*
- * select and set item active or not (add highlight item and his parents)
- * called before start
- */
set_active: function () {
- var classes = _.uniq((this.$target.attr("class") || '').split(/\s+/));
+ var self = this;
this.$el.find('[data-toggle_class], [data-select_class]')
- .add(this.$el)
- .filter('[data-toggle_class], [data-select_class]')
+ .addBack('[data-toggle_class], [data-select_class]')
.removeClass("active")
- .filter('[data-toggle_class="' + classes.join('"], [data-toggle_class="') + '"] ,'+
- '[data-select_class="' + classes.join('"], [data-select_class="') + '"]')
+ .filter(function () {
+ var $elem = $(this);
+ var className = $elem.data("toggle_class") || $elem.data("select_class");
+ return self.$target.hasClass(className);
+ })
.addClass("active");
},
@@ -97,131 +95,115 @@ odoo.define('web_editor.snippets.options', function (require) {
this._bind_li_menu();
},
- on_blur : function () {
- },
-
- on_clone: function ($clone) {
- },
+ on_blur : function () {},
+ on_clone: function ($clone) {},
+ on_move: function () {},
+ on_remove: function () {},
- on_move: function () {
- },
-
- on_remove: function () {
- },
-
- drop_and_build_snippet: function () {
- },
+ drop_and_build_snippet: function () {},
reset: function () {
var self = this;
- var lis = self.$el.add(self.$el.find('li')).filter('.active').get();
- lis.reverse();
- _.each(lis, function (li) {
+ var lis = this.$el.find("li.active").addBack('.active').get();
+ _.each(lis.reverse(), function (li) {
var $li = $(li);
- for (var k in self.reset_methods) {
- var method = self.reset_methods[k];
- if ($li.is('[data-'+method+']') || $li.closest('[data-'+method+']').size()) {
- delete self.reset_methods[k];
+ for (var k in self.methodsToReset) {
+ var method = self.methodsToReset[k];
+ if ($li.closest('[data-' + method + ']').length) {
+ delete self.methodsToReset[k];
}
}
self.select("reset", $li);
});
- for (var k in self.reset_methods) {
- var method = self.reset_methods[k];
+ for (var k in this.methodsToReset) {
+ var method = this.methodsToReset[k];
if (method) {
- self[method]("reset", null);
+ this[method]("reset", null);
}
}
- self.reset_methods = [];
- self.$target.trigger("snippet-option-reset", [this]);
+ this.methodsToReset = [];
+ this.$target.trigger("snippet-option-reset", [this]);
},
- // call data-method args as method (data-only can be used)
+ /**
+ * Handle action type on a particuler $li (sub)option of the option.
+ * @param type action type to handle ("click", "over", "reset")
+ * @param $li the suboption DOM element which is concerned by the action
+ */
select: function (type, $li) {
- var self = this,
- $methods = [],
- el = $li[0];
-
+ // (sub)options can say they respond to only one action type
if ($li.data('only') && type !== $li.data('only')) {
return;
}
-
- if (type==="click") {
+ // If the type is click, the user selected the option for good (so reset and record action)
+ if (type === "click") {
this.reset();
- this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
}
- function filter (k) { return k !== 'oeId' && k !== 'oeModel' && k !== 'oeField' && k !== 'oeXpath' && k !== 'oeSourceId' && k !== 'only';}
- function hasData(el) {
- for (var k in el.dataset) {
- if (filter (k)) {
- return true;
- }
- }
- return false;
- }
- function method(el) {
- var data = {};
- for (var k in el.dataset) {
- if (filter (k)) {
- data[k] = el.dataset[k];
- }
- }
- return data;
- }
-
- while (el && this.$el.is(el) || _.some(this.$el.map(function () {return $.contains(this, el);}).get()) ) {
- if (hasData(el)) {
- $methods.push(el);
+ // Search for methods (data-...) (i.e. data-toggle_class) on the selected (sub)option and its parents
+ var el = $li[0];
+ var methods = [];
+ do {
+ var m = _.omit(el.dataset, ["oeId", "oeModel", "oeField", "oeXpath", "oeSourceId", "only"]);
+ if (_.keys(m).length) {
+ methods.push([el, m]);
}
el = el.parentNode;
- }
+ } while (this.$el.parent().has(el).length);
- $methods.reverse();
-
- _.each($methods, function (el) {
- var $el = $(el);
- var methods = method(el);
+ // Call the found method in the right order (parents -> child) and save them for later reset
+ var self = this;
+ _.each(methods.reverse(), function (data) {
+ var $el = $(data[0]);
+ var methods = data[1];
for (var k in methods) {
if (self[k]) {
- if (type !== "reset" && self.reset_methods.indexOf(k) === -1) {
- self.reset_methods.push(k);
+ if (type !== "reset" && self.methodsToReset.indexOf(k) === -1) {
+ self.methodsToReset.push(k);
}
self[k](type, methods[k], $el);
} else {
- console.error("'"+self.option+"' snippet have not method '"+k+"'");
+ console.error("'" + self.option + "' snippet option have not method '" + k + "'");
}
}
});
},
- // default method for snippet
+ /**
+ * Default option methods toggle_class and selected_class handle the two behavior of having a list
+ * of option classes to toggle or not and having a list of option classes where only one must be chosen.
+ */
toggle_class: function (type, value, $li) {
- var $lis = this.$el.find('[data-toggle_class]').add(this.$el).filter('[data-toggle_class]');
+ var $lis = this.$el.find("[data-toggle_class]").addBack("[data-toggle_class]");
- function map ($lis) {
+ function map($lis) {
return $lis.map(function () {return $(this).data("toggle_class");}).get().join(" ");
}
var classes = map($lis);
var active_classes = map($lis.filter('.active, :has(.active)'));
- this.$target.removeClass(classes);
- this.$target.addClass(active_classes);
-
+ this.$target.removeClass(classes).addClass(active_classes);
if (type !== 'reset') {
this.$target.toggleClass(value);
}
},
select_class: function (type, value, $li) {
- var $lis = this.$el.find('[data-select_class]').add(this.$el).filter('[data-select_class]');
+ var $lis = this.$el.find("[data-select_class]").addBack("[data-select_class]");
- var classes = $lis.map(function () {return $(this).data('select_class');}).get();
+ var classes = $lis.map(function () {return $(this).data("select_class");}).get().join(" ");
- this.$target.removeClass(classes.join(" "));
- if(value) this.$target.addClass(value);
+ this.$target.removeClass(classes);
+ if (value) {
+ this.$target.addClass(value);
+ }
},
+
+ /**
+ * Helper method to evaluate the value expression directly as a function
+ */
eval: function (type, value, $li) {
var fn = new Function("node", "type", "value", "$li", value);
fn.call(this, this, type, value, $li);
@@ -233,51 +215,14 @@ odoo.define('web_editor.snippets.options', function (require) {
/* ----- default options ---- */
- // to remove after 9.0 (keep for compatibility without update with -u)
- registry.media = SnippetOption.extend({
- start: function () {
- var self =this;
- this._super();
- animation.start(true, this.$target);
- this.$target.closest(".o_editable").on("content_changed", function () {
- if (!self.$target.parent().length) {
- self.$target.remove();
- self.$overlay.remove();
- }
- });
- },
- edition: function (type, value) {
- if(type !== "click") return;
- var self = this;
- var _editor = new widget.MediaDialog(this.$target.closest('.o_editable'), this.$target[0]);
- _editor.appendTo(document.body);
- _editor.on('saved', this, function (item, old) {
- self.editor.on_blur();
- self.buildingBlock.make_active(false);
- if (self.$target.parent().data("oe-field") !== "image") {
- setTimeout(function () {
- self.buildingBlock.make_active($(item));
- },0);
- }
- $(item).trigger("content_changed");
- });
- },
- on_focus : function () {
- var self = this;
- var $parent = this.$target.parent();
-
- if ($parent.data("oe-field") === "image" && $parent.hasClass('o_editable')) {
- this.$overlay.addClass("hidden");
- self.edition('click', null);
- self.buildingBlock.make_active(false);
- }
- }
- });
-
+ /**
+ * The colorpicker option is designed to change the background color class of a snippet. This class change the
+ * default background color and text color of the snippet content.
+ */
registry.colorpicker = SnippetOption.extend({
start: function () {
var self = this;
- var res = this._super();
+ var res = this._super.apply(this, arguments);
if (!this.$el.find('.colorpicker').length) {
var $pt = $(qweb.render('web_editor.snippet.option.colorpicker'));
@@ -363,10 +308,13 @@ odoo.define('web_editor.snippets.options', function (require) {
}
});
+ /**
+ * The background option is designed to change the background image of a snippet.
+ */
registry.background = SnippetOption.extend({
start: function ($change_target) {
this.$target = $change_target || this.$target;
- this._super();
+ this._super.apply(this, arguments);
var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
if (this.$target.hasClass('oe_custom_bg')) {
this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
@@ -383,7 +331,7 @@ odoo.define('web_editor.snippets.options', function (require) {
},
select_class : function (type, value, $li) {
this.background(type, '', $li);
- this._super(type, value, $li);
+ this._super.apply(this, arguments);
},
choose_image: function (type, value, $li) {
if(type !== "click") return;
@@ -393,8 +341,7 @@ odoo.define('web_editor.snippets.options', function (require) {
$image.attr("src", value);
$image.appendTo(self.$target);
- var _editor = new widget.MediaDialog(null, $image[0]);
- _editor.appendTo(document.body);
+ var _editor = new widget.MediaDialog(null, {}, null, $image[0]).open();
_editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
_editor.on('saved', self, function () {
@@ -411,9 +358,8 @@ odoo.define('web_editor.snippets.options', function (require) {
});
},
set_active: function () {
- var self = this;
var src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, "");
- this._super();
+ this._super.apply(this, arguments);
if (this.$target.hasClass('oe_custom_bg')) {
this.$el.find('li[data-choose_image]').data("background", src).attr("data-background", src);
@@ -436,16 +382,17 @@ odoo.define('web_editor.snippets.options', function (require) {
}
});
+ /**
+ * The background_position option is designed to change the background image position of a snippet.
+ */
registry.background_position = SnippetOption.extend({
start: function () {
+ this._super.apply(this, arguments);
+
var self = this;
var $btn = this.$overlay.find('.oe_options');
$btn.on('show.bs.dropdown', function () {
- if (self.$target.css('background-image') === 'none') {
- $btn.find('.background_position_li').addClass('hidden');
- } else {
- $btn.find('.background_position_li').removeClass('hidden');
- }
+ $btn.find('.background_position_li').toggleClass('hidden', self.$target.css('background-image') === 'none');
});
},
background_position: function (type, value, $li) {
@@ -654,7 +601,7 @@ odoo.define('web_editor.snippets.options', function (require) {
on_focus: function () {
this.$target.attr('contentEditable', 'false');
this.clear();
- this._super();
+ this._super.apply(this, arguments);
},
clear: function () {
@@ -716,7 +663,7 @@ odoo.define('web_editor.snippets.options', function (require) {
this.ID = +$(li).data("id");
this.$target.attr('data-oe-many2one-id', this.ID).data('oe-many2one-id', this.ID);
- this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+ this.buildingBlock.getParent().rte.historyRecordUndo(this.$target);
this.$target.trigger('content_changed');
if (self.$target.data('oe-type') === "contact") {
@@ -745,7 +692,7 @@ odoo.define('web_editor.snippets.options', function (require) {
});
return {
- registry: registry,
Class: SnippetOption,
+ registry: registry,
};
});
diff --git a/addons/web_editor/static/src/js/translator.js b/addons/web_editor/static/src/js/translator.js
index ead8ea8271c..4fd9825567f 100644
--- a/addons/web_editor/static/src/js/translator.js
+++ b/addons/web_editor/static/src/js/translator.js
@@ -4,18 +4,13 @@ odoo.define('web_editor.translate', function (require) {
var core = require('web.core');
var Model = require('web.Model');
var ajax = require('web.ajax');
-var Class = require('web.Class');
var Widget = require('web.Widget');
var base = require('web_editor.base');
var rte = require('web_editor.rte');
var editor_widget = require('web_editor.widget');
-var qweb = core.qweb;
var _t = core._t;
-ajax.loadXML('/web_editor/static/src/xml/translator.xml', qweb);
-
-
var translatable = !!$('html').data('translatable');
var edit_translations = !!$('html').data('edit_translations');
@@ -72,17 +67,21 @@ var RTE_Translate = rte.Class.extend({
});
var Translate_Modal = editor_widget.Dialog.extend({
- template: 'web_editor.translator.attributes',
- init: function (parent, node) {
- this._super();
+ init: function (p, options, parent, node) {
+ this._super(p, _.extend({}, {
+ title: _t("Translate Attribute"),
+ buttons: [
+ {text: _t("Close"), classes: "btn-primary o_save_button", close: true, click: this.save}
+ ]
+ }, options || {}));
this.parent = parent;
this.$target = $(node);
this.translation = $(node).data('translation');
},
start: function () {
var self = this;
- this._super();
- var $group = this.$el.find('.form-group');
+ var def = this._super.apply(this, arguments);
+ var $group = $("<div/>", {"class": "form-group"}).appendTo(this.$el);
_.each(this.translation, function (node, attr) {
var $node = $(node);
var $label = $('<label class="control-label"></label>').text(attr);
@@ -95,6 +94,7 @@ var Translate_Modal = editor_widget.Dialog.extend({
});
$group.append($label).append($input);
});
+ return def;
}
});
@@ -109,7 +109,7 @@ var Translate = Widget.extend({
this.ir_translation = new Model('ir.translation');
this.lang = lang || base.get_context().lang;
this.setTarget($target);
- this._super();
+ this._super.apply(this, arguments);
this.rte = new RTE_Translate(this, this.config);
this.rte.on('change', this, this.rte_changed);
@@ -211,7 +211,7 @@ var Translate = Widget.extend({
return;
}
- new Translate_Modal(event.data, event.target).appendTo('body');
+ new Translate_Modal(null, {}, event.data, event.target).open();
},
markTranslatableNodes: function (node) {
var self = this;
@@ -267,7 +267,7 @@ var Translate = Widget.extend({
destroy: function () {
this.cancel();
this.$el.remove();
- this._super();
+ this._super.apply(this, arguments);
},
config: function ($editable) {
diff --git a/addons/web_editor/static/src/js/widgets.js b/addons/web_editor/static/src/js/widgets.js
index a4ef757dbd5..aad71c06b6c 100644
--- a/addons/web_editor/static/src/js/widgets.js
+++ b/addons/web_editor/static/src/js/widgets.js
@@ -3,6 +3,7 @@ odoo.define('web_editor.widget', function (require) {
var core = require('web.core');
var ajax = require('web.ajax');
+var Dialog = require('web.Dialog');
var Widget = require('web.Widget');
var base = require('web_editor.base');
var rte = require('web_editor.rte');
@@ -11,43 +12,35 @@ var QWeb = core.qweb;
var range = $.summernote.core.range;
var dom = $.summernote.core.dom;
-//////////////////////////////////////////////////////////////////////////////////////////////////////////
+var _t = core._t;
-var Dialog = Widget.extend({
- events: {
- 'hidden.bs.modal': 'destroy',
- 'keydown.dismiss.bs.modal': 'stop_escape',
- 'click button.save': 'save',
- 'click button[data-dismiss="modal"]': 'cancel',
- },
- init: function () {
- this._super();
- },
- start: function () {
- var sup = this._super();
- this.$el.modal({backdrop: 'static'});
- this.$('input:first').focus();
- return sup;
+/**
+ * Extend Dialog class to handle save/cancel of edition components.
+ */
+Dialog = Dialog.extend({
+ init: function (parent, options) {
+ options = options || {};
+ this._super(parent, _.extend({}, {
+ buttons: [
+ {text: options.save_text || _t("Save"), classes: "btn-primary o_save_button", close: true, click: this.save},
+ {text: _t("Discard"), close: true}
+ ]
+ }, options));
+
+ this.destroyAction = "cancel";
+
+ var self = this;
+ this.opened().then(function () {
+ self.$('input:first').focus();
+ });
+ this.on("closed", this, function () {
+ this.trigger(this.destroyAction);
+ });
},
save: function () {
+ this.destroyAction = "saved";
this.close();
- this.trigger("saved");
},
- cancel: function () {
- this.trigger("cancel");
- },
- close: function () {
- this.$el.modal('hide');
- },
- destroy: function () {
- this._super();
- $("body:has('> .modal:visible')").addClass('modal-open');
- },
- stop_escape: function (event) {
- if($(".modal.in").length>0 && event.which == 27) {
- event.stopPropagation();
- }
- }
});
/**
@@ -55,24 +48,25 @@ var Dialog = Widget.extend({
*/
var alt = Dialog.extend({
template: 'web_editor.dialog.alt',
- init: function ($editable, media) {
+ init: function (parent, options, $editable, media) {
+ this._super(parent, _.extend({}, {
+ title: _t("Change media description and tooltip")
+ }, options));
this.$editable = $editable;
this.media = media;
this.alt = ($(this.media).attr('alt') || "").replace(/&quot;/g, '"');
this.title = ($(this.media).attr('title') || "").replace(/&quot;/g, '"');
- return this._super();
},
save: function () {
- var self = this;
- range.createFromNode(self.media).select();
+ range.createFromNode(this.media).select();
this.$editable.data('NoteHistory').recordUndo();
var alt = this.$('#alt').val();
var title = this.$('#title').val();
$(this.media).attr('alt', alt ? alt.replace(/"/g, "&quot;") : null).attr('title', title ? title.replace(/"/g, "&quot;") : null);
- setTimeout(function () {
- click_event(self.media, "mouseup");
- },0);
- return this._super();
+ _.defer((function () {
+ click_event(this.media, "mouseup");
+ }).bind(this));
+ return this._super.apply(this, arguments);
},
});
@@ -86,7 +80,7 @@ var click_event = function (el, type) {
* MediaDialog widget. Lets users change a media, including uploading a
* new image, font awsome or video and can change a media into an other
* media
- *
+ *
* options: select_images: allow the selection of more of one image
*/
var MediaDialog = Dialog.extend({
@@ -94,8 +88,10 @@ var MediaDialog = Dialog.extend({
events : _.extend({}, Dialog.prototype.events, {
'input input#icon-search': 'search',
}),
- init: function ($editable, media, options) {
- this._super();
+ init: function (parent, options, $editable, media) {
+ this._super(parent, _.extend({}, {
+ title: _t("Select a Media"),
+ }, options));
if ($editable) {
this.$editable = $editable;
this.rte = this.$editable.rte || this.$editable.data('rte');
@@ -105,6 +101,9 @@ var MediaDialog = Dialog.extend({
this.media = media;
this.isNewMedia = !media;
this.range = range.create();
+
+ this.$modal.addClass('note-image-dialog');
+ this.$modal.find('.modal-dialog').addClass('o_select_media_dialog');
},
start: function () {
var self = this;
@@ -114,20 +113,22 @@ var MediaDialog = Dialog.extend({
this.$('[href="#editor-media-document"], [href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden');
}
- if (this.media) {
- if (this.media.nodeName === "IMG") {
- this.$('[href="#editor-media-image"]').tab('show');
- } else if ($(this.media).is('a.o_image')) {
- this.$('[href="#editor-media-document"]').tab('show');
- } else if (this.media.className.match(/(^|\s)media_iframe_video($|\s)/)) {
- this.$('[href="#editor-media-video"]').tab('show');
- } else if (this.media.parentNode.className.match(/(^|\s)media_iframe_video($|\s)/)) {
- this.media = this.media.parentNode;
- this.$('[href="#editor-media-video"]').tab('show');
- } else if (this.media.className.match(/(^|\s)fa($|\s)/)) {
- this.$('[href="#editor-media-icon"]').tab('show');
+ this.opened((function () {
+ if (this.media) {
+ if (this.media.nodeName === "IMG") {
+ this.$('[href="#editor-media-image"]').tab('show');
+ } else if ($(this.media).is('a.o_image')) {
+ this.$('[href="#editor-media-document"]').tab('show');
+ } else if (this.media.className.match(/(^|\s)media_iframe_video($|\s)/)) {
+ this.$('[href="#editor-media-video"]').tab('show');
+ } else if (this.media.parentNode.className.match(/(^|\s)media_iframe_video($|\s)/)) {
+ this.media = this.media.parentNode;
+ this.$('[href="#editor-media-video"]').tab('show');
+ } else if (this.media.className.match(/(^|\s)fa($|\s)/)) {
+ this.$('[href="#editor-media-icon"]').tab('show');
+ }
}
- }
+ }).bind(this));
this.imageDialog = new ImageDialog(this, this.media, this.options);
this.imageDialog.appendTo(this.$("#editor-media-image"));
@@ -142,11 +143,11 @@ var MediaDialog = Dialog.extend({
this.active = this.imageDialog;
- $('a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
+ this.$('a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
if ($(event.target).is('[href="#editor-media-image"]')) {
self.active = self.imageDialog;
self.$('li.search, li.previous, li.next').removeClass("hidden");
- } if ($(event.target).is('[href="#editor-media-document"]')) {
+ } else if ($(event.target).is('[href="#editor-media-document"]')) {
self.active = self.documentDialog;
self.$('li.search, li.previous, li.next').removeClass("hidden");
} else if ($(event.target).is('[href="#editor-media-icon"]')) {
@@ -159,7 +160,7 @@ var MediaDialog = Dialog.extend({
}
});
- return this._super();
+ return this._super.apply(this, arguments);
},
save: function () {
if (this.options.select_images) {
@@ -229,14 +230,14 @@ var MediaDialog = Dialog.extend({
});
/**
- * ImageDialog widget. Lets users change an image, including uploading a
- * new image in OpenERP or selecting the image style (if supported by
+ * ImageDialog widget. Let users change an image, including uploading a
+ * new image in odoo or selecting the image style (if supported by
* the caller).
*/
-var IMAGES_PER_ROW = 6;
-var IMAGES_ROWS = 2;
var ImageDialog = Widget.extend({
template: 'web_editor.dialog.image',
+ IMAGES_PER_ROW: 6,
+ IMAGES_ROWS: 2,
events: _.extend({}, Dialog.prototype.events, {
'change .url-source': function (e) {
this.changed($(e.target));
@@ -258,13 +259,12 @@ var ImageDialog = Widget.extend({
'submit form': 'form_submit',
'change input.url': "change_input",
'keyup input.url': "change_input",
- //'change select.image-style': 'preview_image',
'click .existing-attachments [data-src]': 'select_existing',
'click .o_existing_attachment_remove': 'try_remove',
'keydown.dismiss.bs.modal': function () {},
}),
init: function (parent, media, options) {
- this._super();
+ this._super.apply(this, arguments);
this.options = options || {};
this.accept = this.options.accept || this.options.document ? "*/*" : "image/*";
this.domain = this.options.domain || ['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]];
@@ -277,8 +277,8 @@ var ImageDialog = Widget.extend({
start: function () {
this.$preview = this.$('.preview-container').detach();
var self = this;
- var res = this._super();
- var o = { url: null, alt: null };
+ var res = this._super.apply(this, arguments);
+ var o = {url: null, alt: null};
if ($(this.media).is("img")) {
o.url = this.media.getAttribute('src');
@@ -327,13 +327,14 @@ var ImageDialog = Widget.extend({
img = _.find(this.images, function (img) { return img.id === id;});
}
+ var media;
if (!img.is_document) {
if (this.media.tagName !== "IMG" || !this.old_media) {
this.add_class = "pull-left";
this.style = {"width": "100%"};
}
if(this.media.tagName !== "IMG") {
- var media = document.createElement('img');
+ media = document.createElement('img');
$(this.media).replaceWith(media);
this.media = media;
}
@@ -341,7 +342,7 @@ var ImageDialog = Widget.extend({
} else {
if (this.media.tagName !== "A") {
$('.note-control-selection').hide();
- var media = document.createElement('a');
+ media = document.createElement('a');
$(this.media).replaceWith(media);
this.media = media;
}
@@ -358,17 +359,11 @@ var ImageDialog = Widget.extend({
clear: function () {
this.media.className = this.media.className.replace(/(^|\s+)((img(\s|$)|img-(?!circle|rounded|thumbnail))[^\s]*)/g, ' ');
},
- cancel: function () {
- this.trigger('cancel');
- },
change_input: function (e) {
var $input = $(e.target);
var $button = $input.parent().find("button");
- if ($input.val() === "") {
- $button.addClass("btn-default").removeClass("btn-primary");
- } else {
- $button.removeClass("btn-default").addClass("btn-primary");
- }
+ var emptyValue = ($input.val() === "");
+ $button.toggleClass("btn-default", emptyValue).toggleClass("btn-primary", !emptyValue);
},
search: function (needle) {
var self = this;
@@ -376,19 +371,15 @@ var ImageDialog = Widget.extend({
self.selected_existing();
});
},
- set_image: function (attachment, error) {
- var self = this;
+ set_image: function (attachment) {
this.push(attachment);
this.$('input.url').val('');
- this.fetch_existing().then(function () {
- self.selected_existing();
- });
+ this.search();
},
form_submit: function (event) {
var self = this;
var $form = this.$('form[action="/web_editor/attachment/add"]');
if (!$form.find('input[name="upload"]').val().length) {
- var url = $form.find('input[name="url"]').val();
if (this.selected_existing().size()) {
event.preventDefault();
return false;
@@ -416,10 +407,11 @@ var ImageDialog = Widget.extend({
};
},
file_selection: function () {
+ var $form = this.$('form');
this.$el.addClass('nosave');
- this.$('form').removeClass('has-error').find('.help-block').empty();
+ $form.removeClass('has-error').find('.help-block').empty();
this.$('button.filepicker').removeClass('btn-danger btn-success');
- this.$('form').submit();
+ $form.submit();
},
file_selected: function (attachment, error) {
var $button = this.$('button.filepicker');
@@ -427,14 +419,12 @@ var ImageDialog = Widget.extend({
$button.addClass('btn-success');
this.set_image(attachment);
} else {
- this.$('form').addClass('has-error')
- .find('.help-block').text(error);
+ this.$('form').addClass('has-error').find('.help-block').text(error);
$button.addClass('btn-danger');
}
if (!this.options.select_images) {
- // auto save and close popup
- this.parent.save();
+ this.parent.save(); // auto save and close popup
}
},
fetch_existing: function (needle) {
@@ -448,15 +438,19 @@ var ImageDialog = Widget.extend({
args: [],
kwargs: {
domain: domain,
- fields: ['name', 'mimetype', 'checksum', 'url'], // if we want to use /web/image/xxx with redirect for image url, remove 'url'
+ fields: ['name', 'mimetype', 'checksum', 'url', 'type'],
order: 'id desc',
context: base.get_context()
}
}).then(this.proxy('fetched_existing'));
},
fetched_existing: function (records) {
- this.records = records;
- _.each(records, function (record) {
+ this.records = _.uniq(_.filter(records, function (r) {
+ return (r.type === "binary" || r.url && r.url.length > 0);
+ }), function (r) {
+ return (r.url || r.id);
+ });
+ _.each(this.records, function (record) {
record.src = record.url || '/web/image/' + record.id;
record.is_document = !(/gif|jpe|jpg|png/.test(record.mimetype));
});
@@ -464,22 +458,20 @@ var ImageDialog = Widget.extend({
},
display_attachments: function () {
var self = this;
- var per_screen = IMAGES_PER_ROW * IMAGES_ROWS;
+ var per_screen = this.IMAGES_PER_ROW * this.IMAGES_ROWS;
var from = this.page * per_screen;
var records = this.records;
// Create rows of 3 records
var rows = _(records).chain()
.slice(from, from + per_screen)
- .groupBy(function (_, index) { return Math.floor(index / IMAGES_PER_ROW); })
+ .groupBy(function (_, index) { return Math.floor(index / self.IMAGES_PER_ROW); })
.values()
.value();
this.$('.help-block').empty();
- this.$('.existing-attachments').replaceWith(
- QWeb.render(
- 'web_editor.dialog.image.existing.content', {rows: rows}));
+ this.$('.existing-attachments').replaceWith(QWeb.render('web_editor.dialog.image.existing.content', {rows: rows}));
this.parent.$('.pager')
.find('li.previous').toggleClass('disabled', (from === 0)).end()
.find('li.next').toggleClass('disabled', (from + per_screen >= records.length));
@@ -528,15 +520,20 @@ var ImageDialog = Widget.extend({
return;
}
$both.css({borderWidth: "", borderColor: ""});
- $help_block.replaceWith(QWeb.render(
- 'web_editor.dialog.image.existing.error', {
- views: prevented[id]
- }
- ));
+ $help_block.replaceWith(QWeb.render('web_editor.dialog.image.existing.error', {
+ views: prevented[id]
+ }));
});
},
});
+/* list of font icons to load by editor. The icons are displayed in the media editor and
+ * identified like font and image (can be colored, spinned, resized with fa classes).
+ * To add font, push a new object {base, parser}
+ * - base: class who appear on all fonts (eg: fa fa-refresh)
+ * - parser: regular expression used to select all font in css style sheets
+ */
+var fontIcons = [{'base': 'fa', 'parser': /(?=^|\s)(\.fa-[0-9a-z_-]+::?before)/i}];
var cacheCssSelectors = {};
var getCssSelectors = function (filter) {
@@ -596,7 +593,7 @@ var computeFonts = _.once(function () {
});
rte.Class.include({
- init: function (EditorBar) {
+ init: function () {
this._super.apply(this, arguments);
computeFonts();
},
@@ -611,18 +608,9 @@ rte.Class.include({
},
});
-/* list of font icons to load by editor. The icons are displayed in the media editor and
- * identified like font and image (can be colored, spinned, resized with fa classes).
- * To add font, push a new object {base, parser}
- * - base: class who appear on all fonts (eg: fa fa-refresh)
- * - parser: regular expression used to select all font in css style sheets
- */
-var fontIcons = [{'base': 'fa', 'parser': /(?=^|\s)(\.fa-[0-9a-z_-]+::?before)/i}];
-
-
/**
- * FontIconsDialog widget. Lets users change a font awsome, suport all
- * font awsome loaded in the css files.
+ * FontIconsDialog widget. Lets users change a font awesome, support all
+ * font awesome loaded in the css files.
*/
var fontIconsDialog = Widget.extend({
template: 'web_editor.dialog.font-icons',
@@ -638,28 +626,26 @@ var fontIconsDialog = Widget.extend({
'keydown.dismiss.bs.modal': function () {},
}),
- // extract list of font (like awsome) from the cheatsheet.
- renderElement: function () {
- this.iconsParser = fontIcons;
- this.icons = _.flatten(_.map(fontIcons, function (data) {
- return data.icons;
- }));
- this._super();
- },
-
init: function (parent, media) {
- this._super();
+ this._super.apply(this, arguments);
this.parent = parent;
this.media = media;
computeFonts();
},
start: function () {
- return this._super().then(this.proxy('load_data'));
+ return this._super.apply(this, arguments).then(this.proxy('load_data'));
+ },
+ renderElement: function () { // extract list of font (like awesome) from the cheatsheet.
+ this.iconsParser = fontIcons;
+ this.icons = _.flatten(_.map(fontIcons, function (data) {
+ return data.icons;
+ }));
+ this._super.apply(this, arguments);
},
search: function (needle) {
var iconsParser = this.iconsParser;
if (needle) {
- var iconsParser = [];
+ iconsParser = [];
_.filter(this.iconsParser, function (data) {
var cssData = _.filter(data.cssData, function (cssData) {
return _.find(cssData[3], function (alias) {
@@ -684,7 +670,6 @@ var fontIconsDialog = Widget.extend({
save: function () {
var self = this;
this.parent.trigger("save", this.media);
- var icons = this.icons;
var style = this.media.attributes.style ? this.media.attributes.style.value : '';
var classes = (this.media.className||"").split(/\s+/);
var custom_classes = /^fa(-[1-5]x|spin|rotate-(9|18|27)0|flip-(horizont|vertic)al|fw|border)?$/;
@@ -737,7 +722,7 @@ var fontIconsDialog = Widget.extend({
var classes = (this.media&&this.media.className||"").split(/\s+/);
for (var i = 0; i < classes.length; i++) {
var cls = classes[i];
- switch(cls) {
+ switch (cls) {
case 'fa-1x':case 'fa-2x':case 'fa-3x':case 'fa-4x':case 'fa-5x':
// size classes
this.$('#fa-size').val(cls);
@@ -765,8 +750,7 @@ var fontIconsDialog = Widget.extend({
}
},
/**
- * Serializes the dialog to an array of FontAwesome classes. Includes
- * the base ``fa``.
+ * Serializes the dialog to an array of FontAwesome classes. Includes the base ``fa``.
*/
get_fa_classes: function () {
var font = this.getFont(this.$('#fa-icon').val());
@@ -806,40 +790,52 @@ function createVideoNode(url) {
var $video = $('<iframe>');
if (ytMatch && ytMatch[1].length === 11) {
- var youtubeId = ytMatch[1];
- $video = $('<iframe>')
- .attr('src', '//www.youtube.com/embed/' + youtubeId)
- .attr('width', '640').attr('height', '360');
+ var youtubeId = ytMatch[1];
+ $video = $('<iframe>', {
+ src: '//www.youtube.com/embed/' + youtubeId,
+ width: '640',
+ height: '360'
+ });
} else if (igMatch && igMatch[0].length) {
- $video = $('<iframe>')
- .attr('src', igMatch[0] + '/embed/')
- .attr('width', '612').attr('height', '710')
- .attr('scrolling', 'no')
- .attr('allowtransparency', 'true');
+ $video = $('<iframe>', {
+ src: igMatch[0] + '/embed/',
+ width: '612',
+ height: '710',
+ scrolling: 'no',
+ allowtransparency: 'true'
+ });
} else if (vMatch && vMatch[0].length) {
- $video = $('<iframe>')
- .attr('src', vMatch[0] + '/embed/simple')
- .attr('width', '600').attr('height', '600')
- .attr('class', 'vine-embed');
+ $video = $('<iframe>', {
+ src: vMatch[0] + '/embed/simple',
+ width: '600',
+ height: '600',
+ class: 'vine-embed'
+ });
} else if (vimMatch && vimMatch[3].length) {
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
- .attr('src', '//player.vimeo.com/video/' + vimMatch[3])
- .attr('width', '640').attr('height', '360');
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>', {
+ src: '//player.vimeo.com/video/' + vimMatch[3],
+ width: '640',
+ height: '360'
+ });
} else if (dmMatch && dmMatch[2].length) {
- $video = $('<iframe>')
- .attr('src', '//www.dailymotion.com/embed/video/' + dmMatch[2])
- .attr('width', '640').attr('height', '360');
+ $video = $('<iframe>', {
+ src: '//www.dailymotion.com/embed/video/' + dmMatch[2],
+ width: '640',
+ height: '360'
+ });
} else if (youkuMatch && youkuMatch[1].length) {
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
- .attr('height', '498')
- .attr('width', '510')
- .attr('src', '//player.youku.com/embed/' + youkuMatch[1]);
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>', {
+ height: '498',
+ width: '510',
+ src: '//player.youku.com/embed/' + youkuMatch[1]
+ });
} else {
- // this is not a known video link. Now what, Cat? Now what?
- $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>')
- .attr('width', '640')
- .attr('height', '360')
- .attr('src', url);
+ // this is not a known video link. Now what, Cat? Now what?
+ $video = $('<iframe webkitallowfullscreen mozallowfullscreen allowfullscreen>', {
+ width: '640',
+ height: '360',
+ src: url
+ });
}
$video.attr('frameborder', 0);
@@ -863,7 +859,7 @@ var VideoDialog = Widget.extend({
'keydown.dismiss.bs.modal': function () {},
}),
init: function (parent, media) {
- this._super();
+ this._super.apply(this, arguments);
this.parent = parent;
this.media = media;
},
@@ -876,7 +872,7 @@ var VideoDialog = Widget.extend({
this.$("input#urlvideo").val(src);
this.get_video();
}
- return this._super();
+ return this._super.apply(this, arguments);
},
change_input: function (e) {
var $input = $(e.target);
@@ -910,7 +906,6 @@ var VideoDialog = Widget.extend({
this.$("button.btn-primary").click();
video_id = this.$("#video_id").val();
}
- var video_type = this.$("#video_type").val();
var $iframe = $(
'<div class="media_iframe_video" data-src="'+this.$iframe.attr("src")+'">'+
'<div class="css_editable_mode_display">&nbsp;</div>'+
@@ -932,7 +927,9 @@ var VideoDialog = Widget.extend({
},
});
-/* ----- EDITOR: LINK & MEDIA ---- */
+/**
+ * The Link Dialog allows to customize link content and style.
+ */
var LinkDialog = Dialog.extend({
template: 'web_editor.dialog.link',
events: _.extend({}, Dialog.prototype.events, {
@@ -947,8 +944,10 @@ var LinkDialog = Dialog.extend({
this.preview();
},
}),
- init: function (editable, linkInfo) {
- this._super(editable, linkInfo);
+ init: function (parent, options, editable, linkInfo) {
+ this._super(parent, _.extend({}, {
+ title: _t("Link to")
+ }, options || {}));
this.editable = editable;
this.data = linkInfo || {};
@@ -985,20 +984,20 @@ var LinkDialog = Dialog.extend({
} else if (eo !== ec.textContent.length) {
ec.splitText(eo);
}
-
+
nodes = dom.listBetween(sc, ec);
// browsers can't target a picture or void node
if (dom.isVoid(sc) || dom.isImg(sc)) {
- so = dom.listPrev(sc).length-1;
- sc = sc.parentNode;
+ so = dom.listPrev(sc).length-1;
+ sc = sc.parentNode;
}
if (dom.isBR(ec)) {
- eo = dom.listPrev(ec).length-1;
- ec = ec.parentNode;
+ eo = dom.listPrev(ec).length-1;
+ ec = ec.parentNode;
} else if (dom.isVoid(ec) || dom.isImg(sc)) {
- eo = dom.listPrev(ec).length;
- ec = ec.parentNode;
+ eo = dom.listPrev(ec).length;
+ ec = ec.parentNode;
}
this.data.range = range.create(sc, so, ec, eo);
@@ -1034,7 +1033,7 @@ var LinkDialog = Dialog.extend({
start: function () {
this.bind_data();
this.$('input.url-source:eq(1)').closest('.list-group-item').addClass('active');
- return this._super();
+ return this._super.apply(this, arguments);
},
get_data: function (test) {
var self = this;
@@ -1081,15 +1080,14 @@ var LinkDialog = Dialog.extend({
save: function () {
var self = this;
var _super = this._super.bind(this);
- return this.get_data()
- .then(function (url, new_window, label, classes) {
- self.data.url = url;
- self.data.isNewWindow = new_window;
- self.data.text = label;
- self.data.className = classes.replace(/\s+/gi, ' ').replace(/^\s+|\s+$/gi, '');
-
- self.trigger("save", self.data);
- }).then(_super);
+ return this.get_data().then(function (url, new_window, label, classes) {
+ self.data.url = url;
+ self.data.isNewWindow = new_window;
+ self.data.text = label;
+ self.data.className = classes.replace(/\s+/gi, ' ').replace(/^\s+|\s+$/gi, '');
+
+ self.trigger("save", self.data);
+ }).then(_super);
},
bind_data: function () {
var href = this.data.url;
@@ -1110,8 +1108,8 @@ var LinkDialog = Dialog.extend({
}
if (href) {
- var match;
- if(match = /mailto:(.+)/.exec(href)) {
+ var match = /mailto:(.+)/.exec(href);
+ if (match) {
this.$('input.email-address').val(match = /mailto:(.+)/.exec(href) ? match[1] : '');
} else {
this.$('input.url').val(href);
@@ -1144,12 +1142,11 @@ var LinkDialog = Dialog.extend({
});
return {
- 'getCssSelectors': getCssSelectors,
- 'Dialog': Dialog,
- 'alt': alt,
- 'MediaDialog': MediaDialog,
- 'fontIcons': fontIcons,
- 'LinkDialog': LinkDialog
+ Dialog: Dialog,
+ alt: alt,
+ MediaDialog: MediaDialog,
+ LinkDialog: LinkDialog,
+ getCssSelectors: getCssSelectors,
+ fontIcons: fontIcons,
};
-
});
diff --git a/addons/web_editor/static/src/less/web_editor.ui.components.less b/addons/web_editor/static/src/less/web_editor.ui.components.less
index 6cf994e2f03..d89929bb13d 100644
--- a/addons/web_editor/static/src/less/web_editor.ui.components.less
+++ b/addons/web_editor/static/src/less/web_editor.ui.components.less
@@ -172,6 +172,7 @@ body .modal {
&.o_webimage {
width: 100%;
height: auto;
+ background-image: none!important;
}
}
&.o_selected .o_image {
@@ -329,11 +330,4 @@ body .modal {
}
}
}
-
- &.web_editor-dialog {
- li > label {
- width: 96%;
- margin: 4px 2% 0;
- }
- }
}
diff --git a/addons/web_editor/static/src/less/web_editor.ui.less b/addons/web_editor/static/src/less/web_editor.ui.less
index 57caa2dda0e..6cbff90b572 100644
--- a/addons/web_editor/static/src/less/web_editor.ui.less
+++ b/addons/web_editor/static/src/less/web_editor.ui.less
@@ -180,6 +180,10 @@ html[lang] > body.editor_enable{
opacity: 0.8;
}
}
+
+ &:nth-child(2n+1) {
+ clear: left;
+ }
}
}
}
@@ -495,7 +499,7 @@ html[lang] > body.editor_enable{
cursor: e-resize;
width: @odoo-handle-edge-size;
.o-position-absolute(@o-handles-offset-to-hide, auto, -@o-handles-offset-to-hide, 0);
-
+
&:after {
.o-position-absolute(@top: 50%, @left: -@odoo-handle-border-width);
margin-top: -@o-handle-size/2;
@@ -506,7 +510,7 @@ html[lang] > body.editor_enable{
cursor: w-resize;
width: @odoo-handle-edge-size;
.o-position-absolute(@o-handles-offset-to-hide, 0, -@o-handles-offset-to-hide, auto);
-
+
&:after {
.o-position-absolute(@top: 50%, @right: -@odoo-handle-border-width);
margin-top: -@o-handle-size/2;
@@ -657,7 +661,7 @@ html[lang] > body.editor_enable{
width: auto;
text-align: center;
box-shadow: none;
-
+
.popover-content {
height: @odoo-navbar-height;
@@ -785,7 +789,7 @@ table.colorpicker {
padding: 0;
list-style: none;
text-align: center;
-
+
> li {
display: block;
float: none;
@@ -944,7 +948,7 @@ body.editor_enable {
padding-top: @odoo-navbar-height;
> main {
height: 100%;
- #editable_area {
+ #editable_area {
position: relative;
overflow: hidden;
max-width: 600px;
diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml
index 49743987fc3..f4fb773c8c3 100644
--- a/addons/web_editor/static/src/xml/editor.xml
+++ b/addons/web_editor/static/src/xml/editor.xml
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
+ <!--=================-->
+ <!-- Base components -->
+ <!--=================-->
+
+ <!-- Editor top bar which contains the summernote tools and save/discard buttons -->
<t t-name="web_editor.editorbar">
<div id="web_editor-top-edit" class="o_css_editor">
<div id="web_editor-toolbars"/>
@@ -9,187 +14,72 @@
</form>
</div>
</t>
- <t t-name="web_editor.dialog">
- <div class="modal o_website_modal web_editor-dialog" tabindex="-1" role="dialog" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
- <h3 class="modal-title">
- <t t-esc="title"/>
- </h3>
- </div>
- <div class="modal-body">
- <t t-raw="0"/>
- </div>
- <div class="modal-footer">
- <button class="btn btn-sm btn-primary save" t-if="!button" type="button">Save</button>
- <button class="btn btn-sm btn-primary save" t-if="button" type="button"><t t-raw="button"/></button>
- <button class="btn hidden wait" disabled="disabled" type="button"/>
- <a class="btn btn-sm btn-default" data-dismiss="modal" href="#" aria-hidden="true">Discard</a>
- </div>
- </div>
- </div>
- </div>
+
+ <!-- Custom checkbox (material-design-like toggle) -->
+ <t t-name="web_editor.components.switch">
+ <label class="o_switch" t-att-for="id">
+ <input type="checkbox" t-att-id="id"/>
+ <span/>
+ <t t-if="label"><t t-esc="label"/></t>
+ </label>
</t>
- <div t-name="web_editor.dialog.background_position" class="o_bg_img_opt_modal">
- <label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="cover"/> Cover</label>
- <label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="contain"/> Contain</label>
- <label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="custom"/> Custom</label>
- <div class="o_bg_img_opt" data-value="cover">
- <a class="help-control collapsed" role="button" data-toggle="collapse" href="#cover_help" aria-expanded="false" aria-controls="cover_help"><span class="fa fa-question-circle"/></a>
- <div class="help collapse" id="cover_help">
- <div class="simulator cover_bg">
- <div class="bg"/>
- <div class="el"/>
- </div>
- <p class="help-text">Scale the image to be as large as it needs to fill the whole content area. Part of the image may not be visible.</p>
- </div>
+ <!--=================-->
+ <!-- Edition Dialogs -->
+ <!--=================-->
- <p><strong>Assigning a focal point to ensure that the targeted area will be always visible</strong></p>
- <div class="o_bg_img_opt_cover_edition">
- <h6>Click on the picture</h6>
- <div class="o_bg_img_opt_object">
- <span class="grid grid-1"/>
- <span class="grid grid-2"/>
- <span class="grid grid-3"/>
- <span class="grid grid-4"/>
- <span class="o_focus_point"/>
- <div class="o_bg_img_opt_ui_info">X: <span class="o_x"/> Y: <span class="o_y"/></div>
- </div>
+ <!-- Alt Dialog (allows to change alt and title of page images) -->
+ <form t-name="web_editor.dialog.alt" action="#" class="form-horizontal">
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="alt">Description</label>
+ <div class="col-sm-9">
+ <input class="form-control" id="alt" required="required" t-att-value="widget.alt" type="text"/>
</div>
</div>
- <div class="o_bg_img_opt" data-value="contain">
- <a class="help-control collapsed" role="button" data-toggle="collapse" href="#contain_help" aria-expanded="false" aria-controls="contain_help"><span class="fa fa-question-circle"/></a>
- <div class="help collapse" id="contain_help">
- <div class="simulator contain_bg">
- <div class="bg"/>
- <div class="el"/>
- </div>
- <p class="help-text">Scale the image to the largest size such that both its width and height can fit inside the content area</p>
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="title">Tooltip</label>
+ <div class="col-sm-9">
+ <input class="form-control" id="title" required="required" t-att-value="widget.title" type="text"/>
</div>
+ </div>
+ </form>
- <form class="form-horizontal">
- <div class="form-group">
- <label class="col-sm-2 control-label" for="repeatInput">Repeat</label>
- <div class="col-sm-5">
- <div class="checkbox">
- <label>
- <input type="checkbox" id="o_bg_img_opt_contain_repeat"/>
- </label>
- </div>
+ <!-- Media Dialog (allows to choose an img/pictogram/video) -->
+ <div t-name="web_editor.dialog.media">
+ <ul class="nav nav-tabs">
+ <li class="active"><a href="#editor-media-image" data-toggle="tab">Image</a></li>
+ <li><a href="#editor-media-document" data-toggle="tab">Document</a></li>
+ <li><a href="#editor-media-icon" data-toggle="tab">Pictogram</a></li>
+ <li><a href="#editor-media-video" data-toggle="tab">Video</a></li>
+ <li class="search">
+ <ul class="pager mb0 mt0">
+ <li class="previous disabled">
+ <a href="#">← Previous</a>
+ </li>
+ <li class="next disabled">
+ <a href="#">Next →</a>
+ </li>
+ </ul>
+ </li>
+ <li class="search">
+ <form action="#">
+ <div class="form-group">
+ <input class="form-control" id="icon-search" type="search"/>
+ <span class="fa fa-search"/>
</div>
- </div>
- </form>
+ </form>
+ </li>
+ </ul>
+ <!-- Tab panes -->
+ <div class="tab-content">
+ <div class="tab-pane fade in active" id="editor-media-image"/>
+ <div class="tab-pane fade" id="editor-media-document"/>
+ <div class="tab-pane fade" id="editor-media-icon"/>
+ <div class="tab-pane fade" id="editor-media-video"/>
</div>
- <form class="o_bg_img_opt form-horizontal" data-value="custom">
- <fieldset>
- <legend>Background size</legend>
- <p>Sets the width and height of the background image in percent of the parent element.</p>
- <div class="form-group">
- <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_size_x">Background width</label>
- <div class="col-sm-5">
- <div class="input-group">
- <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_size_x" placeholder="auto"/>
- <div class="input-group-addon">%</div>
- </div>
- </div>
- </div>
- <div class="form-group">
- <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_size_y">Background height</label>
- <div class="col-sm-5">
- <div class="input-group">
- <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_size_y" placeholder="auto"/>
- <div class="input-group-addon">%</div>
- </div>
- </div>
- </div>
- </fieldset>
- <fieldset>
- <legend>Background position</legend>
- <p>Set the starting position of the background image.</p>
- <div class="form-group">
- <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_pos_x">Horizontal</label>
- <div class="col-sm-5">
- <div class="input-group">
- <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_pos_x" placeholder="auto"/>
- <div class="input-group-addon">%</div>
- </div>
- </div>
- </div>
- <div class="form-group">
- <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_pos_y">Vertical</label>
- <div class="col-sm-5">
- <div class="input-group">
- <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_pos_y" placeholder="auto"/>
- <div class="input-group-addon">%</div>
- </div>
- </div>
- </div>
- </fieldset>
- <fieldset>
- <legend>Background repeat</legend>
- <p>Define if/how the background image will be repeated</p>
- <select class="form-control" id="o_bg_img_opt_custom_repeat">
- <option value="">No repeat</option>
- <option value="o_bg_img_opt_repeat">Repeat both</option>
- <option value="o_bg_img_opt_repeat_x">Repeat x</option>
- <option value="o_bg_img_opt_repeat_y">Repeat y</option>
- </select>
- </fieldset>
- </form>
</div>
- <t t-name="web_editor.dialog.media">
- <div class="modal note-image-dialog o_website_modal" tabindex="-1" role="dialog" aria-hidden="true">
- <div class="modal-dialog modal-lg o_select_media_dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
- <h3 class="modal-title">Select a Media</h3>
- </div>
- <div class="modal-body">
- <ul class="nav nav-tabs">
- <li class="active"><a href="#editor-media-image" data-toggle="tab">Image</a></li>
- <li><a href="#editor-media-document" data-toggle="tab">Document</a></li>
- <li><a href="#editor-media-icon" data-toggle="tab">Pictogram</a></li>
- <li><a href="#editor-media-video" data-toggle="tab">Video</a></li>
- <li class="search">
- <ul class="pager mb0 mt0">
- <li class="previous disabled">
- <a href="#">← Previous</a>
- </li>
- <li class="next disabled">
- <a href="#">Next →</a>
- </li>
- </ul>
- </li>
- <li class="search">
- <form action="#">
- <div class="form-group">
- <input class="form-control" id="icon-search" type="search"/>
- <span class="fa fa-search"/>
- </div>
- </form>
- </li>
- </ul>
- <!-- Tab panes -->
- <div class="tab-content">
- <div class="tab-pane fade in active" id="editor-media-image"/>
- <div class="tab-pane fade" id="editor-media-document"/>
- <div class="tab-pane fade" id="editor-media-icon"/>
- <div class="tab-pane fade" id="editor-media-video"/>
- </div>
- </div>
- <div class="modal-footer">
- <button class="btn btn-sm btn-primary save" type="button">Save</button>
- <button class="btn btn-sm hidden wait" disabled="disabled" type="button"/>
- <a class="btn btn-sm btn-default" data-dismiss="modal" href="#" aria-hidden="true">Discard</a>
- </div>
- </div>
- </div>
- </div>
- </t>
+
+ <!-- Image choosing part of the Media Dialog -->
<t t-name="web_editor.dialog.image">
<div>
<form method="POST"
@@ -276,45 +166,8 @@
</ul>
</div>
</t>
- <t t-name="web_editor.discard">
- <div class="modal o_website_modal" tabindex="-1" role="dialog" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
- <h3 class="modal-title">Discard edition</h3>
- </div>
- <div class="modal-body">
- <p>If you discard the current edition, <em>all</em> unsaved changes will be lost.</p>
- <p>You can cancel to return to the edition mode.</p>
- </div>
- <div class="modal-footer">
- <button class="btn btn-sm btn-danger" type="button">Discard</button>
- <a class="btn btn-sm btn-default" data-dismiss="modal" href="#" aria-hidden="true">Cancel</a>
- </div>
- </div>
- </div>
- </div>
- </t>
- <t t-name="web_editor.dialog.alt">
- <t t-call="web_editor.dialog">
- <t t-set="title">Change media description and tooltip</t>
- <form action="#" class="form-horizontal">
- <div class="form-group">
- <label class="col-sm-2 control-label" for="alt">Description</label>
- <div class="col-sm-9">
- <input class="form-control" id="alt" required="required" t-att-value="widget.alt" type="text"/>
- </div>
- </div>
- <div class="form-group">
- <label class="col-sm-2 control-label" for="title">Tooltip</label>
- <div class="col-sm-9">
- <input class="form-control" id="title" required="required" t-att-value="widget.title" type="text"/>
- </div>
- </div>
- </form>
- </t>
- </t>
+
+ <!-- Icon choosing part of the Media Dialog -->
<t t-name="web_editor.dialog.font-icons">
<form action="#">
<input type="hidden" id="fa-icon"/>
@@ -331,6 +184,8 @@
<span t-as="cssData" t-att-data-id="cssData[2]" t-att-title="cssData[3].join(', ')" t-attf-class="#{data.base} font-icons-icon #{cssData[2]}" t-attf-data-alias=",#{cssData[3]}," t-foreach="data.cssData"/>
</t>
</t>
+
+ <!-- Video choosing part of the Media Dialog -->
<t t-name="web_editor.dialog.video">
<form action="#" class="form-inline">
<div class="well">
@@ -361,117 +216,221 @@
</div>
</form>
</t>
- <t t-name="web_editor.dialog.link">
- <t t-call="web_editor.dialog">
- <t t-set="title">Link to</t>
- <form>
- <div class="list-group">
- <div class="form-group list-group-item active">
- <h3 class="list-group-item-heading">
- <label class="control-label" for="link-new">Link Label
- </label>
- </h3>
- <input class="form-control" id="link-text" required="required" type="text"/>
+
+ <!-- Link Dialog (allows to choose a style and content for a link on the page) -->
+ <form t-name="web_editor.dialog.link">
+ <div class="list-group">
+ <div class="form-group list-group-item active">
+ <h3 class="list-group-item-heading">
+ <label class="control-label" for="link-new">Link Label
+ </label>
+ </h3>
+ <input class="form-control" id="link-text" required="required" type="text"/>
+ </div>
+ </div>
+ <ul class="list-group">
+ <li class="list-group-item form-group clearfix active">
+ <div class="pull-right" style="display: none;">
+ <label>
+ <input class="window-new" type="checkbox"/>Open in new window</label>
+ </div>
+ <h4 class="list-group-item-heading">
+ <label class="control-label" for="link-external">URL or Email Address
+ </label>
+ </h4>
+ <input class="form-control url email-address url-source" id="link-external" placeholder="http://openerp.com" type="text"/>
+ </li>
+ </ul>
+ <div class="form-horizontal link-style">
+ <div class="form-group">
+ <label class="col-sm-2 control-label">Style
+ </label>
+ <div class="col-sm-4">
+ <div class="dropdown" style="line-height: 2em;">
+ <a class="btn btn-default" data-toggle="dropdown" href="#">Color Style
+ <span class="caret"/></a>
+ <ul aria-labelledby="dLabel" class="dropdown-menu" role="menu">
+ <li>
+ <label class="btn btn-link">Link
+ <input checked="checked" class="link-style" name="link-style-type" type="radio" value=""/></label>
+ </li>
+ <li>
+ <label class="btn btn-default">Basic
+ <input class="link-style" name="link-style-type" type="radio" value="btn-default"/></label>
+ </li>
+ <li>
+ <label class="btn btn-success">Success
+ <input class="link-style" name="link-style-type" type="radio" value="btn-success"/></label>
+ </li>
+ <li>
+ <label class="btn btn-primary">Primary
+ <input class="link-style" name="link-style-type" type="radio" value="btn-primary"/></label>
+ </li>
+ <li>
+ <label class="btn btn-info">Info
+ <input class="link-style" name="link-style-type" type="radio" value="btn-info"/></label>
+ </li>
+ <li>
+ <label class="btn btn-warning">Warning
+ <input class="link-style" name="link-style-type" type="radio" value="btn-warning"/></label>
+ </li>
+ <li>
+ <label class="btn btn-danger">Danger
+ <input class="link-style" name="link-style-type" type="radio" value="btn-danger"/></label>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <div class="col-sm-4">
+ <div class="dropdown" style="line-height: 2em;">
+ <a class="btn btn-default" data-toggle="dropdown" href="#">Size
+ <span class="caret"/></a>
+ <ul aria-labelledby="dLabel" class="dropdown-menu" role="menu">
+ <li>
+ <label class="btn btn-default btn-xs">Extra Small
+ <input class="link-style" name="link-style-size" type="radio" value="btn-xs"/></label>
+ </li>
+ <li>
+ <label class="btn btn-default btn-sm">Small
+ <input class="link-style" name="link-style-size" type="radio" value="btn-sm"/></label>
+ </li>
+ <li>
+ <label class="btn btn-default">Default
+ <input checked="checked" class="link-style" name="link-style-size" type="radio" value=""/></label>
+ </li>
+ <li>
+ <label class="btn btn-default btn-lg">Large
+ <input class="link-style" name="link-style-size" type="radio" value="btn-lg"/></label>
+ </li>
+ </ul>
</div>
</div>
- <ul class="list-group">
- <li class="list-group-item form-group clearfix active">
- <div class="pull-right" style="display: none;">
+ </div>
+ </div>
+ <div class="form-horizontal link-style">
+ <div class="form-group">
+ <label class="col-sm-2 control-label">Preview
+ </label>
+ <div class="col-sm-10" style="line-height: 2em;">
+ <div style="overflow: auto; max-width: 100%; max-height: 200px;">
+ <a href="#" id="link-preview"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
+
+ <!--=================-->
+ <!-- Snippet options -->
+ <!--=================-->
+
+ <!-- Background position option dialog -->
+ <div t-name="web_editor.dialog.background_position" class="o_bg_img_opt_modal">
+ <label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="cover"/> Cover</label>
+ <label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="contain"/> Contain</label>
+ <label class="radio-inline"><input type="radio" name="o_bg_img_opt" value="custom"/> Custom</label>
+
+ <div class="o_bg_img_opt" data-value="cover">
+ <a class="help-control collapsed" role="button" data-toggle="collapse" href="#cover_help" aria-expanded="false" aria-controls="cover_help"><span class="fa fa-question-circle"/></a>
+ <div class="help collapse" id="cover_help">
+ <div class="simulator cover_bg">
+ <div class="bg"/>
+ <div class="el"/>
+ </div>
+ <p class="help-text">Scale the image to be as large as it needs to fill the whole content area. Part of the image may not be visible.</p>
+ </div>
+
+ <p><strong>Assigning a focal point to ensure that the targeted area will be always visible</strong></p>
+ <div class="o_bg_img_opt_cover_edition">
+ <h6>Click on the picture</h6>
+ <div class="o_bg_img_opt_object">
+ <span class="grid grid-1"/>
+ <span class="grid grid-2"/>
+ <span class="grid grid-3"/>
+ <span class="grid grid-4"/>
+ <span class="o_focus_point"/>
+ <div class="o_bg_img_opt_ui_info">X: <span class="o_x"/> Y: <span class="o_y"/></div>
+ </div>
+ </div>
+ </div>
+ <div class="o_bg_img_opt" data-value="contain">
+ <a class="help-control collapsed" role="button" data-toggle="collapse" href="#contain_help" aria-expanded="false" aria-controls="contain_help"><span class="fa fa-question-circle"/></a>
+ <div class="help collapse" id="contain_help">
+ <div class="simulator contain_bg">
+ <div class="bg"/>
+ <div class="el"/>
+ </div>
+ <p class="help-text">Scale the image to the largest size such that both its width and height can fit inside the content area</p>
+ </div>
+
+ <form class="form-horizontal">
+ <div class="form-group">
+ <label class="col-sm-2 control-label" for="repeatInput">Repeat</label>
+ <div class="col-sm-5">
+ <div class="checkbox">
<label>
- <input class="window-new" type="checkbox"/>Open in new window</label>
- </div>
- <h4 class="list-group-item-heading">
- <label class="control-label" for="link-external">URL or Email Address
+ <input type="checkbox" id="o_bg_img_opt_contain_repeat"/>
</label>
- </h4>
- <input class="form-control url email-address url-source" id="link-external" placeholder="http://openerp.com" type="text"/>
- </li>
- </ul>
- <div class="form-horizontal link-style">
- <div class="form-group">
- <label class="col-sm-2 control-label">Style
- </label>
- <div class="col-sm-4">
- <div class="dropdown" style="line-height: 2em;">
- <a class="btn btn-default" data-toggle="dropdown" href="#">Color Style
- <span class="caret"/></a>
- <ul aria-labelledby="dLabel" class="dropdown-menu" role="menu">
- <li>
- <label class="btn btn-link">Link
- <input checked="checked" class="link-style" name="link-style-type" type="radio" value=""/></label>
- </li>
- <li>
- <label class="btn btn-default">Basic
- <input class="link-style" name="link-style-type" type="radio" value="btn-default"/></label>
- </li>
- <li>
- <label class="btn btn-success">Success
- <input class="link-style" name="link-style-type" type="radio" value="btn-success"/></label>
- </li>
- <li>
- <label class="btn btn-primary">Primary
- <input class="link-style" name="link-style-type" type="radio" value="btn-primary"/></label>
- </li>
- <li>
- <label class="btn btn-info">Info
- <input class="link-style" name="link-style-type" type="radio" value="btn-info"/></label>
- </li>
- <li>
- <label class="btn btn-warning">Warning
- <input class="link-style" name="link-style-type" type="radio" value="btn-warning"/></label>
- </li>
- <li>
- <label class="btn btn-danger">Danger
- <input class="link-style" name="link-style-type" type="radio" value="btn-danger"/></label>
- </li>
- </ul>
- </div>
</div>
- <div class="col-sm-4">
- <div class="dropdown" style="line-height: 2em;">
- <a class="btn btn-default" data-toggle="dropdown" href="#">Size
- <span class="caret"/></a>
- <ul aria-labelledby="dLabel" class="dropdown-menu" role="menu">
- <li>
- <label class="btn btn-default btn-xs">Extra Small
- <input class="link-style" name="link-style-size" type="radio" value="btn-xs"/></label>
- </li>
- <li>
- <label class="btn btn-default btn-sm">Small
- <input class="link-style" name="link-style-size" type="radio" value="btn-sm"/></label>
- </li>
- <li>
- <label class="btn btn-default">Default
- <input checked="checked" class="link-style" name="link-style-size" type="radio" value=""/></label>
- </li>
- <li>
- <label class="btn btn-default btn-lg">Large
- <input class="link-style" name="link-style-size" type="radio" value="btn-lg"/></label>
- </li>
- </ul>
- </div>
+ </div>
+ </div>
+ </form>
+ </div>
+ <form class="o_bg_img_opt form-horizontal" data-value="custom">
+ <fieldset>
+ <legend>Background size</legend>
+ <p>Sets the width and height of the background image in percent of the parent element.</p>
+ <div class="form-group">
+ <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_size_x">Background width</label>
+ <div class="col-sm-5">
+ <div class="input-group">
+ <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_size_x" placeholder="auto"/>
+ <div class="input-group-addon">%</div>
</div>
</div>
</div>
- <div class="form-horizontal link-style">
- <div class="form-group">
- <label class="col-sm-2 control-label">Preview
- </label>
- <div class="col-sm-10" style="line-height: 2em;">
- <div style="overflow: auto; max-width: 100%; max-height: 200px;">
- <a href="#" id="link-preview"/>
- </div>
+ <div class="form-group">
+ <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_size_y">Background height</label>
+ <div class="col-sm-5">
+ <div class="input-group">
+ <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_size_y" placeholder="auto"/>
+ <div class="input-group-addon">%</div>
</div>
</div>
</div>
- </form>
- </t>
- </t>
- <t t-name="web_editor.components.switch">
- <label class="o_switch" t-att-for="id">
- <input type="checkbox" t-att-id="id"/>
- <span/>
- <t t-if="label"><t t-esc="label"/></t>
- </label>
- </t>
+ </fieldset>
+ <fieldset>
+ <legend>Background position</legend>
+ <p>Set the starting position of the background image.</p>
+ <div class="form-group">
+ <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_pos_x">Horizontal</label>
+ <div class="col-sm-5">
+ <div class="input-group">
+ <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_pos_x" placeholder="auto"/>
+ <div class="input-group-addon">%</div>
+ </div>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-3 control-label" for="o_bg_img_opt_custom_pos_y">Vertical</label>
+ <div class="col-sm-5">
+ <div class="input-group">
+ <input type="number" min="0" max="100" class="form-control input-sm" id="o_bg_img_opt_custom_pos_y" placeholder="auto"/>
+ <div class="input-group-addon">%</div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ <fieldset>
+ <legend>Background repeat</legend>
+ <p>Define if/how the background image will be repeated</p>
+ <select class="form-control" id="o_bg_img_opt_custom_repeat">
+ <option value="">No repeat</option>
+ <option value="o_bg_img_opt_repeat">Repeat both</option>
+ <option value="o_bg_img_opt_repeat_x">Repeat x</option>
+ <option value="o_bg_img_opt_repeat_y">Repeat y</option>
+ </select>
+ </fieldset>
+ </form>
+ </div>
</templates>
diff --git a/addons/web_editor/static/src/xml/translator.xml b/addons/web_editor/static/src/xml/translator.xml
deleted file mode 100644
index 74b97905355..00000000000
--- a/addons/web_editor/static/src/xml/translator.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<templates id="template" xml:space="preserve">
-
-<t t-name="web_editor.translator.attributes">
- <div class="modal web_editor-dialog" tabindex="-1" role="dialog" aria-hidden="true">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
- <h3 class="modal-title">Translate Attribute</h3>
- </div>
- <div class="modal-body">
- <div class="form-group"></div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-primary save">Close</button>
- </div>
- </div>
- </div>
- </div>
-</t>
-
-</templates>