diff --git a/addons/website/__openerp__.py b/addons/website/__openerp__.py index b9df6527f62..dcea2ba3327 100644 --- a/addons/website/__openerp__.py +++ b/addons/website/__openerp__.py @@ -16,5 +16,6 @@ OpenERP Website CMS 'views/themes.xml', 'views/res_config.xml', 'website_data.xml', + 'website_view.xml', ], } diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 102c7d7dabe..a263f6e8003 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -57,8 +57,7 @@ class Website(openerp.addons.web.controllers.main.Home): imd = request.registry['ir.model.data'] view = request.registry['ir.ui.view'] view_model, view_id = imd.get_object_reference( - request.cr, request.uid, 'website', 'default_page', - context=request.context) + request.cr, request.uid, 'website', 'default_page') newview_id = view.copy( request.cr, request.uid, view_id, context=request.context) newview = view.browse( @@ -92,7 +91,7 @@ class Website(openerp.addons.web.controllers.main.Home): view = request.registry['ir.ui.view'] view_model, view_option_id = imd.get_object_reference( - request.cr, request.uid, 'website', 'theme', context=request.context) + request.cr, request.uid, 'website', 'theme') views = view.search( request.cr, request.uid, [('inherit_id', '=', view_option_id)], context=request.context) @@ -102,19 +101,21 @@ class Website(openerp.addons.web.controllers.main.Home): if theme_id: module, xml_id = theme_id.split('.') view_model, view_id = imd.get_object_reference( - request.cr, request.uid, module, xml_id, context=request.context) + request.cr, request.uid, module, xml_id) view.write(request.cr, request.uid, [view_id], {'inherit_id': view_option_id}, context=request.context) - return request.webcontext.render('website.themes', {'theme_changed': True}) + return request.website.render('website.themes', {'theme_changed': True}) @website.route('/page/', type='http', auth="admin") def page(self, path, **kwargs): - request.webcontext['path'] = path + values = { + 'path': path, + } try: - html = request.webcontext.render(path) + html = request.website.render(path, values) except ValueError: - html = request.webcontext.render('website.404') + html = request.website.render('website.404', values) return html @website.route('/website/customize_template_toggle', type='json', auth='admin') # FIXME: auth @@ -132,20 +133,21 @@ class Website(openerp.addons.web.controllers.main.Home): return True @website.route('/website/customize_template_get', type='json', auth='admin') # FIXME: auth - def customize_template_get(self, xml_id): + def customize_template_get(self, xml_id, optional=True): imd = request.registry['ir.model.data'] view_model, view_theme_id = imd.get_object_reference( - request.cr, request.uid, 'website', 'theme', context=request.context) + request.cr, request.uid, 'website', 'theme') view = request.registry.get("ir.ui.view") views = view._views_get(request.cr, request.uid, xml_id, request.context) done = {} result = [] for v in views: - if v.inherit_option_id and v.inherit_option_id.id != view_theme_id: + if v.inherit_option_id and v.inherit_option_id.id != view_theme_id or not optional: if v.inherit_option_id.id not in done: result.append({ 'name': v.inherit_option_id.name, + 'id': v.id, 'header': True, 'active': False }) @@ -267,6 +269,6 @@ class Website(openerp.addons.web.controllers.main.Home): @website.route(['/website/kanban/'], type='http', auth="public") def kanban(self, **post): - return request.registry['website'].kanban_col(**post) + return request.website.kanban_col(**post) # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css index df098f28353..834baff3e35 100644 --- a/addons/website/static/src/css/editor.css +++ b/addons/website/static/src/css/editor.css @@ -82,6 +82,10 @@ } /* ---- EDITOR BAR ---- */ +.modal .tab-content { + padding-top: 5px; +} + table.editorbar-panel { cursor: pointer; width: 100%; @@ -90,6 +94,10 @@ table.editorbar-panel td.selected { background-color: #b1c9d9; } +.modal .image-preview-container, .modal .image-preview { + margin-bottom: 0.5em; +} + /* ---- RTE ---- */ .oe_editable .btn { -webkit-user-select: auto; @@ -100,7 +108,7 @@ table.editorbar-panel td.selected { } /* ---- SNIPPET EDITOR ---- */ -.oe_snippets { +#oe_snippets { position: fixed; left: 0px; right: 0px; @@ -112,6 +120,16 @@ table.editorbar-panel td.selected { overflow-y: auto; z-index: 100; } +#oe_snippets .nav-pills { + padding: 3px 8px; + background: #444444; +} +#oe_snippets .nav-pills > li > a { + padding: 2px 10px !important; +} +#oe_snippets .pill-content { + height: 140px; +} .oe_snippet { display: inline-block; @@ -137,6 +155,7 @@ table.editorbar-panel td.selected { line-height: 120px; color: white; text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.95); + box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.07) inset; } .oe_snippet .oe_snippet_body { display: none; @@ -144,6 +163,9 @@ table.editorbar-panel td.selected { .oe_snippet > * { pointer-events: none; } +.oe_snippet > :not(.oe_snippet_body):not(.oe_snippet_thumbnail) { + display: none !important; +} .oe_snippet > *:first-child:not(.oe_snippet_thumbnail) { display: block; -webkit-transform-origin-x: 5px; @@ -195,64 +217,124 @@ table.editorbar-panel td.selected { } .oe_drop_zone.oe_hover { background: rgba(0, 255, 133, 0.22); - z-index: 99; + z-index: 97; } /* ---- SNIPPET MANIPULATOR ---- */ -.oe_snippet_manipulator { +.oe_overlay { display: block; position: absolute; - background: rgba(153, 0, 255, 0.1); + background: transparent; border-radius: 3px; - border: solid 1px rgba(153, 0, 255, 0.5); opacity: 0; transition: opacity 100ms linear; + box-sizing: border-box; + pointer-events: none; } -.oe_snippet_manipulator > .oe_handle { - display: none; +.oe_overlay .oe_handle { + display: none !important; } -.oe_snippet_manipulator:hover, .oe_snippet_manipulator.oe_hover { +.oe_overlay.oe_hover { opacity: 1; + cursor: pointer; } -.oe_snippet_manipulator:hover > .oe_handle, .oe_snippet_manipulator.oe_hover > .oe_handle { +.oe_overlay.oe_selected, .oe_overlay.oe_active { + opacity: 1; + background: transparent; + border-style: dashed; + border-width: 1px; + box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.3), 0px 0px 0px 1px rgba(255, 255, 255, 0.3) inset; +} +.oe_overlay.oe_selected { + border-color: rgba(0, 153, 255, 0.5); +} +.oe_overlay.oe_active { + border-color: rgba(153, 0, 255, 0.5); +} +.oe_overlay.oe_active .oe_handle { + display: block !important; + pointer-events: auto; +} +.oe_overlay .oe_overlay_options { + display: none; + z-index: 99; +} +.oe_overlay .oe_overlay_options .oe_option { + pointer-events: auto; + position: absolute; +} +.oe_overlay .oe_overlay_options .oe_option.n { + top: 0px; +} +.oe_overlay .oe_overlay_options .oe_option.e { + right: 0px; +} +.oe_overlay .oe_overlay_options .oe_option.s { + bottom: 0px; +} +.oe_overlay .oe_overlay_options .oe_option.w { + left: 0px; +} +.oe_overlay.oe_active .oe_overlay_options { display: block; } .oe_handle { + pointer-events: auto; position: absolute; top: 50%; left: 50%; display: block; - background: white; - border: solid 1px rgba(153, 0, 255, 0.5); box-sizing: border-box; - border-radius: 2px; - width: 16px; - height: 16px; - margin: -8px; - cursor: move; - -webkit-transition: background 100ms linear; -} -.oe_handle:hover { - background: #9900ff; + width: 8px; + height: 8px; + margin: -4px; } -.oe_handle.nw, .oe_handle.n, .oe_handle.ne { +.oe_handle.n { + left: 2px; + width: 100%; top: 0px; + cursor: s-resize; } -.oe_handle.ne, .oe_handle.e, .oe_handle.se { +.oe_handle.e { left: auto; + top: 2px; + height: 100%; right: 0px; + cursor: w-resize; } -.oe_handle.sw, .oe_handle.s, .oe_handle.se { +.oe_handle.s { top: auto; + left: 2px; + width: 100%; bottom: 0px; + cursor: n-resize; } -.oe_handle.nw, .oe_handle.w, .oe_handle.sw { +.oe_handle.w { + top: 2px; + height: 100%; left: 0px; + cursor: e-resize; +} + +.s-resize-important, .s-resize-important * { + cursor: s-resize !important; +} + +.n-resize-important, .n-resize-important * { + cursor: n-resize !important; +} + +.e-resize-important, .e-resize-important * { + cursor: e-resize !important; +} + +.w-resize-important, .w-resize-important * { + cursor: w-resize !important; } /* ---- MOBILE PREVIEW ---- */ @@ -312,3 +394,47 @@ table.editorbar-panel td.selected { .oe_seo_configuration .oe_seo_suggestion { cursor: pointer; } + +/* ---- ACE EDITOR ---- */ +.oe_ace_view_editor { + transition: all 0.2s ease-in; + opacity: 0.3; + position: fixed; + top: 51px; + right: 0; + z-index: 1000; + height: 100%; +} +.oe_ace_view_editor .oe_ace_view_editor_title { + width: 100%; + padding-top: 4px; + padding-left: 4px; + height: 40px; + background: #2f3129; +} +.oe_ace_view_editor .oe_ace_view_editor_title .oe_view_list { + width: 50%; + height: 32px; +} +.oe_ace_view_editor .oe_ace_view_editor_title .btn { + height: 32px; + padding: 0 4px 0 4px; +} +.oe_ace_view_editor .ace_editor { + position: absolute; + top: 40px; + right: 0; + bottom: 51px; + left: 0; + width: 720px; +} +.oe_ace_view_editor:hover { + opacity: 1; +} +.oe_ace_view_editor.oe_ace_open { + width: 720px; +} +.oe_ace_view_editor.oe_ace_closed { + width: 0; + opacity: 0; +} diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass index 2da2b4bbeeb..3c1fe6a47f5 100644 --- a/addons/website/static/src/css/editor.sass +++ b/addons/website/static/src/css/editor.sass @@ -74,12 +74,18 @@ /* ---- EDITOR BAR ---- */ +.modal .tab-content + padding-top: 5px + table.editorbar-panel cursor: pointer width: 100% td.selected background-color: #b1c9d9 +.modal .image-preview-container, .modal .image-preview + margin-bottom: 0.5em + /* ---- RTE ---- */ // bootstrap makes .btn elements unselectable -> RTE double-click can't know @@ -93,7 +99,7 @@ table.editorbar-panel /* ---- SNIPPET EDITOR ---- */ -.oe_snippets +#oe_snippets position: fixed left: 0px right: 0px @@ -104,6 +110,13 @@ table.editorbar-panel box-shadow: 0px 10px 10px -10px black inset overflow-y: auto z-index: 100 + .nav-pills + padding: 3px 8px + background: #444444 + > li > a + padding: 2px 10px !important + .pill-content + height: 140px .oe_snippet display: inline-block @@ -129,11 +142,14 @@ table.editorbar-panel line-height: 120px color: white text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.95) + box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.07) inset .oe_snippet_body display: none & > * pointer-events: none + & > :not(.oe_snippet_body):not(.oe_snippet_thumbnail) + display: none !important & > *:first-child:not(.oe_snippet_thumbnail) display: block @@ -179,52 +195,98 @@ table.editorbar-panel border-radius: 3px &.oe_hover background: rgba(0, 255, 133, 0.22) - z-index: 99 + z-index: 97 /* ---- SNIPPET MANIPULATOR ---- */ -.oe_snippet_manipulator +.oe_overlay display: block position: absolute - background: rgba(153, 0, 255, 0.1) + background: transparent //rgba(153, 0, 255, 0.05) border-radius: 3px - border: solid 1px rgba(153, 0, 255, 0.5) opacity: 0 transition: opacity 100ms linear - & > .oe_handle - display: none - &:hover,&.oe_hover + box-sizing: border-box + pointer-events: none + .oe_handle + display: none !important + &.oe_hover opacity: 1 - & > .oe_handle - display: block + cursor: pointer + &.oe_selected, &.oe_active + opacity: 1 + background: transparent + border-style: dashed + border-width: 1px + box-shadow: 0px 0px 0px 1px rgba(255,255,255,0.3), 0px 0px 0px 1px rgba(255,255,255,0.3) inset + &.oe_selected + border-color: rgba(0, 153, 255, 0.5) + &.oe_active + border-color: rgba(153, 0, 255, 0.5) + .oe_handle + display: block !important + pointer-events: auto + + .oe_overlay_options + display: none + z-index: 99 + .oe_option + pointer-events: auto + position: absolute + &.n + top: 0px + &.e + right: 0px + &.s + bottom: 0px + &.w + left: 0px + &.oe_active .oe_overlay_options + display: block .oe_handle + pointer-events: auto position: absolute top: 50% left: 50% display: block - background: rgba(255, 255, 255, 1) - border: solid 1px rgba(153, 0, 255, 0.5) box-sizing: border-box - border-radius: 2px - width: 16px - height: 16px - margin: -8px - cursor: move - -webkit-transition: background 100ms linear - &:hover - background: rgb(153, 0, 255) + width: 8px + height: 8px + margin: -4px -.oe_handle.nw,.oe_handle.n,.oe_handle.ne +.oe_handle.n + left: 2px + width: 100% top: 0px -.oe_handle.ne,.oe_handle.e,.oe_handle.se + cursor: s-resize +.oe_handle.e left: auto + top: 2px + height: 100% right: 0px -.oe_handle.sw,.oe_handle.s,.oe_handle.se + cursor: w-resize +.oe_handle.s top: auto + left: 2px + width: 100% bottom: 0px -.oe_handle.nw,.oe_handle.w,.oe_handle.sw + cursor: n-resize +.oe_handle.w + top: 2px + height: 100% left: 0px + cursor: e-resize + +.s-resize-important, .s-resize-important * + cursor: s-resize !important +.n-resize-important, .n-resize-important * + cursor: n-resize !important +.e-resize-important, .e-resize-important * + cursor: e-resize !important +.w-resize-important, .w-resize-important * + cursor: w-resize !important + /* ---- MOBILE PREVIEW ---- */ @@ -280,3 +342,45 @@ $remove_color: $icon_close color: $remove_color .oe_seo_suggestion cursor: pointer + +/* ---- ACE EDITOR ---- */ + +$ace_width: 720px +$editorbar_height: 40px +// TODO Fix => might break with themes +$navbar_height: 51px + +.oe_ace_view_editor + transition: all 0.2s ease-in + opacity: 0.3 + position: fixed + top: $navbar_height + right: 0 + z-index: 1000 + height: 100% + .oe_ace_view_editor_title + width: 100% + padding-top: 4px + padding-left: 4px + height: $editorbar_height + background: #2F3129 + .oe_view_list + width: 50% + height: $editorbar_height - 8px + .btn + height: $editorbar_height - 8px + padding: 0 4px 0 4px + .ace_editor + position: absolute + top: $editorbar_height + right: 0 + bottom: $navbar_height + left: 0 + width: $ace_width + &:hover + opacity: 1 + &.oe_ace_open + width: $ace_width + &.oe_ace_closed + width: 0 + opacity: 0 diff --git a/addons/website/static/src/css/snippets.css b/addons/website/static/src/css/snippets.css index c9b726affde..5b7d404a1fd 100644 --- a/addons/website/static/src/css/snippets.css +++ b/addons/website/static/src/css/snippets.css @@ -88,7 +88,7 @@ padding: 8px; } -.oe_snippet_editor .oe_snippet.oe_selected{ +.oe_snippet_editor .oe_snippet.oe_selected, .oe_snippet_editor .oe_snippet.oe_active{ border: 2px solid rgb(151, 137, 255); box-shadow: 0px 3px 17px rgba(99, 53, 150, 0.59); @@ -134,7 +134,7 @@ box-shadow: 0px 1px 3px rgb(90,90,90) inset; border: 0px; } -.oe_snippet_editor .oe_snippet.oe_new.oe_selected{ +.oe_snippet_editor .oe_snippet.oe_new.oe_selected, .oe_snippet_editor .oe_snippet.oe_new.oe_active{ box-shadow: 0px 2px 0px 0px rgb(151,137,255) inset, 0px 3px 17px rgba(99, 53, 150, 0.59) inset; } .oe_snippet_editor .oe_snippet.oe_new > *{ @@ -176,3 +176,34 @@ margin-left: 0px !important; } */ + +#website-top-edit li.oe_snippet_editorbar { + 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 li.oe_snippet_editorbar > * { + display: inline-block; + height: 22px; + padding: 4px 6px; + outline: 0; + border: 0; +} +#website-top-edit li.oe_snippet_editorbar a.button .icon { + cursor: inherit; + background-repeat: no-repeat; + margin-top: 1px; + width: 16px; + height: 16px; + display: inline-block; +} + +[data-snippet-id] { + min-height: 10px; + min-width: 10px; +} \ No newline at end of file diff --git a/addons/website/static/src/css/website.css b/addons/website/static/src/css/website.css index 3c8aee57ef2..a016cbc150b 100644 --- a/addons/website/static/src/css/website.css +++ b/addons/website/static/src/css/website.css @@ -127,7 +127,7 @@ footer { margin-bottom: 0px; } -#myCarousel .item { +.carousel .item { color: white; } @@ -193,6 +193,8 @@ a[data-publish][data-publish='on']:hover .css_published { [data-publish]:not(a) { position: relative; overflow: visible; + /*&:hover > [data-publish] */ + /* display: block */ } [data-publish]:not(a) > [data-publish] { position: absolute; @@ -200,6 +202,3 @@ a[data-publish][data-publish='on']:hover .css_published { top: -10px; display: none; } -[data-publish]:not(a):hover > [data-publish] { - display: block; -} diff --git a/addons/website/static/src/css/website.sass b/addons/website/static/src/css/website.sass index b5468a4b0e8..a4b07b2969c 100644 --- a/addons/website/static/src/css/website.sass +++ b/addons/website/static/src/css/website.sass @@ -95,7 +95,7 @@ footer // .nav > li a // display: block -#myCarousel .item +.carousel .item color: white /* ---- HOMEPAGE THEME CUSTOMIZATION ---- */ @@ -156,5 +156,5 @@ a[data-publish] right: -6px top: -10px display: none - &:hover > [data-publish] - display: block \ No newline at end of file + /*&:hover > [data-publish]*/ + /* display: block*/ \ No newline at end of file diff --git a/addons/website/static/src/js/website.ace.js b/addons/website/static/src/js/website.ace.js new file mode 100644 index 00000000000..bc7c58575db --- /dev/null +++ b/addons/website/static/src/js/website.ace.js @@ -0,0 +1,157 @@ +(function () { + 'use strict'; + + var globalEditor; + + var website = openerp.website; + website.templates.push('/website/static/src/xml/website.ace.xml'); + + website.EditorBar.include({ + events: _.extend({}, website.EditorBar.prototype.events, { + 'click a[data-action=ace]': 'launchAce', + }), + launchAce: function () { + if (globalEditor) { + globalEditor.open(); + } else { + globalEditor = new website.ace.ViewEditor(this); + globalEditor.appendTo($(document.body)); + } + }, + }); + + website.ace = {}; + + website.ace.XmlDocument = openerp.Class.extend({ + init: function (text) { + this.xml = text; + }, + isWellFormed: function () { + try { + if (document.implementation.createDocument) { + var dom = new DOMParser().parseFromString(this.xml, "text/xml"); + return dom.getElementsByTagName("parsererror").length === 0; + } else if (window.ActiveXObject) { + // TODO test in IE + var msDom = new ActiveXObject("Microsoft.XMLDOM"); + msDom.async = false; + return !msDom.loadXML(this.xml); + } else { + throw Error("Not implemented"); + } + } catch(exception) { + console.log(exception); + } + }, + format: function () { + return vkbeautify.xml(this.xml, 4); + }, + }); + + website.ace.ViewOption = openerp.Widget.extend({ + template: 'website.ace_view_option', + init: function (parent, options) { + this.view_id = options.id; + this.view_name = options.name; + this._super(parent); + }, + }); + + website.ace.ViewEditor = openerp.Widget.extend({ + template: 'website.ace_view_editor', + events: { + 'change #ace-view-list': 'displayView', + 'click button[data-action=save]': 'saveView', + 'click button[data-action=format]': 'formatXml', + 'click button[data-action=close]': 'close', + }, + start: function () { + var self = this; + var viewId = $(document.documentElement).data('view-xmlid'); + openerp.jsonRpc('/website/customize_template_get', 'call', { + 'xml_id': viewId, + 'optional': false, + }).then(function (views) { + self.loadViewList.call(self, views); + }); + this.$el.hover(); + }, + selectedViewId: function () { + return parseInt(this.$('#ace-view-list').val(), 10); + }, + loadViewList: function (views) { + var activeViews = _.filter(views, function (view) { + return view.active; + }); + var $viewList = this.$('#ace-view-list'); + _.each(activeViews, function (view) { + if (view.id) { + new website.ace.ViewOption(this, view).appendTo($viewList); + } + }); + var editor = ace.edit(this.$('#ace-view-editor')[0]); + editor.setTheme("ace/theme/monokai"); + this.aceEditor = editor; + this.open(); + }, + displayView: function () { + var editor = this.aceEditor; + openerp.jsonRpc('/web/dataset/call', 'call', { + model: 'ir.ui.view', + method: 'read', + args: [[this.selectedViewId()], ['arch'], website.get_context()], + }).then(function(result) { + if (result && result.length > 0) { + var xml = new website.ace.XmlDocument(result[0].arch) + var editingSession = new ace.EditSession(xml.xml); + editingSession.setMode("ace/mode/xml"); + editingSession.setUndoManager(new ace.UndoManager()); + editor.setSession(editingSession); + } else { + throw Error("Could not load view XML"); + } + }); + }, + formatXml: function () { + var xml = new website.ace.XmlDocument(this.aceEditor.getValue()); + this.aceEditor.setValue(xml.format()); + }, + saveView: function () { + var self = this; + var xml = new website.ace.XmlDocument(this.aceEditor.getValue()); + if (xml.isWellFormed()) { + openerp.jsonRpc('/web/dataset/call', 'call', { + model: 'ir.ui.view', + method: 'write', + args: [[this.selectedViewId()], { 'arch': xml.xml }, website.get_context()], + }).then(function(result) { + self.reloadPage(); + }).fail(function (error) { + self.displayError(error); + }); + } else { + self.displayError(); + } + }, + reloadPage: function () { + // TODO Reload { header + div#wrap + footer } only + window.location.reload(); + }, + displayError: function (error) { + // TODO Improve feedback (e.g. update 'Save' button + tooltip) + alert("Malformed XML document"); + }, + open: function () { + this.$el.removeClass('oe_ace_closed').addClass('oe_ace_open'); + this.displayView(); + }, + close: function () { + var self = this; + this.$el.bind('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () { + globalEditor = null; + self.destroy.call(self); + }).removeClass('oe_ace_open').addClass('oe_ace_closed'); + }, + }); + +})(); diff --git a/addons/website/static/src/js/website.editor.js b/addons/website/static/src/js/website.editor.js index 867cef2ed13..ebcfb98635d 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -146,7 +146,11 @@ var editor = new website.EditorBar(); var $body = $(document.body); - editor.prependTo($body); + editor.prependTo($body).then(function () { + if (location.search.indexOf("unable_editor") >= 0) { + editor.edit(); + } + }); $body.css('padding-top', '50px'); // Not working properly: editor.$el.outerHeight()); }; /* ----- TOP EDITOR BAR FOR ADMIN ---- */ @@ -176,10 +180,11 @@ }); // Adding Static Menus menu.append('
  • Change Theme
  • '); + menu.append('
  • Advanced view editor
  • '); } ); }); - menu.on('click', 'a', function (event) { + menu.on('click', 'a[data-action!=ace]', function (event) { var view_id = $(event.currentTarget).data('view-id'); openerp.jsonRpc('/website/customize_template_toggle', 'call', { 'view_id': view_id @@ -251,7 +256,7 @@ }); }).value(); return $.when.apply(null, defs).then(function () { - window.location.reload(); + window.location.href = window.location.href.replace(/unable_editor(=[^&]*)?|#.*/g, ''); }); }, /** @@ -268,11 +273,11 @@ return openerp.jsonRpc('/web/dataset/call', 'call', { model: 'ir.ui.view', method: 'save', - args: [element.data('oe-id'), data, element.data('oe-xpath')], + args: [element.data('oe-id'), data, element.data('oe-xpath'), website.get_context()], }); }, cancel: function () { - window.location.reload(); + window.location.href = window.location.href.replace(/unable_editor(=[^&]*)?|#.*/g, ''); }, }); @@ -284,6 +289,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 @@ -292,6 +302,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 () { @@ -337,8 +348,11 @@ extraPlugins: 'sharedspace,customdialogs,tablebutton,oeref', // Place toolbar in controlled location sharedSpaces: { top: 'oe_rte_toolbar' }, - toolbar: [ - {name: 'basicstyles', items: [ + toolbar: [{ + name: 'clipboard', items: [ + "Undo" + ]},{ + name: 'basicstyles', items: [ "Bold", "Italic", "Underline", "Strike", "Subscript", "Superscript", "TextColor", "BGColor", "RemoveFormat" ]},{ @@ -402,15 +416,11 @@ website.editor.LinkDialog = website.editor.Dialog.extend({ template: 'website.editor.dialog.link', events: _.extend({}, website.editor.Dialog.prototype.events, { - 'change .url-source': function (e) { this.changed($(e.target)); }, - 'click div.existing a': 'select_page', }), init: function (editor) { this._super(editor); // url -> name mapping for existing pages this.pages = Object.create(null); - // name -> url mapping for the same - this.pages_by_name = Object.create(null); }, start: function () { var element; @@ -471,24 +481,24 @@ }, save: function () { var self = this, _super = this._super.bind(this); - var $e = this.$('.url-source').filter(function () { return !!this.value; }); + var $active_tab = this.$('.tab-pane.active'); + + var $e = $active_tab.find('.url-source'); var val = $e.val(), done = $.when(); - if ($e.hasClass('email-address')) { + if ($active_tab.is('#link-email')) { this.make_link('mailto:' + val, false, val); - } else if ($e.hasClass('pages')) { - // ``val`` is the *name* of the page - var url = this.pages_by_name[val]; - if (!url) { - // Create the page, get the URL back - done = $.get(_.str.sprintf( - '/pagenew/%s?noredirect', encodeURIComponent(val))) - .then(function (response) { - url = response; - }); - } + } else if ($active_tab.is('#link-existing')) { + self.make_link(val, false, this.pages[val]); + } else if ($active_tab.is('#link-new')) { + // Create the page, get the URL back + done = $.get(_.str.sprintf( + '/pagenew/%s?noredirect', encodeURIComponent(val))) + .then(function (response) { + val = response; + }); done.then(function () { - self.make_link(url, false, val); + self.make_link(val, false); }); } else { this.make_link(val, this.$('input.window-new').prop('checked')); @@ -502,36 +512,19 @@ var match, $control; if (match = /(mailto):(.+)/.exec(href)) { - $control = this.$('input.email-address').val(match[2]); + $control = this.$('#link-email input').val(match[2]); } else if(href in this.pages) { - $control = this.$('input.pages').val(this.pages[href]); - } - if (!$control) { - $control = this.$('input.url').val(href); + $control = this.$('#link-existing select').val(href); + } else { + $control = this.$('#link-external input:first').val(href); } - this.changed($control); + var tab_name = $control.closest('.tab-pane').attr('id'); + this.$('.nav a[href="#' + tab_name + '"]').tab('show'); this.$('input.window-new').prop( 'checked', this.element.getAttribute('target') === '_blank'); }, - changed: function ($e) { - $e.closest('li.list-group-item').addClass('active') - .siblings().removeClass('active'); - this.$('.url-source').not($e).val(''); - }, - /** - * Selected an existing page in dropdown - */ - select_page: function (e) { - e.preventDefault(); - e.stopPropagation(); - var $target = $(e.target); - this.$('input.pages').val($target.text()).change(); - // No #dropdown('close'), and using #dropdown('toggle') sur - // #closest('.dropdown') makes the dropdown not work correctly - $target.closest('.open').removeClass('open') - }, /** * CKEDITOR.plugins.link.getSelectedLink ignores the editor's root, * if the editor is set directly on a link it will thus not work. @@ -554,37 +547,50 @@ model: 'website', method: 'list_pages', args: [], - kwargs: {} + kwargs: { + context: website.get_context() + }, }); }, fill_pages: function (results) { var self = this; - var $pages = this.$('div.existing ul').empty(); + var pages = this.$('#link-existing-select')[0]; _(results).each(function (result) { self.pages[result.url] = result.name; - self.pages_by_name[result.name] = result.url; - var $link = $('').attr('href', result.url).text(result.name); - $('
  • ').append($link).appendTo($pages); + + pages.options[pages.options.length] = + new Option(result.name, result.url); }); }, }); website.editor.ImageDialog = website.editor.Dialog.extend({ template: 'website.editor.dialog.image', events: _.extend({}, website.editor.Dialog.prototype.events, { - 'change .url-source': function (e) { this.changed($(e.target)); }, 'click button.filepicker': function () { this.$('input[type=file]').click(); }, 'change input[type=file]': 'file_selection', 'change input.url': 'preview_image', + 'change select.image-style': 'preview_image', 'click .existing-attachments a': 'select_existing', }), start: function () { var selection = this.editor.getSelection(); var el = selection && selection.getSelectedElement(); this.element = null; + + var $select = this.$('.image-style'); + var $options = $select.children(); + this.image_styles = $options.map(function () { return this.value; }).get(); + if (el && el.is('img')) { this.element = el; + _(this.image_styles).each(function (style) { + if (el.hasClass(style)) { + $select.val(style); + } + }); + // set_image should follow setup of image style this.set_image(el.getAttribute('src')); } @@ -594,6 +600,7 @@ }, save: function () { var url = this.$('input.url').val(); + var style = this.$('.image-style').val(); var element, editor = this.editor; if (!(element = this.element)) { element = editor.document.createElement('img'); @@ -607,7 +614,10 @@ }, 0); } element.setAttribute('src', url); - this._super(); + $(element.$).removeClass(this.image_styles.join(' ')); + if (style) { element.addClass(style); } + + return this._super(); }, /** @@ -645,7 +655,10 @@ var image = this.$('input.url').val(); if (!image) { return; } - this.$('img.image-preview').attr('src', image); + this.$('img.image-preview') + .attr('src', image) + .removeClass(this.image_styles.join(' ')) + .addClass(this.$('select.image-style').val()); }, fetch_existing: function () { @@ -658,6 +671,7 @@ fields: ['name'], domain: [['res_model', '=', 'ir.ui.view']], order: 'name', + context: website.get_context(), } }); }, diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js index d45e6b4b418..1cfd076f8e3 100644 --- a/addons/website/static/src/js/website.js +++ b/addons/website/static/src/js/website.js @@ -9,6 +9,13 @@ '/website/static/src/xml/website.xml' ]; + website.get_context = function (dict) { + var html = document.documentElement; + return _.extend({ + lang: html.getAttribute('lang').replace('-', '_') + }, dict); + }; + /* ----- TEMPLATE LOADING ---- */ website.add_template = function(template) { templates.push(template); @@ -16,48 +23,19 @@ website.load_templates = function(templates) { var def = $.Deferred(); var count = templates.length; - templates.forEach(function(t) { - openerp.qweb.add_template(t, function(err) { - if (err) { - def.reject(); - } else { - count--; - if (count < 1) { - def.resolve(); + + var dones = _(templates).map(function (t) { + return new $.Deferred(function (d) { + openerp.qweb.add_template(t, function(err) { + if (err) { + d.reject(err); + } else { + d.resolve(); } - } + }); }); }); - return def; - }; - - website.mutations = { - darken: function($el){ - var $parent = $el.parent(); - if($parent.hasClass('dark')){ - $parent.replaceWith($el); - }else{ - $el.replaceWith($("
    ").append($el.clone())); - } - }, - vomify: function($el){ - var hue=0; - var beat = false; - var a = setInterval(function(){ - $el.css({'-webkit-filter':'hue-rotate('+hue+'deg)'}); hue += 5; - }, 10); - setTimeout(function(){ - clearInterval(a); - setInterval(function(){ - var filter = 'hue-rotate('+hue+'deg)'+ (beat ? ' invert()' : ''); - $(document.documentElement).css({'-webkit-filter': filter}); hue += 5; - if(hue % 35 === 0){ - beat = !beat; - } - }, 10); - },5000); - $('').appendTo($el); - }, + return $.when.apply(null, dones); }; diff --git a/addons/website/static/src/js/website.seo.js b/addons/website/static/src/js/website.seo.js index 61561964d67..a4860fcb60b 100644 --- a/addons/website/static/src/js/website.seo.js +++ b/addons/website/static/src/js/website.seo.js @@ -27,7 +27,7 @@ }, headers: function (tag) { return $('#wrap '+tag).map(function () { - return $(this).text(); + return $(this).text(); }); }, images: function () { @@ -70,11 +70,11 @@ perfect: 'success', advised: 'primary', used: 'warning', - new: 'default', + custom: 'default', }, init: function (parent, options) { this.keyword = options.keyword; - this.type = this.types[options.type || 'default']; + this.type = this.types[options.type] || 'default'; this._super(parent); }, destroy: function () { @@ -88,39 +88,33 @@ maxKeywords: 10, keywords: function () { var result = []; - this.$el.find('.js_seo_keyword').each(function () { + this.$('.js_seo_keyword').each(function () { result.push($(this).data('keyword')); }); return result; }, - suggestions: function () { - // TODO Refactor (Ugly) - return $('.js_seo_suggestion').map(function () { - return $(this).data('keyword'); - }); - }, isKeywordListFull: function () { return this.keywords().length >= this.maxKeywords; }, isExistingKeyword: function (word) { return _.contains(this.keywords(), word); }, - add: function (candidate) { + add: function (candidate, suggested) { var self = this; // TODO Refine var word = candidate ? candidate.replace(/[,;.:<>]+/g, " ").replace(/ +/g, " ").trim() : ""; - if (word && !this.isKeywordListFull() && !this.isExistingKeyword(word)) { - var type = _.contains(this.suggestions(), word) ? 'advised' : 'new'; - var keyword = new website.seo.Keyword(this, { + if (word && !self.isKeywordListFull() && !self.isExistingKeyword(word)) { + var type = suggested ? 'advised' : 'custom'; + var keyword = new website.seo.Keyword(self, { keyword: word, type: type, }); keyword.on('removed', null, function () { self.trigger('list-not-full'); }); - keyword.appendTo(this.$el); + keyword.appendTo(self.$el); } - if (this.isKeywordListFull()) { + if (self.isKeywordListFull()) { self.trigger('list-full'); } }, @@ -148,6 +142,46 @@ }, }); + website.seo.SuggestionList = openerp.Widget.extend({ + template: 'website.seo_list', + init: function (parent, companyName) { + this.companyName = companyName; + this._super(parent); + }, + start: function () { + var self = this; + self.$el.append("Loading..."); + // cf. https://github.com/ddm/seo + // TODO Try with /recommend/ + $.getJSON("http://seo.eu01.aws.af.cm/suggest/"+encodeURIComponent(self.companyName), function (list) { + self.$el.empty(); + // TODO Improve algorithm + Ajust based on custom user keywords + var nameRegex = new RegExp(self.companyName, "gi"); + var cleanList = _.map(list, function removeCompanyName (word) { + return word.replace(nameRegex, "").trim(); + }); + // TODO Order properly ? + cleanList.push(self.companyName); + _.each(_.uniq(cleanList), function (keyword) { + if (keyword) { + var suggestion = new website.seo.Suggestion(self, { + keyword: keyword + }); + suggestion.on('selected', self, function () { + self.trigger('selected', keyword); + }); + suggestion.appendTo(self.$el); + } + }); + }); + }, + suggestions: function () { + return this.$('.js_seo_suggestion').map(function () { + return $(this).data('keyword'); + }); + }, + }); + website.seo.Image = openerp.Widget.extend({ template: 'website.seo_image', init: function (parent, options) { @@ -167,7 +201,7 @@ }, images: function () { var result = []; - this.$el.find('input').each(function () { + this.$('input').each(function () { var $input = $(this); result.push({ src: $input.attr('src'), @@ -192,16 +226,15 @@ maxTitleSize: 65, maxDescriptionSize: 155, start: function () { + var self = this; var $modal = this.$el; var pageParser = new website.seo.PageParser(); $modal.find('.js_seo_page_url').text(pageParser.url()); $modal.find('input[name=seo_page_title]').val(pageParser.title()); this.suggestImprovements(pageParser); - this.displayKeywordSuggestions(pageParser); this.imageList = new website.seo.ImageList(this); this.imageList.appendTo($modal.find('.js_seo_image_list')); this.keywordList = new website.seo.KeywordList(this); - this.keywordList.appendTo($modal.find('.js_seo_keywords_list')); this.keywordList.on('list-full', null, function () { $modal.find('input[name=seo_page_keywords]') .attr('readonly', "readonly") @@ -215,6 +248,13 @@ $modal.find('button[data-action=add]') .prop('disabled', false).removeClass('disabled'); }); + this.keywordList.appendTo($modal.find('.js_seo_keywords_list')); + var companyName = pageParser.company().toLowerCase(); + this.suggestionList = new website.seo.SuggestionList(this, companyName); + this.suggestionList.on('selected', self, function (word) { + self.acceptSuggestion(word); + }); + this.suggestionList.appendTo($modal.find('.js_seo_suggestions_list')); $modal.modal(); }, suggestImprovements: function (parser) { @@ -224,7 +264,7 @@ new website.seo.Tip(this, { message: message, type: type - }).appendTo(self.$el.find('.js_seo_tips')); + }).appendTo(self.$('.js_seo_tips')); } var pageParser = parser || new website.seo.PageParser(); if (!pageParser.headers('h1').length === 0) { @@ -247,50 +287,24 @@ displayTip("Your page makup is appropriate for search engines.", 'success'); } }, - displayKeywordSuggestions: function (pageParser) { - var $modal = this.$el; - var self = this; - $modal.find('.js_seo_company_suggestions').append("Loading..."); - var companyName = pageParser.company().toLowerCase(); - // cf. https://github.com/ddm/seo - // TODO Try with /recommend/ - $.getJSON("http://seo.eu01.aws.af.cm/suggest/"+encodeURIComponent(companyName), function (list) { - $modal.find('.js_seo_company_suggestions').empty(); - // TODO Improve algorithm + Ajust based on custom user keywords - var nameRegex = new RegExp(companyName, "gi"); - var cleanList = _.map(list, function removeCompanyName (word) { - return word.replace(nameRegex, "").trim(); - }); - // TODO Order properly ? - cleanList.push(companyName); - _.each(_.uniq(cleanList), function (keyword) { - if (keyword) { - var suggestion = new website.seo.Suggestion(self, { - keyword: keyword - }); - suggestion.on('selected', self, function () { - self.addKeyword(keyword); - }); - suggestion.appendTo($modal.find('.js_seo_company_suggestions')); - } - }); - }); - }, confirmKeyword: function (e) { if (e.keyCode == 13) { - this.addKeyword(this.$el.find('input[name=seo_page_keywords]').val()); + this.addKeyword(); } }, addKeyword: function (word) { - var $input = this.$el.find('input[name=seo_page_keywords]'); + var $input = this.$('input[name=seo_page_keywords]'); var keyword = _.isString(word) ? word : $input.val(); - this.keywordList.add(keyword); + this.keywordList.add(keyword, false); $input.val(""); }, + acceptSuggestion: function (suggestion) { + this.keywordList.add(suggestion, true); + }, update: function () { var data = { - title: this.$el.find('input[name=seo_page_title]').val(), - description: this.$el.find('input[name=seo_page_title]').val(), + title: this.$('input[name=seo_page_title]').val(), + description: this.$('input[name=seo_page_title]').val(), keywords: this.keywordList.keywords(), images: this.imageList.images(), }; diff --git a/addons/website/static/src/js/website.snippets.js b/addons/website/static/src/js/website.snippets.js index 5ebf7aecc8d..a6f4428b575 100644 --- a/addons/website/static/src/js/website.snippets.js +++ b/addons/website/static/src/js/website.snippets.js @@ -2,99 +2,153 @@ '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(); - this.snippets.appendTo($(document.body)); + }.bind(this)); }, + edit: function () { + window.snippets = this.snippets = new website.snippet.BuildingBlock(this); + this.snippets.appendTo($(document.body)); + return this._super.apply(this, arguments); + }, 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; - $('.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); - } - 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); - } - }); - $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('
    '); - break; - case 'image_right': - $('.content', $container).attr("class", "content col-md-6") - .after('
    '); - break; - } - $(this).val(""); - }); - $options.show(); - }); + save: function () { + $('body').trigger("save"); + this._super(); }, }); /* ----- SNIPPET SELECTOR ---- */ - website.Snippets = openerp.Widget.extend({ + + website.snippet = {}; + + // puts $el at the same absolute position as $target + website.snippet.cover_target = function ($el, $target){ + $el.css({ + 'position': 'absolute', + 'width': $target.outerWidth(), + 'height': $target.outerHeight(), + }); + $el.css($target.offset()); + }; + website.snippet.is_empty_dom = function ($dom) { + if ($.trim($dom.text()) !== '') return false; + if ($dom.find('area, base, command, embed, hr, img, input, keygen').length) return false; + return true; + }; + + website.snippet.BuildingBlock = openerp.Widget.extend({ template: 'website.snippets', - init: function () { + activeSnippets: [], + init: function (parent) { + this.parent = parent; this._super.apply(this, arguments); + if(!$('#oe_manipulators').length){ + $("
    ").appendTo('body'); + } + this.$active_snipped_id = false; + this.active = false; + this.parent_of_editable_box = "body > :not(:has(#website-top-view)):not(#oe_manipulators):not(#oe_snippets) "; }, start: function() { var self = this; - $.ajax({ - type: "GET", - url: "/page/website.snippets", - dataType: "text", - success: function(snippets){ - self.$el.html(snippets); - self.start_snippets(); - }, + var snippets_template = []; + _.each(openerp.qweb.compiled_templates, function (val, key) { + if (key.indexOf('website.snippets.') === 0) { + var $snippet = $(openerp.qweb.render(key)).addClass("oe_snippet"); + if ($snippet.data("action")) { + self.$el.find('#snippet_' + $snippet.data('category')).append($snippet); + self.make_snippet_draggable($snippet); + } + } }); + this.bind_selected_manipulator(); + this.bind_snippet_click_editor(); }, + + bind_snippet_click_editor: function () { + var self = this; + var snipped_event_flag = false; + $("body").on('click', "[data-snippet-id]", function (event) { + if (!self.active || snipped_event_flag) { + return; + } + var $target = $(event.currentTarget); + if (self.$active_snipped_id) { + if (self.$active_snipped_id[0] === $target[0] || $.contains(self.$active_snipped_id, $target[0])) { + var $parent = self.$active_snipped_id.parents("[data-snippet-id]:first"); + if ($parent.length) { + $target = $parent; + } + } + } + snipped_event_flag = true; + setTimeout(function () {snipped_event_flag = false;}, 0); + self.make_active($target); + }); + $("body > :not(:has(#website-top-view)):not(#oe_manipulators)").on('click', function (ev) { + if (!snipped_event_flag && self.$active_snipped_id && !self.$active_snipped_id.parents("[data-snippet-id]:first")) { + self.make_active(false); + } + }); + }, + snippet_blur: 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(); + } + } + }, + snippet_focus: 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(); + } + } + }, + make_active: function ($snipped_id) { + if ($snipped_id && this.$active_snipped_id && this.$active_snipped_id.get(0) === $snipped_id.get(0)) { + return; + } + if (this.$active_snipped_id) { + this.snippet_blur(this.$active_snipped_id); + } + if ($snipped_id) { + this.$active_snipped_id = $snipped_id; + this.create_overlay(this.$active_snipped_id); + this.snippet_focus($snipped_id); + } else { + self.$active_snipped_id = false; + } + }, + create_overlay: function ($snipped_id) { + if (typeof $snipped_id.data("snippet-editor") === 'undefined') { + this.activate_overlay_zones($snipped_id); + var editor = website.snippet.editorRegistry[$snipped_id.data("snippet-id")] || website.snippet.Editor; + $snipped_id.data("snippet-editor", new editor(this, $snipped_id)); + } + website.snippet.cover_target($snipped_id.data('overlay'), $snipped_id); + }, + path_eval: function(path){ var obj = window; path = path.split('.'); @@ -104,26 +158,54 @@ return obj; }, - // setup widget and drag and drop - start_snippets: function(){ + bind_selected_manipulator: function () { + var self = this; + var $selected_target = null; + $("body").mouseover(function (event){ + if (!self.active) { + return; + } + if (self.editor_busy) { + if($selected_target){ + $selected_target.data('overlay').removeClass('oe_selected'); + } + } + var $target = $(event.srcElement).parents("[data-snippet-id]:first"); + if($target.length && $selected_target != $target){ + if($selected_target){ + $selected_target.data('overlay').removeClass('oe_selected'); + } + $selected_target = $target; + self.create_overlay($target); + $target.data('overlay').addClass('oe_selected'); + } + }); + }, + + // 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'); - - self.deactivate_snippet_manipulators(); + var action = $snippet.data('action'); 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')); + } else if( action === 'mutate' ){ + + var $targets = self.activate_overlay_zones($snippet.data('selector')); + $targets.each(function(){ + var $clone = $(this).data('overlay').clone(); + $clone.addClass("oe_drop_zone").data('target', $(this)); + $(this).data('overlay').after($clone); + }); + } $('.oe_drop_zone').droppable({ @@ -137,23 +219,66 @@ $(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') ); + var snipped_id = $snippet.data('snippet-id'); + + if (!$(".oe_drop_zone.oe_hover").length) { + return false; + } + + var $target = false; + if($snippet.find('.oe_snippet_body').size()){ + var $toInsert = $snippet.find('.oe_snippet_body').clone(); + $toInsert.removeClass('oe_snippet_body'); + $toInsert.attr('data-snippet-id', snipped_id); + $(".oe_drop_zone.oe_hover").after($toInsert); + $target = $toInsert; + } else { + $target = $(".oe_drop_zone.oe_hover").data('target'); + } + + if (website.snippet.editorRegistry[snipped_id]) { + self.create_overlay($target); + var snippet = new website.snippet.editorRegistry[snipped_id](self, $target); + snippet.build_snippet($target); } }, }); }, stop: function(){ - self.deactivate_zones(); - self.activate_snippet_manipulators(); + $('.oe_drop_zone').droppable('destroy').remove(); }, }); - }, + + // return the original snippet in the editor bar from a snippet id (string) + get_snippet_from_id: function(id){ + return $('.oe_snippet').filter(function(){ + return $(this).data('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.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 +286,21 @@ // 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 child_selector = selector.childs ? this.parent_of_editable_box + (selector.childs).split(",").join(this.parent_of_editable_box) : false; + var sibling_selector = selector.siblings ? this.parent_of_editable_box + (selector.siblings).split(",").join(this.parent_of_editable_box) : false; 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++ ){ + 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); } @@ -189,14 +310,10 @@ } } - // 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 + var $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones count += $zones.length; $zones.remove(); @@ -206,33 +323,38 @@ }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; } } }, - deactivate_zones: function(){ - $('.oe_drop_zone').remove(); - }, + // generate drop zones covering the elements selected by the selector + // we generate overlay drop zones only to get an idea of where the snippet are, the drop activate_overlay_zones: function(selector){ - var $targets = $(selector); + selector = selector || '[data-snippet-id]'; + if (typeof selector === 'string') + selector = this.parent_of_editable_box + selector.split(",").join(this.parent_of_editable_box); + var $targets = $(selector); + function is_visible($el){ return $el.css('display') != 'none' && $el.css('opacity') != '0' @@ -247,94 +369,542 @@ var parents = $(this).parents().filter(function(){ return !is_visible($(this)); }); return parents.length === 0; }); + + $targets.each(function () { + var $target = $(this); + if (!$target.data('overlay')) { + var $zone = $( + '
    '+ + '
    '+ + '
      '+ + '
        '+ + '
          '+ + '
          '+ + '
          '); + $zone.appendTo('#oe_manipulators'); + $zone.data('target',$target); + $target.data('overlay',$zone); - 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(); - } + $target.on("DOMNodeInserted DOMNodeRemoved DOMSubtreeModified", function () { + website.snippet.cover_target($zone, $target); }); - }); + } + website.snippet.cover_target($target.data('overlay'), $target); + }); + return $targets; + }, - } - }, - deactivate_snippet_manipulators: function(){ - $('.oe_snippet_manipulator').remove(); - }, toggle: function(){ - if(this.$el.hasClass('hide')){ + this.active = !this.active; + if(this.active){ this.$el.removeClass('hide'); - this.activate_snippet_manipulators(); - }else{ + this.activate_overlay_zones(); + } else { this.$el.addClass('hide'); - this.deactivate_snippet_manipulators(); } }, }); -})(); + + + website.snippet.animationRegistry = {}; + website.snippet.Animation = 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 + * Read data saved for your snippet animation. + */ + getOptions: function () { + var options = this.$el.data("snippet-options"); + return options ? JSON.parse(options) : undefined; + }, + }); + + + $(document).ready(function () { + $("[data-snippet-id]").each(function() { + var $snipped_id = $(this); + if (typeof $snipped_id.data("snippet-view") === 'undefined' && + website.snippet.animationRegistry[$snipped_id.data("snippet-id")]) { + $snipped_id.data("snippet-view", new website.snippet.animationRegistry[$snipped_id.data("snippet-id")]($snipped_id)); + } + }); + }); + + + website.snippet.editorRegistry = {}; + website.snippet.Editor = openerp.Class.extend({ + init: function (parent, dom) { + this.parent = parent; + this.$target = $(dom); + this.$overlay = this.$target.data('overlay'); + this._readXMLData(); + this.start(); + }, + + /* + * _readXMLData + * Read data XML and set value into: + * this.$el : + * all xml data + * this.$overlay : + * Dom hover the $target who content options + * this.$editor : + * content of .oe_snippet_options + * Displayed into the overlay options on focus + * this.$thumbnail : + * content of .oe_snippet_thumbnail + * Displayed in bottom editor menu, when the user click on "Building Blocks" + * this.$body : + * content of .oe_snippet_body + * Insert into the view when the thumbnail is drag and droped into a drop zone + */ + _readXMLData: function() { + if (this.template) { + this.$el = $(openerp.qweb.render(this.template, {widget: this}).trim()); + this.$editor = this.$el.find(".oe_snippet_options"); + this.$thumbnail = this.$el.find(".oe_snippet_thumbnail"); + this.$body = this.$el.find(".oe_snippet_body"); + } + }, + + /* + * start + * This method is called after init and _readXMLData + */ + start: function () { + if(this.$editor) this.$editor.prependTo(this.$overlay.find(".oe_overlay_options .oe_option.n.w")); + }, + + /* + * build_snippet + * This method is called just after that a thumbnail is drag and droped into a drop zone + * (after the insertion of this.$body, if this.$body exists) + */ + build_snippet: function ($target) { + var self = this; + setTimeout(function () {self.parent.make_active(self.$target);},0); + }, + + /* onFocus + * This method is called when the user click inside the snippet in the dom + */ + onFocus : function () { + this.$overlay.addClass('oe_active'); + }, + + /* onFocus + * This method is called when the user click outide the snippet in the dom, after a focus + */ + onBlur : function () { + this.$overlay.removeClass('oe_active'); + }, + + /* setOptions + * Use this method when you want to save some data for your snippet animation. + */ + setOptions: function (options) { + $target.attr("data-snippet-options", JSON.stringify(options)); + }, + + /* getOptions + * Read data saved for your snippet animation. + */ + getOptions: function () { + var options = this.$target.data("snippet-options"); + return options ? JSON.parse(options) : undefined; + }, + }); + + + website.snippet.editorRegistry.resize = website.snippet.Editor.extend({ + start: function () { + var self = this; + this._super(); + var $box = $(openerp.qweb.render("website.snippets.resize")); + + var resize_values = this.getSize(); + if (!resize_values.n) $box.find(".oe_handle.n").remove(); + if (!resize_values.s) $box.find(".oe_handle.s").remove(); + if (!resize_values.e) $box.find(".oe_handle.e").remove(); + if (!resize_values.w) $box.find(".oe_handle.w").remove(); + + this.$overlay.append($box.find(".oe_handles").html()); + + var $editor = $box.find(".oe_snippet_options"); + $editor.prependTo(this.$overlay.find(".oe_overlay_options .oe_option.n.w")); + + $editor.on('click', '.js_box_remove', function () { + self.$target.detach(); + self.onBlur(); + self.$target.remove(); + }); + + + this.$overlay.find(".oe_handle").on('mousedown', function (event){ + event.preventDefault(); + + var $handle = $(this); + + var resize_values = self.getSize(); + var compass = false; + var XY = false; + if ($handle.hasClass('n')) { + compass = 'n'; + XY = 'Y'; + } + else if ($handle.hasClass('s')) { + compass = 's'; + XY = 'Y'; + } + else if ($handle.hasClass('e')) { + compass = 'e'; + XY = 'X'; + } + else if ($handle.hasClass('w')) { + compass = 'w'; + XY = 'X'; + } + + var resize = resize_values[compass]; + if (!resize) return; + + var current = resize[2] || 0; + _.each(resize[0], function (val, key) { + if (self.$target.hasClass(val)) { + if (current) + self.$target.removeClass(val); + else + current = key; + } + }); + + self.parent.editor_busy = true; + + var xy = event['page'+XY]; + var begin = current; + var beginClass = self.$target.attr("class"); + var regClass = new RegExp("\\s*" + resize[0][begin].replace(/[-]*[0-9]+/, '[0-9-]+'), 'g'); + + var cursor = $handle.css("cursor")+'-important'; + $("body").addClass(cursor); + self.$overlay.addClass('oe_hover'); + + var body_mousemove = function (event){ + event.preventDefault(); + var dd = event['page'+XY] - xy + resize[1][begin]; + var next = current+1 === resize[1].length ? current : (current+1); + var prev = current ? (current-1) : 0; + + var change = false; + if (dd > (2*resize[1][next] + resize[1][current])/3) { + self.$target.attr("class",self.$target.attr("class").replace(regClass, '')); + self.$target.addClass(resize[0][next]); + current = next; + change = true; + } + if (prev != current && dd < (2*resize[1][prev] + resize[1][current])/3) { + self.$target.attr("class",self.$target.attr("class").replace(regClass, '')); + self.$target.addClass(resize[0][prev]); + current = prev; + change = true; + } + + if (change) { + self.on_resize(compass, beginClass, current); + website.snippet.cover_target(self.$overlay, self.$target); + } + }; + $('body').mousemove(body_mousemove); + + var body_mouseup = function(){ + $('body').unbind('mousemove', body_mousemove); + $('body').unbind('mouseup', body_mouseup); + $("body").removeClass(cursor); + self.parent.editor_busy = false; + }; + $('body').mouseup(body_mouseup); + }); + }, + getSize: function () { + var grid = [0,4,8,16,32,48,64,92,128]; + this.grid = { + n: [_.map(grid, function (v) {return 'mt'+v;}), grid], + s: [_.map(grid, function (v) {return 'mb'+v;}), grid] + }; + return this.grid; + }, + + /* on_resize + * called when the box is resizing and the class change, before the cover_target + * @compass: resize direction : 'n', 's', 'e', 'w' + * @beginClass: attributes class at the begin + * @current: curent increment in this.grid + */ + on_resize: function (compass, beginClass, current) { + + } + }); + + website.snippet.editorRegistry.colmd = website.snippet.editorRegistry.resize.extend({ + getSize: function () { + this.grid = this._super(); + var width = this.$target.parents(".row:first").first().outerWidth(); + + var grid = [0,1,2,3,4,5,6,7,8,9,10,11,12]; + this.grid.e = [_.map(grid, function (v) {return 'col-md-'+v;}), _.map(grid, function (v) {return width/12*v;})]; + + var grid = [-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7,8,9,10,11]; + this.grid.w = [_.map(grid, function (v) {return 'col-lg-offset-'+v;}), _.map(grid, function (v) {return width/12*v;}), 12]; + + return this.grid; + }, + resizeHeight: function () { + var height = 0; + var $parent = this.$target.parent(); + var $cols = $parent.find('>'); + $cols.css('height', ''); + if ($cols.length <= 1) { + $parent.css('height', ''); + return; + } + var both = '
          .
          '; + $parent.append(both).prepend(both); + $cols.each(function () { + if (height < $(this).outerHeight()) { + height = $(this).outerHeight(); + } + }); + $parent.css('height', height + 'px'); + $parent.find('.oe_remove_me').remove(); + $cols.css('height', '99%'); + }, + on_resize: function (compass, beginClass, current) { + if (compass !== 'e' && compass !== 'w' ) + return; + + var self = this; + var currentClass = this.grid[compass][0][current]; + var beginSize = Number(beginClass.match(/col-md-([0-9]+)|$/)[1] || 0) + Number(beginClass.match(/col-lg-offset-([0-9-]+)|$/)[1] || 0); + + // don't change the rigth border position when we change the offset (replace col size) + var colsize = compass === 'w' ? + beginSize - Number(currentClass.match(/col-lg-offset-([0-9-]+)|$/)[1] || 0) : + Number(currentClass.match(/col-md-([0-9-]+)|$/)[1] || 0); + if (colsize > 12) colsize = 12; + if (colsize < 1) { + colsize = website.snippet.is_empty_dom(self.$target) ? 0 : 1; + } + this.$target.attr("class", + this.$target.attr("class").replace(/\s*(col-lg-offset-|col-md-)([0-9-]+)/g, '') + ' col-md-' + colsize + ); + + // calculate col size (0 < size <= 12) + var size = 0; + var $parent = this.$target.parent(); + _.each(this.grid.e[0], function (val) { + size += $parent.find('.' + val).length * Number(val.replace(/[^0-9]+/, '')); + }); + _.each(this.grid.w[0], function (val) { + size += $parent.find('.' + val).length * Number(val.replace(/[^0-9]+/, '')); + }); + + function change_empty_col ($col) { + var _size = Number($col.attr("class").match(/col-md-([0-9-]+)|$/)[1] || 0); + if (12 - (size - _size) > 0) { + size -= _size; + $col.attr("class", $col.attr("class").replace(/\s*col-md-([0-9-]+)/g, '') + ' col-md-' + (12 - size)); + size = 12; + } else if(website.snippet.is_empty_dom($col)) { + size -= _size; + $col.remove(); + } + } + + function insert_empty_col () { + var cleanClass = beginClass.replace(/\s*(col-lg-offset-|col-md-)([0-9-]+)/g, ''); + var $insert = $('


          '); + $insert.addClass('col-md-'+(12-size)); + size = 12; + return $insert; + } + + // change previous or next col if empty + if (compass === 'w') { + var $prev = this.$target.prev(); + if ($prev.length) { + change_empty_col($prev); + } + if(size < 12 && size > 0) { + this.$target.before(insert_empty_col()); + } + } else { + var $next = this.$target.next(); + if ($next.length) { + change_empty_col($next); + } + if(size < 12 && size > 0) { + this.$target.after(insert_empty_col()); + } + } + if(size > 12 || size <= 0) { + this.$target.attr("class", + this.$target.attr("class").replace(/\s*col-md-([0-9-]+)/g, '') + ' col-md-' + (size-12) + ); + } + + // remove empty col + if (this.$target.hasClass("col-md-0")) { + $('body').mouseup(); + setTimeout(function () { + self.$overlay.remove(); + var $div = self.$target.prev(); + if (!$div.length) { + $div = self.$target.next(); + } + self.$target.remove(); + self.parent.make_active($div); + },0); + } + + setTimeout(function () { + self.resizeHeight(); + },0); + }, + start: function () { + var self = this; + this._super(); + this.resizeHeight(); + $("body").on("save", function () { + self.$target.parent().css('height', '').find('>').css('height', ''); + }); + }, + }); + + website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.resize.extend({ + template : "website.snippets.carousel", + build_snippet: function($target) { + var id = "myCarousel" + $("body .carousel").size(); + $target.attr("id", id); + $target.find(".carousel-control").attr("href", "#"+id); + }, + start : function () { + this._super(); + var self = this; + + this.$editor.find(".js_add").on('click', this.on_add); + this.$editor.find(".js_remove").on('click', this.on_remove); + + var bg = this.$target.find('.carousel-inner .item.active').css('background-image').replace(/url\((.*)\)/g, '\$1'); + this.$editor.find('select[name="carousel-background"] option[value="'+bg+'"], select[name="carousel-background"] option[value="'+bg.replace(window.location.protocol+'//'+window.location.host, '')+'"]') + .prop('selected', true); + + this.$editor.find('select[name="carousel-background"]').on('change', function () { + self.$target.find('.carousel-inner .item.active').css('background-image', 'url(' + $(this).val() + ')'); + }); + + var style = false; + if (this.$target.find('.carousel-inner .item.active .container .content_image.col-lg-offset-1')) + style = 'image_right'; + if (this.$target.find('.carousel-inner .item.active .container .content_image')) + style = 'image_left'; + this.$editor.find('select[name="carousel-style"] option[value="'+style+'"]').prop('selected', true); + + this.$editor.find('select[name="carousel-style"]').on('change', this.on_bg_change); + }, + on_add: function (e) { + e.preventDefault(); + var $inner = this.$target.find('.carousel-inner'); + var cycle = $inner.find('.item').size(); + $inner.append(this.$('> .item')); + this.$target.carousel(cycle); + }, + on_remove: function (e) { + e.preventDefault(); + var $inner = this.$target.find('.carousel-inner'); + if ($inner.find('.item').size() > 1) { + $inner + .find('.item.active').remove().end() + .find('.item:first').addClass('active'); + this.$target.carousel(0); + } + }, + on_bg_change: function (e) { + var $container = this.$target.find('.carousel-inner .item.active .container'); + var img_url = $('.content_image img', $container).attr("src"); + if (!img_url) { + img_url = this.img_url || "/website/static/src/img/china.jpg"; + } else { + this.img_url = img_url; + } + + $('.content_image', $container).remove(); + switch ($(e.currentTarget).val()) { + case 'no_image': + $('.content', $container).attr("class", "content"); + break; + case 'image_left': + $('.content', $container).attr("class", "content col-md-6") + .before('
          '); + break; + case 'image_right': + $('.content', $container).attr("class", "content col-md-6") + .after('
          '); + break; + } + }, + }); + + website.snippet.editorRegistry.darken = website.snippet.Editor.extend({ + build_snippet: function($target) { + $target.toggleClass('dark'); + var $parent = $target.parent(); + if($parent.hasClass('dark')){ + $parent.replaceWith($target); + }else{ + $parent = $("
          "); + $target.after($parent); + $parent.append($target); + } + this._super(); + } + }); + + website.snippet.animationRegistry.vomify = website.snippet.Animation.extend({ + build_snippet: function($target) { + var hue=0; + var beat = false; + var a = setInterval(function(){ + $target.css({'-webkit-filter':'hue-rotate('+hue+'deg)'}); hue += 5; + }, 10); + setTimeout(function(){ + clearInterval(a); + setInterval(function(){ + var filter = 'hue-rotate('+hue+'deg)'+ (beat ? ' invert()' : ''); + $(document.documentElement).css({'-webkit-filter': filter}); hue += 5; + if(hue % 35 === 0){ + beat = !beat; + } + }, 10); + },5000); + $('').appendTo($target); + } + }); + +})(); \ No newline at end of file diff --git a/addons/website/static/src/xml/website.ace.xml b/addons/website/static/src/xml/website.ace.xml new file mode 100644 index 00000000000..b546d41a7ec --- /dev/null +++ b/addons/website/static/src/xml/website.ace.xml @@ -0,0 +1,19 @@ + + + +
          +
        • - + placeholder="example: you@yourwebsite.com"/> + + Image: -
          -
          -
          - -

          Image URL

          - -
          -
          -

          — or —

          - - -
          -

          — or —

          -
          -

          Pick an existing attachment

          -
          -
          - - -
          +
          +
          +
          +
          + +
          +
          + + +
          +
          +
          +
          +
          + +
          +
          + + +