[IMP] website editor: better rendering of the popover when failure write raise by...
[odoo/odoo.git] / addons / website / static / src / js / website.editor.js
index 734d4b0..30af6fd 100644 (file)
@@ -2,7 +2,7 @@
     'use strict';
 
     var website = openerp.website;
-    // $.fn.data automatically parses value, '0'|'1' -> 0|1
+    var _t = openerp._t;
 
     website.add_template_file('/website/static/src/xml/website.editor.xml');
     website.dom_ready.done(function () {
             website.ready().then(website.init_editor);
         }
 
+        $(document).on('click', 'a.js_link2post', function (ev) {
+            ev.preventDefault();
+            website.form(this.pathname, 'POST');
+        });
+
         $(document).on('hide.bs.dropdown', '.dropdown', function (ev) {
             // Prevent dropdown closing when a contenteditable children is focused
             if (ev.originalEvent
                     link_dialog(editor);
                 }, null, null, 500);
 
+                var previousSelection;
+                editor.on('selectionChange', function (evt) {
+                    var selected = evt.data.path.lastElement;
+                    if (previousSelection) {
+                        // cleanup previous selection
+                        $(previousSelection).next().remove();
+                        previousSelection = null;
+                    }
+                    if (!selected.is('img')
+                            || selected.data('cke-realelement')
+                            || selected.isReadOnly()
+                            || selected.data('oe-model') === 'ir.ui.view') {
+                        return;
+                    }
+
+                    // display button
+                    var $el = $(previousSelection = selected.$);
+                    var $btn = $('<button type="button" class="btn btn-primary image-edit-button" contenteditable="false">Edit</button>')
+                        .insertAfter($el)
+                        .click(function (e) {
+                            e.preventDefault();
+                            e.stopPropagation();
+                            image_dialog(editor);
+                        });
+
+                    var position = $el.position();
+                    $btn.css({
+                        position: 'absolute',
+                        top: $el.height() / 2 + position.top - $btn.outerHeight() / 2,
+                        left: $el.width() / 2 + position.left - $btn.outerWidth() / 2,
+                    });
+                });
+                editor.on('destroy', function (evt) {
+                    if (previousSelection) {
+                        $('.image-edit-button').remove();
+                    }
+                });
+
                 //noinspection JSValidateTypes
                 editor.addCommand('link', {
                     exec: function (editor) {
             'click button[data-action=edit]': 'edit',
             'click button[data-action=save]': 'save',
             'click button[data-action=cancel]': 'cancel',
+            'click a[data-action=new_page]': 'new_page',
         },
         container: 'body',
         customize_setup: function() {
                             }
                         });
                         // Adding Static Menus
-                        menu.append('<li class="divider"></li><li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
-                        menu.append('<li class="divider"></li><li><a data-action="ace" href="#">HTML Editor</a></li>');
+                        menu.append('<li class="divider"></li>');
+                       menu.append('<li><a data-action="ace" href="#">HTML Editor</a></li>');
+                        menu.append('<li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
+                        menu.append('<li><a href="/web#return_label=Website&action=website.action_module_website">Install Apps</a></li>');
                         self.trigger('rte:customize_menu_ready');
                     }
                 );
                     if (!_.isArray(failed)) { failed = [failed]; }
 
                     _(failed).each(function (failure) {
+                        var html = failure.error.exception_type === "except_osv";
+                        if (html) {
+                            var msg = $("<div/>").text(failure.error.message).html();
+                            var data = msg.substring(3,msg.length-2).split(/', u'/);
+                            failure.error.message = '<b>' + data[0] + '</b><br/>' + data[1];
+                        }
                         $(root).find('.' + failure.id)
                             .removeClass(failure.id)
                             .popover({
+                                html: html,
                                 trigger: 'hover',
                                 content: failure.error.message,
                                 placement: 'auto top',
                             })
                             // Force-show popovers so users will notice them.
                             .popover('show');
-                    })
+                    });
                 });
             });
         },
         cancel: function () {
             website.reload();
         },
+        new_page: function (ev) {
+            ev.preventDefault();
+            website.prompt({
+                window_title: "New Page",
+                input: "Page Title",
+            }).then(function (val) {
+                document.location = '/pagenew/' + encodeURI(val);
+            });
+        },
     });
 
     var blocks_selector = _.keys(CKEDITOR.dtd.$block).join(',');
 
                 self.setup_editables(root);
 
-                // disable firefox's broken table resizing thing
-                document.execCommand("enableObjectResizing", false, "false");
-                document.execCommand("enableInlineTableEditing", false, "false");
+                try {
+                    // disable firefox's broken table resizing thing
+                    document.execCommand("enableObjectResizing", false, "false");
+                    document.execCommand("enableInlineTableEditing", false, "false");
+                } catch (e) {}
 
                 self.trigger('rte:ready');
                 def.resolve();
     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)); },
