summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src/js/snippets.editor.js
diff options
context:
space:
mode:
authorChristophe Matthieu <chm@odoo.com>2015-07-07 16:02:53 +0200
committerChristophe Matthieu <chm@odoo.com>2015-07-10 17:00:11 +0200
commit8c559044a5e1be111dcb577ad7e5ab462c1002a0 (patch)
treeba1602e15b2bca38babd6cca63a41da1d05b4321 /addons/web_editor/static/src/js/snippets.editor.js
parenta616c57c609da0e5e56835dcb45e1c05590d25b8 (diff)
[IMP] web_editor: split website into web_editor (to use editor in backend for html field) and website
Diffstat (limited to 'addons/web_editor/static/src/js/snippets.editor.js')
-rw-r--r--addons/web_editor/static/src/js/snippets.editor.js1137
1 files changed, 1137 insertions, 0 deletions
diff --git a/addons/web_editor/static/src/js/snippets.editor.js b/addons/web_editor/static/src/js/snippets.editor.js
new file mode 100644
index 00000000000..9d37a9f84c8
--- /dev/null
+++ b/addons/web_editor/static/src/js/snippets.editor.js
@@ -0,0 +1,1137 @@
+odoo.define('web_editor.snippet.editor', function (require) {
+'use strict';
+
+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();
+ this._super();
+ },
+});
+
+/* ----- SNIPPET SELECTOR ---- */
+
+$.extend($.expr[':'],{
+ hasData: function(node,i,m){
+ return !!_.toArray(node.dataset).length;
+ },
+ data: function(node,i,m){
+ return $(node).data(m[3]);
+ }
+});
+
+var 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({
+ template: 'web_editor.snippets',
+ activeSnippets: [],
+ init: function (parent, $editable) {
+ this.parent = parent;
+ this.$editable = $editable;
+
+ this._super.apply(this, arguments);
+ if(!$('#oe_manipulators').length){
+ $("<div id='oe_manipulators' class='o_css_editor'></div>").appendTo('body');
+ }
+ this.$active_snipped_id = false;
+ this.snippets = [];
+
+ data.instance = this;
+ },
+ start: function() {
+ var self = this;
+ this.$snippet = $("#oe_snippets");
+
+ this.$el
+ .on("mouseenter", function () { self.show(); })
+ .on("mouseleave", function (event) { if (event.clientX>0 && event.clientY>0) self.hide(); });
+
+ $(window).resize(function () {
+ setTimeout('$(document).click()',0);
+ });
+
+ this.fetch_snippet_templates();
+ this.bind_snippet_click_editor();
+
+ $(document).on('click', '.dropdown-submenu a[tabindex]', function (e) {
+ e.preventDefault();
+ });
+
+ var _isNotBreakable = $.summernote.core.dom.isNotBreakable;
+ $.summernote.core.dom.isNotBreakable = function (node) {
+ return _isNotBreakable(node) || $(node).is('div') || globalSelector.is($(node));
+ };
+
+ $(window).on('resize', function () {
+ if (self.$active_snipped_id && self.$active_snipped_id.data("snippet-editor")) {
+ self.cover_target(self.$active_snipped_id.data("snippet-editor").$overlay, self.$active_snipped_id);
+ }
+ });
+
+ $(document).on('mousemove', function () {
+ if (self.$active_snipped_id && self.$active_snipped_id.data("snippet-editor")) {
+ self.$active_snipped_id.data("snippet-editor").$overlay.removeClass('o_keypress');
+ }
+ });
+ $(document).on('keydown', function (event) {
+ if (self.$active_snipped_id && self.$active_snipped_id.data("snippet-editor")) {
+ self.$active_snipped_id.data("snippet-editor").$overlay.addClass('o_keypress');
+ }
+ if ((event.metaKey || (event.ctrlKey && !event.altKey)) && event.shiftKey && event.keyCode >= 48 && event.keyCode <= 57) {
+ self.$snippet.find('.scroll:first > ul li:eq('+(event.keyCode-49)+') a').trigger("click");
+ self.show();
+ event.preventDefault();
+ }
+ });
+ },
+
+ _get_snippet_url: function () {
+ return '/web_editor/snippets';
+ },
+ _add_check_selector : function (selector, no_check) {
+ var self = this;
+ var selector = selector.split(/\s*,/).join(":not(.o_snippet_not_selectable), ") + ":not(.o_snippet_not_selectable)";
+
+ if (no_check) {
+ return {
+ closest: function ($from, parentNode) {
+ return $from.closest(selector, parentNode);
+ },
+ all: function ($from) {
+ return $from ? $from.find(selector) : $(selector);
+ },
+ is: function ($from) {
+ return $from.is(selector);
+ }
+ };
+ } else {
+ var selector = selector.split(/\s*,/).join(":o_editable, ") + ":o_editable";
+ return {
+ closest: function ($from, parentNode) {
+ var parents = self.$editable.get();
+ return $from.closest(selector, parentNode).filter(function () {
+ var node = this;
+ while (node.parentNode) {
+ if (parents.indexOf(node)!==-1) {
+ return true;
+ }
+ node = node.parentNode;
+ }
+ return false;
+ });
+ },
+ all: function ($from) {
+ return $from ? $from.find(selector) : self.$editable.filter(selector).add(self.$editable.find(selector));
+ },
+ is: function ($from) {
+ return $from.is(selector);
+ }
+ };
+ }
+ },
+
+ fetch_snippet_templates: function () {
+ var self = this;
+ var url = this._get_snippet_url();
+ if (!url || !url.length) {
+ this.$snippet.detach();
+ return;
+ }
+ this.$el.find("#o_left_bar").addClass("hidden");
+ return ajax.jsonRpc(url, 'call', {}).then(function (html) {
+ self.compute_snippet_templates(html);
+ self.trigger("snippets:ready");
+ });
+ },
+ compute_snippet_templates: function (html) {
+ var self = this;
+ var $html = $(html);
+ var $left_bar = this.$el.find("#o_left_bar");
+ var $ul = $html.siblings("ul");
+ var $scroll = $html.siblings("#o_scroll");
+
+ if (!$scroll.length) {
+ throw new Error("Wrong snippets xml definition");
+ }
+
+ $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');});
+
+ // t-snippet
+ $html.find('[data-oe-type="snippet"][data-oe-name]').each(function () {
+ var $div = $('<div/>').insertAfter(this).append(this).attr('name', $(this).data('oe-name'));
+ });
+ // end
+
+ self.templateOptions = [];
+ var selector = [];
+ var $styles = $html.find("[data-js], [data-selector]");
+ $styles.each(function () {
+ var $style = $(this);
+ var no_check = $style.data('no-check');
+ var option_id = $style.data('js');
+ var option = {
+ 'option' : option_id,
+ 'base_selector': $style.data('selector'),
+ 'selector': self._add_check_selector($style.data('selector'), no_check),
+ '$el': $style,
+ 'drop-near': $style.data('drop-near') && self._add_check_selector($style.data('drop-near'), no_check),
+ 'drop-in': $style.data('drop-in') && self._add_check_selector($style.data('drop-in'), no_check),
+ 'data': $style.data()
+ };
+ self.templateOptions.push(option);
+ selector.push(option.selector);
+ });
+
+ $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;
+ }
+ }
+ return $target;
+ },
+ 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;
+ },
+ is: function ($from) {
+ var len = selector.length;
+ for (var i = 0; i<len; i++) {
+ if (selector[i].is($from)) {
+ return true;
+ }
+ }
+ return false;
+ },
+ };
+
+ var number = 0;
+
+ // oe_snippet_body
+ self.$snippets = $scroll.find(".o_panel_body").children()
+ .addClass("oe_snippet")
+ .each(function () {
+ if (!$('.oe_snippet_thumbnail', this).size()) {
+ var $div = $(
+ '<div class="oe_snippet_thumbnail">'+
+ '<div class="oe_snippet_thumbnail_img"/>'+
+ '<span class="oe_snippet_thumbnail_title"></span>'+
+ '</div>');
+ $div.find('span').text($(this).attr("name"));
+ $(this).prepend($div);
+
+ // from t-snippet
+ var thumbnail = $("[data-oe-thumbnail]", this).data("oe-thumbnail");
+ if (thumbnail) {
+ $div.find('.oe_snippet_thumbnail_img').css('background-image', 'url(' + thumbnail + ')');
+ }
+ // end
+ }
+ if (!$(this).data("selector")) {
+ $("> *:not(.oe_snippet_thumbnail)", this).addClass('oe_snippet_body');
+ }
+ number++;
+ });
+
+ // hide scroll if no snippets defined
+ if (!number) {
+ this.$snippet.detach();
+ } else {
+ this.$el.find("#o_left_bar").removeClass("hidden");
+ }
+ $("body").toggleClass("editor_has_snippets", !!number);
+
+ // select all default text to edit (if snippet default text)
+ /*self.$snippets.find('.oe_snippet_body, .oe_snippet_body *')
+ .contents()
+ .filter(function() {
+ return this.nodeType === 3 && this.textContent.match(/\S/);
+ }).parent().addClass("o_default_snippet_text");*/
+ $(document).on("mouseup", ".o_default_snippet_text", function (event) {
+ $(event.target).selectContent();
+ });
+ $(document).on("keyup", function (event) {
+ var r = $.summernote.core.range.create();
+ $(r && r.sc).closest(".o_default_snippet_text").removeClass("o_default_snippet_text");
+ });
+ // end
+
+ // clean t-oe
+ $html.find('[data-oe-model], [data-oe-type]').each(function () {
+ for (var k=0; k<this.attributes.length; k++) {
+ if (this.attributes[k].name.indexOf('data-oe-') === 0) {
+ $(this).removeAttr(this.attributes[k].name);
+ k--;
+ }
+ }
+ });
+ // end
+
+ $html.find('.o_not_editable').attr("contentEditable", false);
+
+ $left_bar.append($html);
+
+ // animate for list of snippet blocks
+ $left_bar.on('click', '.scroll-link', function (event) {
+ event.preventDefault();
+ var targetOffset = $($(this).attr("href")).position().top - $ul.outerHeight() + $scroll[0].scrollTop;
+ $scroll.animate({'scrollTop': targetOffset}, 750);
+ });
+ $scroll.on('scroll', function () {
+ var middle = $scroll.height()/4;
+ var $li = $ul.find("a").parent().removeClass('active');
+ var last;
+ for (var k=0; k<$li.length; k++) {
+ var li = $($li[k]);
+ if (!li.data('target')) {
+ li.data('target', $($("a", li).attr("href")));
+ }
+ if (li.data('target').position().top > middle) {
+ break;
+ }
+ last = $li[k];
+ }
+ $(last).addClass("active");
+ });
+ // end
+
+ // display scrollbar
+ $(window).on('resize', function () {
+ $scroll.css("overflow", "");
+ var height = $left_bar.height() - $ul.outerHeight();
+ $scroll.css("height", height);
+ var $last = $scroll.children(":visible").last().children(".o_panel_body");
+ $last.css({'min-height': (height-$last.prev().outerHeight())+'px'});
+ if ($scroll[0].scrollHeight + $ul[0].scrollHeight > document.body.clientHeight) {
+ $scroll.css("overflow", "auto").css("width", "226px");
+ } else {
+ $scroll.css("width", "");
+ }
+ }).trigger('resize');
+ // end
+
+ self.make_snippet_draggable(self.$snippets);
+ },
+
+ cover_target: function ($el, $target){
+ if($el.data('not-cover_target')) return;
+ var pos = $target.offset();
+ var mt = parseInt($target.css("margin-top") || 0);
+ var mb = parseInt($target.css("margin-bottom") || 0);
+ var width = $target.outerWidth();
+ var bigger = pos.left+width > $("body").outerWidth()-8;
+ $el.css({
+ 'width': width,
+ 'top': pos.top - mt - 5,
+ 'left': pos.left-1
+ });
+ $el.find(".oe_handle.e,.oe_handle.w").css({'height': $target.outerHeight() + mt + mb+1});
+ if (bigger) {
+ $el.find(".oe_handle.e").css({right: 0, margin: 0})
+ .find("div").css({right: 0, left: 'auto'});
+ } else {
+ $el.find(".oe_handle.e").css({right: "", margin: ""})
+ .find("div").css({right: "", left: ""});
+ }
+ $el.find(".oe_handle.s").css({'top': $target.outerHeight() + mt + mb});
+ $el.find(".oe_handle.size").css({'top': $target.outerHeight() + mt});
+ $el.find(".oe_handle.s,.oe_handle.n").css({'width': width-2});
+ },
+
+ show_blocks: function () {
+ var self = this;
+ var cache = {};
+ this.$snippet.find(".o_panel").each(function () {
+ var catcheck = false;
+ var $category = $(this);
+ $category.find(".oe_snippet_body").each(function () {
+ var $snippet = $(this);
+
+ var check = false;
+
+ for (var k in self.templateOptions) {
+ var option = self.templateOptions[k];
+ if ($snippet.is(option.base_selector)) {
+
+ cache[k] = cache[k] || {
+ 'drop-near': option['drop-near'] ? option['drop-near'].all() : [],
+ 'drop-in': option['drop-in'] ? option['drop-in'].all() : []
+ };
+
+ if (cache[k]['drop-near'].length || cache[k]['drop-in'].length) {
+ catcheck = true;
+ check = true;
+ break;
+ }
+ }
+ }
+
+ if (check) {
+ $snippet.closest(".oe_snippet").removeClass("disable");
+ } else {
+ $snippet.closest(".oe_snippet").addClass("disable");
+ }
+ });
+
+ $('#oe_snippets .scroll a[data-toggle="tab"][href="#' + $category.attr("id") + '"]').toggle(catcheck);
+ });
+ },
+ show: function () {
+ var self = this;
+ this.make_active(false);
+ this.$el.addClass("o_open");
+ this.show_blocks();
+ },
+ hide: function () {
+ this.$el.removeClass("o_open");
+ },
+ bind_snippet_click_editor: function () {
+ var self = this;
+ var snipped_event_flag;
+ $(document).on('click', '*', function (event) {
+ var srcElement = event.srcElement || (event.originalEvent && (event.originalEvent.originalTarget || event.originalEvent.target) || event.target);
+ if (self.editor_busy || snipped_event_flag===srcElement || !srcElement) {
+ return;
+ }
+ snipped_event_flag = srcElement;
+
+ setTimeout(function () {snipped_event_flag = false;}, 0);
+ var $target = $(srcElement);
+
+ if ($target.closest(".oe_overlay, .note-popover").length) {
+ return;
+ }
+
+ if (!globalSelector.is($target)) {
+ $target = globalSelector.closest($target);
+ }
+
+ if (self.$active_snipped_id && self.$active_snipped_id.is($target)) {
+ return;
+ }
+ self.make_active($target);
+ });
+ },
+ snippet_blur: function ($snippet) {
+ if ($snippet) {
+ if ($snippet.data("snippet-editor")) {
+ $snippet.data("snippet-editor").on_blur();
+ }
+ }
+ },
+ snippet_focus: function ($snippet) {
+ if ($snippet) {
+ if ($snippet.data("snippet-editor")) {
+ $snippet.data("snippet-editor").on_focus();
+ }
+ }
+ },
+ clean_for_save: function () {
+ var self = this;
+ var opt = options.registry;
+ var template = self.templateOptions;
+ for (var k in template) {
+ var Option = opt[template[k]['option']];
+ if (Option && Option.prototype.clean_for_save !== dummy) {
+ template[k].selector.all().filter(".o_dirty").each(function () {
+ new Option(self, null, $(this), k).clean_for_save();
+ });
+ }
+ }
+ this.$editable.find("*[contentEditable], *[attributeEditable]")
+ .removeAttr('contentEditable')
+ .removeProp('contentEditable')
+ .removeAttr('attributeEditable')
+ .removeProp('attributeEditable');
+ },
+ make_active: function ($snippet) {
+ if ($snippet && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snippet.get(0)) {
+ return;
+ }
+ if (this.$active_snipped_id) {
+ this.snippet_blur(this.$active_snipped_id);
+ this.$active_snipped_id = false;
+ }
+ if ($snippet && $snippet.length) {
+ if(_.indexOf(this.snippets, $snippet.get(0)) === -1) {
+ this.snippets.push($snippet.get(0));
+ }
+ this.$active_snipped_id = $snippet;
+ this.create_overlay(this.$active_snipped_id);
+ this.snippet_focus($snippet);
+ }
+ this.$snippet.trigger('snippet-activated', $snippet);
+ if ($snippet) {
+ $snippet.trigger('snippet-activated', $snippet);
+ }
+ },
+ create_overlay: function ($snippet) {
+ if (typeof $snippet.data("snippet-editor") === 'undefined') {
+ var $targets = this.activate_overlay_zones($snippet);
+ if (!$targets.length) return;
+ $snippet.data("snippet-editor", new Editor(this, $snippet));
+ }
+ this.cover_target($snippet.data('overlay'), $snippet);
+ },
+
+ // activate drag and drop for the snippets in the snippet toolbar
+ make_snippet_draggable: function($snippets){
+ var self = this;
+ 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;
+
+ $snippets.draggable({
+ greedy: true,
+ helper: 'clone',
+ zIndex: '1000',
+ appendTo: 'body',
+ cursor: "move",
+ handle: ".oe_snippet_thumbnail",
+ cursorAt: {
+ 'left': left,
+ 'top': top
+ },
+ start: function(){
+ self.hide();
+ dropped = false;
+ // snippet_selectors => to get drop-near, drop-in
+ $snippet = $(this);
+ 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)) {
+ if (temp[k]['drop-near']) {
+ if (!$selector_siblings) $selector_siblings = temp[k]['drop-near'].all();
+ else $selector_siblings = $selector_siblings.add(temp[k]['drop-near'].all());
+ }
+ if (temp[k]['drop-in']) {
+ if (!$selector_children) $selector_children = temp[k]['drop-in'].all();
+ else $selector_children = $selector_children.add(temp[k]['drop-in'].all());
+ }
+ }
+ }
+
+ $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");
+ return;
+ }
+
+ self.activate_insertion_zones($selector_siblings, $selector_children);
+
+ $('.oe_drop_zone').droppable({
+ over: function(){
+ dropped = true;
+ $(this).first().after($toInsert);
+ },
+ out: function(){
+ var prev = $toInsert.prev();
+ if(this === prev[0]){
+ dropped = false;
+ $toInsert.detach();
+ }
+ }
+ });
+ },
+ 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) {
+ el.after($toInsert);
+ dropped = true;
+ }
+ }
+
+ 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;
+
+ if (prev) {
+ $toInsert.detach();
+ rte.historyRecordUndo($(prev));
+ $toInsert.insertAfter(prev);
+ } else if (next) {
+ $toInsert.detach();
+ rte.historyRecordUndo($(next));
+ $toInsert.insertBefore(next);
+ } else {
+ var $parent = $toInsert.parent();
+ $toInsert.detach();
+ rte.historyRecordUndo($parent);
+ $parent.prepend($toInsert);
+ }
+
+ $toInsert.closest(".o_editable").trigger("content_changed");
+
+ var $target = false;
+ $target = $toInsert;
+
+ setTimeout(function () {
+ self.$snippet.trigger('snippet-dropped', $target);
+
+ animation.start(true, $target);
+
+ // drop_and_build_snippet
+ self.create_overlay($target);
+ if ($target.data("snippet-editor")) {
+ $target.data("snippet-editor").drop_and_build_snippet();
+ }
+ for (var k in self.templateOptions) {
+ self.templateOptions[k].selector.all($target).each(function () {
+ var $snippet = $(this);
+ self.create_overlay($snippet);
+ if ($snippet.data("snippet-editor")) {
+ $snippet.data("snippet-editor").drop_and_build_snippet();
+ }
+ });
+ }
+ $target.closest(".o_editable").trigger("content_changed");
+ // end
+
+ self.make_active($target);
+ },0);
+ } else {
+ $toInsert.remove();
+ }
+ },
+ });
+ },
+
+ // 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();
+ },
+
+ // Create element insertion drop zones. two css selectors can be provided
+ // selector.children -> will insert drop zones as direct child of the selected elements
+ // in case the selected elements have children themselves, dropzones will be interleaved
+ // with them.
+ // selector.siblings -> will insert drop zones after and before selected elements
+ activate_insertion_zones: function($selector_siblings, $selector_children){
+ var self = this;
+ var zone_template = $("<div class='oe_drop_zone oe_insert'></div>");
+
+ if ($selector_children) {
+ $selector_children.each(function (){
+ var $zone = $(this);
+ var css = window.getComputedStyle(this);
+ var float = css.float || css.cssFloat;
+ var $drop = zone_template.clone();
+
+ $zone.append($drop);
+ var node = $drop[0].previousSibling;
+ var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
+ if (test) {
+ $drop.addClass("oe_vertical oe_vertical_text").css({
+ '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));
+ }
+
+ $drop = $drop.clone();
+
+ $zone.prepend($drop);
+ var node = $drop[0].nextSibling;
+ var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === "BR"));
+ if (test) {
+ $drop.addClass("oe_vertical oe_vertical_text").css({
+ '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));
+ }
+ if (test) {
+ $drop.css({'float': 'none', 'display': 'inline-block'});
+ }
+ });
+
+ // add children near drop zone
+ $selector_siblings = $(_.uniq(($selector_siblings || $()).add($selector_children.children()).get()));
+ }
+
+ if ($selector_siblings) {
+ $selector_siblings.filter(':not(.oe_drop_zone):not(.oe_drop_clone)').each(function (){
+ var $zone = $(this);
+ var $drop;
+ var css = window.getComputedStyle(this);
+ var float = css.float || css.cssFloat;
+
+ if($zone.prev('.oe_drop_zone:visible').length === 0){
+ $drop = zone_template.clone();
+ if (float === "left" || float === "right") {
+ $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.prev().outerHeight() || Infinity), 30));
+ }
+ $zone.before($drop);
+ }
+ if($zone.next('.oe_drop_zone:visible').length === 0){
+ $drop = zone_template.clone();
+ if (float === "left" || float === "right") {
+ $drop.addClass("oe_vertical").css('height', Math.max(Math.min($zone.outerHeight(), $zone.next().outerHeight() || Infinity), 30));
+ }
+ $zone.after($drop);
+ }
+ });
+ }
+
+ var count;
+ do {
+ count = 0;
+ $zones = self.$editable.find('.oe_drop_zone > .oe_drop_zone').remove(); // no recursive zones
+ count += $zones.length;
+ $zones.remove();
+ } while (count > 0);
+
+ // Cleaning consecutive zone and up zones placed between floating or inline elements. We do not like these kind of zones.
+ var $zones = self.$editable.find('.oe_drop_zone:not(.oe_vertical)');
+ $zones.each(function (){
+ var zone = $(this);
+ var prev = zone.prev();
+ var next = zone.next();
+ // remove consecutive zone
+ if (prev.is('.oe_drop_zone') || next.is('.oe_drop_zone')) {
+ zone.remove();
+ return;
+ }
+ var float_prev = prev.css('float') || 'none';
+ var float_next = next.css('float') || 'none';
+ var disp_prev = prev.css('display') || null;
+ var disp_next = next.css('display') || null;
+ if( (float_prev === 'left' || float_prev === 'right')
+ && (float_next === 'left' || float_next === 'right') ){
+ zone.remove();
+ }else if( !( disp_prev === null
+ || disp_next === null
+ || disp_prev === 'block'
+ || disp_next === 'block' )){
+ zone.remove();
+ }
+ });
+ },
+
+ // generate drop zones covering the elements selected by the selector
+ // we generate overlay drop zones only to get an idea of where the snippet are, the drop
+ activate_overlay_zones: function($targets){
+ var self = this;
+
+ function is_visible($el){
+ return $el.css('display') != 'none'
+ && $el.css('opacity') != '0'
+ && $el.css('visibility') != 'hidden';
+ }
+
+ // filter out invisible elements
+ $targets = $targets.filter(function(){ return is_visible($(this)); });
+
+ // filter out elements with invisible parents
+ $targets = $targets.filter(function(){
+ var parents = $(this).parents().filter(function(){ return !is_visible($(this)); });
+ return parents.length === 0;
+ });
+
+ $targets.each(function () {
+ var $target = $(this);
+ if (!$target.data('overlay')) {
+ var $zone = $(qweb.render('web_editor.snippet_overlay'));
+
+ // fix for pointer-events: none with ie9
+ if (document.body && document.body.addEventListener) {
+ $zone.on("click mousedown mousedown", function passThrough(event) {
+ event.preventDefault();
+ $target.each(function() {
+ // check if clicked point (taken from event) is inside element
+ event.srcElement = this;
+ $(this).trigger(event.type);
+ });
+ return false;
+ });
+ }
+
+ $zone.appendTo('#oe_manipulators');
+ $zone.data('target',$target);
+ $target.data('overlay',$zone);
+
+ var timer;
+ $target.closest('.o_editable').on("content_changed", function (event) {
+ if ($target.data('overlay') && $target.data('overlay').hasClass("oe_active")) {
+ clearTimeout(timer);
+ timer = setTimeout(function () {
+ if ($target.data('overlay')) {
+ self.cover_target($target.data('overlay'), $target);
+ }
+ },50);
+ }
+ });
+
+ var resize = function () {
+ if ($zone.parent().length) {
+ self.cover_target($zone, $target);
+ } else {
+ $('body').off("resize", resize);
+ }
+ };
+ $('body').on("resize", resize);
+ }
+ self.cover_target($target.data('overlay'), $target);
+ });
+ return $targets;
+ }
+});
+
+/* ----- Editor (object who contain overlay and the option list) ---- */
+
+var Editor = Class.extend({
+ init: function (BuildingBlock, dom) {
+ this.buildingBlock = BuildingBlock;
+ this.$target = $(dom);
+ this.$overlay = this.$target.data('overlay');
+ this.load_style_options();
+ this.get_parent_block();
+ this.start();
+ },
+
+ // activate drag and drop for the snippets in the snippet toolbar
+ _drag_and_drop: function(){
+ var self = this;
+ this.dropped = false;
+ this.$overlay.draggable({
+ greedy: true,
+ appendTo: 'body',
+ cursor: "move",
+ handle: ".oe_snippet_move",
+ cursorAt: {
+ left: 18,
+ top: 14
+ },
+ helper: function() {
+ var $clone = $(this).clone().css({width: "24px", height: "24px", border: 0});
+ $clone.find(".oe_overlay_options >:not(:contains(.oe_snippet_move)), .oe_handle").remove();
+ $clone.find(":not(.glyphicon)").css({position: 'absolute', top: 0, left: 0});
+ $clone.appendTo("body").removeClass("hidden");
+ return $clone;
+ },
+ start: _.bind(self._drag_and_drop_start, self),
+ stop: _.bind(self._drag_and_drop_stop, self)
+ });
+ },
+ _drag_and_drop_after_insert_dropzone: function (){},
+ _drag_and_drop_active_drop_zone: function ($zones){
+ var self = this;
+ $zones.droppable({
+ over: function(){
+ $(".oe_drop_zone.hide").removeClass("hide");
+ $(this).addClass("hide").first().after(self.$target);
+ self.dropped = true;
+ },
+ out: function(){
+ $(this).removeClass("hide");
+ self.$target.detach();
+ self.dropped = false;
+ },
+ });
+ },
+ _drag_and_drop_start: function (){
+ var self = this;
+ self.buildingBlock.hide();
+ self.buildingBlock.editor_busy = true;
+ self.size = {
+ width: self.$target.width(),
+ height: self.$target.height()
+ };
+ self.$target.after("<div class='oe_drop_clone' style='display: none;'/>");
+ self.$target.detach();
+ self.$overlay.addClass("hidden");
+
+ var $selector_siblings;
+ 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++) {
+ if (!$selector_children) $selector_children = self.selector_children[i].all();
+ else $selector_children = $selector_children.add(self.selector_children[i].all());
+ }
+
+ self.buildingBlock.activate_insertion_zones($selector_siblings, $selector_children);
+
+ $("body").addClass('move-important');
+
+ self._drag_and_drop_after_insert_dropzone();
+ self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
+ },
+ _drag_and_drop_stop: function (){
+ var self = this;
+ var $dropzone = this.$target.prev();
+ var prev = $dropzone.length && $dropzone[0].previousSibling;
+ var next = this.$target.last()[0].nextSibling;
+ var $parent = this.$target.parent();
+
+ $(".oe_drop_clone").after(this.$target);
+
+ this.$overlay.removeClass("hidden");
+ $("body").removeClass('move-important');
+ $('.oe_drop_zone').droppable('destroy').remove();
+ $(".oe_drop_clone, .oe_drop_to_remove").remove();
+
+ if (this.dropped) {
+ this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+
+ if (prev) {
+ this.$target.insertAfter(prev);
+ } else if (next) {
+ this.$target.insertBefore(next);
+ } else {
+ $parent.prepend(this.$target);
+ }
+
+ for (var i in this.styles){
+ this.styles[i].on_move();
+ }
+ }
+
+ self.buildingBlock.editor_busy = false;
+
+ self.get_parent_block();
+ setTimeout(function () {self.buildingBlock.create_overlay(self.$target);},0);
+ },
+
+ load_style_options: function () {
+ var self = this;
+ var $styles = this.$overlay.find('.oe_options');
+ var $ul = $styles.find('ul:first');
+ this.styles = {};
+ this.selector_siblings = [];
+ this.selector_children = [];
+ _.each(this.buildingBlock.templateOptions, function (val, option_id) {
+ if (!val.selector.is(self.$target)) {
+ return;
+ }
+ if (val['drop-near']) self.selector_siblings.push(val['drop-near']);
+ if (val['drop-in']) self.selector_children.push(val['drop-in']);
+
+ var option = val['option'];
+ var Editor = options.registry[option] || options.Class;
+ var editor = self.styles[option] = new Editor(self.buildingBlock, self, self.$target, option_id);
+ $ul.append(editor.$el.addClass("snippet-option-" + option));
+ editor.start();
+ });
+
+ if (!this.selector_siblings.length && !this.selector_children.length) {
+ this.$overlay.find(".oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove").addClass('hidden');
+ }
+
+ if ($ul.find("li").length) {
+ $styles.removeClass("hidden");
+ }
+ this.$overlay.find('[data-toggle="dropdown"]').dropdown();
+ },
+
+ get_parent_block: function () {
+ var self = this;
+ var $button = this.$overlay.find('.oe_snippet_parent');
+ var $parent = globalSelector.closest(this.$target.parent());
+ if ($parent.length) {
+ $button.removeClass("hidden");
+ $button.off("click").on('click', function (event) {
+ event.preventDefault();
+ setTimeout(function () {
+ self.buildingBlock.make_active($parent);
+ }, 0);
+ });
+ } else {
+ $button.addClass("hidden");
+ }
+ },
+
+ /*
+ * start
+ * This method is called after init and _readXMLData
+ */
+ start: function () {
+ var self = this;
+ if (!this.$target.parent().is(':o_editable')) {
+ this.$overlay.find('.oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove').remove();
+ } else {
+ this.$overlay.on('click', '.oe_snippet_clone', _.bind(this.on_clone, this));
+ this.$overlay.on('click', '.oe_snippet_remove', _.bind(this.on_remove, this));
+ this._drag_and_drop();
+ }
+ },
+
+ on_clone: function (event) {
+ event.preventDefault();
+ var $clone = this.$target.clone(false);
+
+ this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+
+ this.$target.after($clone);
+ for (var i in this.styles){
+ this.styles[i].on_clone($clone);
+ }
+ this.buildingBlock.create_overlay(this.$target);
+ return false;
+ },
+
+ on_remove: function (event) {
+ event.preventDefault();
+ this.on_blur();
+
+ this.buildingBlock.parent.rte.historyRecordUndo(this.$target);
+
+ var index = _.indexOf(this.buildingBlock.snippets, this.$target.get(0));
+ for (var i in this.styles){
+ this.styles[i].on_remove();
+ }
+ delete this.buildingBlock.snippets[index];
+
+ var $editable = this.$target.closest(".o_editable");
+
+ // remove node and his empty
+ var node = this.$target.parent()[0];
+
+ this.$target.remove();
+ this.$overlay.remove();
+
+ if (node && node.firstChild) {
+ $.summernote.core.dom.removeSpace(node, node.firstChild, 0, node.lastChild, 1);
+ if (!node.firstChild.tagName && node.firstChild.textContent === " ") {
+ node.firstChild.parentNode.removeChild(node.firstChild);
+ }
+ }
+
+ // clean editor if they are image or table in deleted content
+ $(".note-control-selection").hide();
+ $('.o_table_handler').remove();
+
+ return false;
+ },
+
+ /*
+ * drop_and_build_snippet
+ * This method is called just after that a thumbnail is drag and dropped into a drop zone
+ * (after the insertion of this.$body, if this.$body exists)
+ */
+ drop_and_build_snippet: function () {
+ for (var i in this.styles){
+ this.styles[i].drop_and_build_snippet();
+ }
+ },
+
+ /* on_focus
+ * This method is called when the user click inside the snippet in the dom
+ */
+ on_focus : function () {
+ this.$overlay.addClass('oe_active');
+ for (var i in this.styles){
+ this.styles[i].on_focus();
+ }
+ },
+
+ /* on_focus
+ * This method is called when the user click outside the snippet in the dom, after a focus
+ */
+ on_blur : function () {
+ for (var i in this.styles){
+ this.styles[i].on_blur();
+ }
+ this.$overlay.removeClass('oe_active');
+ },
+});
+
+var data = {
+ 'Class': BuildingBlock,
+ 'globalSelector': globalSelector,
+};
+return data;
+
+});