From 796dcee2c3b082eba4819bf95e501c3604431da8 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 29 Aug 2013 12:29:56 +0200 Subject: [PATCH] [IMP] split out website.js bzr revid: xmo@openerp.com-20130829102956-prfe9t5308m6y0vn --- .../website/static/src/js/website.editor.js | 292 ++++++ addons/website/static/src/js/website.js | 898 +----------------- .../website/static/src/js/website.mobile.js | 29 + addons/website/static/src/js/website.seo.js | 163 ++++ .../website/static/src/js/website.snippets.js | 315 ++++++ addons/website/views/views.xml | 4 + 6 files changed, 804 insertions(+), 897 deletions(-) create mode 100644 addons/website/static/src/js/website.editor.js create mode 100644 addons/website/static/src/js/website.mobile.js create mode 100644 addons/website/static/src/js/website.seo.js create mode 100644 addons/website/static/src/js/website.snippets.js diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js new file mode 100644 index 00000000000..047a5b17557 --- /dev/null +++ b/addons/website/static/src/js/website.editor.js @@ -0,0 +1,292 @@ +(function () { + 'use strict'; + + var website = openerp.website; + + website.init_editor = function () { + var editor = new website.EditorBar(); + var $body = $(document.body); + editor.prependTo($body); + $body.css('padding-top', '50px'); // Not working properly: editor.$el.outerHeight()); + }; + /* ----- TOP EDITOR BAR FOR ADMIN ---- */ + website.EditorBar = openerp.Widget.extend({ + template: 'website.editorbar', + events: { + 'click button[data-action=edit]': 'edit', + 'click button[data-action=save]': 'save', + 'click button[data-action=cancel]': 'cancel', + }, + container: 'body', + customize_setup: function() { + var self = this; + var view_name = $(document.documentElement).data('view-xmlid'); + var menu = $('#customize-menu'); + this.$('#customize-menu-button').click(function(event) { + menu.empty(); + openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': view_name }).then( + function(result) { + _.each(result, function (item) { + if (item.header) { + menu.append(''); + } else { + menu.append(_.str.sprintf('
  • %s
  • ', + item.id, item.active ? '' : '-empty', item.name)); + } + }); + // Adding Static Menus + menu.append('
  • Change Theme
  • '); + } + ); + }); + menu.on('click', 'a', function (event) { + var view_id = $(event.target).data('view-id'); + openerp.jsonRpc('/website/customize_template_toggle', 'call', { + 'view_id': view_id + }).then( function(result) { + window.location.reload(); + }); + }); + }, + start: function() { + var self = this; + + this.saving_mutex = new openerp.Mutex(); + + this.$('#website-top-edit').hide(); + this.$('#website-top-view').show(); + + $('.dropdown-toggle').dropdown(); + this.customize_setup(); + + this.$buttons = { + edit: this.$('button[data-action=edit]'), + save: this.$('button[data-action=save]'), + cancel: this.$('button[data-action=cancel]'), + }; + + this.rte = new website.RTE(this); + this.rte.on('change', this, this.proxy('rte_changed')); + + return $.when( + this._super.apply(this, arguments), + this.rte.prependTo(this.$('#website-top-edit .nav.pull-right')) + ); + }, + edit: function () { + this.$buttons.edit.prop('disabled', true); + this.$('#website-top-view').hide(); + this.$('#website-top-edit').show(); + + // this.$buttons.cancel.add(this.$buttons.snippet).prop('disabled', false) + // .add(this.$buttons.save) + // .parent().show(); + // + // TODO: span edition changing edition state (save button) + var $editables = $('[data-oe-model]') + .not('link, script') + // FIXME: propagation should make "meta" blocks non-editable in the first place... + .not('.oe_snippets,.oe_snippet, .oe_snippet *') + .prop('contentEditable', true) + .addClass('oe_editable'); + var $rte_ables = $editables.not('[data-oe-type]'); + var $raw_editables = $editables.not($rte_ables); + + // temporary fix until we fix ckeditor + $raw_editables.each(function () { + $(this).parents().add($(this).find('*')).on('click', function(ev) { + ev.preventDefault(); + ev.stopPropagation(); + }); + }); + + this.rte.start_edition($rte_ables); + $raw_editables.on('keydown keypress cut paste', function (e) { + var $target = $(e.target); + if ($target.hasClass('oe_dirty')) { + return; + } + + $target.addClass('oe_dirty'); + this.$buttons.save.prop('disabled', false); + }.bind(this)); + }, + rte_changed: function () { + this.$buttons.save.prop('disabled', false); + }, + save: function () { + var self = this; + var defs = []; + observer.disconnect(); + $('.oe_dirty').each(function (i, v) { + var $el = $(this); + // TODO: Add a queue with concurrency limit in webclient + // https://github.com/medikoo/deferred/blob/master/lib/ext/function/gate.js + var def = self.saving_mutex.exec(function () { + return self.saveElement($el).then(function () { + $el.removeClass('oe_dirty'); + }).fail(function () { + var data = $el.data(); + console.error(_.str.sprintf('Could not save %s#%d#%s', data.oeModel, data.oeId, data.oeField)); + }); + }); + defs.push(def); + }); + return $.when.apply(null, defs).then(function () { + window.location.reload(); + }); + }, + saveElement: function ($el) { + var data = $el.data(); + var html = $el.html(); + var xpath = data.oeXpath; + if (xpath) { + var $w = $el.clone(); + $w.removeClass('oe_dirty'); + _.each(['model', 'id', 'field', 'xpath'], function(d) {$w.removeAttr('data-oe-' + d);}); + $w + .removeClass('oe_editable') + .prop('contentEditable', false); + html = $w.wrap('
    ').parent().html(); + } + return openerp.jsonRpc('/web/dataset/call', 'call', { + model: 'ir.ui.view', + method: 'save', + args: [data.oeModel, data.oeId, data.oeField, html, xpath] + }); + }, + cancel: function () { + window.location.reload(); + }, + }); + + /* ----- RICH TEXT EDITOR ---- */ + website.RTE = openerp.Widget.extend({ + tagName: 'li', + id: 'oe_rte_toolbar', + className: 'oe_right oe_rte_toolbar', + // editor.ui.items -> possible commands &al + // editor.applyStyle(new CKEDITOR.style({element: "span",styles: {color: "#(color)"},overrides: [{element: "font",attributes: {color: null}}]}, {color: '#ff0000'})); + + start_edition: function ($elements) { + var self = this; + $elements + .not('span, [data-oe-type]') + .each(function () { + var node = this; + var $node = $(node); + var editor = CKEDITOR.inline(this, self._config()); + editor.on('instanceReady', function () { + observer.observe(node, { + childList: true, + attributes: true, + characterData: true, + subtree: true + }); + }); + $node.one('content_changed', function () { + $node.addClass('oe_dirty'); + self.trigger('change'); + }); + }); + }, + + _current_editor: function () { + return CKEDITOR.currentInstance; + }, + _config: function () { + var removed_plugins = [ + // remove custom context menu + 'contextmenu,tabletools,liststyle', + // magicline captures mousein/mouseout => draggable does not work + 'magicline' + ]; + return { + // Disable auto-generated titles + // FIXME: accessibility, need to generate user-sensible title, used for @title and @aria-label + title: false, + removePlugins: removed_plugins.join(','), + uiColor: '', + // Ensure no config file is loaded + customConfig: '', + // Disable ACF + allowedContent: true, + // Don't insert paragraphs around content in e.g.
  • + autoParagraph: false, + filebrowserImageUploadUrl: "/website/attach", + // Support for sharedSpaces in 4.x + extraPlugins: 'sharedspace', + // Place toolbar in controlled location + sharedSpaces: { top: 'oe_rte_toolbar' }, + toolbar: [ + {name: 'basicstyles', items: [ + "Bold", "Italic", "Underline", "Strike", "Subscript", + "Superscript", "TextColor", "BGColor", "RemoveFormat" + ]},{ + name: 'span', items: [ + "Link", "Unlink", "Blockquote", "BulletedList", + "NumberedList", "Indent", "Outdent" + ]},{ + name: 'justify', items: [ + "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock" + ]},{ + name: 'special', items: [ + "Image", "Table" + ]},{ + name: 'styles', items: [ + "Format", "Styles" + ]} + ], + // styles dropdown in toolbar + stylesSet: [ + // emphasis + {name: "Muted", element: 'span', attributes: {'class': 'text-muted'}}, + {name: "Primary", element: 'span', attributes: {'class': 'text-primary'}}, + {name: "Warning", element: 'span', attributes: {'class': 'text-warning'}}, + {name: "Danger", element: 'span', attributes: {'class': 'text-danger'}}, + {name: "Success", element: 'span', attributes: {'class': 'text-success'}}, + {name: "Info", element: 'span', attributes: {'class': 'text-info'}} + ], + }; + }, + }); + + + var Observer = window.MutationObserver || window.WebkitMutationObserver || window.JsMutationObserver; + var observer = new Observer(function (mutations) { + _(mutations).chain() + .filter(function (m) { + switch(m.type) { + case 'attributes': + // ignore cke_focus being added & removed from RTE root + // FIXME: what if snippets are configured by adding/removing classes on their root element? + return !$(m.target).hasClass('oe_editable'); + case 'childList': + //
    appears when focusing RTE in FF, ignore + return m.addedNodes.length !== 1 || m.addedNodes[0].nodeName !== 'BR'; + default: + return true; + } + }) + .map(function (m) { + var node = m.target; + while (node && !$(node).hasClass('oe_editable')) { + node = node.parentNode; + } + return node; + }) + .compact() + .uniq() + .each(function (node) { $(node).trigger('content_changed'); }) + }); + + website.dom_ready.done(function () { + // $.fn.data automatically parses value, '0'|'1' -> 0|1 + website.is_editable = $(document.documentElement).data('editable'); + var is_smartphone = $(document.body)[0].clientWidth < 767; + + if (website.is_editable && !is_smartphone) { + website.ready().then(website.init_editor); + } + }); +})(); diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index 3a49b1571ba..f55ebc34bf2 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -31,468 +31,6 @@ return def; }; - website.init_editor = function () { - CKEDITOR.on('dialogDefinition', function (ev) { - (alter_dialog[ev.data.name] || noop)(ev.data.definition); - }); - var editor = new website.EditorBar(); - var $body = $('body'); - editor.prependTo($body); - $body.css('padding-top', '50px'); // Not working properly: editor.$el.outerHeight()); - }; - - /* ----- TOP EDITOR BAR FOR ADMIN ---- */ - website.EditorBar = openerp.Widget.extend({ - template: 'website.editorbar', - events: { - 'click button[data-action=edit]': 'edit', - 'click button[data-action=save]': 'save', - 'click button[data-action=cancel]': 'cancel', - 'click button[data-action=snippet]': 'snippet', - 'click a[data-action=show-mobile-preview]': 'mobilePreview', - 'click a[data-action=promote-current-page]': 'promotePage', - }, - container: 'body', - customize_setup: function() { - var self = this; - var view_name = $(document.documentElement).data('view-xmlid'); - var menu = $('#customize-menu'); - this.$('#customize-menu-button').click(function(event) { - menu.empty(); - openerp.jsonRpc('/website/customize_template_get', 'call', { 'xml_id': view_name }).then( - function(result) { - _.each(result, function (item) { - if (item.header) { - menu.append('
  • '); - } else { - menu.append(_.str.sprintf('
  • %s
  • ', - item.id, item.active ? '' : '-empty', item.name)); - } - }); - // Adding Static Menus - menu.append('
  • Change Theme
  • '); - } - ); - }); - menu.on('click', 'a', function (event) { - var view_id = $(event.target).data('view-id'); - openerp.jsonRpc('/website/customize_template_toggle', 'call', { - 'view_id': view_id - }).then( function(result) { - window.location.reload(); - }); - }); - }, - start: function() { - var self = this; - - this.saving_mutex = new openerp.Mutex(); - - this.$('#website-top-edit').hide(); - this.$('#website-top-view').show(); - - $('.dropdown-toggle').dropdown(); - this.customize_setup(); - - this.$buttons = { - edit: this.$('button[data-action=edit]'), - save: this.$('button[data-action=save]'), - cancel: this.$('button[data-action=cancel]'), - snippet: this.$('button[data-action=snippet]'), - }; - - this.rte = new website.RTE(this); - this.rte.on('change', this, this.proxy('rte_changed')); - - this.snippets = new website.Snippets(); - this.snippets.appendTo($(document.body)); - window.snippets = this.snippets; - - return $.when( - this._super.apply(this, arguments), - this.rte.insertBefore(this.$buttons.snippet.parent()) - ); - }, - edit: function () { - this.$buttons.edit.prop('disabled', true); - this.$('#website-top-view').hide(); - this.$('#website-top-edit').show(); - - // this.$buttons.cancel.add(this.$buttons.snippet).prop('disabled', false) - // .add(this.$buttons.save) - // .parent().show(); - // - // TODO: span edition changing edition state (save button) - var $editables = $('[data-oe-model]') - .not('link, script') - // FIXME: propagation should make "meta" blocks non-editable in the first place... - .not('.oe_snippets,.oe_snippet, .oe_snippet *') - .prop('contentEditable', true) - .addClass('oe_editable'); - var $rte_ables = $editables.not('[data-oe-type]'); - var $raw_editables = $editables.not($rte_ables); - - // temporary fix until we fix ckeditor - $raw_editables.each(function () { - $(this).parents().add($(this).find('*')).on('click', function(ev) { - ev.preventDefault(); - ev.stopPropagation(); - }); - }); - - this.rte.start_edition($rte_ables); - $raw_editables.on('keydown keypress cut paste', function (e) { - var $target = $(e.target); - if ($target.hasClass('oe_dirty')) { - return; - } - - $target.addClass('oe_dirty'); - this.$buttons.save.prop('disabled', false); - }.bind(this)); - }, - rte_changed: function () { - this.$buttons.save.prop('disabled', false); - }, - save: function () { - var self = this; - var defs = []; - observer.disconnect(); - $('.oe_dirty').each(function (i, v) { - var $el = $(this); - // TODO: Add a queue with concurrency limit in webclient - // https://github.com/medikoo/deferred/blob/master/lib/ext/function/gate.js - var def = self.saving_mutex.exec(function () { - return self.saveElement($el).then(function () { - $el.removeClass('oe_dirty'); - }).fail(function () { - var data = $el.data(); - console.error(_.str.sprintf('Could not save %s#%d#%s', data.oeModel, data.oeId, data.oeField)); - }); - }); - defs.push(def); - }); - return $.when.apply(null, defs).then(function () { - window.location.reload(); - }); - }, - saveElement: function ($el) { - var data = $el.data(); - var html = $el.html(); - var xpath = data.oeXpath; - if (xpath) { - var $w = $el.clone(); - $w.removeClass('oe_dirty'); - _.each(['model', 'id', 'field', 'xpath'], function(d) {$w.removeAttr('data-oe-' + d);}); - $w - .removeClass('oe_editable') - .prop('contentEditable', false); - html = $w.wrap('
    ').parent().html(); - } - return openerp.jsonRpc('/web/dataset/call', 'call', { - model: 'ir.ui.view', - method: 'save', - args: [data.oeModel, data.oeId, data.oeField, html, xpath] - }); - }, - cancel: function () { - window.location.reload(); - }, - snippet: function (ev) { - this.snippets.toggle(); - }, - mobilePreview: function () { - (new website.MobilePreview()).appendTo($(document.body)); - }, - promotePage: function () { - (new website.seo.Configurator()).appendTo($(document.body)); - }, - }); - - /* ----- MOBILE PREVIEW ---- */ - website.MobilePreview = openerp.Widget.extend({ - template: 'website.mobile_preview', - events: { - 'hidden': 'close' - }, - start: function () { - $(document.body).addClass('oe_stop_scrolling'); - document.getElementById("mobile-viewport").src = window.location.href + "?mobile-preview=true"; - this.$el.modal(); - }, - close: function () { - $(document.body).removeClass('oe_stop_scrolling'); - this.destroy(); - }, - }); - - /* ----- SEO TOOLS ---- */ - website.seo = {}; - website.seo.Keyword = openerp.Widget.extend({ - template: 'website.seo_keyword', - events: { - 'click a[data-action=remove-keyword]': 'destroy', - }, - init: function (parent, options) { - this._super(parent); - this.keyword = options.keyword; - this.onDelete = options.onDelete; - }, - destroy: function () { - if (_.isFunction(this.onDelete)) { - this.onDelete(this.keyword); - } - this._super(); - }, - }); - website.seo.cleanupKeyword = function (word) { - return word ? word.replace(/[,;.:]+/g, " ").replace(/ +/g, " ").trim() : ""; - }; - website.seo.PageParser = function () { - function currentURL () { - var url = window.location.href; - var hashIndex = url.indexOf('#'); - return hashIndex >= 0 ? url.substring(0, hashIndex) : url; - } - var parsedPage = { - url: currentURL(), - title: $('title').text(), - headers: {}, - content: {} - }; - _.each([ 'h1', 'h2', 'h3'], function (header) { - parsedPage.headers[header] = []; - $(header).each(function () { - var text = $(this).text(); - parsedPage.headers[header].push(text); - }); - }); - this.url = function () { - return parsedPage.url; - }; - this.title = function () { - return parsedPage.title; - }; - this.headers = function () { - return parsedPage.headers; - }; - this.keywordSuggestions = function () { - return _.map(_.uniq(parsedPage.headers.h1.concat(parsedPage.headers.h2)), website.seo.cleanupKeyword); - }; - }; - website.seo.Configurator = openerp.Widget.extend({ - template: 'website.seo_configuration', - events: { - 'keypress input[name=seo_page_keywords]': 'confirmKeyword', - 'click button[data-action=add]': 'addKeyword', - 'click a[data-action=update]': 'update', - 'hidden': 'destroy' - }, - - maxTitleSize: 65, - maxDescriptionSize: 155, - maxNumberOfKeywords: 10, - maxWordsPerKeyword: 4, - - start: function () { - var pageParser = new website.seo.PageParser(); - var currentKeywords = this.keywords; - this.$el.find('.js_seo_page_url').text(pageParser.url()); - this.$el.find('input[name=seo_page_title]').val(pageParser.title()); - this.$el.find('input[name=seo_page_keywords]').typeahead({ - source: function () { - var suggestions = pageParser.keywordSuggestions(); - var alreadyChosen = currentKeywords(); - return _.difference(suggestions, alreadyChosen); - }, - items: 4 - }); - - $(document.body).addClass('oe_stop_scrolling'); - this.$el.modal(); - }, - currentPage: function () { - var url = window.location.href; - var hashIndex = url.indexOf('#'); - return hashIndex > 0 ? url.substring(0, hashIndex): url; - }, - keywords: function () { - return _.uniq($('.js_seo_keyword').map(function () { - return $(this).text(); - })); - }, - isExistingKeyword: function (word) { - return _.contains(this.keywords(), word); - }, - isKeywordListFull: function () { - return this.keywords().length >= this.maxNumberOfKeywords; - }, - confirmKeyword: function (e) { - if (e.keyCode == 13) { - this.addKeyword(); - this.$el.find('input[name=seo_page_keywords]').val(""); - } - }, - addKeyword: function () { - var $modal = this.$el; - function enableNewKeywords () { - $modal.find('input[name=seo_page_keywords]') - .removeAttr('readonly').attr('placeholder', "New keyword"); - $modal.find('button[data-action=add]') - .prop('disabled', false).removeClass('disabled'); - } - function disableNewKeywords () { - $modal.find('input[name=seo_page_keywords]') - .attr('readonly', "readonly") - .attr('placeholder', "Remove a keyword first"); - $modal.find('button[data-action=add]') - .prop('disabled', true).addClass('disabled'); - } - var candidate = this.$el.find('input[name=seo_page_keywords]').val(); - var word = website.seo.cleanupKeyword(candidate); - if (word && !this.isKeywordListFull() && !this.isExistingKeyword(word)) { - new website.seo.Keyword(this, { - keyword: word, - onDelete: enableNewKeywords - }).appendTo(this.$el.find('.js_seo_keywords_list')); - this.scrollDown(); - } - if (this.isKeywordListFull()) { - disableNewKeywords(); - } - }, - scrollDown: function () { - var $body = this.$el.find('.modal-body'); - $body.animate({ - scrollTop: $body[0].scrollHeight - }, 500); - }, - update: function () { - // TODO: Persist changes - }, - destroy: function () { - $(document.body).removeClass('oe_stop_scrolling'); - this._super(); - }, - }); - - /* ----- RICH TEXT EDITOR ---- */ - website.RTE = openerp.Widget.extend({ - tagName: 'li', - id: 'oe_rte_toolbar', - className: 'oe_right oe_rte_toolbar', - // editor.ui.items -> possible commands &al - // editor.applyStyle(new CKEDITOR.style({element: "span",styles: {color: "#(color)"},overrides: [{element: "font",attributes: {color: null}}]}, {color: '#ff0000'})); - - start_edition: function ($elements) { - var self = this; - this.snippet_carousel(); - $elements - .not('span, [data-oe-type]') - .each(function () { - var node = this; - var $node = $(node); - var editor = CKEDITOR.inline(this, self._config()); - editor.on('instanceReady', function () { - observer.observe(node, { - childList: true, - attributes: true, - characterData: true, - subtree: true - }); - }); - $node.one('content_changed', function () { - $node.addClass('oe_dirty'); - self.trigger('change'); - }); - }); - }, - - _current_editor: function () { - return CKEDITOR.currentInstance; - }, - _config: function () { - var removed_plugins = [ - // remove custom context menu - 'contextmenu,tabletools,liststyle', - // magicline captures mousein/mouseout => draggable does not work - 'magicline' - ]; - return { - // Disable auto-generated titles - // FIXME: accessibility, need to generate user-sensible title, used for @title and @aria-label - title: false, - removePlugins: removed_plugins.join(','), - uiColor: '', - // Ensure no config file is loaded - customConfig: '', - // Disable ACF - allowedContent: true, - // Don't insert paragraphs around content in e.g.
  • - autoParagraph: false, - filebrowserImageUploadUrl: "/website/attach", - // Support for sharedSpaces in 4.x - extraPlugins: 'sharedspace', - // Place toolbar in controlled location - sharedSpaces: { top: 'oe_rte_toolbar' }, - toolbar: [ - {name: 'basicstyles', items: [ - "Bold", "Italic", "Underline", "Strike", "Subscript", - "Superscript", "TextColor", "BGColor", "RemoveFormat" - ]},{ - name: 'span', items: [ - "Link", "Unlink", "Blockquote", "BulletedList", - "NumberedList", "Indent", "Outdent" - ]},{ - name: 'justify', items: [ - "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock" - ]},{ - name: 'special', items: [ - "Image", "Table" - ]},{ - name: 'styles', items: [ - "Format", "Styles" - ]} - ], - // styles dropdown in toolbar - stylesSet: [ - // emphasis - {name: "Muted", element: 'span', attributes: {'class': 'text-muted'}}, - {name: "Primary", element: 'span', attributes: {'class': 'text-primary'}}, - {name: "Warning", element: 'span', attributes: {'class': 'text-warning'}}, - {name: "Danger", element: 'span', attributes: {'class': 'text-danger'}}, - {name: "Success", element: 'span', attributes: {'class': 'text-success'}}, - {name: "Info", element: 'span', attributes: {'class': 'text-info'}} - ], - }; - }, - // TODO clean - snippet_carousel: function () { - var self = this; - var $carousel_options = $('.carousel .js_carousel_options'); - $carousel_options.on('click', '.label', function (e) { - e.preventDefault(); - var $button = $(e.currentTarget); - var $c = $button.parents(".carousel:first"); - var $carousel_inner = $c.find('.carousel-inner'); - - if($button.hasClass("js_add")) { - var cycle = $carousel_inner.find('.item').size(); - $carousel_inner.append(openerp.qweb.render('website.carousel')); - $c.carousel(cycle); - } - else { - $carousel_inner - .find('.item.active').remove().end() - .find('.item:first').addClass('active'); - $c.carousel(0); - self.trigger('change', self, null); - } - }); - $carousel_options.show(); - } - }); - website.mutations = { darken: function($el){ var $parent = $el.parent(); @@ -522,436 +60,9 @@ }, }; - /* ----- SNIPPET SELECTOR ---- */ - website.Snippets = openerp.Widget.extend({ - template: 'website.snippets', - init: function () { - this._super.apply(this, arguments); - }, - start: function() { - var self = this; - - $.ajax({ - type: "GET", - url: "/page/website.snippets", - dataType: "text", - success: function(snippets){ - self.$el.html(snippets); - self.start_snippets(); - }, - }); - - }, - path_eval: function(path){ - var obj = window; - path = path.split('.'); - do{ - obj = obj[path.shift()]; - }while(path.length && obj); - return obj; - }, - - // setup widget and drag and drop - start_snippets: function(){ - var self = this; - - this.$('.oe_snippet').draggable({ - helper: 'clone', - zIndex: '1000', - appendTo: 'body', - start: function(){ - var snippet = $(this); - var action = snippet.data('action'); - - self.deactivate_snippet_manipulators(); - if( action === 'insert'){ - self.activate_insertion_zones({ - siblings: snippet.data('selector-siblings'), - childs: snippet.data('selector-childs') - }); - }else if( action === 'mutate' ){ - self.activate_overlay_zones(snippet.data('selector')); - } - - $('.oe_drop_zone').droppable({ - over: function(){ - // FIXME: stupid hack to prevent multiple droppable to activate at once ... - // it's not even working properly but it's better than nothing. - $(".oe_drop_zone.oe_hover").removeClass("oe_hover"); - $(this).addClass("oe_hover"); - }, - out: function(){ - $(this).removeClass("oe_hover"); - }, - drop: function(){ - if( action === 'insert' ){ - $(".oe_drop_zone.oe_hover") - .replaceWith(snippet.find('.oe_snippet_body').clone()) - .removeClass('oe_snippet_body'); - }else if( action === 'mutate' ){ - self.path_eval(snippet.data('action-function'))( $(".oe_drop_zone.oe_hover").data('target') ); - } - }, - }); - }, - stop: function(){ - self.deactivate_zones(); - self.activate_snippet_manipulators(); - }, - }); - - }, - // Create element insertion drop zones. two css selectors can be provided - // selector.childs -> 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){ - var self = this; - var i, len, $zones; - var child_selector = selector.childs || ''; - var sibling_selector = selector.siblings || ''; - var zone_template = "
    "; - var $drop_zone = $('.oe_drop_zone'); - - $drop_zone.remove(); - - if(child_selector){ - $zones = $(child_selector); - for(i = 0, len = $zones.length; i < len; i++ ){ - $zones.eq(i).find('> *:not(.oe_drop_zone)').after(zone_template); - $zones.eq(i).prepend(zone_template); - } - } - - if(sibling_selector){ - $zones = $(sibling_selector); - for(i = 0, len = $zones.length; i < len; i++ ){ - if($zones.eq(i).prev('.oe_drop_zone').length === 0){ - $zones.eq(i).before(zone_template); - } - if($zones.eq(i).next('.oe_drop_zone').length === 0){ - $zones.eq(i).after(zone_template); - } - } - } - - // Cleaning up unnecessary zones - $('.oe_snippets .oe_drop_zone').remove(); // no zone in the snippet selector ... - $('#website-top-view').find('.oe_drop_zone').remove(); // no zone in the top bars ... - $('#website-top-edit').find('.oe_drop_zone').remove(); - var count; - do { - count = 0; - $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones - count += $zones.length; - $zones.remove(); - - $zones = $('.oe_drop_zone > .oe_drop_zone').remove(); // no recusrive zones - count += $zones.length; - $zones.remove(); - }while(count > 0); - - // Cleaning up zones placed between floating or inline elements. We do not like these kind of zones. - $zones = $drop_zone; - for(i = 0, len = $zones.length; i < len; i++ ){ - var zone = $zones.eq(i); - var prev = zone.prev(); - var next = zone.next(); - 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(); - } - } - }, - deactivate_zones: function(){ - $('.oe_drop_zone').remove(); - }, - - activate_overlay_zones: function(selector){ - var $targets = $(selector); - - 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; - }); - - var zone_template = "
    "; - $('.oe_drop_zone').remove(); - - for(var i = 0, len = $targets.length; i < len; i++){ - var $target = $targets.eq(i); - var $zone = $(zone_template); - this.cover_target($zone,$target); - $zone.appendTo('body'); - $zone.data('target',$target); - } - }, - cover_target: function($el, $target){ - $el.css({ - 'position': 'absolute', - 'width': $target.outerWidth(), - 'height': $target.outerHeight(), - }); - $el.css($target.offset()); - }, - activate_snippet_manipulators: function(){ - var self = this; - this.activate_overlay_zones('#wrap .container'); - var $snippets = $('.oe_drop_zone'); - - for(var i = 0, len = $snippets.length; i < len; i++){ - var $snippet = $snippets.eq(i); - var $manipulator = $(openerp.qweb.render('website.snippet_manipulator')); - $manipulator.css({ - 'top': $snippet.css('top'), - 'left': $snippet.css('left'), - 'width': $snippet.css('width'), - 'height': $snippet.css('height'), - }); - $manipulator.data('target',$snippet.data('target')); - $manipulator.appendTo('body'); - $snippet.remove(); - - $manipulator.find('.oe_handle').mousedown(function(event){ - var $handle = $(this); - var $manipulator = $handle.parent(); - var $snippet = $manipulator.data('target'); - var x = event.pageX; - var y = event.pageY; - - var pt = $snippet.css('padding-top'); - var pb = $snippet.css('padding-bottom'); - pt = Number(pt.slice(0, -2)) || 0; //FIXME something cleaner to remove 'px' - pb = Number(pb.slice(0, -2)) || 0; - - $manipulator.addClass('oe_hover'); - event.preventDefault(); - - $(document.body).on({ - mousemove: function(event){ - var dx = event.pageX - x; - var dy = event.pageY - y; - event.preventDefault(); - if($handle.hasClass('n') || $handle.hasClass('nw') || $handle.hasClass('ne')){ - $snippet.css('padding-top',pt-dy+'px'); - self.cover_target($manipulator,$snippet); - }else if($handle.hasClass('s') || $handle.hasClass('sw') || $handle.hasClass('se')){ - - $snippet.css('padding-bottom',pb+dy+'px'); - self.cover_target($manipulator,$snippet); - } - }, - mouseup: function(){ - $body.off('mouseup mousemove'); - self.deactivate_snippet_manipulators(); - self.activate_snippet_manipulators(); - } - }); - }); - - } - }, - deactivate_snippet_manipulators: function(){ - $('.oe_snippet_manipulator').remove(); - }, - toggle: function(){ - if(this.$el.hasClass('oe_hidden')){ - this.$el.removeClass('oe_hidden'); - this.activate_snippet_manipulators(); - }else{ - this.$el.addClass('oe_hidden'); - this.deactivate_snippet_manipulators(); - } - }, - }); - - function noop() {} - var alter_dialog = { - image: function (definition) { - definition.removeContents('Link'); - definition.removeContents('advanced'); - - var upload = definition.getContents('Upload'); - upload.add({ - type: 'select', - label: 'Existing attachments', - id: 'ir_attachment', - items: [['']], - /** - * On dialog load, fetch all attachments (on ir.ui.view => - * previously uploaded images) and add them to the select's - * options (items array & add method) - */ - onLoad: function () { - var field = this; - - // FIXME: fuck this garbage, also fuck openerp.Model - return openerp.jsonRpc('/web/dataset/call_kw', 'call', { - model: 'ir.attachment', - method: 'search_read', - args: [], - kwargs: { - fields: ['name'], - domain: [['res_model', '=', 'ir.ui.view']], - order: 'name', - } - }).then(function (results) { - _(results).each(function (result) { - field.add(result.name, result.id); - }); - }); - }, - /** - * The image widgets uses "txtUrl" to do most of its stuff. - * Synchronize select & txtUrl by generating the correct URL - * and setting it there - */ - onChange: function () { - var id = this.getValue(); - var url = this.getDialog().getContentElement('info', 'txtUrl'); - if (!id) { - url.setValue(''); - return; - } - - url.setValue('/website/attachment/' + id); - }, - }); - // Override uploadButton to send its information to the select - // created above instead of directly to txtUrl. The select will - // propagate to txtUrl - upload.get('uploadButton').filebrowser = { - onSelect: function (url) { - var id = url.split('/').pop(); - var attachments = this.getDialog().getContentElement('Upload', 'ir_attachment'); - // TODO: return supplementary info to get image/attachment name? - attachments.add(id, id); - attachments.setValue(id); - } - }; - - var old_show = definition.onShow; - definition.onShow = function () { - // CKEDITOR does not *override* onShow, is smashes the existing - // one instead, so override "by hand" - if (old_show) { - old_show.call(this); - } - // Assloads of code in the image plugin just go and tear into - // the info tab without a care, so can't just remove the tab or - // its content. Hide the tab instead, the effect is roughly the - // same. - this.hidePage('info'); - // Force the dialog to always and only display the Upload tab - this.selectPage('Upload'); - this.on('selectPage', function (e) { - setTimeout(function () { - if (e.data.page !== 'Upload') { - this.selectPage('Upload'); - } - }.bind(this), 0); - }); - } - }, - link: function (definition) { - definition.removeContents('target'); - definition.removeContents('advanced'); - - var info = definition.getContents('info'); - info.remove('linkType'); - info.remove('anchorOptions'); - info.remove('emailOptions'); - - info.get('urlOptions').children[0].widths = [ '0%', '100%' ]; - info.get('protocol').style = 'display: none'; - // TODO: sync edition of url to website_pages? - info.add({ - type: 'select', - label: "Existing page", - id: 'website_pages', - items: [['']], - /** - * onload fetch all the pages existing in the website, then - * display that. - */ - onLoad: function () { - var field = this; - - return openerp.jsonRpc('/web/dataset/call_kw', 'call', { - model: 'website', - method: 'list_pages', - args: [], - kwargs: {} - }).then(function (results) { - _(results).each(function (result) { - field.add(result.name, result.url); - }); - }); - }, - onChange: function () { - var url = this.getValue(); - var url_field = this.getDialog().getContentElement('info', 'url'); - if (!url) { - url_field.setValue(''); - return; - } - - url_field.setValue(url); - } - }) - } - }; - - var Observer = window.MutationObserver || window.WebkitMutationObserver || window.JsMutationObserver; - var observer = new Observer(function (mutations) { - _(mutations).chain() - .filter(function (m) { - switch(m.type) { - case 'attributes': - // ignore cke_focus being added & removed from RTE root - // FIXME: what if snippets are configured by adding/removing classes on their root element? - return !$(m.target).hasClass('oe_editable'); - case 'childList': - //
    appears when focusing RTE in FF, ignore - return m.addedNodes.length !== 1 || m.addedNodes[0].nodeName !== 'BR'; - default: - return true; - } - }) - .map(function (m) { - var node = m.target; - while (node && !$(node).hasClass('oe_editable')) { - node = node.parentNode; - } - return node; - }) - .compact() - .uniq() - .each(function (node) { $(node).trigger('content_changed'); }) - }); var all_ready = null; - var dom_ready = $.Deferred(); + var dom_ready = website.dom_ready = $.Deferred(); $(dom_ready.resolve); website.init_kanban = function ($kanban) { @@ -1026,13 +137,6 @@ }; dom_ready.then(function () { - website.is_editable = $(document.documentElement).attr('data-editable') === '1'; - var is_smartphone = $(document.body)[0].clientWidth < 767; - - if (website.is_editable && !is_smartphone) { - website.ready().then(website.init_editor); - } - /* ----- PUBLISHING STUFF ---- */ $(document).on('click', '.js_publish, .js_unpublish', function (e) { e.preventDefault(); diff --git a/addons/website/static/src/js/website.mobile.js b/addons/website/static/src/js/website.mobile.js new file mode 100644 index 00000000000..c48cfaed4cd --- /dev/null +++ b/addons/website/static/src/js/website.mobile.js @@ -0,0 +1,29 @@ +(function () { + 'use strict'; + + var website = openerp.website; + website.EditorBar.include({ + events: _.extend({}, website.EditorBar.prototype.events, { + 'click a[data-action=show-mobile-preview]': 'mobilePreview', + }), + mobilePreview: function () { + (new website.MobilePreview()).appendTo($(document.body)); + }, + }); + + website.MobilePreview = openerp.Widget.extend({ + template: 'website.mobile_preview', + events: { + 'hidden': 'close' + }, + start: function () { + $(document.body).addClass('oe_stop_scrolling'); + document.getElementById("mobile-viewport").src = window.location.href + "?mobile-preview=true"; + this.$el.modal(); + }, + close: function () { + $(document.body).removeClass('oe_stop_scrolling'); + this.destroy(); + }, + }); +})(); diff --git a/addons/website/static/src/js/website.seo.js b/addons/website/static/src/js/website.seo.js new file mode 100644 index 00000000000..af3e30d7fcf --- /dev/null +++ b/addons/website/static/src/js/website.seo.js @@ -0,0 +1,163 @@ +(function () { + 'use strict'; + + var website = openerp.website; + website.EditorBar.include({ + events: _.extend({}, website.EditorBar.prototype.events, { + 'click a[data-action=promote-current-page]': 'promotePage', + }), + promotePage: function () { + (new website.seo.Configurator()).appendTo($(document.body)); + }, + }); + + website.seo = {}; + website.seo.Keyword = openerp.Widget.extend({ + template: 'website.seo_keyword', + events: { + 'click a[data-action=remove-keyword]': 'destroy', + }, + init: function (parent, options) { + this._super(parent); + this.keyword = options.keyword; + this.onDelete = options.onDelete; + }, + destroy: function () { + if (_.isFunction(this.onDelete)) { + this.onDelete(this.keyword); + } + this._super(); + }, + }); + website.seo.cleanupKeyword = function (word) { + return word ? word.replace(/[,;.:]+/g, " ").replace(/ +/g, " ").trim() : ""; + }; + website.seo.PageParser = openerp.Class.extend({ + init: function () { + this._url = this._currentURL(); + this._title = $(document.title).text(); + this._headers = {}; + + var self = this; + _.each([ 'h1', 'h2', 'h3'], function (header) { + self._headers[header] = $(header).map(function () { + return $(this).text(); + }).get(); + }); + }, + url: function () { + return this._url; + }, + title: function () { + return this._title; + }, + headers: function () { + return this._headers; + }, + _currentURL: function () { + var url = window.location.href; + var hashIndex = url.indexOf('#'); + return hashIndex >= 0 ? url.substring(0, hashIndex) : url; + }, + keywordSuggestions: function () { + var headers = this.headers(); + return _.map(_.uniq(headers.h1.concat(headers.h2)), + website.seo.cleanupKeyword); + }, + }); + website.seo.Configurator = openerp.Widget.extend({ + template: 'website.seo_configuration', + events: { + 'keypress input[name=seo_page_keywords]': 'confirmKeyword', + 'click button[data-action=add]': 'addKeyword', + 'click a[data-action=update]': 'update', + 'hidden': 'destroy' + }, + + maxTitleSize: 65, + maxDescriptionSize: 155, + maxNumberOfKeywords: 10, + maxWordsPerKeyword: 4, + + start: function () { + var pageParser = new website.seo.PageParser(); + var currentKeywords = this.keywords; + this.$el.find('.js_seo_page_url').text(pageParser.url()); + this.$el.find('input[name=seo_page_title]').val(pageParser.title()); + this.$el.find('input[name=seo_page_keywords]').typeahead({ + source: function () { + var suggestions = pageParser.keywordSuggestions(); + var alreadyChosen = currentKeywords(); + return _.difference(suggestions, alreadyChosen); + }, + items: 4 + }); + + $(document.body).addClass('oe_stop_scrolling'); + this.$el.modal(); + }, + currentPage: function () { + var url = window.location.href; + var hashIndex = url.indexOf('#'); + return hashIndex > 0 ? url.substring(0, hashIndex): url; + }, + keywords: function () { + return _.uniq($('.js_seo_keyword').map(function () { + return $(this).text(); + }).get()); + }, + isExistingKeyword: function (word) { + return _.contains(this.keywords(), word); + }, + isKeywordListFull: function () { + return this.keywords().length >= this.maxNumberOfKeywords; + }, + confirmKeyword: function (e) { + if (e.keyCode == 13) { + this.addKeyword(); + this.$el.find('input[name=seo_page_keywords]').val(""); + } + }, + addKeyword: function () { + var $modal = this.$el; + function enableNewKeywords () { + $modal.find('input[name=seo_page_keywords]') + .removeAttr('readonly').attr('placeholder', "New keyword"); + $modal.find('button[data-action=add]') + .prop('disabled', false).removeClass('disabled'); + } + function disableNewKeywords () { + $modal.find('input[name=seo_page_keywords]') + .attr('readonly', "readonly") + .attr('placeholder', "Remove a keyword first"); + $modal.find('button[data-action=add]') + .prop('disabled', true).addClass('disabled'); + } + var candidate = this.$el.find('input[name=seo_page_keywords]').val(); + var word = website.seo.cleanupKeyword(candidate); + if (word && !this.isKeywordListFull() && !this.isExistingKeyword(word)) { + new website.seo.Keyword(this, { + keyword: word, + onDelete: enableNewKeywords + }).appendTo(this.$el.find('.js_seo_keywords_list')); + this.scrollDown(); + } + if (this.isKeywordListFull()) { + disableNewKeywords(); + } + }, + scrollDown: function () { + var $body = this.$el.find('.modal-body'); + $body.animate({ + scrollTop: $body[0].scrollHeight + }, 500); + }, + update: function () { + // TODO: Persist changes + }, + destroy: function () { + $(document.body).removeClass('oe_stop_scrolling'); + this._super(); + }, + }); +})(); diff --git a/addons/website/static/src/js/website.snippets.js b/addons/website/static/src/js/website.snippets.js new file mode 100644 index 00000000000..053f3f8ec91 --- /dev/null +++ b/addons/website/static/src/js/website.snippets.js @@ -0,0 +1,315 @@ +(function () { + 'use strict'; + + var website = openerp.website; + website.EditorBar.include({ + events: _.extend({}, website.EditorBar.prototype.events, { + 'click button[data-action=snippet]': 'snippet', + }), + start: function () { + return this._super().then(function () { + this.$buttons.snippet = this.$('button[data-action=snippet]'); + window.snippets = this.snippets = new website.Snippets(); + this.snippets.appendTo($(document.body)); + }.bind(this)); + }, + snippet: function (ev) { + this.snippets.toggle(); + }, + }); + + website.RTE.include({ + start_edition: function ($elements) { + this.snippet_carousel(); + return this._super($elements); + }, + // TODO clean + snippet_carousel: function () { + var self = this; + var $carousel_options = $('.carousel .js_carousel_options'); + $carousel_options.on('click', '.label', function (e) { + e.preventDefault(); + var $button = $(e.currentTarget); + var $c = $button.parents(".carousel:first"); + var $carousel_inner = $c.find('.carousel-inner'); + + if($button.hasClass("js_add")) { + var cycle = $carousel_inner.find('.item').size(); + $carousel_inner.append(openerp.qweb.render('website.carousel')); + $c.carousel(cycle); + } + else { + $carousel_inner + .find('.item.active').remove().end() + .find('.item:first').addClass('active'); + $c.carousel(0); + self.trigger('change', self, null); + } + }); + $carousel_options.show(); + }, + }); + + /* ----- SNIPPET SELECTOR ---- */ + website.Snippets = openerp.Widget.extend({ + template: 'website.snippets', + init: function () { + this._super.apply(this, arguments); + }, + start: function() { + var self = this; + + $.ajax({ + type: "GET", + url: "/page/website.snippets", + dataType: "text", + success: function(snippets){ + self.$el.html(snippets); + self.start_snippets(); + }, + }); + + }, + path_eval: function(path){ + var obj = window; + path = path.split('.'); + do{ + obj = obj[path.shift()]; + }while(path.length && obj); + return obj; + }, + + // setup widget and drag and drop + start_snippets: function(){ + var self = this; + + this.$('.oe_snippet').draggable({ + helper: 'clone', + zIndex: '1000', + appendTo: 'body', + start: function(){ + var snippet = $(this); + var action = snippet.data('action'); + + self.deactivate_snippet_manipulators(); + if( action === 'insert'){ + self.activate_insertion_zones({ + siblings: snippet.data('selector-siblings'), + childs: snippet.data('selector-childs') + }); + }else if( action === 'mutate' ){ + self.activate_overlay_zones(snippet.data('selector')); + } + + $('.oe_drop_zone').droppable({ + over: function(){ + // FIXME: stupid hack to prevent multiple droppable to activate at once ... + // it's not even working properly but it's better than nothing. + $(".oe_drop_zone.oe_hover").removeClass("oe_hover"); + $(this).addClass("oe_hover"); + }, + out: function(){ + $(this).removeClass("oe_hover"); + }, + drop: function(){ + if( action === 'insert' ){ + $(".oe_drop_zone.oe_hover") + .replaceWith(snippet.find('.oe_snippet_body').clone()) + .removeClass('oe_snippet_body'); + }else if( action === 'mutate' ){ + self.path_eval(snippet.data('action-function'))( $(".oe_drop_zone.oe_hover").data('target') ); + } + }, + }); + }, + stop: function(){ + self.deactivate_zones(); + self.activate_snippet_manipulators(); + }, + }); + + }, + // Create element insertion drop zones. two css selectors can be provided + // selector.childs -> 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){ + var self = this; + var i, len, $zones; + var child_selector = selector.childs || ''; + var sibling_selector = selector.siblings || ''; + var zone_template = "
    "; + var $drop_zone = $('.oe_drop_zone'); + + $drop_zone.remove(); + + if(child_selector){ + $zones = $(child_selector); + for(i = 0, len = $zones.length; i < len; i++ ){ + $zones.eq(i).find('> *:not(.oe_drop_zone)').after(zone_template); + $zones.eq(i).prepend(zone_template); + } + } + + if(sibling_selector){ + $zones = $(sibling_selector); + for(i = 0, len = $zones.length; i < len; i++ ){ + if($zones.eq(i).prev('.oe_drop_zone').length === 0){ + $zones.eq(i).before(zone_template); + } + if($zones.eq(i).next('.oe_drop_zone').length === 0){ + $zones.eq(i).after(zone_template); + } + } + } + + // Cleaning up unnecessary zones + $('.oe_snippets .oe_drop_zone').remove(); // no zone in the snippet selector ... + $('#website-top-view').find('.oe_drop_zone').remove(); // no zone in the top bars ... + $('#website-top-edit').find('.oe_drop_zone').remove(); + var count; + do { + count = 0; + $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones + count += $zones.length; + $zones.remove(); + + $zones = $('.oe_drop_zone > .oe_drop_zone').remove(); // no recusrive zones + count += $zones.length; + $zones.remove(); + }while(count > 0); + + // Cleaning up zones placed between floating or inline elements. We do not like these kind of zones. + $zones = $drop_zone; + for(i = 0, len = $zones.length; i < len; i++ ){ + var zone = $zones.eq(i); + var prev = zone.prev(); + var next = zone.next(); + 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(); + } + } + }, + deactivate_zones: function(){ + $('.oe_drop_zone').remove(); + }, + + activate_overlay_zones: function(selector){ + var $targets = $(selector); + + 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; + }); + + var zone_template = "
    "; + $('.oe_drop_zone').remove(); + + for(var i = 0, len = $targets.length; i < len; i++){ + var $target = $targets.eq(i); + var $zone = $(zone_template); + this.cover_target($zone,$target); + $zone.appendTo('body'); + $zone.data('target',$target); + } + }, + cover_target: function($el, $target){ + $el.css({ + 'position': 'absolute', + 'width': $target.outerWidth(), + 'height': $target.outerHeight(), + }); + $el.css($target.offset()); + }, + activate_snippet_manipulators: function(){ + var self = this; + this.activate_overlay_zones('#wrap .container'); + var $snippets = $('.oe_drop_zone'); + + for(var i = 0, len = $snippets.length; i < len; i++){ + var $snippet = $snippets.eq(i); + var $manipulator = $(openerp.qweb.render('website.snippet_manipulator')); + $manipulator.css({ + 'top': $snippet.css('top'), + 'left': $snippet.css('left'), + 'width': $snippet.css('width'), + 'height': $snippet.css('height'), + }); + $manipulator.data('target',$snippet.data('target')); + $manipulator.appendTo('body'); + $snippet.remove(); + + $manipulator.find('.oe_handle').mousedown(function(event){ + var $handle = $(this); + var $manipulator = $handle.parent(); + var $snippet = $manipulator.data('target'); + var x = event.pageX; + var y = event.pageY; + + var pt = $snippet.css('padding-top'); + var pb = $snippet.css('padding-bottom'); + pt = Number(pt.slice(0, -2)) || 0; //FIXME something cleaner to remove 'px' + pb = Number(pb.slice(0, -2)) || 0; + + $manipulator.addClass('oe_hover'); + event.preventDefault(); + + $(document.body).on({ + mousemove: function(event){ + var dx = event.pageX - x; + var dy = event.pageY - y; + event.preventDefault(); + if($handle.hasClass('n') || $handle.hasClass('nw') || $handle.hasClass('ne')){ + $snippet.css('padding-top',pt-dy+'px'); + self.cover_target($manipulator,$snippet); + }else if($handle.hasClass('s') || $handle.hasClass('sw') || $handle.hasClass('se')){ + + $snippet.css('padding-bottom',pb+dy+'px'); + self.cover_target($manipulator,$snippet); + } + }, + mouseup: function(){ + $body.off('mouseup mousemove'); + self.deactivate_snippet_manipulators(); + self.activate_snippet_manipulators(); + } + }); + }); + + } + }, + deactivate_snippet_manipulators: function(){ + $('.oe_snippet_manipulator').remove(); + }, + toggle: function(){ + if(this.$el.hasClass('oe_hidden')){ + this.$el.removeClass('oe_hidden'); + this.activate_snippet_manipulators(); + }else{ + this.$el.addClass('oe_hidden'); + this.deactivate_snippet_manipulators(); + } + }, + }); +})(); diff --git a/addons/website/views/views.xml b/addons/website/views/views.xml index 5db8629e3b3..15bfea3f446 100644 --- a/addons/website/views/views.xml +++ b/addons/website/views/views.xml @@ -42,6 +42,10 @@ + + + +