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 db1596e947a..a263f6e8003 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -56,9 +56,12 @@ class Website(openerp.addons.web.controllers.main.Home): request.cr.execute('SAVEPOINT pagenew') 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') - newview_id = view.copy(request.cr, request.uid, view_id) - newview = view.browse(request.cr, request.uid, newview_id) + view_model, view_id = imd.get_object_reference( + request.cr, request.uid, 'website', 'default_page') + newview_id = view.copy( + request.cr, request.uid, view_id, context=request.context) + newview = view.browse( + request.cr, request.uid, newview_id, context=request.context) newview.write({ 'arch': newview.arch.replace("website.default_page", path), 'name': "page/%s" % path, @@ -72,7 +75,7 @@ class Website(openerp.addons.web.controllers.main.Home): 'model': 'ir.ui.view', 'res_id': newview_id, 'noupdate': True - }) + }, context=request.context) except psycopg2.IntegrityError: request.cr.execute('ROLLBACK TO SAVEPOINT pagenew') else: @@ -87,30 +90,39 @@ class Website(openerp.addons.web.controllers.main.Home): imd = request.registry['ir.model.data'] view = request.registry['ir.ui.view'] - view_model, view_option_id = imd.get_object_reference(request.cr, request.uid, 'website', 'theme') - views = view.search(request.cr, request.uid, [('inherit_id','=',view_option_id)]) - view.write(request.cr, request.uid, views, {'inherit_id': False}) + view_model, view_option_id = imd.get_object_reference( + request.cr, request.uid, 'website', 'theme') + views = view.search( + request.cr, request.uid, [('inherit_id', '=', view_option_id)], + context=request.context) + view.write(request.cr, request.uid, views, {'inherit_id': False}, + context=request.context) if theme_id: module, xml_id = theme_id.split('.') - view_model, view_id = imd.get_object_reference(request.cr, request.uid, module, xml_id) - view.write(request.cr, request.uid, [view_id], {'inherit_id':view_option_id}) + view_model, view_id = imd.get_object_reference( + 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 def customize_template_set(self, view_id): view_obj = request.registry.get("ir.ui.view") - view = view_obj.browse(request.cr, request.uid, int(view_id), context=request.context) + view = view_obj.browse(request.cr, request.uid, int(view_id), + context=request.context) if view.inherit_id: value = False else: @@ -121,19 +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') + view_model, view_theme_id = imd.get_object_reference( + 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 }) @@ -221,8 +235,10 @@ class Website(openerp.addons.web.controllers.main.Home): width = int(resize[0]) height = int(resize[1]) # resize maximum 500*500 - if width > 500: width = 500 - if height > 500: height = 500 + if width > 500: + width = 500 + if height > 500: + height = 500 image_base64 = openerp.tools.image_resize_image(base64_source=image_base64, size=(width, height), encoding='base64', filetype='PNG') image_data = base64.b64decode(image_base64) @@ -244,13 +260,15 @@ class Website(openerp.addons.web.controllers.main.Home): _object = request.registry[post['object']] obj = _object.browse(request.cr, request.uid, _id) - _object.write(request.cr, request.uid, [_id], {'website_published': not obj.website_published}) + _object.write(request.cr, request.uid, [_id], + {'website_published': not obj.website_published}, + context=request.context) obj = _object.browse(request.cr, request.uid, _id) return obj.website_published and "1" or "0" @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 bc776904891..33845547c46 100644 --- a/addons/website/static/src/css/editor.css +++ b/addons/website/static/src/css/editor.css @@ -21,12 +21,11 @@ position: fixed; top: 0; right: 0; - z-index: 10; display: block; width: 100%; padding: 2px; - margin: 0px; - z-index: 10000; + margin: 0; + z-index: 20000; background-color: #414141; background: -webkit-linear-gradient(#646060, #262626); box-sizing: border-box; @@ -66,7 +65,7 @@ } .css_non_editable_mode_hidden { - display: none + display: none; } .oe_carousel_options { @@ -76,16 +75,41 @@ z-index: 1; display: none; } - .oe_carousel_options > * { display: inline-block !important; vertical-align: middle !important; position: relative !important; - top: +2px; + top: 2px; +} + +/* ---- EDITOR BAR ---- */ +.modal .tab-content { + padding-top: 5px; +} + +table.editorbar-panel { + cursor: pointer; + width: 100%; +} +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; + -moz-user-select: auto; + -ms-user-select: auto; + -o-user-select: auto; + user-select: auto; } /* ---- SNIPPET EDITOR ---- */ -.oe_snippets { +#oe_snippets { position: fixed; left: 0px; right: 0px; @@ -95,7 +119,17 @@ background: #282828; box-shadow: 0px 10px 10px -10px black inset; overflow-y: auto; - z-index: 100; + z-index: 20000; +} +#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 { @@ -122,6 +156,7 @@ 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; @@ -129,6 +164,9 @@ .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; @@ -180,64 +218,134 @@ } .oe_drop_zone.oe_hover { background: rgba(0, 255, 133, 0.22); - z-index: 99; + z-index: 1001; } /* ---- 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: 1002; +} +.oe_overlay .oe_overlay_options .oe_option { + top: 50%; + left: 50%; + pointer-events: auto; + position: absolute; + display: block; + z-index: 1002; +} +.oe_overlay .oe_overlay_options .oe_option.n { + top: -11px; +} +.oe_overlay .oe_overlay_options .oe_option.e { + left: auto; + right: -6px; +} +.oe_overlay .oe_overlay_options .oe_option.s { + top: auto; + bottom: -10px; +} +.oe_overlay .oe_overlay_options .oe_option.w { + left: -6px; +} +.oe_overlay .oe_overlay_options .oe_option > a { + text-decoration: none; + font-size: 1.2em; +} +.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 ---- */ @@ -297,3 +405,47 @@ .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 e68ce289526..d3a58c91a13 100644 --- a/addons/website/static/src/css/editor.sass +++ b/addons/website/static/src/css/editor.sass @@ -21,12 +21,11 @@ position: fixed top: 0 right: 0 - z-index: 10 display: block width: 100% padding: 2px - margin: 0px - z-index: 10000 + margin: 0 + z-index: 20000 background-color: #414141 background: -webkit-linear-gradient(#646060, #262626) box-sizing: border-box @@ -58,7 +57,7 @@ .oe_editable:focus outline: none !important -.css_non_editable_mode_hidden: +.css_non_editable_mode_hidden display: none .oe_carousel_options @@ -71,11 +70,36 @@ display: inline-block !important vertical-align: middle !important position: relative !important - top: +2px; + top: +2px + +/* ---- 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 +// about them either +.oe_editable .btn + -webkit-user-select: auto + -moz-user-select: auto + -ms-user-select: auto + -o-user-select: auto + user-select: auto /* ---- SNIPPET EDITOR ---- */ -.oe_snippets +#oe_snippets position: fixed left: 0px right: 0px @@ -85,7 +109,14 @@ background: rgb(40,40,40) box-shadow: 0px 10px 10px -10px black inset overflow-y: auto - z-index: 100 + z-index: 20000 + .nav-pills + padding: 3px 8px + background: #444444 + > li > a + padding: 2px 10px !important + .pill-content + height: 140px .oe_snippet display: inline-block @@ -111,11 +142,14 @@ 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 @@ -161,52 +195,107 @@ border-radius: 3px &.oe_hover background: rgba(0, 255, 133, 0.22) - z-index: 99 + z-index: 1001 /* ---- 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 + 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: 1002 + .oe_option + top: 50% + left: 50% + pointer-events: auto + position: absolute display: block + z-index: 1002 + &.n + top: -11px + &.e + left: auto + right: -6px + &.s + top: auto + bottom: -10px + &.w + left: -6px + > a + text-decoration: none + font-size: 1.2em + &.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 ---- */ @@ -261,4 +350,46 @@ $remove_color: $icon_close .oe_remove color: $remove_color .oe_seo_suggestion - cursor: pointer \ No newline at end of file + 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..fd26c50aefd --- /dev/null +++ b/addons/website/static/src/js/website.ace.js @@ -0,0 +1,148 @@ +(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 () { + 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); + } + return true; + }, + 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) { + 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); + }); + }, + 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 32e77acedab..b323d4845a0 100644 --- a/addons/website/static/src/js/website.editor.js +++ b/addons/website/static/src/js/website.editor.js @@ -53,12 +53,76 @@ canUndo: false, editorFocus: true, }); - } }); + CKEDITOR.plugins.add( 'tablebutton', { + requires: 'panelbutton,floatpanel', + init: function( editor ) { + var label = "Table"; + + editor.ui.add('TableButton', CKEDITOR.UI_PANELBUTTON, { + label: label, + title: label, + // use existing 'table' icon + icon: 'table', + modes: { wysiwyg: true }, + editorFocus: true, + // panel opens in iframe, @css is CSS file -ed within + // frame document, @attributes are set on iframe itself. + panel: { + css: '/website/static/src/css/editor.css', + attributes: { 'role': 'listbox', 'aria-label': label, }, + }, + + onBlock: function (panel, block) { + block.autoSize = true; + block.element.setHtml(openerp.qweb.render('website.editor.table.panel', { + rows: 5, + cols: 5, + })); + + var $table = $(block.element.$).on('mouseenter', 'td', function (e) { + var $e = $(e.target); + var y = $e.index() + 1; + var x = $e.closest('tr').index() + 1; + + $table + .find('td').removeClass('selected').end() + .find('tr:lt(' + String(x) + ')') + .children().filter(function () { return $(this).index() < y; }) + .addClass('selected'); + }).on('click', 'td', function (e) { + var $e = $(e.target); + + var table = new CKEDITOR.dom.element( + $(openerp.qweb.render('website.editor.table', { + rows: $e.closest('tr').index() + 1, + cols: $e.index() + 1, + }))[0]); + + editor.insertElement(table); + setTimeout(function () { + var firstCell = new CKEDITOR.dom.element(table.$.rows[0].cells[0]); + var range = editor.createRange(); + range.moveToPosition(firstCell, CKEDITOR.POSITION_AFTER_START); + range.select(); + }, 0); + }); + + block.element.getDocument().getBody().setStyle('overflow', 'hidden'); + CKEDITOR.ui.fire('ready', this); + }, + }); + } + }); + 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 ---- */ @@ -88,10 +152,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 @@ -132,11 +197,6 @@ this.$('#website-top-edit').show(); $('.css_non_editable_mode_hidden').removeClass("css_non_editable_mode_hidden"); - // this.$buttons.cancel.add(this.$buttons.snippet).prop('disabled', false) - // .add(this.$buttons.save) - // .parent().show(); - // - // TODO: span edition changing edition state (save button) var $editables = $('[data-oe-model]') .not('link, script') // FIXME: propagation should make "meta" blocks non-editable in the first place... @@ -185,7 +245,7 @@ defs.push(def); }); return $.when.apply(null, defs).then(function () { - window.location.reload(); + window.location.href = window.location.href.replace(/unable_editor(=[^&]*)?|#.*/g, ''); }); }, saveElement: function ($el) { @@ -204,11 +264,11 @@ return openerp.jsonRpc('/web/dataset/call', 'call', { model: 'ir.ui.view', method: 'save', - args: [data.oeModel, data.oeId, data.oeField, html, xpath] + args: [data.oeModel, data.oeId, data.oeField, html, xpath, website.get_context()] }); }, cancel: function () { - window.location.reload(); + window.location.href = window.location.href.replace(/unable_editor(=[^&]*)?|#.*/g, ''); }, }); @@ -220,6 +280,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 +294,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 () { @@ -262,11 +328,14 @@ autoParagraph: false, filebrowserImageUploadUrl: "/website/attach", // Support for sharedSpaces in 4.x - extraPlugins: 'sharedspace,customdialogs', + extraPlugins: 'sharedspace,customdialogs,tablebutton', // 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" ]},{ @@ -278,7 +347,7 @@ "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock" ]},{ name: 'special', items: [ - "Image", "Table" + "Image", "TableButton" ]},{ name: 'styles', items: [ "Styles" @@ -330,15 +399,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; @@ -397,24 +462,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')); @@ -428,36 +493,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. @@ -480,37 +528,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')); } @@ -520,6 +581,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'); @@ -533,7 +595,10 @@ }, 0); } element.setAttribute('src', url); - this._super(); + $(element.$).removeClass(this.image_styles.join(' ')); + if (style) { element.addClass(style); } + + return this._super(); }, /** @@ -571,7 +636,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 () { @@ -584,6 +652,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 1be97c02df0..8f2bd483ca8 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 66462bb2976..e9053740eae 100644 --- a/addons/website/static/src/js/website.seo.js +++ b/addons/website/static/src/js/website.seo.js @@ -1,8 +1,9 @@ (function () { 'use strict'; - // TODO l10n ? var website = openerp.website; + website.templates.push('/website/static/src/xml/website.seo.xml'); + website.EditorBar.include({ events: _.extend({}, website.EditorBar.prototype.events, { 'click a[data-action=promote-current-page]': 'promotePage', @@ -14,6 +15,154 @@ website.seo = {}; + website.seo.Suggestion = openerp.Widget.extend({ + template: 'website.seo_suggestion', + events: { + 'click .js_seo_suggestion': 'select', + }, + init: function (parent, keyword) { + this.keyword = keyword; + // cf. http://getbootstrap.com/components/#labels + // default, primary, success, info, warning, danger + this.type = 'default'; + this._super(parent); + }, + select: function () { + this.trigger('selected', this.keyword); + }, + }); + + website.seo.SuggestionList = openerp.Widget.extend({ + template: 'website.seo_list', + init: function (parent, word) { + this.word = word; + this._super(parent); + }, + start: function () { + this.refresh(); + }, + refresh: function () { + var self = this; + self.$el.append("Loading..."); + function addSuggestions (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 ? + _.each(_.uniq(cleanList), function (keyword) { + if (keyword) { + var suggestion = new website.seo.Suggestion(self, keyword); + suggestion.on('selected', self, function (word) { + self.trigger('selected', word); + }); + suggestion.appendTo(self.$el); + } + }); + } + $.getJSON("http://seo.eu01.aws.af.cm/suggest/"+encodeURIComponent(this.word + " "), addSuggestions); + }, + }); + + website.seo.Keyword = openerp.Widget.extend({ + template: 'website.seo_keyword', + events: { + 'click a[data-action=remove-keyword]': 'destroy', + }, + maxWordsPerKeyword: 4, // TODO Check + init: function (parent, keyword) { + this.keyword = keyword; + // cf. http://getbootstrap.com/components/#labels + // default, primary, success, info, warning, danger + this.type = 'warning'; + this._super(parent); + }, + start: function () { + var self = this; + self.suggestionList = new website.seo.SuggestionList(self, this.keyword); + self.suggestionList.on('selected', self, function (word) { + self.trigger('selected', word); + }); + this.suggestionList.appendTo(this.$('.js_seo_keyword_suggestion')); + }, + destroy: function () { + this.trigger('removed'); + this._super(); + }, + }); + + website.seo.KeywordList = openerp.Widget.extend({ + template: 'website.seo_list', + maxKeywords: 10, + keywords: function () { + var result = []; + this.$('span.js_seo_keyword').each(function () { + result.push($(this).data('keyword')); + }); + return result; + }, + isKeywordListFull: function () { + return this.keywords().length >= this.maxKeywords; + }, + isExistingKeyword: function (word) { + return _.contains(this.keywords(), word); + }, + add: function (candidate) { + var self = this; + // TODO Refine + var word = candidate ? candidate.replace(/[,;.:<>]+/g, " ").replace(/ +/g, " ").trim() : ""; + if (word && !self.isKeywordListFull() && !self.isExistingKeyword(word)) { + var keyword = new website.seo.Keyword(self, word); + keyword.on('removed', self, function () { + self.trigger('list-not-full'); + self.trigger('removed', word); + }); + keyword.on('selected', self, function (word) { + self.trigger('selected', word); + }); + keyword.appendTo(self.$el); + } + if (self.isKeywordListFull()) { + self.trigger('list-full'); + } + }, + }); + + website.seo.Image = openerp.Widget.extend({ + template: 'website.seo_image', + init: function (parent, options) { + this.src = options.src; + this.alt = options.alt; + this._super(parent); + }, + }); + + + website.seo.ImageList = openerp.Widget.extend({ + start: function () { + var self = this; + new website.seo.PageParser().images().each(function (index, image) { + new website.seo.Image(self, image).appendTo(self.$el); + }); + }, + images: function () { + var result = []; + this.$('input').each(function () { + var $input = $(this); + result.push({ + src: $input.attr('src'), + alt: $input.val(), + }); + }); + return result; + }, + add: function (image) { + new website.seo.Image(this, image).appendTo(this.$el); + }, + }); + website.seo.PageParser = openerp.Class.extend({ url: function () { var url = window.location.href; @@ -24,8 +173,8 @@ return $(document.title).text(); }, headers: function (tag) { - return $('#wrap h1').map(function () { - return $(this).text(); + return $('#wrap '+tag).map(function () { + return $(this).text(); }); }, images: function () { @@ -56,129 +205,6 @@ }, }); - website.seo.Keyword = openerp.Widget.extend({ - template: 'website.seo_keyword', - events: { - 'click a[data-action=remove-keyword]': 'destroy', - }, - maxWordsPerKeyword: 4, // TODO Check - types: { - // cf. http://getbootstrap.com/components/#labels - // default, primary, success, info, warning, danger - perfect: 'success', - advised: 'primary', - used: 'warning', - new: 'default', - }, - init: function (parent, options) { - this.keyword = options.keyword; - this.type = this.types[options.type || 'default']; - this._super(parent); - }, - destroy: function () { - this.trigger('removed'); - this._super(); - }, - }); - - website.seo.KeywordList = openerp.Widget.extend({ - template: 'website.seo_list', - maxKeywords: 10, - keywords: function () { - var result = []; - this.$el.find('.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) { - 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, { - keyword: word, - type: type, - }); - keyword.on('removed', null, function () { - self.trigger('list-not-full'); - }); - keyword.appendTo(this.$el); - } - if (this.isKeywordListFull()) { - self.trigger('list-full'); - } - }, - }); - - website.seo.Suggestion = openerp.Widget.extend({ - template: 'website.seo_suggestion', - events: { - 'click .js_seo_suggestion': 'select' - }, - types: { - // cf. http://getbootstrap.com/components/#labels - // default, primary, success, info, warning, danger - primary: 'primary', - secondary: 'info', - - }, - init: function (parent, options) { - this.keyword = options.keyword; - this.type = this.types[options.type || 'primary']; - this._super(parent); - }, - select: function () { - this.trigger('selected'); - }, - }); - - website.seo.Image = openerp.Widget.extend({ - template: 'website.seo_image', - init: function (parent, options) { - this.src = options.src; - this.alt = options.alt; - this._super(parent); - }, - }); - - - website.seo.ImageList = openerp.Widget.extend({ - start: function () { - var self = this; - new website.seo.PageParser().images().each(function (index, image) { - new website.seo.Image(self, image).appendTo(self.$el); - }); - }, - images: function () { - var result = []; - this.$el.find('input').each(function () { - var $input = $(this); - result.push({ - src: $input.attr('src'), - alt: $input.val(), - }); - }); - return result; - }, - add: function (image) { - new website.seo.Image(this, image).appendTo(this.$el); - }, - }); - website.seo.Configurator = openerp.Widget.extend({ template: 'website.seo_configuration', events: { @@ -190,42 +216,47 @@ maxTitleSize: 65, maxDescriptionSize: 155, start: function () { - var $modal = this.$el; + var self = this; + var $modal = self.$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 () { + self.suggestImprovements(pageParser); + self.imageList = new website.seo.ImageList(self); + self.imageList.appendTo($modal.find('.js_seo_image_list')); + self.keywordList = new website.seo.KeywordList(self); + self.keywordList.on('list-full', self, function () { $modal.find('input[name=seo_page_keywords]') .attr('readonly', "readonly") .attr('placeholder', "Remove a keyword first"); $modal.find('button[data-action=add]') .prop('disabled', true).addClass('disabled'); }); - this.keywordList.on('list-not-full', null, function () { + self.keywordList.on('list-not-full', self, function () { $modal.find('input[name=seo_page_keywords]') .removeAttr('readonly').attr('placeholder', ""); $modal.find('button[data-action=add]') .prop('disabled', false).removeClass('disabled'); }); + self.keywordList.on('selected', self, function (word) { + self.keywordList.add(word); + }); + self.keywordList.appendTo($modal.find('.js_seo_keywords_list')); + var companyName = pageParser.company().toLowerCase(); + self.addKeyword(companyName); $modal.modal(); }, suggestImprovements: function (parser) { var tips = []; var self = this; function displayTip(message, type) { - new website.seo.Tip(this, { + new website.seo.Tip(self, { message: message, - type: type - }).appendTo(self.$el.find('.js_seo_tips')); + type: type, + }).appendTo(self.$('.js_seo_tips')); } var pageParser = parser || new website.seo.PageParser(); - if (!pageParser.headers('h1').length === 0) { + if (pageParser.headers('h1').length === 0) { tips.push({ type: 'warning', message: "You don't have an <h1> tag on your page.", @@ -245,50 +276,21 @@ 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); $input.val(""); }, 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..a7f69a8fd19 100644 --- a/addons/website/static/src/js/website.snippets.js +++ b/addons/website/static/src/js/website.snippets.js @@ -2,99 +2,161 @@ '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()); + }; + function hack_to_add_snippet_id () { + _.each(website.snippet.selector, function (val) { + $(val[0]).each(function() { + if ($(this).is("[data-snippet-id='"+ val[1]+"']")) + $(this).removeAttr("data-snippet-id"); + if (!$(this).is("[data-snippet-id]") && $(this).parents("[data-oe-model]").length) + $(this).attr("data-snippet-id", val[1]); + }); + }); + } + + + website.snippet.selector = []; + 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) "; + hack_to_add_snippet_id(); }, 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 +166,53 @@ 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; + } + var $target = $(event.srcElement).parents("[data-snippet-id]:first"); + if($target.length && !self.editor_busy) { + if($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'); + } + } else if($selected_target) { + $selected_target.data('overlay').removeClass('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 +226,71 @@ $(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; + hack_to_add_snippet_id(); + } else { + $target = $(".oe_drop_zone.oe_hover").data('target'); + } + + if (website.snippet.animationRegistry[snipped_id]) { + new website.snippet.animationRegistry[snipped_id]($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 +298,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 +322,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 +335,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 +381,431 @@ 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 = $(openerp.qweb.render('website.snippet_overlay')); + $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 = this.$target = $(dom); + }, + /* 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 () { + hack_to_add_snippet_id(); + $("[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 () { + var self = this; + if(this.$editor) this.$editor.prependTo(this.$overlay.find(".oe_overlay_options .oe_option.n.w ul")); + else this.$overlay.find(".oe_overlay_options .oe_option.n.w").hide(); + this.$overlay.on('click', '.js_box_remove', function () { + self.$target.detach(); + self.onBlur(); + self.$target.remove(); + return false; + }); + + }, + + /* + * 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()); + + 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)) { + 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 = [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; + }, + on_resize: function (compass, beginClass, current) { + if (compass !== 'w') + return; + + // don't change the rigth border position when we change the offset (replace col size) + var beginCol = Number(beginClass.match(/col-md-([0-9]+)|$/)[1] || 0); + var beginOffset = Number(beginClass.match(/col-lg-offset-([0-9-]+)|$/)[1] || 0); + var offset = Number(this.grid.w[0][current].match(/col-lg-offset-([0-9-]+)|$/)[1] || 0); + + this.$target.attr("class",this.$target.attr("class").replace(/\s*(col-lg-offset-|col-md-)([0-9-]+)/g, '')); + + var colSize = beginCol - (offset - beginOffset); + this.$target.addClass('col-md-' + (colSize > 12 ? 12 : colSize)); + if (offset > 0) { + this.$target.addClass('col-lg-offset-' + offset); + } + }, + }); + website.snippet.selector.push([ _.map([1,2,3,4,5,6,7,8,9,10,11,12], function (v) {return '.row > .col-md-'+v;}).join(","), 'colmd']); + + + 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({ + init: function() { + this._super(); + var hue=0; + var beat = false; + var self = this; + var a = setInterval(function(){ + self.$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(self.$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

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