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('').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 @@
+
+
+
+