flectra.define('web_editor.translate', function (require) { 'use strict'; var core = require('web.core'); var Dialog = require('web.Dialog'); var localStorage = require('web.local_storage'); var Widget = require('web.Widget'); var weContext = require('web_editor.context'); var rte = require('web_editor.rte'); var weWidgets = require('web_editor.widget'); var Dialog = require('web.Dialog'); var _t = core._t; var localStorageNoDialogKey = 'website_translator_nodialog'; var RTETranslatorWidget = rte.Class.extend({ //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- /** * If the element holds a translation, saves it. Otherwise, fallback to the * standard saving but with the lang kept. * * @override */ _saveElement: function ($el, context, withLang) { var self = this; if ($el.data('oe-translation-id')) { return this._rpc({ model: 'ir.translation', method: 'save_html', args: [ [+$el.data('oe-translation-id')], this._getEscapedElement($el).html(), context || weContext.get() ], }).fail(function (error) { Dialog.alert(null, error.data.message); }); } return this._super($el, context, withLang === undefined ? true : withLang); }, }); var AttributeTranslateDialog = weWidgets.Dialog.extend({ /** * @constructor */ init: function (parent, options, node) { this._super(parent, _.extend({ title: _t("Translate Attribute"), buttons: [ {text: _t("Close"), classes: 'btn-primary o_save_button', click: this.save} ], }, options || {})); this.$target = $(node); this.translation = $(node).data('translation'); }, /** * @override */ start: function () { var self = this; var $group = $('
', {class: 'form-group'}).appendTo(this.$el); _.each(this.translation, function (node, attr) { var $node = $(node); var $label = $('').text(attr); var $input = $('').val($node.html()); $input.on('change keyup', function () { var value = $input.val(); $node.html(value).trigger('change', node); $node.data('$node').attr($node.data('attribute'), value).trigger('translate'); self.trigger_up('rte_change', {target: node}); }); $group.append($label).append($input); }); return this._super.apply(this, arguments); } }); var TranslatorInfoDialog = Dialog.extend({ template: 'web_editor.TranslatorInfoDialog', xmlDependencies: Dialog.prototype.xmlDependencies.concat( ['/web_editor/static/src/xml/translator.xml'] ), /** * @constructor */ init: function (parent, options) { this._super(parent, _.extend({ title: _t("Translation Info"), buttons: [ {text: _t("Ok, never show me this again"), classes: 'btn-primary', close: true, click: this._onStrongOk.bind(this)}, {text: _t("Ok"), close: true} ], }, options || {})); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * Called when the "strong" ok is clicked -> adapt localstorage to make sure * the dialog is never displayed again. * * @private */ _onStrongOk: function () { localStorage.setItem(localStorageNoDialogKey, true); }, }); var TranslatorMenuBar = Widget.extend({ template: 'web_editor.editorbar', xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], events: { 'click [data-action="save"]': '_onSaveClick', 'click [data-action="cancel"]': '_onCancelClick', }, custom_events: { 'rte_change': '_onRTEChange', }, /** * @constructor */ init: function (parent, $target, lang) { this._super.apply(this, arguments); var $edit = $target.find('[data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field]'); $edit.filter(':has([data-oe-translation-id], [data-oe-model][data-oe-id][data-oe-field])').attr('data-oe-readonly', true); this.$target = $edit.not('[data-oe-readonly]'); var attrs = ['placeholder', 'title', 'alt']; _.each(attrs, function (attr) { $target.find('['+attr+'*="data-oe-translation-id="]').filter(':empty, input, select, textarea, img').each(function () { var $node = $(this); var translation = $node.data('translation') || {}; var trans = $node.attr(attr); var match = trans.match(/]*data-oe-translation-id="([0-9]+)"[^>]*>(.*)<\/span>/); var $trans = $(trans).addClass('hidden o_editable o_editable_translatable_attribute').appendTo('body'); $trans.data('$node', $node).data('attribute', attr); translation[attr] = $trans[0]; $node.attr(attr, match[2]); var select2 = $node.data('select2'); if (select2) { select2.blur(); $node.on('translate', function () { select2.blur(); }); $node = select2.container.find('input'); } $node.addClass('o_translatable_attribute').data('translation', translation); }); }); this.$target_attr = $target.find('.o_translatable_attribute'); this.$target_attribute = $('.o_editable_translatable_attribute'); this.lang = lang || weContext.get().lang; this.rte = new RTETranslatorWidget(this, this._getRTEConfig); }, /** * @override */ start: function () { this.$('#web_editor-toolbars').remove(); var flag = false; window.onbeforeunload = function (event) { if ($('.o_editable.o_dirty').length && !flag) { flag = true; setTimeout(function () { flag = false; }, 0); return _t('This document is not saved!'); } }; this.$target.addClass('o_editable'); this.rte.start(); this.translations = []; this._markTranslatableNodes(); this.$el.show(); // TODO seems useless this.trigger('edit'); if (!localStorage.getItem(localStorageNoDialogKey)) { new TranslatorInfoDialog(this).open(); } return this._super.apply(this, arguments); }, /** * @override */ destroy: function () { this._cancel(); this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- /** * Leaves translation mode by hiding the translator bar. * * @todo should be destroyed? * @private */ _cancel: function () { var self = this; this.rte.cancel(); this.$target.each(function () { $(this).html(self._getTranlationObject(this).value); }); this._unmarkTranslatableNode(); this.trigger('cancel'); this.$el.hide(); window.onbeforeunload = null; }, /** * Returns the RTE summernote configuration for translation mode. * * @private * @param {jQuery} $editable */ _getRTEConfig: function ($editable) { return { airMode : true, focus: false, airPopover: $editable.data('oe-model') ? [ ['history', ['undo', 'redo']], ] : [ ['font', ['bold', 'italic', 'underline', 'clear']], ['fontsize', ['fontsize']], ['color', ['color']], ['history', ['undo', 'redo']], ], styleWithSpan: false, inlinemedia : ['p'], lang: 'flectra', onChange: function (html, $editable) { $editable.trigger('content_changed'); }, }; }, /** * @private */ _getTranlationObject: function (node) { var $node = $(node); var id = +$node.data('oe-translation-id'); if (!id) { id = $node.data('oe-model')+','+$node.data('oe-id')+','+$node.data('oe-field'); } var trans = _.find(this.translations, function (trans) { return trans.id === id; }); if (!trans) { this.translations.push(trans = {'id': id}); } return trans; }, /** * @private */ _markTranslatableNodes: function () { var self = this; this.$target.add(this.$target_attribute).each(function () { var $node = $(this); var trans = self._getTranlationObject(this); trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' '); }); this.$target.parent().prependEvent('click.translator', function (ev) { if (ev.ctrlKey || !$(ev.target).is(':o_editable')) { return; } ev.preventDefault(); ev.stopPropagation(); }); // attributes this.$target_attr.each(function () { var $node = $(this); var translation = $node.data('translation'); _.each(translation, function (node, attr) { var trans = self._getTranlationObject(node); trans.value = (trans.value ? trans.value : $node.html() ).replace(/[ \t\n\r]+/, ' '); $node.attr('data-oe-translation-state', (trans.state || 'to_translate')); }); }); this.$target_attr.prependEvent('mousedown.translator click.translator mouseup.translator', function (ev) { if (ev.ctrlKey) { return; } ev.preventDefault(); ev.stopPropagation(); if (ev.type !== 'mousedown') { return; } new AttributeTranslateDialog(self, {}, ev.target).open(); }); }, /** * Saves the translation and reloads the page to leave edit mode. * * @private * @returns {Deferred} (never resolved as the page is reloading anyway) */ _save: function () { return this.rte.save(weContext.get({lang: this.lang})).then(function () { window.location.href = window.location.href.replace(/&?edit_translations(=[^&]*)?/g, ''); return $.Deferred(); }); }, /** * @private */ _unmarkTranslatableNode: function () { this.$target.removeClass('o_editable').removeAttr('contentEditable'); this.$target.parent().off('.translator'); this.$target_attr.off('.translator'); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * Called when the "cancel" button is clicked -> undo changes and leaves * edition. * * @private */ _onCancelClick: function () { this._cancel(); }, /** * Called when text is edited -> make sure text is not messed up and mark * the element as dirty. * * @private * @param {FlectraEvent} ev */ _onRTEChange: function (ev) { var $node = $(ev.data.target); $node.find('p').each(function () { // remove

element which might have been inserted because of copy-paste var $p = $(this); $p.after($p.html()).remove(); }); var trans = this._getTranlationObject($node[0]); $node.toggleClass('o_dirty', trans.value !== $node.html().replace(/[ \t\n\r]+/, ' ')); }, /** * Called when the "save" button is clicked -> saves the translations. * * @private */ _onSaveClick: function () { this._save(); }, }); return { Class: TranslatorMenuBar, }; });