diff --git a/addons/website/static/src/css/snippets.css b/addons/website/static/src/css/snippets.css index c9b726affde4b720e78d9ca71bcf7fc46767d4ff..1d99722e48bb784d96c4559c7d9ab573fd515bb2 100644 --- a/addons/website/static/src/css/snippets.css +++ b/addons/website/static/src/css/snippets.css @@ -176,3 +176,29 @@ margin-left: 0px !important; } */ + +#website-top-edit-snippet-option { + padding: 1px 8px 2px; + font: normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif; + float: left; + margin-top: 6px; + border: 1px solid #a6a6a6; + border-bottom-color: #979797; + background: #eeeeee; + border-radius: 3px; +} +#website-top-edit-snippet-option > * { + display: inline-block; + height: 22px; + padding: 4px 6px; + outline: 0; + border: 0; +} +#website-top-edit-snippet-option a.button .icon { + cursor: inherit; + background-repeat: no-repeat; + margin-top: 1px; + width: 16px; + height: 16px; + display: inline-block; +} \ No newline at end of file diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 32e77acedab88ecb82dfad68f46be061a1f3a3ca..b4c3504235051eed9362929f0d3578bd8a828141 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -220,6 +220,11 @@ // editor.ui.items -> possible commands &al // editor.applyStyle(new CKEDITOR.style({element: "span",styles: {color: "#(color)"},overrides: [{element: "font",attributes: {color: null}}]}, {color: '#ff0000'})); + init: function (EditorBar) { + this.EditorBar = EditorBar; + this._super.apply(this, arguments); + }, + start_edition: function ($elements) { var self = this; $elements @@ -229,6 +234,7 @@ var $node = $(node); var editor = CKEDITOR.inline(this, self._config()); editor.on('instanceReady', function () { + self.trigger('instanceReady'); observer.observe(node, OBSERVER_CONFIG); }); $node.one('content_changed', function () { diff --git a/addons/website/static/src/js/website.snippets.js b/addons/website/static/src/js/website.snippets.js index 5ebf7aecc8d4f74a394adee1ea783350ddbdf3a4..8662bc1522d85e01f40f624eb9c23e52d1a4f711 100644 --- a/addons/website/static/src/js/website.snippets.js +++ b/addons/website/static/src/js/website.snippets.js @@ -2,84 +2,106 @@ 'use strict'; var website = openerp.website; + website.templates.push('/website/static/src/xml/website.snippets.xml'); + 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(); + window.snippets = this.snippets = new website.snippet.BuildingBlock(); this.snippets.appendTo($(document.body)); + }.bind(this)); }, + edit: function () { + var res = this._super.apply(this, arguments); + var self = this; + var instanceReady = false; + this.rte.on('instanceReady', this, function () { + clearTimeout(instanceReady); + instanceReady = setTimeout(function () { self.snippetBind(); }, 0); + }); + return res; + }, 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 () { + snippetBind: function () { var self = this; - $('.carousel').each(function () { - var $carousel = $(this); - var $options = $('.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); + var $snipped_id = false; + var snipped_event_flag = false; + $("[data-snippet-id]") + .filter(function() { + return !!website.snippet.editorRegistry[$(this).data("snippet-id")]; + }) + .on('click', function (event) { + if (snipped_event_flag) { + return; } - else if ($carousel_inner.find('.item').size() > 1) { - $carousel_inner - .find('.item.active').remove().end() - .find('.item:first').addClass('active'); - $c.carousel(0); - self.trigger('change', self, null); + + snipped_event_flag = true; + setTimeout(function () {snipped_event_flag = false;}, 0); + + if ($snipped_id && $snipped_id.get(0) == event.currentTarget) { + return; } + + self.snippetblur($snipped_id); + + $snipped_id = $(event.currentTarget); + + if (typeof $snipped_id.data("snippet-editor") === 'undefined' && + website.snippet.editorRegistry[$snipped_id.data("snippet-id")]) { + $snipped_id.data("snippet-editor", new website.snippet.editorRegistry[$snipped_id.data("snippet-id")](self, $snipped_id)); + } + self.snippetFocus($snipped_id); }); - $options.on('change', 'select[name="carousel-background"]', function () { - $('.carousel-inner .item.active', $carousel).css('background-image', 'url(' + $(this).val() + ')'); - $(this).val(""); - }); - $options.on('change', 'select[name="carousel-style"]', function () { - var $container = $('.carousel-inner .item.active .container', $carousel); - $('.content_image', $container).remove(); - switch ($(this).val()) { - case 'no_image': - $('.content', $container).attr("class", "content"); - break; - case 'image_left': - $('.content', $container).attr("class", "content col-md-6") - .before('<div class="content_image col-md-5"><img class="img-rounded img-responsive" src="/website/static/src/img/china.jpg"></div>'); - break; - case 'image_right': - $('.content', $container).attr("class", "content col-md-6") - .after('<div class="content_image col-md-5 col-lg-offset-1"><img class="img-rounded img-responsive" src="/website/static/src/img/china.jpg"></div>'); - break; + $("body > :not(:has(#website-top-view))").on('click', function (ev) { + if (!snipped_event_flag && $snipped_id) { + self.snippetblur($snipped_id); + $snipped_id = false; } - $(this).val(""); }); - $options.show(); - }); + }, + + snippetblur: function ($snipped_id) { + if ($snipped_id) { + if ($snipped_id.data("snippet-editor")) { + $snipped_id.data("snippet-editor").onBlur(); + } + if ($snipped_id.data("snippet-view")) { + $snipped_id.data("snippet-view").onBlurEdit(); + } + } + }, + snippetFocus: function ($snipped_id) { + if ($snipped_id) { + if ($snipped_id.data("snippet-view")) { + $snipped_id.data("snippet-view").onFocusEdit(); + } + if ($snipped_id.data("snippet-editor")) { + $snipped_id.data("snippet-editor").onFocus(); + } + } }, }); /* ----- SNIPPET SELECTOR ---- */ - website.Snippets = openerp.Widget.extend({ + + website.snippet = {}; + + website.snippet.BuildingBlock = openerp.Widget.extend({ template: 'website.snippets', init: function () { this._super.apply(this, arguments); + if(!$('#oe_manipulators').length){ + $("<div id='oe_manipulators'></div>").appendTo('body'); + } }, start: function() { var self = this; @@ -90,7 +112,9 @@ dataType: "text", success: function(snippets){ self.$el.html(snippets); - self.start_snippets(); + self.$('.oe_snippet').each(function(index,snippet){ + self.make_snippet_draggable($(snippet)); + }); }, }); @@ -104,26 +128,25 @@ return obj; }, - // setup widget and drag and drop - start_snippets: function(){ + // activate drag and drop for the snippets in the snippet toolbar + make_snippet_draggable: function($snippet){ var self = this; - this.$('.oe_snippet').draggable({ + $snippet.draggable({ helper: 'clone', zIndex: '1000', appendTo: 'body', start: function(){ - var snippet = $(this); - var action = snippet.data('action'); + 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') + siblings: $snippet.data('selector-siblings'), + childs: $snippet.data('selector-childs') }); }else if( action === 'mutate' ){ - self.activate_overlay_zones(snippet.data('selector')); + self.activate_overlay_zones($snippet.data('selector')); } $('.oe_drop_zone').droppable({ @@ -138,11 +161,13 @@ }, drop: function(){ if( action === 'insert' ){ - $(".oe_drop_zone.oe_hover") - .replaceWith(snippet.find('.oe_snippet_body').clone()) - .removeClass('oe_snippet_body'); + var $toInsert = $snippet.find('.oe_snippet_body').clone(); + $toInsert.removeClass('oe_snippet_body'); + $toInsert.addClass('oe_snippet_instance'); + $toInsert.data('snippet-id',$snippet.data('snippet-id')); + $(".oe_drop_zone.oe_hover").replaceWith($toInsert); }else if( action === 'mutate' ){ - self.path_eval(snippet.data('action-function'))( $(".oe_drop_zone.oe_hover").data('target') ); + self.path_eval($snippet.data('action-function'))( $(".oe_drop_zone.oe_hover").data('target') ); } }, }); @@ -152,8 +177,38 @@ self.activate_snippet_manipulators(); }, }); + }, + // return the original snippet in the editor bar from a snippet id (string) + get_snippet_from_id: function(id){ + return $('.oe_snippet').filter(function(){ + return $(this).data('snippet-id') === id; + }).first(); }, + // WIP + make_draggable_instance: function($instance){ + var self = this; + var $snippet = get_snippet_from_id($instance.data('snippet-id')); + + $instance.draggable({ + helper: 'clone', + zIndex: '1000', + appendTo: 'body', + start: function(){ + var action = $snippet.data('action'); + if(action === 'insert'){ + + self.deactivate_snippet_manipulators(); + self.activate_insertion_zones({ + siblings: $snippet.data('selector-siblings'), + child: $snippet.data('selector-childs') + }); + + } + } + }); + }, + // 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 @@ -161,25 +216,23 @@ // 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 = "<div class='oe_drop_zone oe_insert'></div>"; - var $drop_zone = $('.oe_drop_zone'); - $drop_zone.remove(); + $('.oe_drop_zone').remove(); if(child_selector){ - $zones = $(child_selector); - for(i = 0, len = $zones.length; i < len; i++ ){ + var $zones = $(child_selector); + for( var 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++ ){ + var $zones = $(sibling_selector); + for( var i = 0, len = $zones.length; i < len; i++ ){ if($zones.eq(i).prev('.oe_drop_zone').length === 0){ $zones.eq(i).before(zone_template); } @@ -191,12 +244,12 @@ // 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(); + $('#website-top-view .oe_drop_zone').remove(); // no zone in the top bars ... + $('#website-top-edit .oe_drop_zone').remove(); var count; do { count = 0; - $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones + var $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones count += $zones.length; $zones.remove(); @@ -206,23 +259,25 @@ }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 $zones = $('.oe_drop_zone'); + for( var 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; + var float_prev = zone.prev().css('float') || 'none'; + var float_next = zone.next().css('float') || 'none'; + var disp_prev = zone.prev().css('display') || null; + var disp_next = zone.next().css('display') || null; if( (float_prev === 'left' || float_prev === 'right') && (float_next === 'left' || float_next === 'right') ){ zone.remove(); + continue; }else if( !( disp_prev === null || disp_next === null || disp_prev === 'block' || disp_next === 'block' )){ zone.remove(); + continue; } } }, @@ -230,9 +285,10 @@ $('.oe_drop_zone').remove(); }, + // generate drop zones covering the elements selected by the selector activate_overlay_zones: function(selector){ var $targets = $(selector); - + function is_visible($el){ return $el.css('display') != 'none' && $el.css('opacity') != '0' @@ -247,7 +303,7 @@ var parents = $(this).parents().filter(function(){ return !is_visible($(this)); }); return parents.length === 0; }); - + var zone_template = "<div class='oe_drop_zone oe_overlay'></div>"; $('.oe_drop_zone').remove(); @@ -255,10 +311,12 @@ var $target = $targets.eq(i); var $zone = $(zone_template); this.cover_target($zone,$target); - $zone.appendTo('body'); + $zone.appendTo('#oe_manipulators'); $zone.data('target',$target); } }, + + // puts $el at the same absolute position as $target cover_target: function($el, $target){ $el.css({ 'position': 'absolute', @@ -267,62 +325,89 @@ }); $el.css($target.offset()); }, + + // activates the manipulator boxes (with resizing handles) for all snippets activate_snippet_manipulators: function(){ var self = this; + // we generate overlay drop zones only to get an idea of where the snippet are, the drop + // zones are replaced by manipulators 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 $active_manipulator = null; + var locked = false; + + $('.oe_drop_zone').each(function(){; + + var $zone = $(this); + var $snippet = $zone.data('target'); 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'), + + self.cover_target($manipulator, $zone); + $manipulator.data('target',$snippet); + $manipulator.appendTo('#oe_manipulators'); + $zone.remove(); + + $manipulator.mouseover(function(){ + if(!locked && $active_manipulator != $manipulator){ + if($active_manipulator){ + $active_manipulator.removeClass('oe_selected'); + } + $active_manipulator = $manipulator; + $manipulator.addClass('oe_selected'); + } }); - $manipulator.data('target',$snippet.data('target')); - $manipulator.appendTo('body'); - $snippet.remove(); + /*$manipulator.mouseleave(function(){ + if(!locked && $active_manipulator){ + $active_manipulator.removeClass('oe_selected'); + $active_manipulator = null; + } + });*/ + /* + $manipulator.click(function(){ + if($active_manipulator === $manipulator){ + selected = !selected; + $manipulator.toggleClass('oe_selected',selected); + } + }); + */ $manipulator.find('.oe_handle').mousedown(function(event){ + locked = true; 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; - + var pb = $snippet.css('padding-bottom'); + pt = Number(pt.slice(0,pt.length - 2)) || 0; //FIXME something cleaner to remove 'px' + pb = Number(pb.slice(0,pb.length - 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(); + $('body').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); } }); - }); - } + $('body').mouseup(function(){ + locked = false; + $('body').unbind('mousemove'); + $('body').unbind('mouseup'); + self.deactivate_snippet_manipulators(); + self.activate_snippet_manipulators(); + }); + }); + + }); }, deactivate_snippet_manipulators: function(){ $('.oe_snippet_manipulator').remove(); @@ -337,4 +422,145 @@ } }, }); + + + website.snippet.viewRegistry = {}; + website.snippet.View = openerp.Class.extend({ + $: function () { + return this.$el.find.apply(this.$el, arguments); + }, + init: function (dom) { + this.$el = $(dom); + this._super.apply(this, arguments); + }, + /* onFocusEdit + * if they are an editor for this snippet-id + * Called before onFocus of snippet editor + */ + onFocusEdit : function () {}, + + /* onBlurEdit + * if they are an editor for this snippet-id + * Called after onBlur of snippet editor + */ + onBlurEdit : function () {}, + + /* getOptions + * get the options saved in the html view + */ + getOptions: function () { + var options = this.$el.data("snippet-options"); + return options ? JSON.parse(options) : undefined; + }, + }); + + + website.snippet.editorRegistry = {}; + website.snippet.Editor = openerp.Widget.extend({ + init: function (parent, dom) { + this.$target = $(dom); + this.parent = parent; + this._super.apply(this, arguments); + this.renderElement(); + this.start(); + }, + + renderElement: function() { + var $el; + if (this.template) { + $el = $(openerp.qweb.render(this.template, {widget: this}).trim()); + } else { + $el = this._make_descriptive(); + } + $el = $('<li id="website-top-edit-snippet-option"></li>').append($el); + this.replaceElement($el); + }, + + /* onFocus + * called when the user click inside the snippet dom + */ + onFocus : function () { + this.$el.prependTo(this.parent.$('#website-top-edit .nav.pull-right')); + }, + + /* onFocus + * called when the user click outide the snippet dom + */ + onBlur : function () { + this.$el.detach(); + }, + + /* setOptions + * saved the options in the html view + */ + setOptions: function (options) { + $target.attr("data-snippet-options", JSON.stringify(options)); + }, + + /* getOptions + * get the options saved in the html view + */ + getOptions: function () { + var options = this.$target.data("snippet-options"); + return options ? JSON.parse(options) : undefined; + }, + }); + + + website.snippet.editorRegistry.carousel = website.snippet.Editor.extend({ + template : "website.snippets.EditorBar.carousel", + start : function () { + var self = this; + + self.$(".js_add").on('click', function (e) { + e.preventDefault(); + var $inner = self.$target.find('.carousel-inner'); + var cycle = $inner.find('.item').size(); + $inner.append(openerp.qweb.render('website.carousel')); + self.$target.carousel(cycle); + }); + + + self.$(".js_remove").on('click', function (e) { + e.preventDefault(); + var $inner = self.$target.find('.carousel-inner'); + if ($inner.find('.item').size() > 1) { + $inner + .find('.item.active').remove().end() + .find('.item:first').addClass('active'); + self.$target.carousel(0); + } + }); + + + var bg = this.$target.find('.carousel-inner .item.active').css('background-image').replace(/url\((.*)\)/g, '\$1'); + this.$( 'select[name="carousel-background"] option[value="'+bg+'"], select[name="carousel-background"] option[value="'+bg.replace(window.location.protocol+'//'+window.location.host, '')+'"]') + .prop('selected', true); + self.$('select[name="carousel-background"]').on('change', function () { + self.$target.find('.carousel-inner .item.active').css('background-image', 'url(' + $(this).val() + ')'); + $(this).val(""); + }); + + + self.$('select[name="carousel-style"]').on('change', function () { + var $container = self.$target.find('.carousel-inner .item.active .container'); + $('.content_image', $container).remove(); + switch ($(this).val()) { + case 'no_image': + $('.content', $container).attr("class", "content"); + break; + case 'image_left': + $('.content', $container).attr("class", "content col-md-6") + .before('<div class="content_image col-md-5"><img class="img-rounded img-responsive" src="/website/static/src/img/china.jpg"></div>'); + break; + case 'image_right': + $('.content', $container).attr("class", "content col-md-6") + .after('<div class="content_image col-md-5 col-lg-offset-1"><img class="img-rounded img-responsive" src="/website/static/src/img/china.jpg"></div>'); + break; + } + $(this).val(""); + }); + } + }); + })(); diff --git a/addons/website/static/src/xml/website.snippets.xml b/addons/website/static/src/xml/website.snippets.xml new file mode 100644 index 0000000000000000000000000000000000000000..8665a1ce06e3418b0544f9861e3531acc76eaa50 --- /dev/null +++ b/addons/website/static/src/xml/website.snippets.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<templates id="template" xml:space="preserve"> + + <t t-name="website.snippets.EditorBar.carousel"> + <a href="#" class="button js_add"><i class="icon icon-plus-sign"></i></a> + <a href="#" class="button js_remove"><i class="icon icon-minus-sign"></i></a> + <select name="carousel-background"> + <option value="">Chose an other background</option> + <option value="/website/static/src/img/greenfields.jpg">greenfields</option> + <option value="/website/static/src/img/landscape.png">landscape</option> + <option value="/website/static/src/img/aqua.jpg">aqua</option> + </select> + <select name="carousel-style"> + <option value="">Chose an other style</option> + <option value="no_image">No image</option> + <option value="image_left">Image left</option> + <option value="image_right">Image right</option> + </select> + </t> + +</templates> diff --git a/addons/website/views/views.xml b/addons/website/views/views.xml index 25f3869826e1f2c84eb9baca82ab635ef601828b..a48068b82ed919b0616267a03975654573c1028f 100644 --- a/addons/website/views/views.xml +++ b/addons/website/views/views.xml @@ -156,24 +156,9 @@ <template id="homepage" page="True"> <t t-call="website.layout"> + <div data-snippet-id="carousel"> TEST <br/> <br/> </div> <section> - <div id="myCarousel" class="carousel slide" data-interval="1000000"> - <div class="oe_carousel_options js_carousel_options" t-ignore="1" t-if="editable"> - <span class="label label-default js_add"><i class="icon-plus-sign"></i></span> - <span class="label label-default js_remove"><i class="icon-minus-sign"></i></span> - <select name="carousel-background" class="form-control"> - <option value="">Chose an other background</option> - <option value="/website/static/src/img/greenfields.jpg">greenfields</option> - <option value="/website/static/src/img/landscape.png">landscape</option> - <option value="/website/static/src/img/aqua.jpg">aqua</option> - </select> - <select name="carousel-style" class="form-control"> - <option value="">Chose an other style</option> - <option value="no_image">No image</option> - <option value="image_left">Image left</option> - <option value="image_right">Image right</option> - </select> - </div> + <div id="myCarousel" data-snippet-id="carousel" class="carousel slide" data-interval="1000000"> <!-- Carousel items --> <div class="carousel-inner"> <div class="item active" style="background-image: url(/website/static/src/img/greenfields.jpg); background-size: cover;">