summaryrefslogtreecommitdiff
path: root/addons/website/static/src/js/menu
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2017-06-07 17:45:15 +0200
committerqsm-odoo <qsm@odoo.com>2017-08-16 10:59:39 +0200
commit4f925af06fd3cebfcfb56eb947ab68c8a2ca4490 (patch)
treeb13bd049b6b3e7bbdfb537d19b53c95fc4025ea5 /addons/website/static/src/js/menu
parentf205bfcd4ddcf3f1d8676ee87ee0b29941bd6773 (diff)
[MOV] web_editor, website: reorganize files
Diffstat (limited to 'addons/website/static/src/js/menu')
-rw-r--r--addons/website/static/src/js/menu/content.js361
-rw-r--r--addons/website/static/src/js/menu/mobile_view.js49
-rw-r--r--addons/website/static/src/js/menu/planner.js41
-rw-r--r--addons/website/static/src/js/menu/seo.js582
-rw-r--r--addons/website/static/src/js/menu/translate.js104
5 files changed, 1137 insertions, 0 deletions
diff --git a/addons/website/static/src/js/menu/content.js b/addons/website/static/src/js/menu/content.js
new file mode 100644
index 00000000000..70f35871e74
--- /dev/null
+++ b/addons/website/static/src/js/menu/content.js
@@ -0,0 +1,361 @@
+odoo.define('website.contentMenu', function (require) {
+"use strict";
+
+var core = require('web.core');
+var ajax = require('web.ajax');
+var Widget = require('web.Widget');
+var base = require('web_editor.base');
+var editor = require('web_editor.editor');
+var widget = require('web_editor.widget');
+var website = require('website.website');
+
+var _t = core._t;
+var qweb = core.qweb;
+
+ajax.loadXML('/website/static/src/xml/website.contentMenu.xml', qweb);
+
+var TopBarContent = Widget.extend({
+ start: function () {
+ var self = this;
+
+ // Add page modal + menu content event
+ this.$el.add($('#o_website_add_page_modal')).on('click', 'a[data-action]', function (e) {
+ e.preventDefault();
+ self[$(this).data('action')]();
+ });
+
+ return this._super();
+ },
+ edit_menu: function (action_before_reload) {
+ var context = base.get_context();
+ var def = $.Deferred();
+ if ($("[data-content_menu_id]").length) {
+ var select = new SelectEditMenuDialog();
+ select.on('save', this, function (root) {
+ def.resolve(root);
+ });
+ select.open();
+ } else {
+ def.resolve(null);
+ }
+
+ return def.then(function (root_id) {
+ return ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'website.menu',
+ method: 'get_tree',
+ args: [context.website_id, root_id],
+ kwargs: {
+ context: context
+ },
+ }).then(function (menu) {
+ var dialog = new EditMenuDialog(this, {}, menu).open();
+ dialog.on("save", null, function () {
+ $.when(action_before_reload && action_before_reload()).then(function () {
+ editor.reload();
+ });
+ });
+ return dialog;
+ });
+ });
+ },
+ new_page: function () {
+ website.prompt({
+ id: "editor_new_page",
+ window_title: _t("New Page"),
+ input: _t("Page Title"),
+ init: function () {
+ var $group = this.$dialog.find("div.form-group");
+ $group.removeClass("mb0");
+
+ var $add = $('<div/>', {'class': 'form-group mb0'})
+ .append($('<span/>', {'class': 'col-sm-offset-3 col-sm-9 text-left'})
+ .append(qweb.render('web_editor.components.switch', {id: 'switch_addTo_menu', label: _t("Add page in menu")})));
+ $add.find('input').prop('checked', true);
+ $group.after($add);
+ }
+ }).then(function (val, field, $dialog) {
+ if (val) {
+ var url = '/website/add/' + encodeURIComponent(val);
+ if ($dialog.find('input[type="checkbox"]').is(':checked')) url +="?add_menu=1";
+ document.location = url;
+ }
+ });
+ },
+ rename_page: function () {
+ var self = this;
+ var context = base.get_context();
+ self.mo_id = self.getMainObject().id;
+
+ ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'website',
+ method: 'page_search_dependencies',
+ args: [self.mo_id],
+ kwargs: {
+ context: context
+ },
+ }).then(function (deps) {
+ website.prompt({
+ id: "editor_rename_page",
+ window_title: _t("Rename This Page"),
+ dependencies: deps,
+ }, 'website.rename_page').then(function (val, field, $dialog) {
+ ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'website',
+ method: 'rename_page',
+ args: [
+ self.mo_id,
+ val,
+ ],
+ kwargs: {
+ context: context
+ },
+ }).then(function (new_name) {
+ window.location = "/page/" + encodeURIComponent(new_name);
+ });
+ });
+ });
+ },
+ delete_page: function () {
+ var self = this;
+ var context = base.get_context();
+ self.mo_id = self.getMainObject().id;
+
+ ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'website',
+ method: 'page_search_dependencies',
+ args: [self.mo_id],
+ kwargs: {
+ context: context,
+ },
+ }).then(function (deps) {
+ website.prompt({
+ id: "editor_delete_page",
+ window_title: _t("Delete Page"),
+ dependencies: deps,
+ init: function () { $('.btn-continue').prop("disabled", true); },
+ }, 'website.delete_page').then(function (val, field, $dialog) {
+
+ if ($dialog.find('input[type="checkbox"]').is(':checked')) {
+ ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'website',
+ method: 'delete_page',
+ args: [self.mo_id],
+ kwargs: {
+ context: context
+ },
+ }).then(function () {
+ window.location = "/";
+ });
+ }
+ });
+ });
+ },
+ getMainObject: function () {
+ var repr = $('html').data('main-object');
+ var m = repr.match(/(.+)\((\d+),(.*)\)/);
+ if (!m) {
+ return null;
+ } else {
+ return {
+ model: m[1],
+ id: m[2]|0
+ };
+ }
+ }
+});
+
+website.TopBar.include({
+ start: function () {
+ this.content_menu = new TopBarContent();
+ var def = this.content_menu.attachTo($('.oe_content_menu'));
+ return $.when(this._super(), def);
+ }
+});
+
+var SelectEditMenuDialog = widget.Dialog.extend({
+ template: 'website.contentMenu.dialog.select',
+ init: function (parent, options) {
+ var self = this;
+ self.roots = [{id: null, name: _t("Top Menu")}];
+ $("[data-content_menu_id]").each(function () {
+ self.roots.push({id: $(this).data("content_menu_id"), name: $(this).attr("name")});
+ });
+ this._super(parent, _.extend({}, {
+ title: _t("Select a Menu"),
+ save_text: _t("Continue")
+ }, options || {}));
+ },
+ save: function () {
+ this.final_data = parseInt(this.$el.find("select").val() || null);
+ this._super.apply(this, arguments);
+ }
+});
+
+var EditMenuDialog = widget.Dialog.extend({
+ template: 'website.contentMenu.dialog.edit',
+ events: _.extend({}, widget.Dialog.prototype.events, {
+ 'click a.js_add_menu': 'add_menu',
+ 'click button.js_edit_menu': 'edit_menu',
+ 'click button.js_delete_menu': 'delete_menu',
+ }),
+ init: function (parent, options, menu) {
+ this.menu = menu;
+ this.root_menu_id = menu.id;
+ this.flat = this.flatenize(menu);
+ this.to_delete = [];
+ this._super(parent, _.extend({}, {
+ title: _t("Edit Menu"),
+ size: 'medium',
+ }, options || {}));
+ },
+ start: function () {
+ var r = this._super.apply(this, arguments);
+ this.$('.oe_menu_editor').nestedSortable({
+ listType: 'ul',
+ handle: 'div',
+ items: 'li',
+ maxLevels: 2,
+ toleranceElement: '> div',
+ forcePlaceholderSize: true,
+ opacity: 0.6,
+ placeholder: 'oe_menu_placeholder',
+ tolerance: 'pointer',
+ attribute: 'data-menu-id',
+ expression: '()(.+)', // nestedSortable takes the second match of an expression (*sigh*)
+ });
+ return r;
+ },
+ flatenize: function (node, dict) {
+ dict = dict || {};
+ var self = this;
+ dict[node.id] = node;
+ node.children.forEach(function (child) {
+ self.flatenize(child, dict);
+ });
+ return dict;
+ },
+ add_menu: function () {
+ var self = this;
+ var dialog = new MenuEntryDialog(this, {menu_link_options: true}, undefined, {});
+ dialog.on('save', this, function (link) {
+ var new_menu = {
+ id: _.uniqueId('new-'),
+ name: link.text,
+ url: link.url,
+ new_window: link.isNewWindow,
+ parent_id: false,
+ sequence: 0,
+ children: [],
+ };
+ self.flat[new_menu.id] = new_menu;
+ self.$('.oe_menu_editor').append(
+ qweb.render('website.contentMenu.dialog.submenu', { submenu: new_menu }));
+ });
+ dialog.open();
+ },
+ edit_menu: function (ev) {
+ var self = this;
+ var menu_id = $(ev.currentTarget).closest('[data-menu-id]').data('menu-id');
+ var menu = self.flat[menu_id];
+ if (menu) {
+ var dialog = new MenuEntryDialog(this, {}, undefined, menu);
+ dialog.on('save', this, function (link) {
+ var id = link.id;
+ var menu_obj = self.flat[id];
+ _.extend(menu_obj, {
+ 'name': link.text,
+ 'url': link.url,
+ 'new_window': link.isNewWindow,
+ });
+ var $menu = self.$('[data-menu-id="' + id + '"]');
+ $menu.find('.js_menu_label').first().text(menu_obj.name);
+ });
+ dialog.open();
+ } else {
+ alert("Could not find menu entry");
+ }
+ },
+ delete_menu: function (ev) {
+ var $menu = $(ev.currentTarget).closest('[data-menu-id]');
+ var mid = $menu.data('menu-id')|0;
+ if (mid) {
+ this.to_delete.push(mid);
+ }
+ $menu.remove();
+ },
+ save: function () {
+ var _super = this._super.bind(this);
+ var self = this;
+ var new_menu = this.$('.oe_menu_editor').nestedSortable('toArray', {startDepthCount: 0});
+ var levels = [];
+ var data = [];
+ var context = base.get_context();
+ // Resequence, re-tree and remove useless data
+ new_menu.forEach(function (menu) {
+ if (menu.id) {
+ levels[menu.depth] = (levels[menu.depth] || 0) + 1;
+ var mobj = self.flat[menu.id];
+ mobj.sequence = levels[menu.depth];
+ mobj.parent_id = (menu.parent_id|0) || menu.parent_id || self.root_menu_id;
+ delete(mobj.children);
+ data.push(mobj);
+ }
+ });
+ ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'website.menu',
+ method: 'save',
+ args: [[context.website_id], { data: data, to_delete: self.to_delete }],
+ kwargs: {
+ context: context
+ },
+ }).then(function () {
+ return _super();
+ });
+ },
+});
+
+var MenuEntryDialog = widget.LinkDialog.extend({
+ init: function (parent, options, editor, data) {
+ data.text = data.name || '';
+ data.isNewWindow = data.new_window;
+ this.data = data;
+ this.menu_link_options = options.menu_link_options;
+ return this._super.apply(this, arguments);
+ },
+ start: function () {
+ var self = this;
+ this.$(".o_link_dialog_preview").remove();
+ this.$(".window-new, .link-style").closest(".form-group").remove();
+ this.$("label[for='o_link_dialog_label_input']").text(_t("Menu Label"));
+ if (this.menu_link_options) { // add menu link option only when adding new menu
+ this.$('#o_link_dialog_label_input').closest('.form-group').after(qweb.render('website.contentMenu.dialog.edit.link_menu_options'));
+ this.$('input[name=link_menu_options]').on('change', function() {
+ self.$('#o_link_dialog_url_input').closest('.form-group').toggle();
+ });
+ }
+ this.$modal.find('.modal-lg').removeClass('modal-lg')
+ .find('.col-md-8').removeClass('col-md-8').addClass('col-xs-12');
+
+ return this._super.apply(this, arguments);
+ },
+ save: function () {
+ var $e = this.$('#o_link_dialog_label_input');
+ if (!$e.val() || !$e[0].checkValidity()) {
+ $e.closest('.form-group').addClass('has-error');
+ $e.focus();
+ return;
+ }
+ if (this.$('input[name=link_menu_options]:checked').val() === 'new_page') {
+ window.location = '/website/add/' + encodeURIComponent($e.val()) + '?add_menu=1';
+ return;
+ }
+ return this._super.apply(this, arguments);
+ }
+});
+
+return {
+ 'TopBar': TopBarContent,
+ 'EditMenuDialog': EditMenuDialog,
+};
+
+});
diff --git a/addons/website/static/src/js/menu/mobile_view.js b/addons/website/static/src/js/menu/mobile_view.js
new file mode 100644
index 00000000000..791775f57b8
--- /dev/null
+++ b/addons/website/static/src/js/menu/mobile_view.js
@@ -0,0 +1,49 @@
+odoo.define('website.mobile', function (require) {
+'use strict';
+
+var ajax = require('web.ajax');
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var Widget = require('web.Widget');
+var base = require('web_editor.base');
+var website = require('website.website');
+
+var _t = core._t;
+
+var MobilePreviewDialog = Dialog.extend({
+ template: 'website.mobile_preview',
+
+ init: function () {
+ this._super.apply(this, arguments);
+ this.mobile_src = $.param.querystring(window.location.href, 'mobilepreview');
+ },
+
+ start: function () {
+ var self = this;
+ this.$modal.addClass('oe_mobile_preview');
+ this.$modal.on('click', '.modal-header', function () {
+ self.$el.toggleClass('o_invert_orientation');
+ });
+ this.$iframe = this.$('iframe');
+ this.$iframe.on('load', function (e) {
+ self.$iframe.contents().find('body').removeClass('o_connected_user');
+ self.$iframe.contents().find('#oe_main_menu_navbar, #o_website_add_page_modal').remove();
+ });
+
+ return this._super.apply(this, arguments);
+ },
+});
+
+website.TopBar.include({
+ start: function () {
+ var self = this;
+ this.$el.on('click', 'a[data-action=show-mobile-preview]', function () {
+ new MobilePreviewDialog(self, {
+ title: _t('Mobile preview') + " <span class='fa fa-refresh'/>",
+ }).open();
+ });
+ return this._super();
+ },
+});
+
+});
diff --git a/addons/website/static/src/js/menu/planner.js b/addons/website/static/src/js/menu/planner.js
new file mode 100644
index 00000000000..59adfef92a0
--- /dev/null
+++ b/addons/website/static/src/js/menu/planner.js
@@ -0,0 +1,41 @@
+odoo.define('web.planner.website', function (require) {
+'use strict';
+
+var ajax = require('web.ajax');
+var core = require('web.core');
+var planner = require('web.planner.common');
+var rpc = require('web.rpc');
+var session = require('web.session');
+var website = require('website.website');
+
+var qweb = core.qweb;
+
+var WebsitePlannerLauncher = planner.PlannerLauncher.extend({
+ _fetch_planner_data: function () {
+ return rpc.query({
+ model: 'web.planner',
+ method: 'search_read',
+ args: [[['planner_application', '=', 'planner_website']]],
+ })
+ .then((function (planner) {
+ if (!planner.length) return;
+
+ planner[0].data = $.parseJSON(planner[0].data) || {};
+ this._setup_for_planner(planner[0]);
+ }).bind(this));
+ },
+});
+
+if (session.is_system) {
+ website.TopBar.include({
+ start: function () {
+ var websitePlannerLauncher = new WebsitePlannerLauncher();
+ var def = ajax.loadXML('/web_planner/static/src/xml/web_planner.xml', qweb).then((function() {
+ return websitePlannerLauncher.prependTo(this.$(".o_menu_systray"));
+ }).bind(this));
+ return $.when(this._super.apply(this, arguments), def);
+ },
+ });
+}
+
+});
diff --git a/addons/website/static/src/js/menu/seo.js b/addons/website/static/src/js/menu/seo.js
new file mode 100644
index 00000000000..93b434a5f44
--- /dev/null
+++ b/addons/website/static/src/js/menu/seo.js
@@ -0,0 +1,582 @@
+odoo.define('website.seo', function (require) {
+'use strict';
+
+var core = require('web.core');
+var ajax = require('web.ajax');
+var Class = require('web.Class');
+var Dialog = require('web.Dialog');
+var mixins = require('web.mixins');
+var rpc = require("web.rpc");
+var Widget = require('web.Widget');
+var base = require('web_editor.base');
+var website = require('website.website');
+
+var _t = core._t;
+
+var qweb = core.qweb;
+
+ajax.loadXML('/website/static/src/xml/website.seo.xml', qweb);
+
+ // This replaces \b, because accents(e.g. à, é) are not seen as word boundaries.
+ // Javascript \b is not unicode aware, and words beginning or ending by accents won't match \b
+ var WORD_SEPARATORS_REGEX = '([\\u2000-\\u206F\\u2E00-\\u2E7F\'!"#\\$%&\\(\\)\\*\\+,\\-\\.\\/:;<=>\\?¿¡@\\[\\]\\^_`\\{\\|\\}~\\s]+|^|$)';
+
+function analyzeKeyword(htmlPage, keyword) {
+ return htmlPage.isInTitle(keyword) ? {
+ title: 'label label-success',
+ description: "This keyword is used in the page title",
+ } : htmlPage.isInDescription(keyword) ? {
+ title: 'label label-primary',
+ description: "This keyword is used in the page description",
+ } : htmlPage.isInBody(keyword) ? {
+ title: 'label label-info',
+ description: "This keyword is used in the page content."
+ } : {
+ title: 'label label-default',
+ description: "This keyword is not used anywhere on the page."
+ };
+}
+
+var Suggestion = Widget.extend({
+ template: 'website.seo_suggestion',
+ events: {
+ 'click .js_seo_suggestion': 'select',
+ },
+ init: function (parent, options) {
+ this.root = options.root;
+ this.keyword = options.keyword;
+ this.language = options.language;
+ this.htmlPage = options.page;
+ this._super(parent);
+ },
+ start: function () {
+ this.htmlPage.on('title-changed', this, this.renderElement);
+ this.htmlPage.on('description-changed', this, this.renderElement);
+ },
+ analyze: function () {
+ return analyzeKeyword(this.htmlPage, this.keyword);
+ },
+ highlight: function () {
+ return this.analyze().title;
+ },
+ tooltip: function () {
+ return this.analyze().description;
+ },
+ select: function () {
+ this.trigger('selected', this.keyword);
+ },
+});
+
+var SuggestionList = Widget.extend({
+ template: 'website.seo_suggestion_list',
+ init: function (parent, options) {
+ this.root = options.root;
+ this.language = options.language;
+ this.htmlPage = options.page;
+ this._super(parent);
+ },
+ start: function () {
+ this.refresh();
+ },
+ refresh: function () {
+ var self = this;
+ self.$el.append(_t("Loading..."));
+ var language = self.language || base.get_context().lang.toLowerCase();
+ ajax.jsonRpc('/website/seo_suggest', 'call', {
+ 'keywords': self.root,
+ 'lang': language,
+ }).then(function (keyword_list) {
+ self.addSuggestions(JSON.parse(keyword_list));
+ });
+ },
+ addSuggestions: function (keywords) {
+ var self = this;
+ self.$el.empty();
+ // TODO Improve algorithm + Ajust based on custom user keywords
+ var regex = new RegExp(self.root, "gi");
+ var keywords = _.map(_.uniq(keywords), function (word) {
+ return word.replace(regex, "").trim();
+ });
+ // TODO Order properly ?
+ _.each(keywords, function (keyword) {
+ if (keyword) {
+ var suggestion = new Suggestion(self, {
+ root: self.root,
+ language: self.language,
+ keyword: keyword,
+ page: self.htmlPage,
+ });
+ suggestion.on('selected', self, function (word, language) {
+ self.trigger('selected', word, language);
+ });
+ suggestion.appendTo(self.$el);
+ }
+ });
+ },
+});
+
+var Keyword = Widget.extend({
+ template: 'website.seo_keyword',
+ events: {
+ 'click a[data-action=remove-keyword]': 'destroy',
+ },
+ maxWordsPerKeyword: 4, // TODO Check
+ init: function (parent, options) {
+ this.keyword = options.word;
+ this.language = options.language;
+ this.htmlPage = options.page;
+ this._super(parent);
+ },
+ start: function () {
+ this.htmlPage.on('title-changed', this, this.updateLabel);
+ this.htmlPage.on('description-changed', this, this.updateLabel);
+ this.suggestionList = new SuggestionList(this, {
+ root: this.keyword,
+ language: this.language,
+ page: this.htmlPage,
+ });
+ this.suggestionList.on('selected', this, function (word, language) {
+ this.trigger('selected', word, language);
+ });
+ this.suggestionList.appendTo(this.$('.js_seo_keyword_suggestion'));
+ },
+ analyze: function () {
+ return analyzeKeyword(this.htmlPage, this.keyword);
+ },
+ highlight: function () {
+ return this.analyze().title;
+ },
+ tooltip: function () {
+ return this.analyze().description;
+ },
+ updateLabel: function () {
+ var cssClass = "oe_seo_keyword js_seo_keyword " + this.highlight();
+ this.$(".js_seo_keyword").attr('class', cssClass);
+ this.$(".js_seo_keyword").attr('title', this.tooltip());
+ },
+ destroy: function () {
+ this.trigger('removed');
+ this._super();
+ },
+});
+
+var KeywordList = Widget.extend({
+ template: 'website.seo_list',
+ maxKeywords: 10,
+ init: function (parent, options) {
+ this.htmlPage = options.page;
+ this._super(parent);
+ },
+ start: function () {
+ var self = this;
+ var existingKeywords = self.htmlPage.keywords();
+ if (existingKeywords.length > 0) {
+ _.each(existingKeywords, function (word) {
+ self.add.call(self, word);
+ });
+ }
+ },
+ keywords: function () {
+ var result = [];
+ this.$('.js_seo_keyword').each(function () {
+ result.push($(this).data('keyword'));
+ });
+ return result;
+ },
+ isFull: function () {
+ return this.keywords().length >= this.maxKeywords;
+ },
+ exists: function (word) {
+ return _.contains(this.keywords(), word);
+ },
+ add: function (candidate, language) {
+ var self = this;
+ // TODO Refine
+ var word = candidate ? candidate.replace(/[,;.:<>]+/g, " ").replace(/ +/g, " ").trim().toLowerCase() : "";
+ if (word && !self.isFull() && !self.exists(word)) {
+ var keyword = new Keyword(self, {
+ word: word,
+ language: language,
+ page: this.htmlPage,
+ });
+ keyword.on('removed', self, function () {
+ self.trigger('list-not-full');
+ self.trigger('removed', word);
+ self.trigger('content-updated', true);
+ });
+ keyword.on('selected', self, function (word, language) {
+ self.trigger('selected', word, language);
+ });
+ keyword.appendTo(self.$el);
+ }
+ if (self.isFull()) {
+ self.trigger('list-full');
+ }
+ self.trigger('content-updated');
+ },
+});
+
+var Image = Widget.extend({
+ template: 'website.seo_image',
+ init: function (parent, options) {
+ this.src = options.src;
+ this.alt = options.alt;
+ this._super(parent);
+ },
+});
+
+var ImageList = Widget.extend({
+ init: function (parent, options) {
+ this.htmlPage = options.page;
+ this._super(parent);
+ },
+ start: function () {
+ var self = this;
+ this.htmlPage.images().each(function (index, image) {
+ new 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 Image(this, image).appendTo(this.$el);
+ },
+});
+
+var Preview = Widget.extend({
+ template: 'website.seo_preview',
+ init: function (parent, options) {
+ this.title = options.title;
+ this.url = options.url;
+ this.description = options.description || "[ The description will be generated by google unless you specify one ]";
+ this._super(parent);
+ },
+});
+
+var HtmlPage = Class.extend(mixins.PropertiesMixin, {
+ url: function () {
+ var url = window.location.href;
+ var hashIndex = url.indexOf('#');
+ return hashIndex >= 0 ? url.substring(0, hashIndex) : url;
+ },
+ title: function () {
+ var $title = $('title');
+ return ($title.length > 0) && $title.text() && $title.text().trim();
+ },
+ changeTitle: function (title) {
+ // TODO create tag if missing
+ $('title').text(title);
+ this.trigger('title-changed', title);
+ },
+ description: function () {
+ var $description = $('meta[name=description]');
+ return ($description.length > 0) && ($description.attr('content') && $description.attr('content').trim());
+ },
+ changeDescription: function (description) {
+ // TODO create tag if missing
+ $('meta[name=description]').attr('content', description);
+ this.trigger('description-changed', description);
+ },
+ keywords: function () {
+ var $keywords = $('meta[name=keywords]');
+ var parsed = ($keywords.length > 0) && $keywords.attr('content') && $keywords.attr('content').split(",");
+ return (parsed && parsed[0]) ? parsed: [];
+ },
+ changeKeywords: function (keywords) {
+ // TODO create tag if missing
+ $('meta[name=keywords]').attr('content', keywords.join(","));
+ this.trigger('keywords-changed', keywords);
+ },
+ headers: function (tag) {
+ return $('#wrap '+tag).map(function () {
+ return $(this).text();
+ });
+ },
+ images: function () {
+ return $('#wrap img').map(function () {
+ var $img = $(this);
+ return {
+ src: $img.attr('src'),
+ alt: $img.attr('alt'),
+ };
+ });
+ },
+ company: function () {
+ return $('html').attr('data-oe-company-name');
+ },
+ bodyText: function () {
+ return $('body').children().not('.js_seo_configuration').text();
+ },
+ isInBody: function (text) {
+ return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, "gi").test(this.bodyText());
+ },
+ isInTitle: function (text) {
+ return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, "gi").test(this.title());
+ },
+ isInDescription: function (text) {
+ return new RegExp(WORD_SEPARATORS_REGEX+text+WORD_SEPARATORS_REGEX, "gi").test(this.description());
+ },
+});
+
+var Tip = Widget.extend({
+ template: 'website.seo_tip',
+ events: {
+ 'closed.bs.alert': 'destroy',
+ },
+ init: function (parent, options) {
+ this.message = options.message;
+ // cf. http://getbootstrap.com/components/#alerts
+ // success, info, warning or danger
+ this.type = options.type || 'info';
+ this._super(parent);
+ },
+});
+
+var Configurator = Dialog.extend({
+ template: 'website.seo_configuration',
+ events: {
+ 'keyup input[name=seo_page_keywords]': 'confirmKeyword',
+ 'blur input[name=seo_page_title]': 'titleChanged',
+ 'blur textarea[name=seo_page_description]': 'descriptionChanged',
+ 'click button[data-action=add]': 'addKeyword',
+ },
+ canEditTitle: false,
+ canEditDescription: false,
+ canEditKeywords: false,
+ canEditLanguage: false,
+ maxTitleSize: 65,
+ maxDescriptionSize: 160, // TODO master: remove me and add warning
+
+ init: function (parent, options) {
+ options = options || {};
+ _.defaults(options, {
+ title: _t('Promote This Page'),
+ subtitle: _t('Get this page efficiently referenced in Google to attract more visitors.'),
+ buttons: [
+ {text: _t('Save'), classes: 'btn-primary', click: this.update},
+ {text: _t('Discard'), close: true},
+ ],
+ });
+
+ this._super(parent, options);
+ },
+ start: function () {
+ var self = this;
+
+ this.$modal.addClass('oe_seo_configuration js_seo_configuration');
+
+ this.htmlPage = new HtmlPage();
+ this.$('.js_seo_page_url').text(this.htmlPage.url());
+ this.$('input[name=seo_page_title]').val(this.htmlPage.title());
+ this.$('textarea[name=seo_page_description]').val(this.htmlPage.description());
+
+ this.keywordList = new KeywordList(self, { page: this.htmlPage });
+ this.keywordList.on('list-full', self, function () {
+ self.$('input[name=seo_page_keywords]').attr({
+ readonly: "readonly",
+ placeholder: "Remove a keyword first"
+ });
+ self.$('button[data-action=add]').prop('disabled', true).addClass('disabled');
+ });
+ this.keywordList.on('list-not-full', self, function () {
+ self.$('input[name=seo_page_keywords]').removeAttr('readonly').attr('placeholder', "");
+ self.$('button[data-action=add]').prop('disabled', false).removeClass('disabled');
+ });
+ this.keywordList.on('selected', self, function (word, language) {
+ self.keywordList.add(word, language);
+ });
+ this.keywordList.on('content-updated', self, function (removed) {
+ self.updateTable(removed);
+ });
+ this.keywordList.insertAfter(this.$('.table thead'));
+ this.disableUnsavableFields();
+ this.renderPreview();
+ this.getLanguages();
+ this.updateTable();
+ },
+ getLanguages: function () {
+ var self = this;
+ ajax.jsonRpc('/web/dataset/call_kw', 'call', {
+ model: 'website',
+ method: 'get_languages',
+ args: [[base.get_context().website_id]],
+ kwargs: {context: base.get_context()}
+ }).then( function (data) {
+ self.$('#language-box').html(core.qweb.render('Configurator.language_promote', {
+ 'language': data,
+ 'def_lang': base.get_context().lang
+ }));
+ });
+ },
+ disableUnsavableFields: function () {
+ var self = this;
+ this.loadMetaData().then(function (data) {
+ self.canEditTitle = data && ('website_meta_title' in data);
+ self.canEditDescription = data && ('website_meta_description' in data);
+ self.canEditKeywords = data && ('website_meta_keywords' in data);
+ if (!self.canEditTitle) {
+ self.$('input[name=seo_page_title]').attr('disabled', true);
+ }
+ if (!self.canEditDescription) {
+ self.$('textarea[name=seo_page_description]').attr('disabled', true);
+ }
+ if (!self.canEditTitle && !self.canEditDescription && !self.canEditKeywords) {
+ self.$footer.find('button[data-action=update]').attr('disabled', true);
+ }
+ });
+ },
+ suggestImprovements: function () {
+ var self = this;
+ var tips = [];
+ _.each(tips, function (tip) {
+ displayTip(tip.message, tip.type);
+ });
+
+ function displayTip(message, type) {
+ new Tip(self, {
+ message: message,
+ type: type,
+ }).appendTo(self.$('.js_seo_tips'));
+ }
+ },
+ confirmKeyword: function (e) {
+ if (e.keyCode == 13) {
+ this.addKeyword();
+ }
+ },
+ addKeyword: function (word) {
+ var $input = this.$('input[name=seo_page_keywords]');
+ var $language = this.$('select[name=seo_page_language]');
+ var keyword = _.isString(word) ? word : $input.val();
+ var language = $language.val().toLowerCase();
+ this.keywordList.add(keyword, language);
+ $input.val("").focus();
+ },
+ update: function () {
+ var self = this;
+ var data = {};
+ if (this.canEditTitle) {
+ data.website_meta_title = this.htmlPage.title();
+ }
+ if (this.canEditDescription) {
+ data.website_meta_description = this.htmlPage.description();
+ }
+ if (this.canEditKeywords) {
+ data.website_meta_keywords = this.keywordList.keywords().join(", ");
+ }
+ this.saveMetaData(data).then(function () {
+ self.close();
+ });
+ },
+ getMainObject: function () {
+ var repr = $('html').data('main-object');
+ var m = repr.match(/(.+)\((\d+),(.*)\)/);
+ if (!m) {
+ return null;
+ } else {
+ return {
+ model: m[1],
+ id: m[2]|0
+ };
+ }
+ },
+ loadMetaData: function () {
+ var obj = this.getMainObject();
+ var def = $.Deferred();
+ if (!obj) {
+ // return $.Deferred().reject(new Error("No main_object was found."));
+ def.resolve(null);
+ } else {
+ var fields = ['website_meta_title', 'website_meta_description', 'website_meta_keywords'];
+ rpc.query({
+ model: obj.model,
+ method: 'read',
+ args: [[obj.id], fields, base.get_context()],
+ }).then(function (data) {
+ if (data.length) {
+ var meta = data[0];
+ meta.model = obj.model;
+ def.resolve(meta);
+ } else {
+ def.resolve(null);
+ }
+ }).fail(function () {
+ def.reject();
+ });
+ }
+ return def;
+ },
+ saveMetaData: function (data) {
+ var obj = this.getMainObject();
+ if (!obj) {
+ return $.Deferred().reject();
+ } else {
+ return rpc.query({
+ model: obj.model,
+ method: 'write',
+ args: [[obj.id], data, base.get_context()],
+ });
+ }
+ },
+ titleChanged: function () {
+ var self = this;
+ _.defer(function () {
+ var title = self.$('input[name=seo_page_title]').val();
+ self.htmlPage.changeTitle(title);
+ self.renderPreview();
+ });
+ },
+ descriptionChanged: function () {
+ var self = this;
+ _.defer(function () {
+ var description = self.$('textarea[name=seo_page_description]').val();
+ self.htmlPage.changeDescription(description);
+ self.renderPreview();
+ });
+ },
+ renderPreview: function () {
+ var preview = new Preview(this, {
+ title: this.htmlPage.title(),
+ description: this.htmlPage.description(),
+ url: this.htmlPage.url(),
+ });
+ var $preview = this.$('.js_seo_preview');
+ $preview.empty();
+ preview.appendTo($preview);
+ },
+ destroy: function () {
+ this.htmlPage.changeKeywords(this.keywordList.keywords());
+ this._super.apply(this, arguments);
+ },
+ updateTable : function (removed) {
+ var self = this,
+ val = removed ? (this.$el.find('tbody > tr').length - 1) : (this.$el.find('tbody > tr').length);
+ this.$('table').toggleClass('js_seo_has_content', val > 0 );
+ this.$el.scrollTop(self.$el[0].scrollHeight);
+ },
+});
+
+website.TopBar.include({
+ start: function () {
+ var self = this;
+ this.$el.on('click', 'a[data-action=promote-current-page]', function () {
+ new Configurator(self).open();
+ });
+ return this._super();
+ }
+});
+
+return {
+ Configurator: Configurator,
+};
+
+});
diff --git a/addons/website/static/src/js/menu/translate.js b/addons/website/static/src/js/menu/translate.js
new file mode 100644
index 00000000000..c9b7de4ed8b
--- /dev/null
+++ b/addons/website/static/src/js/menu/translate.js
@@ -0,0 +1,104 @@
+odoo.define('website.translator', function (require) {
+'use strict';
+
+var core = require('web.core');
+var ajax = require('web.ajax');
+var Widget = require('web.Widget');
+var base = require('web_editor.base');
+var translate = require('web_editor.translate');
+var website = require('website.website');
+var local_storage = require('web.local_storage');
+var utils = require('web.utils');
+
+var qweb = core.qweb;
+
+if (!translate.translatable) {
+ return;
+}
+
+
+website.TopBar.include({
+ events: _.extend({}, website.TopBar.prototype.events, {
+ 'click [data-action="edit_master"]': 'edit_master',
+ 'click [data-action="translate"]': 'translate',
+ }),
+ translate: function (ev) {
+ ev.preventDefault();
+ if (translate.edit_translations) {
+ translate.instance.edit();
+ } else {
+ location.search += '&edit_translations';
+ }
+ },
+ edit_master: function (ev) {
+ ev.preventDefault();
+
+ var lang = '/' + utils.get_cookie('frontend_lang');
+
+ var current = document.createElement('a');
+ current.href = window.location.toString();
+ current.search += (current.search ? '&' : '?') + 'enable_editor=1';
+ if (current.pathname.indexOf(lang) === 0) {
+ current.pathname = current.pathname.replace(lang, '');
+ }
+
+ var link = document.createElement('a');
+ link.href = '/website/lang/default';
+ link.search += (link.search ? '&' : '?') + 'r=' + encodeURIComponent(current.pathname + current.search + current.hash);
+
+ window.location = link.href;
+ },
+});
+
+
+if (!translate.edit_translations) {
+ return;
+}
+
+ajax.loadXML('/website/static/src/xml/website.translator.xml', qweb);
+
+var nodialog = 'website_translator_nodialog';
+
+var Translate = translate.Class.include({
+ onTranslateReady: function () {
+ if(this.gengo_translate) {
+ this.translation_gengo_display();
+ }
+ this._super();
+ },
+ edit: function () {
+ $("#oe_main_menu_navbar").hide();
+ if (!local_storage.getItem(nodialog)) {
+ var dialog = new TranslatorDialog();
+ dialog.appendTo($(document.body));
+ dialog.on('activate', this, function () {
+ if (dialog.$('input[name=do_not_show]').prop('checked')) {
+ local_storage.removeItem(nodialog);
+ } else {
+ local_storage.setItem(nodialog, true);
+ }
+ dialog.$el.modal('hide');
+ });
+ }
+ return this._super();
+ },
+ cancel: function () {
+ $("#oe_main_menu_navbar").show();
+ return this._super();
+ }
+});
+
+var TranslatorDialog = Widget.extend({
+ events: _.extend({}, website.TopBar.prototype.events, {
+ 'hidden.bs.modal': 'destroy',
+ 'click button[data-action=activate]': function (ev) {
+ this.trigger('activate');
+ },
+ }),
+ template: 'website.TranslatorDialog',
+ start: function () {
+ this.$el.modal();
+ },
+});
+
+});