diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-02-09 16:13:08 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-02-09 16:13:08 +0100 |
| commit | 5f3301e5cbbb2c6e7bff732761eef80e8dd5a33d (patch) | |
| tree | 48ab6bd9bbda92dea713c0c52bca3396ef939d82 /addons/web_editor | |
| parent | 715e602bf04ae5a0c9704c599f2145b2808b31b7 (diff) | |
| parent | 20e4cd5bcd1816a478aedd441efa90a34cf63334 (diff) | |
[MERGE] forward port branch 11.0 up to 20e4cd5bcd1
Diffstat (limited to 'addons/web_editor')
| -rw-r--r-- | addons/web_editor/controllers/main.py | 15 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/backend/fields.js | 3 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/editor/rte.summernote.js | 6 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/editor/snippets.options.js | 7 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/editor/summernote.js | 24 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/editor/transcoder.js | 32 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/inline.js | 1 | ||||
| -rw-r--r-- | addons/web_editor/static/src/js/widgets/widgets.js | 137 | ||||
| -rw-r--r-- | addons/web_editor/static/src/xml/editor.xml | 7 |
9 files changed, 161 insertions, 71 deletions
diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 50a9540323a..cc8d91cbaef 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -146,6 +146,9 @@ class Web_Editor(http.Controller): # therefore we have to recover the files from the request object Attachments = request.env['ir.attachment'] # registry for the attachment table + res_model = kwargs.get('res_model', 'ir.ui.view') + res_id = res_model != 'ir.ui.view' and kwargs.get('res_id') or None + uploads = [] message = None if not upload: # no image provided, storing the link and the image name @@ -155,9 +158,11 @@ class Web_Editor(http.Controller): 'type': 'url', 'url': url, 'public': True, - 'res_model': 'ir.ui.view', + 'res_id': res_id, + 'res_model': res_model, }) - uploads += attachment.read(['name', 'mimetype', 'checksum', 'url']) + attachment.generate_access_token() + uploads += attachment.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token']) else: # images provided try: attachments = request.env['ir.attachment'] @@ -180,10 +185,12 @@ class Web_Editor(http.Controller): 'datas': base64.b64encode(data), 'datas_fname': c_file.filename, 'public': True, - 'res_model': 'ir.ui.view', + 'res_id': res_id, + 'res_model': res_model, }) + attachment.generate_access_token() attachments += attachment - uploads += attachments.read(['name', 'mimetype', 'checksum', 'url']) + uploads += attachments.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token']) except Exception as e: logger.exception("Failed to upload image to attachment") message = pycompat.text_type(e) diff --git a/addons/web_editor/static/src/js/backend/fields.js b/addons/web_editor/static/src/js/backend/fields.js index b84fcd21c91..d3d601bac73 100644 --- a/addons/web_editor/static/src/js/backend/fields.js +++ b/addons/web_editor/static/src/js/backend/fields.js @@ -118,6 +118,7 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi */ _getValue: function () { if (this.nodeOptions['style-inline']) { + transcoder.linkImgToAttachmentThumbnail(this.$content); transcoder.classToStyle(this.$content); transcoder.fontToImg(this.$content); } @@ -133,6 +134,8 @@ var FieldTextHtmlSimple = basic_fields.DebouncedField.extend(TranslatableFieldMi this.$textarea.summernote(this._getSummernoteConfig()); this.$content = this.$('.note-editable:first'); this.$content.html(this._textToHtml(this.value)); + this.$content.data('oe-id', this.recordData.res_id || this.res_id); + this.$content.data('oe-model', this.recordData.model || this.model); // trigger a mouseup to refresh the editor toolbar this.$content.trigger('mouseup'); if (this.nodeOptions['style-inline']) { diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js index 2ac9df2d693..71982f8137b 100644 --- a/addons/web_editor/static/src/js/editor/rte.summernote.js +++ b/addons/web_editor/static/src/js/editor/rte.summernote.js @@ -1080,8 +1080,12 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, { return; } data.__alreadyDone = true; + var mediaDialog = new weWidgets.MediaDialog(this, - data.options || {}, + _.extend({ + res_model: data.$editable.data('oe-model'), + res_id: data.$editable.data('oe-id'), + }, data.options), data.$editable, data.media ); diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 38dcacf9e77..382bae0d4a7 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -872,7 +872,12 @@ registry.background = SnippetOption.extend({ // Put fake image in the DOM, edit it and use it as background-image var $image = $('<img/>', {class: 'hidden', src: value}).appendTo(this.$target); - var _editor = new widget.MediaDialog(this, {}, null, $image[0]).open(); + var $editable = this.$target.closest('.o_editable'); + var options = { + res_model: $editable.data('oe-model'), + res_id: $editable.data('oe-id'), + }; + var _editor = new widget.MediaDialog(this, options, null, $image[0]).open(); _editor.opened(function () { _editor.$('[href="#editor-media-video"], [href="#editor-media-icon"]').addClass('hidden'); }); diff --git a/addons/web_editor/static/src/js/editor/summernote.js b/addons/web_editor/static/src/js/editor/summernote.js index 59609eab8a5..121a1a6d3d7 100644 --- a/addons/web_editor/static/src/js/editor/summernote.js +++ b/addons/web_editor/static/src/js/editor/summernote.js @@ -913,15 +913,21 @@ range.WrappedRange.prototype.deleteContents = function (towrite) { return this; } - var prevBP = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite); + var r; + var image = this.isOnImg(); + if (image) { + // If the range matches/is in an image, then the image is to be removed + // and the cursor moved to its previous position + var parentNode = image.parentNode; + var index = _.indexOf(parentNode.childNodes, image); + parentNode.removeChild(image); + r = new range.WrappedRange(parentNode, index, parentNode, index); + } else { + r = dom.removeBetween(this.sc, this.so, this.ec, this.eo, towrite); + } - $(dom.node(prevBP.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler - return new range.WrappedRange( - prevBP.sc, - prevBP.so, - prevBP.ec, - prevBP.eo - ); + $(dom.node(r.sc)).trigger("click"); // trigger click to disable and reanable editor and image handler + return new range.WrappedRange(r.sc, r.so, r.ec, r.eo); }; range.WrappedRange.prototype.clean = function (mergeFilter, all) { var node = dom.node(this.sc === this.ec ? this.sc : this.commonAncestor()); @@ -1258,7 +1264,7 @@ $.summernote.pluginEvents.visible = function (event, editor, layoutInfo) { }; function remove_table_content(r) { - var nodes = dom.listBetween(r.sc, r.ec); + var nodes = dom.listBetween(r.sc, r.ec, r.so, r.eo); if (dom.isText(r.sc)) { r.sc.textContent = r.sc.textContent.slice(0, r.so); } diff --git a/addons/web_editor/static/src/js/editor/transcoder.js b/addons/web_editor/static/src/js/editor/transcoder.js index 11d23bf603b..54bbc803645 100644 --- a/addons/web_editor/static/src/js/editor/transcoder.js +++ b/addons/web_editor/static/src/js/editor/transcoder.js @@ -18,7 +18,7 @@ function getMatchedCSSRules(a) { var sheets = document.styleSheets; for (i = sheets.length-1 ; i >= 0 ; i--) { var rules; - if (sheets[i].rules) { + if (sheets[i].hasOwnProperty('rules')) { rules = sheets[i].rules; } else { // try...catch because Firefox not able to enumerate @@ -259,10 +259,40 @@ function styleToClass($editable) { $c.remove(); } +/** + * Converts css display for attachment link to real image. + * Without this post process, the display depends on the css and the picture + * does not appear when we use the html without css (to send by email for e.g.) + * + * @param {jQuery} $editable + */ +function attachmentThumbnailToLinkImg($editable) { + $editable.find('a[href*="/web/content/"][data-mimetype]:empty').each(function () { + var $link = $(this); + var $img = $('<img/>') + .attr('src', $link.css('background-image').replace(/(^url\(['"])|(['"]\)$)/g, '')) + .css('height', Math.max(1, $link.height()) + 'px') + .css('width', Math.max(1, $link.width()) + 'px'); + $link.append($img); + }); +} + +/** + * Revert attachmentThumbnailToLinkImg changes + * + * @see attachmentThumbnailToLinkImg + * @param {jQuery} $editable + */ +function linkImgToAttachmentThumbnail($editable) { + $editable.find('a[href*="/web/content/"][data-mimetype] > img').remove(); +} + return { fontToImg: fontToImg, imgToFont: imgToFont, classToStyle: classToStyle, styleToClass: styleToClass, + attachmentThumbnailToLinkImg: attachmentThumbnailToLinkImg, + linkImgToAttachmentThumbnail: linkImgToAttachmentThumbnail, }; }); diff --git a/addons/web_editor/static/src/js/inline.js b/addons/web_editor/static/src/js/inline.js index ac0dee435ae..8715b5b7f24 100644 --- a/addons/web_editor/static/src/js/inline.js +++ b/addons/web_editor/static/src/js/inline.js @@ -53,6 +53,7 @@ snippet_editor.Class.include({ this._super.apply(this, arguments); var $editable = $('#editable_area'); + transcoder.linkImgToAttachmentThumbnail($editable); transcoder.fontToImg($editable); transcoder.classToStyle($editable); diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index e499e83843d..b93724c7a30 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -120,7 +120,13 @@ var ImageDialog = Widget.extend({ this._super.apply(this, arguments); this.options = options || {}; this.accept = this.options.accept || this.options.document ? "*/*" : "image/*"; - this.domain = this.options.domain || ['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]]; + if (this.options.res_id) { + this.domain = ['|', + '&', ['res_model', '=', this.options.res_model], ['res_id', '=', this.options.res_id], + ['res_model', '=', 'ir.ui.view']]; + } else { + this.domain = [['res_model', '=', 'ir.ui.view']]; + } this.parent = parent; this.old_media = media; this.media = media; @@ -171,6 +177,7 @@ var ImageDialog = Widget.extend({ } }, save: function () { + var self = this; if (this.options.select_images) { return this.images; } @@ -181,34 +188,55 @@ var ImageDialog = Widget.extend({ img = _.find(this.images, function (img) { return img.id === id;}); } - var media; - if (!img.is_document) { - if (this.media.tagName !== "IMG" || !this.old_media) { - this.add_class = "pull-left"; - this.style = {"width": "100%"}; - } - if (this.media.tagName !== "IMG") { - media = document.createElement('img'); - $(this.media).replaceWith(media); - this.media = media; - } - this.media.setAttribute('src', img.src); - } else { - if (this.media.tagName !== "A") { - $('.note-control-selection').hide(); - media = document.createElement('a'); - $(this.media).replaceWith(media); - this.media = media; - } - this.media.setAttribute('href', '/web/content/' + img.id + '?unique=' + img.checksum + '&download=true'); - $(this.media).addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype); + var def = $.when(); + if (!img.access_token) { + def = this._rpc({ + model: 'ir.attachment', + method: 'generate_access_token', + args: [[img.id]] + }).then(function (access_token) { + img.access_token = access_token[0]; + }); } - $(this.media).attr('alt', img.alt); - var style = this.style; - if (style) { $(this.media).css(style); } + return def.then(function () { + var media; + if (!img.is_document) { + if (img.access_token && self.options.res_model !== 'ir.ui.view') { + img.src += _.str.sprintf('?access_token=%s', img.access_token); + } + if (self.media.tagName !== "IMG" || !self.old_media) { + self.add_class = "pull-left"; + self.style = {"width": "100%"}; + } + if (self.media.tagName !== "IMG") { + media = document.createElement('img'); + $(self.media).replaceWith(media); + self.media = media; + } + self.media.setAttribute('src', img.src); + } else { + if (self.media.tagName !== "A") { + $('.note-control-selection').hide(); + media = document.createElement('a'); + $(self.media).replaceWith(media); + self.media = media; + } + var href = '/web/content/' + img.id + '?'; + if (img.access_token && self.options.res_model !== 'ir.ui.view') { + href += _.str.sprintf('access_token=%s&', img.access_token); + } + href += 'unique=' + img.checksum + '&download=true'; + self.media.setAttribute('href', href); + $(self.media).addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype); + } - return this.media; + $(self.media).attr('alt', img.alt); + var style = self.style; + if (style) { $(self.media).css(style); } + + return self.media; + }); }, clear: function () { this.media.className = this.media.className.replace(/(^|\s+)((img(\s|$)|img-(?!circle|rounded|thumbnail))[^\s]*)/g, ' '); @@ -282,7 +310,7 @@ var ImageDialog = Widget.extend({ } }, fetch_existing: function (needle) { - var domain = [['res_model', '=', 'ir.ui.view']].concat(this.domain); + var domain = this.domain.concat(['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]]); if (needle && needle.length) { domain.push('|', ['datas_fname', 'ilike', needle], ['name', 'ilike', needle]); } @@ -292,7 +320,7 @@ var ImageDialog = Widget.extend({ args: [], kwargs: { domain: domain, - fields: ['name', 'mimetype', 'checksum', 'url', 'type'], + fields: ['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'access_token'], order: [{name: 'id', asc: false}], context: weContext.get(), } @@ -305,7 +333,7 @@ var ImageDialog = Widget.extend({ return (r.url || r.id); }); _.each(this.records, function (record) { - record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, record.name); // Name is added for SEO purposes + record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes record.is_document = !(/gif|jpe|jpg|png/.test(record.mimetype)); }); this.display_attachments(); @@ -1001,18 +1029,21 @@ var MediaDialog = Dialog.extend({ return this._super.apply(this, arguments); }, save: function () { + var self = this; + var args = arguments; + var _super = this._super; if (this.options.select_images) { - this.final_data = this.active.save(); - this._super.apply(this, arguments); - return; + return $.when(this.active.save()).then(function (data) { + self.final_data = data; + return _super.apply(self, args); + }); } if (this.rte) { this.range.select(); this.rte.historyRecordUndo(this.media); } - var self = this; - if (self.media) { + if (this.media) { this.media.innerHTML = ""; if (this.active !== this.imageDialog && this.active !== this.documentDialog) { this.imageDialog.clear(); @@ -1029,27 +1060,27 @@ var MediaDialog = Dialog.extend({ this.range.insertNode(this.media, true); this.active.media = this.media; } - this.active.save(); - if (this.active.add_class) { - $(this.active.media).addClass(this.active.add_class); - } - var media = this.active.media; - - this.final_data = [media, self.old_media]; - $(document.body).trigger("media-saved", this.final_data); - $(self.old_media).trigger("save", this.final_data); - $(this.final_data).trigger('input'); - - // Update editor bar after image edition (in case the image change to icon or other) - _.defer(function () { - if (!media.parentNode) return; - range.createFromNode(media).select(); - click_event(media, "mousedown"); - click_event(media, "mouseup"); + return $.when(this.active.save()).then(function () { + if (self.active.add_class) { + $(self.active.media).addClass(self.active.add_class); + } + var media = self.active.media; + + self.final_data = [media, self.old_media]; + $(document.body).trigger("media-saved", self.final_data); + $(self.old_media).trigger("save", self.final_data); + $(self.final_data).trigger('input'); + + // Update editor bar after image edition (in case the image change to icon or other) + _.defer(function () { + if (!media.parentNode) return; + range.createFromNode(media).select(); + click_event(media, "mousedown"); + click_event(media, "mouseup"); + }); + return _super.apply(self, args); }); - - this._super.apply(this, arguments); }, searchTimer: null, search: function () { diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml index e8e6374305e..8f975b0d784 100644 --- a/addons/web_editor/static/src/xml/editor.xml +++ b/addons/web_editor/static/src/xml/editor.xml @@ -86,6 +86,8 @@ target="fileframe" class="form-inline"> <input type="hidden" name="csrf_token" t-att-value="csrf_token"/> + <input t-if="widget.options.res_id" type="hidden" name="res_id" t-att-value="widget.options.res_id"/> + <input t-if="widget.options.res_model" type="hidden" name="res_model" t-att-value="widget.options.res_model"/> <div class="well"> <div class="form-group pull-left"> <input type="file" name="upload" t-att-accept="widget.accept" multiple="multiple" style="position: absolute; opacity: 0; width: 1px; height: 1px;"/> @@ -145,8 +147,9 @@ <div class="existing-attachments"> <div class="row mt16" t-as="row" t-foreach="rows"> <div class="col-sm-2 o_existing_attachment_cell" t-as="attachment" t-foreach="row"> - <i class="fa fa-times o_existing_attachment_remove" t-att-data-id="attachment.id"/> - <div class="o_attachment_border"><div t-att-data-src="attachment.src" t-att-data-url="attachment.url" t-att-alt="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" t-att-data-mimetype="attachment.mimetype" class="o_image"/></div> + <i t-if="attachment.res_model === 'ir.ui.view'" class="fa fa-times o_existing_attachment_remove" title="This file is a public view attachment" t-att-data-id="attachment.id"/> + <i t-else="" class="fa fa-times o_existing_attachment_remove" title="This file is attached to the current record" t-att-data-id="attachment.id"/> + <div class="o_attachment_border" t-att-style="attachment.res_model === 'ir.ui.view' ? null : 'border: 1px solid #5cb85c;'"><div t-att-data-src="attachment.src" t-att-data-url="attachment.url" t-att-alt="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" t-att-data-mimetype="attachment.mimetype" class="o_image"/></div> </div> </div> </div> |
