summaryrefslogtreecommitdiff
path: root/addons/website/static/src
diff options
context:
space:
mode:
authorbth-openerp <bth@tinyerp.com>2013-08-29 12:07:17 +0530
committerbth-openerp <bth@tinyerp.com>2013-08-29 12:07:17 +0530
commit4eee602d6ea368ce640078fc409bf827e8ff368a (patch)
treea3398d26ec8d21f156d4489e03c8b1169621c983 /addons/website/static/src
parent93c9f491b3fe752cea35fce6ebc2dd0a3ddeecaf (diff)
parented35895126a1e24b4af0b0fb7dced13ff8be873f (diff)
[MERGE]Merge lp:~openerp-dev/openobject-addons/trunk-website-al.
bzr revid: bth@tinyerp.com-20130829063717-t8kqjombb25gqmsb
Diffstat (limited to 'addons/website/static/src')
-rw-r--r--addons/website/static/src/css/editor.css38
-rw-r--r--addons/website/static/src/css/editor.sass39
-rw-r--r--addons/website/static/src/js/website.js268
-rw-r--r--addons/website/static/src/xml/website.xml14
4 files changed, 268 insertions, 91 deletions
diff --git a/addons/website/static/src/css/editor.css b/addons/website/static/src/css/editor.css
index 00d3127e286..410f4849d06 100644
--- a/addons/website/static/src/css/editor.css
+++ b/addons/website/static/src/css/editor.css
@@ -1,4 +1,22 @@
@charset "utf-8";
+/* ---- CKEditor Minimal Reset ---- */
+.navbar.navbar-inverse .cke_chrome {
+ border: none;
+}
+
+.navbar.navbar-inverse .cke_inner {
+ background: transparent;
+}
+
+.navbar.navbar-inverse .cke_top {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+}
+
+/* ---- OpenERP Style ---- */
.oe_hidden {
display: none !important;
}
@@ -215,12 +233,13 @@
left: 0px;
}
-/* ---- MOBILE PREVIEW ---- */
+/* ---- MODAL DIALOGS ---- */
.oe_stop_scrolling {
height: 100%;
overflow: hidden;
}
+/* ---- MOBILE PREVIEW ---- */
.oe_mobile_preview.modal {
height: 660px;
background-color: black;
@@ -269,11 +288,19 @@
opacity: 1;
}
+/* ---- SEO TOOLS ---- */
+.oe_seo_configuration {
+ min-width: 42em;
+}
+.oe_seo_configuration .modal-body {
+ max-height: inherit;
+ min-height: 27em;
+}
.oe_seo_configuration input {
- width: inherit;
+ width: 90%;
}
.oe_seo_configuration textarea {
- width: inherit;
+ width: 90%;
}
.oe_seo_configuration ul {
list-style: none;
@@ -283,9 +310,10 @@
}
.oe_seo_keywords_list {
- width: 39em;
+ width: 100%;
+ margin: 0.1em;
}
.oe_seo_keywords_list li {
float: left;
- width: 12em;
+ width: 19em;
}
diff --git a/addons/website/static/src/css/editor.sass b/addons/website/static/src/css/editor.sass
index c75946d7c6b..74885a380ee 100644
--- a/addons/website/static/src/css/editor.sass
+++ b/addons/website/static/src/css/editor.sass
@@ -1,5 +1,22 @@
@charset "utf-8"
+/* ---- CKEditor Minimal Reset ---- */
+
+.navbar.navbar-inverse .cke_chrome
+ border: none
+
+.navbar.navbar-inverse .cke_inner
+ background: transparent
+
+.navbar.navbar-inverse .cke_top
+ background: transparent
+ border: none
+ box-shadow: none
+ -webkit-box-shadow: none
+ -moz-box-shadow: none
+
+/* ---- OpenERP Style ---- */
+
.oe_hidden
display: none !important
@@ -186,12 +203,14 @@
.oe_handle.nw,.oe_handle.w,.oe_handle.sw
left: 0px
-/* ---- MOBILE PREVIEW ---- */
+/* ---- MODAL DIALOGS ---- */
.oe_stop_scrolling
height: 100%
overflow: hidden
+/* ---- MOBILE PREVIEW ---- */
+
$mobile_preview_background: #000000
$mobile_preview_border: #1A1A1A
$icon_close: #E00101
@@ -236,18 +255,26 @@ $icon_close: #E00101
color: $icon_close
opacity: 1
+/* ---- SEO TOOLS ---- */
+
+$remove_color: $icon_close
.oe_seo_configuration
+ min-width: 42em
+ .modal-body
+ max-height: inherit
+ min-height: 27em
input
- width: inherit
+ width: 90%
textarea
- width: inherit
+ width: 90%
ul
list-style: none
.oe_remove
- color: $icon_close
+ color: $remove_color
.oe_seo_keywords_list
- width: 39em
+ width: 100%
+ margin: 0.1em
li
float: left
- width: 12em \ No newline at end of file
+ width: 19em
diff --git a/addons/website/static/src/js/website.js b/addons/website/static/src/js/website.js
index 890c49dfdaf..83452934d94 100644
--- a/addons/website/static/src/js/website.js
+++ b/addons/website/static/src/js/website.js
@@ -32,9 +32,13 @@
};
website.init_editor = function () {
+ CKEDITOR.on('dialogDefinition', function (ev) {
+ (alter_dialog[ev.data.name] || noop)(ev.data.definition);
+ });
var editor = new website.EditorBar();
- editor.prependTo($('body'));
- $('body').css('padding-top', '50px'); // Not working properly: editor.$el.outerHeight());
+ var $body = $('body');
+ editor.prependTo($body);
+ $body.css('padding-top', '50px'); // Not working properly: editor.$el.outerHeight());
};
/* ----- TOP EDITOR BAR FOR ADMIN ---- */
@@ -153,6 +157,7 @@
save: function () {
var self = this;
var defs = [];
+ observer.disconnect();
$('.oe_dirty').each(function (i, v) {
var $el = $(this);
// TODO: Add a queue with concurrency limit in webclient
@@ -236,36 +241,96 @@
init: function (parent, options) {
this._super(parent);
this.keyword = options.keyword;
+ this.onDelete = options.onDelete;
+ },
+ destroy: function () {
+ if (_.isFunction(this.onDelete)) {
+ this.onDelete(this.keyword);
+ }
+ this._super();
},
});
+ website.seo.cleanupKeyword = function (word) {
+ return word ? word.replace(/[,;.:]+/g, " ").replace(/ +/g, " ").trim() : "";
+ };
+ website.seo.PageParser = function () {
+ function currentURL () {
+ var url = window.location.href;
+ var hashIndex = url.indexOf('#');
+ return hashIndex >= 0 ? url.substring(0, hashIndex) : url;
+ }
+ var parsedPage = {
+ url: currentURL(),
+ title: $('title').text(),
+ headers: {},
+ content: {}
+ };
+ _.each([ 'h1', 'h2', 'h3'], function (header) {
+ parsedPage.headers[header] = [];
+ $(header).each(function () {
+ var text = $(this).text();
+ parsedPage.headers[header].push(text);
+ });
+ });
+ this.url = function () {
+ return parsedPage.url;
+ };
+ this.title = function () {
+ return parsedPage.title;
+ };
+ this.headers = function () {
+ return parsedPage.headers;
+ };
+ this.keywordSuggestions = function () {
+ return _.map(_.uniq(parsedPage.headers.h1.concat(parsedPage.headers.h2)), website.seo.cleanupKeyword);
+ };
+ };
website.seo.Configurator = openerp.Widget.extend({
template: 'website.seo_configuration',
events: {
'keypress input[name=seo_page_keywords]': 'confirmKeyword',
'click button[data-action=add]': 'addKeyword',
'click a[data-action=update]': 'update',
- 'hidden': 'close'
+ 'hidden': 'destroy'
},
- start: function () {
- $('body').addClass('oe_stop_scrolling');
- this.$el.find('input[name=seo_page_url]').val(window.location.href);
- this.$el.find('input[name=seo_page_title]').val($('title').text());
+ maxTitleSize: 65,
+ maxDescriptionSize: 155,
+ maxNumberOfKeywords: 10,
+ maxWordsPerKeyword: 4,
+ start: function () {
+ var pageParser = new website.seo.PageParser();
+ var currentKeywords = this.keywords;
+ this.$el.find('.js_seo_page_url').text(pageParser.url());
+ this.$el.find('input[name=seo_page_title]').val(pageParser.title());
+ this.$el.find('input[name=seo_page_keywords]').typeahead({
+ source: function () {
+ var suggestions = pageParser.keywordSuggestions();
+ var alreadyChosen = currentKeywords();
+ return _.difference(suggestions, alreadyChosen);
+ },
+ items: 4
+ });
+
+ $('body').addClass('oe_stop_scrolling');
this.$el.modal();
},
+ currentPage: function () {
+ var url = window.location.href;
+ var hashIndex = url.indexOf('#');
+ return hashIndex > 0 ? url.substring(0, hashIndex): url;
+ },
keywords: function () {
- var result = [];
- this.$el.find('.js_seo_keyword').each(function () {
- result.push($(this).text());
- })
- return _.uniq(result);
+ return _.uniq($('.js_seo_keyword').map(function () {
+ return $(this).text();
+ }));
},
isExistingKeyword: function (word) {
return _.contains(this.keywords(), word);
},
isKeywordListFull: function () {
- return this.keywords().length >= 10;
+ return this.keywords().length >= this.maxNumberOfKeywords;
},
confirmKeyword: function (e) {
if (e.keyCode == 13) {
@@ -274,24 +339,45 @@
}
},
addKeyword: function () {
- var word = this.$el.find('input[name=seo_page_keywords]').val().trim();
+ var $modal = this.$el;
+ function enableNewKeywords () {
+ $modal.find('input[name=seo_page_keywords]')
+ .removeAttr('readonly').attr('placeholder', "New keyword");
+ $modal.find('button[data-action=add]')
+ .prop('disabled', false).removeClass('disabled');
+ }
+ function disableNewKeywords () {
+ $modal.find('input[name=seo_page_keywords]')
+ .attr('readonly', "readonly")
+ .attr('placeholder', "Remove a keyword first");
+ $modal.find('button[data-action=add]')
+ .prop('disabled', true).addClass('disabled');
+ }
+ var candidate = this.$el.find('input[name=seo_page_keywords]').val();
+ var word = website.seo.cleanupKeyword(candidate);
if (word && !this.isKeywordListFull() && !this.isExistingKeyword(word)) {
new website.seo.Keyword(this, {
- keyword: word
+ keyword: word,
+ onDelete: enableNewKeywords
}).appendTo(this.$el.find('.js_seo_keywords_list'));
- var $body = this.$el.find('.modal-body');
- $body.animate({
- scrollTop: $body[0].scrollHeight
- }, 500);
+ this.scrollDown();
+ }
+ if (this.isKeywordListFull()) {
+ disableNewKeywords();
}
},
+ scrollDown: function () {
+ var $body = this.$el.find('.modal-body');
+ $body.animate({
+ scrollTop: $body[0].scrollHeight
+ }, 500);
+ },
update: function () {
- console.log(this.keywords());
// TODO: Persist changes
},
- close: function () {
+ destroy: function () {
$('body').removeClass('oe_stop_scrolling');
- this.destroy();
+ this._super();
},
});
@@ -309,10 +395,20 @@
$elements
.not('span, [data-oe-type]')
.each(function () {
- var $this = $(this);
- CKEDITOR.inline(this, self._config()).on('change', function () {
- $this.addClass('oe_dirty');
- self.trigger('change', this, null);
+ var node = this;
+ var $node = $(node);
+ var editor = CKEDITOR.inline(this, self._config());
+ editor.on('instanceReady', function () {
+ observer.observe(node, {
+ childList: true,
+ attributes: true,
+ characterData: true,
+ subtree: true
+ });
+ });
+ $node.one('content_changed', function () {
+ $node.addClass('oe_dirty');
+ self.trigger('change');
});
});
},
@@ -378,31 +474,35 @@
// TODO clean
snippet_carousel: function () {
var self = this;
- $('.carousel .js_carousel_options .label').on('click', function (e) {
+ var $carousel_options = $('.carousel .js_carousel_options');
+ $carousel_options.on('click', '.label', function (e) {
e.preventDefault();
var $button = $(e.currentTarget);
var $c = $button.parents(".carousel:first");
+ var $carousel_inner = $c.find('.carousel-inner');
if($button.hasClass("js_add")) {
- var cycle = $c.find(".carousel-inner .item").size();
- $c.find(".carousel-inner").append(openerp.qweb.render("website.carousel"));
+ var cycle = $carousel_inner.find('.item').size();
+ $carousel_inner.append(openerp.qweb.render('website.carousel'));
$c.carousel(cycle);
}
else {
- var cycle = $c.find(".carousel-inner .item.active").remove();
- $c.find(".carousel-inner .item:first").addClass("active");
+ $carousel_inner
+ .find('.item.active').remove().end()
+ .find('.item:first').addClass('active');
$c.carousel(0);
self.trigger('change', self, null);
}
});
- $('.carousel .js_carousel_options').show();
+ $carousel_options.show();
}
});
website.mutations = {
darken: function($el){
- if($el.parent().hasClass('dark')){
- $el.parent().replaceWith($el);
+ var $parent = $el.parent();
+ if($parent.hasClass('dark')){
+ $parent.replaceWith($el);
}else{
$el.replaceWith($("<div class='dark'></div>").append($el.clone()));
}
@@ -513,23 +613,25 @@
// selector.siblings -> will insert drop zones after and before selected elements
activate_insertion_zones: function(selector){
var self = this;
+ var i, len, $zones;
var child_selector = selector.childs || '';
var sibling_selector = selector.siblings || '';
var zone_template = "<div class='oe_drop_zone oe_insert'></div>";
+ var $drop_zone = $('.oe_drop_zone');
- $('.oe_drop_zone').remove();
+ $drop_zone.remove();
if(child_selector){
- var $zones = $(child_selector);
- for( var i = 0, len = $zones.length; i < len; i++ ){
+ $zones = $(child_selector);
+ for(i = 0, len = $zones.length; i < len; i++ ){
$zones.eq(i).find('> *:not(.oe_drop_zone)').after(zone_template);
$zones.eq(i).prepend(zone_template);
}
}
if(sibling_selector){
- var $zones = $(sibling_selector);
- for( var i = 0, len = $zones.length; i < len; i++ ){
+ $zones = $(sibling_selector);
+ for(i = 0, len = $zones.length; i < len; i++ ){
if($zones.eq(i).prev('.oe_drop_zone').length === 0){
$zones.eq(i).before(zone_template);
}
@@ -541,12 +643,12 @@
// Cleaning up unnecessary zones
$('.oe_snippets .oe_drop_zone').remove(); // no zone in the snippet selector ...
- $('#website-top-view .oe_drop_zone').remove(); // no zone in the top bars ...
- $('#website-top-edit .oe_drop_zone').remove();
+ $('#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;
- var $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones
+ $zones = $('.oe_drop_zone + .oe_drop_zone'); // no two consecutive zones
count += $zones.length;
$zones.remove();
@@ -556,25 +658,23 @@
}while(count > 0);
// Cleaning up zones placed between floating or inline elements. We do not like these kind of zones.
- var $zones = $('.oe_drop_zone');
- for( var i = 0, len = $zones.length; i < len; i++ ){
+ $zones = $drop_zone;
+ for(i = 0, len = $zones.length; i < len; i++ ){
var zone = $zones.eq(i);
var prev = zone.prev();
var next = zone.next();
- var float_prev = 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;
+ var float_prev = prev.css('float') || 'none';
+ var float_next = next.css('float') || 'none';
+ var disp_prev = prev.css('display') || null;
+ var disp_next = next.css('display') || null;
if( (float_prev === 'left' || float_prev === 'right')
&& (float_next === 'left' || float_next === 'right') ){
zone.remove();
- continue;
}else if( !( disp_prev === null
|| disp_next === null
|| disp_prev === 'block'
|| disp_next === 'block' )){
zone.remove();
- continue;
}
}
},
@@ -646,31 +746,32 @@
var pt = $snippet.css('padding-top');
var pb = $snippet.css('padding-bottom');
- pt = Number(pt.slice(0,pt.length - 2)) || 0; //FIXME something cleaner to remove 'px'
- pb = Number(pb.slice(0,pb.length - 2)) || 0;
+ 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();
- $('body').mousemove(function(event){
- var dx = event.pageX - x;
- var dy = event.pageY - y;
- event.preventDefault();
- if($handle.hasClass('n') || $handle.hasClass('nw') || $handle.hasClass('ne')){
- $snippet.css('padding-top',pt-dy+'px');
- self.cover_target($manipulator,$snippet);
- }else if($handle.hasClass('s') || $handle.hasClass('sw') || $handle.hasClass('se')){
-
- $snippet.css('padding-bottom',pb+dy+'px');
- self.cover_target($manipulator,$snippet);
+ $('body').on({
+ mousemove: function(event){
+ var dx = event.pageX - x;
+ var dy = event.pageY - y;
+ event.preventDefault();
+ if($handle.hasClass('n') || $handle.hasClass('nw') || $handle.hasClass('ne')){
+ $snippet.css('padding-top',pt-dy+'px');
+ self.cover_target($manipulator,$snippet);
+ }else if($handle.hasClass('s') || $handle.hasClass('sw') || $handle.hasClass('se')){
+
+ $snippet.css('padding-bottom',pb+dy+'px');
+ self.cover_target($manipulator,$snippet);
+ }
+ },
+ mouseup: function(){
+ $body.off('mouseup mousemove');
+ self.deactivate_snippet_manipulators();
+ self.activate_snippet_manipulators();
}
});
- $('body').mouseup(function(){
- $('body').unbind('mousemove');
- $('body').unbind('mouseup');
- self.deactivate_snippet_manipulators();
- self.activate_snippet_manipulators();
- });
});
}
@@ -825,8 +926,33 @@
})
}
};
- CKEDITOR.on('dialogDefinition', function (ev) {
- (alter_dialog[ev.data.name] || noop)(ev.data.definition);
+
+ var Observer = window.MutationObserver || window.WebkitMutationObserver || window.JsMutationObserver;
+ var observer = new Observer(function (mutations) {
+ _(mutations).chain()
+ .filter(function (m) {
+ switch(m.type) {
+ case 'attributes':
+ // ignore cke_focus being added & removed from RTE root
+ // FIXME: what if snippets are configured by adding/removing classes on their root element?
+ return !$(m.target).hasClass('oe_editable');
+ case 'childList':
+ // <br type="_moz"> appears when focusing RTE in FF, ignore
+ return m.addedNodes.length !== 1 || m.addedNodes[0].nodeName !== 'BR';
+ default:
+ return true;
+ }
+ })
+ .map(function (m) {
+ var node = m.target;
+ while (node && !$(node).hasClass('oe_editable')) {
+ node = node.parentNode;
+ }
+ return node;
+ })
+ .compact()
+ .uniq()
+ .each(function (node) { $(node).trigger('content_changed'); })
});
var all_ready = null;
diff --git a/addons/website/static/src/xml/website.xml b/addons/website/static/src/xml/website.xml
index ea835d0280c..f5f13f7bbcd 100644
--- a/addons/website/static/src/xml/website.xml
+++ b/addons/website/static/src/xml/website.xml
@@ -51,19 +51,15 @@
</t>
<t t-name="website.seo_configuration">
- <div class="modal hide fade oe_seo_configuration" tabindex="-1" role="dialog" aria-labelledby="seo-configuration-title" aria-hidden="true">
+ <div class="modal hide fade oe_seo_configuration" tabindex="-1" role="dialog" aria-labelledby="js_seo_page_url" aria-hidden="true">
<div class="modal-header">
<button title="Close" type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
- <h3 id="seo-configuration-title">Promote your page on the web</h3>
+ <h3>Promote your page on the Web</h3>
+ <br/>
+ <span class="js_seo_page_url"></span>
</div>
<div class="modal-body">
<div class="control-group">
- <label class="control-label" for="seo_page_url">URL</label>
- <div class="controls">
- <input type="text" name="seo_page_url" maxlength="70" size="70" readonly="readonly"/>
- </div>
- </div>
- <div class="control-group">
<label class="control-label" for="seo_page_title">Title</label>
<div class="controls">
<input type="text" name="seo_page_title" maxlength="70" size="70"/>
@@ -79,7 +75,7 @@
<label class="control-label" for="seo_page_keywords">Keywords</label>
<div class="controls">
<div class="input-append">
- <input type="text" name="seo_page_keywords" maxlength="20" size="24" placeholder="New keyword"/>
+ <input type="text" name="seo_page_keywords" data-provide="typeahead" autocomplete="off" maxlength="20" size="24" placeholder="New keyword"/>
<button data-action="add" class="btn" type="button">Add</button>
</div>
<ul class="oe_seo_keywords_list js_seo_keywords_list">