+            'change :input.url-source': function (e) { this.changed($(e.target)); },
             'mousedown': function (e) {
                 var $target = $(e.target).closest('.list-group-item');
                 if (!$target.length || $target.hasClass('active')) {
                     return;
                 }
 
-                this.changed($target.find('.url-source'));
+                this.changed($target.find('.url-source').filter(':input'));
             },
             'click button.remove': 'remove_link',
             'change input#link-text': function (e) {
         }),
         init: function (editor) {
             this._super(editor);
-            // url -> name mapping for existing pages
-            this.pages = Object.create(null);
             this.text = null;
+            // Store last-performed request to be able to cancel/abort it.
+            this.req = null;
         },
         start: function () {
             var self = this;
-            return $.when(
-                this.fetch_pages().done(this.proxy('fill_pages')),
-                this._super()
-            ).done(function () {
-                self.bind_data();
+            this.$('#link-page').select2({
+                minimumInputLength: 3,
+                placeholder: _t("New or existing page"),
+                query: function (q) {
+                    // FIXME: out-of-order, abort
+                    self.fetch_pages(q.term).then(function (results) {
+                        var rs = _.map(results, function (r) {
+                            return { id: r.url, text: r.name, };
+                        });
+                        rs.push({
+                            create: true,
+                            id: q.term,
+                            text: _.str.sprintf(_t("Create page '%s'"), q.term),
+                        });
+                        q.callback({
+                            more: false,
+                            results: rs
+                        });
+                    });
+                },
             });
+            return this._super().then(this.proxy('bind_data'));
         },
         save: function () {
             var self = this, _super = this._super.bind(this);
-            var $e = this.$('.list-group-item.active .url-source');
+            var $e = this.$('.list-group-item.active .url-source').filter(':input');
             var val = $e.val();
             if (!val || !$e[0].checkValidity()) {
                 // FIXME: error message
             var done = $.when();
             if ($e.hasClass('email-address')) {
                 this.make_link('mailto:' + val, false, val);
-            } else if ($e.hasClass('existing')) {
-                self.make_link(val, false, this.pages[val]);
-            } else if ($e.hasClass('pages')) {
-                // Create the page, get the URL back
-                done = $.get(_.str.sprintf(
-                        '/pagenew/%s?noredirect', encodeURI(val)))
-                    .then(function (response) {
-                        self.make_link(response, false, val);
-                    });
+            } else if ($e.hasClass('page')) {
+                var data = $e.select2('data');
+                if (!data.create) {
+                    self.make_link(data.id, false, data.text);
+                } else {
+                    // Create the page, get the URL back
+                    done = $.get(_.str.sprintf(
+                            '/pagenew/%s?noredirect', encodeURI(data.id)))
+                        .then(function (response) {
+                            self.make_link(response, false, data.id);
+                        });
+                }
             } else {
                 this.make_link(val, this.$('input.window-new').prop('checked'));
             }
             var match, $control;
             if ((match = /mailto:(.+)/.exec(href))) {
                 $control = this.$('input.email-address').val(match[1]);
-            } else if (href in this.pages) {
-                $control = this.$('select.existing').val(href);
-            } else if ((match = /\/page\/(.+)/.exec(href))) {
-                var actual_href = '/page/website.' + match[1];
-                if (actual_href in this.pages) {
-                    $control = this.$('select.existing').val(actual_href);
-                }
             }
             if (!$control) {
                 $control = this.$('input.url').val(href);
             this.$('input.window-new').prop('checked', new_window);
         },
         changed: function ($e) {
-            this.$('.url-source').not($e).val('');
+            this.$('.url-source').filter(':input').not($e).val('')
+                    .filter(function () { return !!$(this).data('select2'); })
+                    .select2('data', null);
             $e.closest('.list-group-item')
                 .addClass('active')
                 .siblings().removeClass('active')
                 .addBack().removeClass('has-error');
         },
-        fetch_pages: function () {
-            return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
+        fetch_pages: function (term) {
+            var self = this;
+            if (this.req) { this.req.abort(); }
+            return this.req = openerp.jsonRpc('/web/dataset/call_kw', 'call', {
                 model: 'website',
-                method: 'list_pages',
-                args: [null],
+                method: 'search_pages',
+                args: [null, term],
                 kwargs: {
+                    limit: 9,
                     context: website.get_context()
                 },
-            });
-        },
-        fill_pages: function (results) {
-            var self = this;
-            var pages = this.$('select.existing')[0];
-            _(results).each(function (result) {
-                self.pages[result.url] = result.name;
-
-                pages.options[pages.options.length] =
-                        new Option(result.name, result.url);
+            }).done(function () {
+                // request completed successfully -> unstore it
+                self.req = null;
             });
         },
     });
         }),
 
         start: function () {
+            this.$('.modal-footer [disabled]').text("Uploading…");
             var $options = this.$('.image-style').children();
             this.image_styles = $options.map(function () { return this.value; }).get();
 
         },
 
         file_selection: function () {
+            this.$el.addClass('nosave');
             this.$('button.filepicker').removeClass('btn-danger btn-success');
 
             var self = this;
             this.set_image(url);
         },
         preview_image: function () {
+            this.$el.removeClass('nosave');
             var image = this.$('input.url').val();
             if (!image) { return; }
 
             if (!(element = this.element)) {
                 element = editor.document.createElement('img');
                 element.addClass('img');
+                element.addClass('img-responsive');
                 // focus event handler interactions between bootstrap (modal)
                 // and ckeditor (RTE) lead to blowing the stack in Safari and
                 // Chrome (but not FF) when this is done synchronously =>