From fee8cb0fff1773648ea3c246a14470d92840686b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 21 Nov 2013 12:07:48 +0100 Subject: [PATCH 1/5] [FIX] reimplement [Edit] button on images Button implementation turned out to be crap: * Didn't work at all in Firefox (!) * Unreliable in webkit, some types of image selection wouldn't correctly activate the button -> * changed RTEImageDialog to take an (optional) image element, used as the "source element" if provided. This allows greater reliability for events which may leave selection in debatable or variable (across browsers) state * setup and teardown button on hover, using selection triggered issues with oe_overlay thing where e.g. the first click on the button would work but the second one would not (it'd be intercepted by the overlay as far as I can tell). This also saves a click for user, they will see the [Edit] button immediately when putting the cursor over the image. bzr revid: xmo@openerp.com-20131121110748-s5e2ne3etpzwi2a6 --- .../website/static/src/js/website.editor.js | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index feb8e403c54..e8544221b00 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -30,8 +30,8 @@ function link_dialog(editor) { return new website.editor.RTELinkDialog(editor).appendTo(document.body); } - function image_dialog(editor) { - return new website.editor.RTEImageDialog(editor).appendTo(document.body); + function image_dialog(editor, image) { + return new website.editor.RTEImageDialog(editor, image).appendTo(document.body); } // only enable editors manually @@ -53,7 +53,7 @@ && !element.data('cke-realelement') && !element.isReadOnly() && (element.data('oe-model') !== 'ir.ui.view')) { - image_dialog(editor); + image_dialog(editor, element); return; } @@ -69,13 +69,9 @@ }, null, null, 500); var previousSelection; - editor.on('selectionChange', function (evt) { - var selected = evt.data.path.lastElement; - if (previousSelection) { - // cleanup previous selection - $(previousSelection).next().remove(); - previousSelection = null; - } + $(editor.element.$).on('mouseenter', 'img', function () { + if (previousSelection) { return; } + var selected = new CKEDITOR.dom.element(this); if (!selected.is('img') || selected.data('cke-realelement') || selected.isReadOnly() @@ -84,13 +80,13 @@ } // display button - var $el = $(previousSelection = selected.$); + var $el = $((previousSelection = selected).$); var $btn = $('') .insertAfter($el) .click(function (e) { e.preventDefault(); e.stopPropagation(); - image_dialog(editor); + image_dialog(editor, selected); }); var position = $el.position(); @@ -99,6 +95,14 @@ top: $el.height() / 2 + position.top - $btn.outerHeight() / 2, left: $el.width() / 2 + position.left - $btn.outerWidth() / 2, }); + }).on('mouseleave', 'img', function (e) { + var $button = $(previousSelection.$).next('button'); + $button.css('visibility', 'hidden'); + var el = document.elementFromPoint(e.clientX, e.clientY); + $button.css('visibility', ''); + if (el === this) { return; } + $button.remove(); + previousSelection = null; }); editor.on('destroy', function (evt) { if (previousSelection) { @@ -1182,26 +1186,30 @@ }, }); website.editor.RTEImageDialog = website.editor.ImageDialog.extend({ - init: function () { - this._super.apply(this, arguments); + init: function (editor, image) { + this._super(editor); + + this.element = image; this.on('start', this, this.proxy('started')); this.on('save', this, this.proxy('saved')); }, started: function (holder) { - var selection = this.editor.getSelection(); - var el = selection && selection.getSelectedElement(); - this.element = null; - - if (el && el.is('img')) { - this.element = el; - _(this.image_styles).each(function (style) { - if (el.hasClass(style)) { - holder.style = style; - } - }); - holder.url = el.getAttribute('src'); + if (!this.element) { + var selection = this.editor.getSelection(); + this.element = selection && selection.getSelectedElement(); } + + var el = this.element; + if (!el || !el.is('img')) { + return; + } + _(this.image_styles).each(function (style) { + if (el.hasClass(style)) { + holder.style = style; + } + }); + holder.url = el.getAttribute('src'); }, saved: function (data) { var element, editor = this.editor; From 82b40bf84ca89a5da60b96fe8c688fa74b0c70fd Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 21 Nov 2013 13:12:35 +0100 Subject: [PATCH 2/5] [FIX] urlparse.parse_qs returns a dict *of lists*, eq test on values with a string is always false bzr revid: xmo@openerp.com-20131121121235-hafy7n61h3v6kb0k --- addons/website/models/ir_qweb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website/models/ir_qweb.py b/addons/website/models/ir_qweb.py index 6e33742803d..00b63e88f63 100644 --- a/addons/website/models/ir_qweb.py +++ b/addons/website/models/ir_qweb.py @@ -227,7 +227,7 @@ class Image(orm.AbstractModel): url = element.find('img').get('src') url_object = urlparse.urlsplit(url) - query = urlparse.parse_qs(url_object.query) + query = dict(urlparse.parse_qsl(url_object.query)) if url_object.path == '/website/image' and query['model'] == 'ir.attachment': attachment = self.pool['ir.attachment'].browse( cr, uid, int(query['id']), context=context) From 31f64313c033b346530980f3d84f5a2cc5fe71c4 Mon Sep 17 00:00:00 2001 From: Christophe Matthieu Date: Thu, 21 Nov 2013 13:34:39 +0100 Subject: [PATCH 3/5] [IMP] website snippet: create website.snippet.StyleEditor to add styles with html class and other logical process into snippet options bzr revid: chm@openerp.com-20131121123439-wvvcdegvc1bornux --- addons/website/static/src/css/website.css | 7 +- addons/website/static/src/css/website.sass | 8 +- .../src/js/website.snippets.animation.js | 4 +- .../static/src/js/website.snippets.editor.js | 525 +++++++++--------- addons/website/views/snippets.xml | 143 +++-- 5 files changed, 332 insertions(+), 355 deletions(-) diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css index 68e40e9118b..f10cb6f52e0 100644 --- a/addons/website/static/src/css/website.css +++ b/addons/website/static/src/css/website.css @@ -1,3 +1,4 @@ +@charset "utf-8"; /* THIS CSS FILE IS FOR WEBSITE THEMING CUSTOMIZATION ONLY * * css for editor buttons, openerp widget included in the website and other @@ -434,7 +435,6 @@ ul.nav-stacked > li > a { .oe_dark { background: #eff8f8; background: rgba(100, 200, 200, 0.14); - background-image: none !important; -webkit-box-shadow: 0px 5px 9px -7px rgba(0, 0, 255, 0.5) inset, 0px -3px 9px -7px rgba(0, 0, 255, 0.5) inset; -moz-box-shadow: 0px 5px 9px -7px rgba(0, 0, 255, 0.5) inset, 0px -3px 9px -7px rgba(0, 0, 255, 0.5) inset; box-shadow: 0px 5px 9px -7px rgba(0, 0, 255, 0.5) inset, 0px -3px 9px -7px rgba(0, 0, 255, 0.5) inset; @@ -442,20 +442,17 @@ ul.nav-stacked > li > a { .oe_black { background-color: rgba(0, 0, 0, 0.9); - background-image: none !important; } .oe_green { background-color: rgba(0, 128, 0, 0.8); - background-image: none !important; } .oe_red { background-color: rgba(255, 0, 0, 0.8); - background-image: none !important; } -.oe_custom_bg { +.oe_img_bg { height: 100%; background-size: 100%; } diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass index ec992256d6e..932f46a1912 100644 --- a/addons/website/static/src/css/website.sass +++ b/addons/website/static/src/css/website.sass @@ -328,17 +328,13 @@ ul.nav-stacked > li > a .oe_dark background: #eff8f8 background: rgba(100, 200, 200, 0.14) - background-image: none !important +box-shadow(0px 5px 9px -7px rgba(0, 0, 255, 0.5) inset, 0px -3px 9px -7px rgba(0, 0, 255, 0.5) inset) .oe_black background-color: rgba(0, 0, 0, 0.9) - background-image: none !important .oe_green background-color: rgba(0, 128, 0, 0.8) - background-image: none !important .oe_red background-color: rgba(255, 0, 0, 0.8) - background-image: none !important -.oe_custom_bg +.oe_img_bg height: 100% - background-size: 100% + background-size: 100% \ No newline at end of file diff --git a/addons/website/static/src/js/website.snippets.animation.js b/addons/website/static/src/js/website.snippets.animation.js index 7277f80ba95..34edfe9d259 100644 --- a/addons/website/static/src/js/website.snippets.animation.js +++ b/addons/website/static/src/js/website.snippets.animation.js @@ -82,7 +82,7 @@ var self = this; var speed = parseFloat(self.$target.attr("data-scroll-background-ratio") || 0); - if (speed == 1) { + if (speed === 1 || this.$target.css("background-image") === "none") { this.$target.css("background-attachment", "fixed").css("background-position", "0px 0px"); return; } else { @@ -101,7 +101,7 @@ } else { offset = - self.$target.offset().top * speed; } - self.$target.attr("data-scroll-background-offset", offset); + self.$target.attr("data-scroll-background-offset", offset > 0 ? 0 : offset); $(window).scroll(); }; img.src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)/g, ""); diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index 7d7aade081f..a4f51c6ab39 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -77,6 +77,7 @@ hack_to_add_snippet_id(); }); + website.snippet.styles = {}; website.snippet.selector = []; website.snippet.BuildingBlock = openerp.Widget.extend({ template: 'website.snippets', @@ -138,22 +139,19 @@ }, fetch_snippet_templates: function () { var self = this; - this.style_templates = {}; openerp.jsonRpc("/website/snippets", 'call', {}) .then(function (html) { var $html = $(html); - var $styles = $html.find("#snippet_styles"); - $styles.find("> [data-snippet-id]").each(function () { + var $styles = $html.find("[data-snippet-style-id]"); + $styles.each(function () { var $style = $(this); - var snipped_id = $style.data('snippet-id'); - self.style_templates[snipped_id] = { - 'snipped-id' : snipped_id, + var style_id = $style.data('snippet-style-id'); + website.snippet.styles[style_id] = { + 'snippet-style-id' : style_id, 'selector': $style.data('selector'), - 'class': $style.data("class"), - 'label': $style.find(".oe_snippet_label").text(), - 'group': $style.find(".oe_snippet_group").text() + '$el': $style, }; }); $styles.addClass("hidden"); @@ -529,13 +527,212 @@ }); + website.snippet.styleRegistry = {}; + website.snippet.StyleEditor = openerp.Class.extend({ + // initialisation (don't overwrite) + init: function (parent, $target, snippet_id) { + this.parent = parent; + this.$target = $target; + var styles = this.$target.data("snippet-style-ids") || {}; + styles[snippet_id] = this; + this.$target.data("snippet-style-ids", styles); + this.$overlay = this.$target.data('overlay'); + this['snippet-style-id'] = snippet_id; + this.$el = website.snippet.styles[snippet_id].$el.find(">li").clone(); + + this.required = this.$el.data("required"); + + this.set_active(); + this.$el.find('li[data-class] a').on('mouseover mouseout click', _.bind(this._mouse, this)); + this.$target.on('snippet-style-reset', _.bind(this.set_active, this)); + + this.start(); + }, + _mouse: function (event) { + var self = this; + + if (event.type === 'mouseout') { + if (!this.over) return; + this.over = false; + } else if (event.type === 'click') { + this.over = false; + }else { + this.over = true; + } + + var $prev, $next; + if (event.type === 'mouseout') { + $prev = $(event.currentTarget).parent(); + $next = this.$el.find("li[data-class].active"); + } else { + $prev = this.$el.find("li[data-class].active"); + $next = $(event.currentTarget).parent(); + } + if (!$prev.length) { + $prev = false; + } + if ($prev && $prev[0] === $next[0]) { + $next = false; + if (this.required) { + return; + } + } + + var np = {'$next': $next, '$prev': $prev}; + + if (event.type === 'click') { + setTimeout(function () { + self.set_active(); + self.$target.trigger("snippet-style-change", [self, np]); + },0); + this.select(event, {'$next': $next, '$prev': $prev}); + } else { + setTimeout(function () { + self.$target.trigger("snippet-style-preview", [self, np]); + },0); + this.preview(event, np); + } + }, + // start is call just after the init + start: function () { + }, + /* select + * called when a user select an item + * variables: np = {$next, $prev} + * $next is false if they are no next item selected + * $prev is false if they are no previous item selected + */ + select: function (event, np) { + var self = this; + // add or remove html class + if (np.$prev) { + this.$target.removeClass(np.$prev.data('class' || "")); + } + if (np.$next) { + this.$target.addClass(np.$next.data('class') || ""); + } + }, + /* preview + * called when a user is on mouse over or mouse out of an item + * variables: np = {$next, $prev} + * $next is false if they are no next item selected + * $prev is false if they are no previous item selected + */ + preview: function (event, np) { + var self = this; + + // add or remove html class + if (np.$prev) { + this.$target.removeClass(np.$prev.data('class') || ""); + } + if (np.$next) { + this.$target.addClass(np.$next.data('class') || ""); + } + }, + /* set_active + * select and set item active or not (add highlight item and his parents) + * called before start + */ + set_active: function () { + var self = this; + this.$el.find('li').removeClass("active"); + var $active = this.$el.find('li[data-class]') + .filter(function () { + var $li = $(this); + return ($li.data('class') && self.$target.hasClass($li.data('class'))); + }) + .first() + .addClass("active"); + this.$el.find('li:has(li[data-class].active)').addClass("active"); + } + }); + + + website.snippet.styleRegistry['size'] = website.snippet.StyleEditor.extend({ + select: function(event, np) { + this._super(event, np); + this.parent.parent.cover_target(this.$overlay, this.$target); + }, + preview: function (event, np) { + this._super(event, np); + this.parent.parent.cover_target(this.$overlay, this.$target); + } + }); + + website.snippet.styleRegistry.background = website.snippet.StyleEditor.extend({ + _get_bg: function () { + return this.$target.css("background-image").replace(/url\(['"]*|['"]*\)|^none$/g, ""); + }, + _set_bg: function (src) { + this.$target.css("background-image", src && src !== "" ? 'url(' + src + ')' : ""); + }, + start: function () { + this._super(); + var src = this._get_bg(); + this.$el.find("li[data-class].active.oe_custom_bg").data("src", src); + }, + select: function(event, np) { + var self = this; + this._super(event, np); + if (np.$next) { + if (np.$next.hasClass("oe_custom_bg")) { + var editor = new website.editor.ImageDialog(); + editor.on('start', self, function (o) {o.url = np.$prev && np.$prev.data("src") || "";}); + editor.on('save', self, function (o) { + self._set_bg(o.url); + np.$next.data("src", o.url); + self.$target.trigger("snippet-style-change", [self, np]); + }); + editor.on('cancel', self, function () { + if (!np.$prev || np.$prev.data("src") === "") { + np.$next.removeClass('active'); + self.$target.removeClass(np.$next.data("class")); + self.$target.trigger("snippet-style-change", [self, np]); + } + }); + editor.appendTo($('body')); + } else { + this._set_bg(np.$next.data("src")); + } + } else { + this._set_bg(false); + } + }, + preview: function (event, np) { + this._super(event, np); + if (np.$next) { + this._set_bg(np.$next.data("src")); + } + }, + set_active: function () { + var self = this; + var bg = self.$target.css("background-image"); + this.$el.find('li').removeClass("active"); + var $active = this.$el.find('li[data-class]') + .filter(function () { + var $li = $(this); + return ($li.data('src') && bg.indexOf($li.data('src')) >= 0) || + (!$li.data('src') && self.$target.hasClass($li.data('class'))); + }) + .first(); + if (!$active.length) { + $active = this.$target.css("background-image") !== 'none' ? + this.$el.find('li[data-class].oe_custom_bg') : + this.$el.find('li[data-class=""]'); + } + $active.addClass("active"); + this.$el.find('li:has(li[data-class].active)').addClass("active"); + } + }); + + website.snippet.editorRegistry = {}; website.snippet.Editor = openerp.Class.extend({ - init: function (parent, dom, force_snippet_id) { + init: function (parent, dom) { this.parent = parent; this.$target = $(dom); this.$overlay = this.$target.data('overlay'); - this.snippet_id = force_snippet_id || this.$target.data("snippet-id"); + this.snippet_id = this.$target.data("snippet-id"); this._readXMLData(); this.load_style_options(); this.get_parent_block(); @@ -652,90 +849,21 @@ }, load_style_options: function () { - if (this.$target.data('snippetStyles')) { - return; - } - var self = this; var $styles = this.$overlay.find('.oe_options'); var $ul = $styles.find('ul:first'); - _.each(this.parent.style_templates, function (val) { + _.each(website.snippet.styles, function (val) { if (!self.parent.dom_filter(val.selector).is(self.$target)) { return; } - var $li = $("
  • ").data(val); - $li.append($('').text(val.label)); - if (self.$target.hasClass( val.class )) { - $li.addClass("active"); - } - - if (val.group.length) { - var $group = $ul.find("li.dropdown-submenu").filter(function () { - return $("a:first", this).text() === val.group; - }); - if (!$group.length) { - $group = $('